diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2023-01-25 17:57:01 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2023-01-25 17:57:01 +0000 |
commit | 9837de570c5972f98e74848edc97c297a13136ea (patch) | |
tree | cc948611912d116a3f98a744e690d3d7b6e2f59a | |
parent | c367400b73d207833d51e09d663f969ffab37531 (diff) | |
parent | 3c48d3c83fc21dbc0841f9210f04bdb073d73cd1 (diff) | |
download | rneovim-9837de570c5972f98e74848edc97c297a13136ea.tar.gz rneovim-9837de570c5972f98e74848edc97c297a13136ea.tar.bz2 rneovim-9837de570c5972f98e74848edc97c297a13136ea.zip |
Merge remote-tracking branch 'upstream/master' into colorcolchar
1055 files changed, 85148 insertions, 64301 deletions
diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000000..1fe87ba501 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,42 @@ +Checks: > + -*, + + bugprone-*, + google-*, + misc-*, + modernize-*, + performance-*, + portability-*, + readability-*, + + -bugprone-assignment-in-if-condition, + -bugprone-branch-clone, + -bugprone-easily-swappable-parameters, + -bugprone-implicit-widening-of-multiplication-result, + -bugprone-macro-parentheses, + -bugprone-narrowing-conversions, + -bugprone-not-null-terminated-result, + -bugprone-reserved-identifier, + -bugprone-sizeof-expression, + -bugprone-suspicious-include, + -bugprone-suspicious-memory-comparison, + -bugprone-unused-return-value, + -google-readability-braces-around-statements, + -google-readability-function-size, + -misc-misplaced-const, + -misc-no-recursion, + -misc-unused-parameters, + -modernize-macro-to-enum, + -performance-no-int-to-ptr, + -readability-avoid-const-params-in-decls, + -readability-braces-around-statements, + -readability-else-after-return, + -readability-function-cognitive-complexity, + -readability-function-size, + -readability-identifier-length, + -readability-isolate-declaration, + -readability-magic-numbers, + -readability-misleading-indentation, + -readability-redundant-declaration, + -readability-redundant-function-ptr-dereference, + -readability-suspicious-call-argument, diff --git a/.editorconfig b/.editorconfig index 2aa956b1fc..07993e25b9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,6 +10,9 @@ insert_final_newline = true [*.{c,h,in,lua}] max_line_length = 100 +[*.py] +indent_size = 4 + [{Makefile,**/Makefile,runtime/doc/*.txt}] indent_style = tab indent_size = 8 diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index b9830dd2fd..4dba469ebe 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -53,10 +53,29 @@ aa4f9c5341f5280f16cce0630ea54b84eef717b3 93f24403f8cc760ff47979c596976b53a8b16358 1ffd527c837fb2465c9659273bbe5447a1352db2 2498e9feb025361576603a0101c86393d211e31e +0b3ae64480ea28bb57783c2269a61f0a60ffc55e +0fc8597f011e0927e529abd11bf0ddd8d0d1eaab +6ff245732a5a8ab821598a38fb0c5805e6bd3779 +abf758a2977c4e6cab4dfa217f56da853d85851c +cb84f5ee530f0f32b92bed5b4ad41344e8b551aa # typos d238b8f6003d34cae7f65ff7585b48a2cd9449fb 4547137aaff32b20172870a549d3a28a3c7adf1c +08616571f47cc367a5fe59b52295708b9fda3b09 +09c6ce8c4e4c6415cca9b834539ed0df461373f6 +0b0c4f7dfa4a9a564cbf44262d4bea9bdefe2dc9 +4a96e7809f4d9f6ce21869817eb95ff6dcaa1693 +61205c1defb64ac5466496b5451e4a7f3171e21e +64116d78502e0ca611e13adf9323ef2d3fe708c2 +abc087f4c65ca547cae58518b42aee82ff4a07f6 +b8dcbcc732baf84fc48d6b272c3ade0bcb129b3b +c815aadfccd6bada47ecfb09fe188ee7f7c5caf3 +caa6992a1071a2ac373bec21085685da4a1790d6 +df646572c53f55268a5dbb61628d7c3b302d5663 +e63e5d1dbd3dd4711efa0ecf9e844ff308b370a6 +eb123b565e201418dd135d2602dc20eea3cead39 +ff20d40321399fa187bd350f9619cf6418d7eb6e # generated docs ea333badd24f691c753d8048f911d1db349bc2cd diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 11f27dde21..5fd7bc37b6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -8,6 +8,33 @@ body: value: | _Before reporting:_ search [existing issues](https://github.com/neovim/neovim/issues?q=is%3Aissue+is%3Aopen+label%3Abug) and check the [FAQ](https://github.com/neovim/neovim/wiki/FAQ). Usage questions such as "How do I...?" belong on the [Neovim Discourse](https://neovim.discourse.group/c/7-category/7) and will be closed. + - type: textarea + attributes: + label: "Describe the bug" + description: "Describe the current behavior. May include logs, images, or videos." + validations: + required: true + + - type: textarea + attributes: + label: "Steps to reproduce" + description: | + - For build failures: list the exact steps including CMake flags (if any). + - For startup or shell-related problems: try `env -i TERM=ansi-256color "$(which nvim)"`. + placeholder: | + nvim --clean + :edit foo + yiwp + validations: + required: true + + - type: textarea + attributes: + label: "Expected behavior" + description: "Describe the behavior you expect." + validations: + required: true + - type: input attributes: label: "Neovim version (nvim -v)" @@ -47,29 +74,3 @@ body: placeholder: "Arch User Repository (AUR)" validations: required: true - - - type: textarea - attributes: - label: "How to reproduce the issue" - description: | - - Steps to reproduce using `nvim --clean` ("factory defaults"). - - For build failures: list the exact steps including CMake flags (if any). - - For shell-related problems: try `env -i TERM=ansi-256color "$(which nvim)"`. - placeholder: | - nvim --clean - :edit foo - yiwp - validations: - required: true - - - type: textarea - attributes: - label: "Expected behavior" - description: "Describe the behavior you expect. May include logs, images, or videos." - validations: - required: true - - type: textarea - attributes: - label: "Actual behavior" - validations: - required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 2b6fa3daf4..4b9a443064 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -8,14 +8,17 @@ body: value: | Before requesting: search [existing issues](https://github.com/neovim/neovim/labels/enhancement) and check the [FAQ](https://github.com/neovim/neovim/wiki/FAQ). - - type: input + - type: textarea attributes: - label: "Feature already in Vim?" - description: "Does the feature already exist in Vim? If possible, specify which version (or commit) that introduced it." - placeholder: "Yes, Vim 7.3.432" + label: "Problem" + description: "Describe the problem to be solved. Include references to other projects (Vim, Emacs, etc.) if relevant." + placeholder: "No smurf icons available. Smurfs are useful because ..." + validations: + required: true - type: textarea attributes: - label: "Feature description" + label: "Expected behavior" + description: "Describe what the new feature or behavior would look like. How does it solve the problem? Is it worth the cost?" validations: required: true diff --git a/.github/ISSUE_TEMPLATE/lsp_bug_report.yml b/.github/ISSUE_TEMPLATE/lsp_bug_report.yml index 3789fb866b..88867ce644 100644 --- a/.github/ISSUE_TEMPLATE/lsp_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/lsp_bug_report.yml @@ -31,8 +31,38 @@ body: attributes: label: 'Steps to reproduce using "nvim -u minimal_init.lua"' description: | - - Create a minimal_init.lua using vim.lsp.start - read `:h lsp-quickstart` and `:h vim.lsp.start` for more information. - - _Note_: if the issue is with an autocompletion or other LSP plugin, report to that plugin's issue tracker. + - Create a minimal_init.lua using vim.lsp.start: + + ```lua + --- CHANGE THESE + local pattern = 'the-filetype' + local cmd = {'name-of-language-server-executable'} + -- Add files/folders here that indicate the root of a project + local root_markers = {'.git', '.editorconfig'} + -- Change to table with settings if required + local settings = vim.empty_dict() + + vim.api.nvim_create_autocmd('FileType', { + pattern = pattern, + callback = function(args) + local match = vim.fs.find(root_markers, { path = args.file, upward = true })[1] + local root_dir = match and vim.fn.fnamemodify(match, ':p:h') or vim.NIL + vim.lsp.start({ + name = 'bugged-ls', + cmd = cmd, + root_dir = root_dir, + settings = settings + }) + end + }) + ``` + + See `:h lsp-quickstart` and `:h vim.lsp.start` for more information + + - Provide a short code example and describe the folder layout + - Describe how to trigger the issue. E.g. using `:lua vim.lsp.buf.*` commands + + _Note_: if the issue is with an autocompletion or other LSP plugin, report to that plugin's issue tracker. validations: required: true diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000000..a376d87912 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,2 @@ +To report a security vulnerability to Neovim, use +https://github.com/neovim/neovim/security/advisories/new diff --git a/.github/actions/cache/action.yml b/.github/actions/cache/action.yml new file mode 100644 index 0000000000..858045c02a --- /dev/null +++ b/.github/actions/cache/action.yml @@ -0,0 +1,22 @@ +name: 'Cache' +description: "This action caches neovim dependencies" +runs: + using: "composite" + steps: + - run: echo "CACHE_KEY=${{ github.job }}-${{ github.base_ref }}" >> $GITHUB_ENV + shell: bash + + - if: ${{ matrix }} + run: echo "CACHE_KEY=$CACHE_KEY-${{ join(matrix.*, '-') }}" >> $GITHUB_ENV + shell: bash + + # Avoid using '**/CMakeLists.txt' (or any pattern starting with '**/') even + # if it makes the expression below simpler. hashFiles() has a timer that + # will fail the job if it times out, which can happen if there are too many + # files to search through. + - uses: actions/cache@v3 + with: + path: ${{ env.CACHE_NVIM_DEPS_DIR }} + key: ${{ env.CACHE_KEY }}-${{ hashFiles('cmake**', 'ci/**', + '.github/workflows/ci.yml', 'CMakeLists.txt', + 'runtime/CMakeLists.txt', 'src/nvim/**/CMakeLists.txt') }} diff --git a/.github/labeler.yml b/.github/labeler.yml index b4f6f054cb..ad48287246 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -63,3 +63,8 @@ "filetype": - runtime/lua/vim/filetype.lua + - runtime/lua/vim/filetype/detect.lua + +"platform:nix": + - contrib/flake.lock + - contrib/flake.nix diff --git a/.github/scripts/install_deps_ubuntu.sh b/.github/scripts/install_deps_ubuntu.sh new file mode 100755 index 0000000000..012409ba4a --- /dev/null +++ b/.github/scripts/install_deps_ubuntu.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +PACKAGES=( + autoconf + automake + build-essential + cmake + cpanminus + curl + gettext + libtool-bin + locales-all + ninja-build + pkg-config + unzip +) + +sudo apt-get update +sudo apt-get install -y "${PACKAGES[@]}" diff --git a/.github/scripts/remove-reviewers.js b/.github/scripts/remove-reviewers.js index 631f08e57d..40a8eca423 100644 --- a/.github/scripts/remove-reviewers.js +++ b/.github/scripts/remove-reviewers.js @@ -6,11 +6,13 @@ module.exports = async ({github, context}) => { }); const reviewers = requestedReviewers.data.users.map(e => e.login) + const team_reviewers = requestedReviewers.data.teams.map(e => e.name); github.rest.pulls.removeRequestedReviewers({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number, - reviewers: reviewers + reviewers: reviewers, + team_reviewers: team_reviewers }); } diff --git a/.github/scripts/reviews.js b/.github/scripts/reviews.js index c76f25b7da..cc6aaa1e8b 100644 --- a/.github/scripts/reviews.js +++ b/.github/scripts/reviews.js @@ -7,6 +7,7 @@ module.exports = async ({github, context}) => { const labels = pr_data.data.labels.map(e => e.name) const reviewers = new Set() + const team_reviewers = new Array() if (labels.includes('api')) { reviewers.add("bfredl") reviewers.add("muniter") @@ -18,14 +19,17 @@ module.exports = async ({github, context}) => { } if (labels.includes('ci')) { - reviewers.add("dundargoc") - reviewers.add("jamessan") + team_reviewers.push('ci'); } if (labels.includes('column')) { reviewers.add("lewis6991") } + if (labels.includes('dependencies')) { + reviewers.add("jamessan") + } + if (labels.includes('diagnostic')) { reviewers.add("gpanders") } @@ -34,10 +38,6 @@ module.exports = async ({github, context}) => { reviewers.add("lewis6991") } - if (labels.includes('dependencies')) { - reviewers.add("jamessan") - } - if (labels.includes('distribution')) { reviewers.add("jamessan") } @@ -53,17 +53,32 @@ module.exports = async ({github, context}) => { if (labels.includes('filetype')) { reviewers.add("clason") reviewers.add("gpanders") + reviewers.add("smjonas") } if (labels.includes('lsp')) { - reviewers.add("mfussenegger") - reviewers.add("glepnir") + team_reviewers.push('lsp'); } - if (labels.includes('treesitter')) { + if (labels.includes('platform:nix')) { + reviewers.add("teto") + } + + if (labels.includes('project-management')) { reviewers.add("bfredl") - reviewers.add("clason") - reviewers.add("vigoux") + reviewers.add("justinmk") + } + + if (labels.includes('refactor')) { + reviewers.add("bfredl") + } + + if (labels.includes('test')) { + reviewers.add("justinmk") + } + + if (labels.includes('treesitter')) { + team_reviewers.push('treesitter'); } if (labels.includes('typo')) { @@ -87,6 +102,7 @@ module.exports = async ({github, context}) => { owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number, - reviewers: Array.from(reviewers) + reviewers: Array.from(reviewers), + team_reviewers: team_reviewers }); } diff --git a/.github/scripts/unstale.js b/.github/scripts/unstale.js new file mode 100644 index 0000000000..f645fca5cb --- /dev/null +++ b/.github/scripts/unstale.js @@ -0,0 +1,19 @@ +module.exports = async ({ github, context }) => { + const commenter = context.actor; + const issue = await github.rest.issues.get({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + const author = issue.data.user.login; + const labels = issue.data.labels.map((e) => e.name); + + if (author === commenter && labels.includes("needs:response")) { + github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + name: "needs:response", + }); + } +}; diff --git a/.github/workflows/reviews.yml b/.github/workflows/add-reviewers.yml index 34ce19d830..f1abab5c53 100644 --- a/.github/workflows/reviews.yml +++ b/.github/workflows/add-reviewers.yml @@ -1,7 +1,7 @@ name: "Request reviews" on: pull_request_target: - types: [labeled, ready_for_review] + types: [labeled, ready_for_review, reopened] jobs: request-reviewer: if: github.event.pull_request.state == 'open' && github.event.pull_request.draft == false @@ -13,6 +13,7 @@ jobs: - name: 'Request reviewers' uses: actions/github-script@v6 with: + github-token: ${{ secrets.TEAM_REVIEW }} script: | const script = require('./.github/scripts/reviews.js') await script({github, context}) diff --git a/.github/workflows/api-docs.yml b/.github/workflows/api-docs.yml index 83554e2a3d..fa8a7dbca0 100644 --- a/.github/workflows/api-docs.yml +++ b/.github/workflows/api-docs.yml @@ -21,13 +21,17 @@ on: jobs: regen-api-docs: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest permissions: contents: write pull-requests: write env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: + - uses: rhysd/action-setup-vim@v1 + with: + neovim: true + version: nightly - uses: actions/checkout@v3 with: # Fetch depth 0 is required if called through workflow_call. In order @@ -38,7 +42,7 @@ 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 doxygen python3 python3-msgpack - name: Setup git config run: | @@ -52,7 +56,7 @@ jobs: run: | git checkout -b ${DOC_BRANCH} python3 scripts/gen_vimdoc.py - printf '::set-output name=UPDATED_DOCS::%s\n' $([ -z "$(git diff)" ]; echo $?) + printf 'UPDATED_DOCS=%s\n' $([ -z "$(git diff)" ]; echo $?) >> $GITHUB_OUTPUT - name: FAIL, PR has not committed doc changes if: ${{ steps.docs.outputs.UPDATED_DOCS != 0 && inputs.check_only }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 992c59454b..b158d966e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,10 +16,15 @@ concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} cancel-in-progress: true +env: + UNCRUSTIFY_VERSION: uncrustify-0.75.0 + # TEST_FILE: test/functional/core/startup_spec.lua + # TEST_FILTER: foo + jobs: lint: if: (github.event_name == 'pull_request' && github.base_ref == 'master') || (github.event_name == 'push' && github.ref == 'refs/heads/master') - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 10 env: CC: gcc @@ -31,34 +36,8 @@ jobs: - name: Install apt packages run: | - sudo add-apt-repository ppa:neovim-ppa/stable - sudo apt-get update - sudo apt-get install -y \ - autoconf \ - automake \ - build-essential \ - cmake \ - flake8 \ - gettext \ - 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 + ./.github/scripts/install_deps_ubuntu.sh + sudo apt-get install -y lua-check - name: Cache uncrustify id: cache-uncrustify @@ -85,41 +64,40 @@ jobs: mkdir -p $HOME/.cache cp $build_dir/uncrustify ${{ env.CACHE_UNCRUSTIFY }} - - name: Cache artifacts - uses: actions/cache@v3 - with: - path: | - ${{ env.CACHE_NVIM_DEPS_DIR }} - key: lint-${{ hashFiles('cmake/*', '**/CMakeLists.txt', '!cmake.deps/**CMakeLists.txt') }}-${{ github.base_ref }} + - uses: ./.github/actions/cache - name: Build third-party deps run: ./ci/before_script.sh - if: "!cancelled()" + name: Determine if run should be aborted + id: abort_job + run: echo "status=${{ job.status }}" >> $GITHUB_OUTPUT + + - if: success() || failure() && steps.abort_job.outputs.status == 'success' name: lintstylua - uses: JohnnyMorganz/stylua-action@v1 + uses: JohnnyMorganz/stylua-action@v2 with: token: ${{ secrets.GITHUB_TOKEN }} + version: latest args: --check runtime/ - - if: "!cancelled()" - name: lintlua - run: make lintlua - - - if: "!cancelled()" - name: lintpy - run: make lintpy + - if: success() || failure() && steps.abort_job.outputs.status == 'success' + name: luacheck + run: | + cmake -B $BUILD_DIR -G Ninja + cmake --build $BUILD_DIR --target lintlua-luacheck - - if: "!cancelled()" + - if: success() || failure() && steps.abort_job.outputs.status == 'success' name: lintsh run: make lintsh - - if: "!cancelled()" + - if: success() || failure() && steps.abort_job.outputs.status == 'success' name: uncrustify run: | ${{ env.CACHE_UNCRUSTIFY }} -c ./src/uncrustify.cfg -q --replace --no-backup $(find ./src/nvim -name "*.[ch]") - - if: "!cancelled()" + - if: success() || failure() && steps.abort_job.outputs.status == 'success' name: suggester / uncrustify uses: reviewdog/action-suggester@v1 with: @@ -127,7 +105,7 @@ jobs: tool_name: uncrustify cleanup: false - - if: "!cancelled()" + - if: success() || failure() && steps.abort_job.outputs.status == 'success' name: check uncrustify run: | git diff --color --exit-code @@ -142,7 +120,7 @@ jobs: # of the bundled dependencies. if: (github.event_name == 'pull_request' && github.base_ref == 'master') || (github.event_name == 'push' && github.ref == 'refs/heads/master') - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 10 env: CC: gcc @@ -155,39 +133,24 @@ jobs: - name: Install apt packages run: | sudo add-apt-repository ppa:neovim-ppa/stable - sudo apt-get update + ./.github/scripts/install_deps_ubuntu.sh sudo apt-get install -y \ - autoconf \ - automake \ - build-essential \ - cmake \ - gettext \ 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 + luajit - - name: Cache artifacts - uses: actions/cache@v3 - with: - path: | - ${{ env.CACHE_NVIM_DEPS_DIR }} - key: lint-${{ hashFiles('cmake/*', '**/CMakeLists.txt', '!cmake.deps/**CMakeLists.txt') }}-${{ github.base_ref }} + - uses: ./.github/actions/cache - name: Build third-party deps run: ./ci/before_script.sh @@ -196,10 +159,15 @@ jobs: run: ./ci/run_tests.sh build_nvim - if: "!cancelled()" - name: lintc - run: make lintc + name: Determine if run should be aborted + id: abort_job + run: echo "status=${{ job.status }}" >> $GITHUB_OUTPUT - - if: "!cancelled()" + - if: success() || failure() && steps.abort_job.outputs.status == 'success' + name: clint.py + run: cmake --build build --target lintc-clint + + - if: success() || failure() && steps.abort_job.outputs.status == 'success' name: check-single-includes run: make check-single-includes @@ -213,19 +181,19 @@ jobs: matrix: include: - flavor: asan - cc: clang-13 - runner: ubuntu-20.04 + cc: clang + runner: ubuntu-22.04 os: linux - flavor: tsan - cc: clang-13 - runner: ubuntu-20.04 + cc: clang + runner: ubuntu-22.04 os: linux - flavor: uchar cc: gcc - runner: ubuntu-20.04 + runner: ubuntu-22.04 os: linux - cc: clang - runner: macos-11 + runner: macos-12 os: osx # functionaltest-lua is our dumping ground for non-mainline configurations. @@ -236,7 +204,7 @@ jobs: # 3. No treesitter parsers installed. - flavor: functionaltest-lua cc: gcc - runner: ubuntu-20.04 + runner: ubuntu-22.04 os: linux cmake: minimum_required runs-on: ${{ matrix.runner }} @@ -252,9 +220,7 @@ jobs: - name: Install apt packages if: matrix.os == 'linux' - run: | - sudo apt-get update - sudo apt-get install -y autoconf automake build-essential cmake cpanminus cscope gcc-multilib gdb gettext language-pack-tr libtool-bin locales ninja-build pkg-config python3 python3-pip python3-setuptools unzip valgrind xclip + run: ./.github/scripts/install_deps_ubuntu.sh - name: Install minimum required version of cmake if: matrix.cmake == 'minimum_required' @@ -274,14 +240,6 @@ jobs: exit 1 } - - name: Install new clang - if: matrix.flavor == 'asan' || matrix.flavor == 'tsan' - run: | - wget https://apt.llvm.org/llvm.sh - chmod a+x llvm.sh - sudo ./llvm.sh 13 - rm llvm.sh - - name: Install brew packages if: matrix.os == 'osx' run: | @@ -291,12 +249,7 @@ jobs: - name: Setup interpreter packages run: ./ci/install.sh - - name: Cache dependencies - uses: actions/cache@v3 - with: - path: | - ${{ env.CACHE_NVIM_DEPS_DIR }} - key: ${{ matrix.runner }}-${{ matrix.flavor }}-${{ matrix.cc }}-${{ hashFiles('cmake/*', 'cmake.deps/**', '**/CMakeLists.txt') }}-${{ github.base_ref }} + - uses: ./.github/actions/cache - name: Build third-party deps run: ./ci/before_script.sh @@ -304,19 +257,24 @@ jobs: - name: Build run: ./ci/run_tests.sh build_nvim - - if: matrix.flavor != 'tsan' && matrix.flavor != 'functionaltest-lua' && !cancelled() + - if: "!cancelled()" + name: Determine if run should be aborted + id: abort_job + run: echo "status=${{ job.status }}" >> $GITHUB_OUTPUT + + - if: matrix.flavor != 'tsan' && matrix.flavor != 'functionaltest-lua' && (success() || failure() && steps.abort_job.outputs.status == 'success') name: Unittests run: ./ci/run_tests.sh unittests - - if: matrix.flavor != 'tsan' && !cancelled() + - if: success() || failure() && steps.abort_job.outputs.status == 'success' name: Functionaltests run: ./ci/run_tests.sh functionaltests - - if: "!cancelled()" + - if: matrix.flavor != 'tsan' && (success() || failure() && steps.abort_job.outputs.status == 'success') name: Oldtests run: ./ci/run_tests.sh oldtests - - if: "!cancelled()" + - if: success() || failure() && steps.abort_job.outputs.status == 'success' name: Install nvim run: ./ci/run_tests.sh install_nvim @@ -327,32 +285,67 @@ jobs: runs-on: windows-2019 timeout-minutes: 45 env: - DEPS_BUILD_DIR: ${{ format('{0}/nvim-deps', github.workspace) }} - DEPS_PREFIX: ${{ format('{0}/nvim-deps/usr', github.workspace) }} - CMAKE_BUILD_TYPE: "RelWithDebInfo" + DEPS_BUILD_DIR: ${{ github.workspace }}/nvim-deps + CACHE_NVIM_DEPS_DIR: ${{ github.workspace }}/nvim-deps + DEPS_PREFIX: ${{ github.workspace }}/nvim-deps/usr name: windows (MSVC_64) steps: - uses: actions/checkout@v3 - - uses: actions/cache@v3 - with: - path: ${{ env.DEPS_BUILD_DIR }} - key: ${{ hashFiles('cmake.deps\**') }} + - uses: ./.github/actions/cache + + - name: Set env + run: ./.github/workflows/env.ps1 - name: Build deps - run: .\ci\build.ps1 -BuildDeps + run: | + cmake -S cmake.deps -B $env:DEPS_BUILD_DIR -G Ninja -DCMAKE_BUILD_TYPE='RelWithDebInfo' + cmake --build $env:DEPS_BUILD_DIR - name: Build nvim - run: .\ci\build.ps1 -Build + run: | + cmake -B build -G Ninja -DCMAKE_BUILD_TYPE='RelWithDebInfo' -DDEPS_PREFIX="$env:DEPS_PREFIX" -DCI_BUILD=ON + cmake --build build - name: Install test deps - continue-on-error: false - run: .\ci\build.ps1 -EnsureTestDeps + run: | + $PSNativeCommandArgumentPassing = 'Legacy' - - if: "!cancelled()" - name: Run tests - run: .\ci\build.ps1 -Test + & build\bin\nvim.exe "--version" + + # Ensure that the "win32" feature is set. + & build\bin\nvim -u NONE --headless -c 'exe !has(\"win32\").\"cq\"' + + python -m pip install pynvim + # Sanity check + python -c "import pynvim; print(str(pynvim))" + + gem.cmd install --pre neovim + Get-Command -CommandType Application neovim-ruby-host.bat + + node --version + npm.cmd --version + + npm.cmd install -g neovim + Get-Command -CommandType Application neovim-node-host.cmd + npm.cmd link neovim - if: "!cancelled()" - name: Run old tests - run: .\ci\build.ps1 -TestOld + name: Determine if run should be aborted + id: abort_job + run: | + "status=${{ job.status }}" >> $env:GITHUB_OUTPUT + + - if: success() || failure() && steps.abort_job.outputs.status == 'success' + name: Run functionaltests + run: cmake --build build --target functionaltest + + - if: success() || failure() && steps.abort_job.outputs.status == 'success' + name: Run oldtests + run: | + # Add MSYS to path, required for e.g. `find` used in test scripts. + # But would break functionaltests, where its `more` would be used then. + $OldPath = $env:PATH + $env:PATH = "C:\msys64\usr\bin;$env:PATH" + & "C:\msys64\mingw64\bin\mingw32-make.exe" -C $(Convert-Path src\nvim\testdir) VERBOSE=1 + $env:PATH = $OldPath diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql.yml index b31382af37..a11a87f93a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql.yml @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'cpp', 'python' ] + language: [ 'cpp' ] steps: - name: Checkout repository @@ -26,9 +26,7 @@ jobs: run: ./.github/workflows/env.sh - name: Install apt packages - run: | - sudo apt-get update - sudo apt-get install -y autoconf automake build-essential cmake cpanminus cscope gcc-multilib gdb gettext language-pack-tr libtool-bin locales ninja-build pkg-config python3 python3-pip python3-setuptools unzip valgrind xclip + run: ./.github/scripts/install_deps_ubuntu.sh - name: Initialize CodeQL uses: github/codeql-action/init@v2 diff --git a/.github/workflows/coverity-scan.yml b/.github/workflows/coverity.yml index ce7822b5c1..87e2cb1453 100644 --- a/.github/workflows/coverity-scan.yml +++ b/.github/workflows/coverity.yml @@ -37,7 +37,7 @@ jobs: --form email=$EMAIL \ --form file=@cov-scan.tgz \ --form version="$(git rev-parse HEAD)" \ - --form description="Weekly GHA scan" \ + --form description="Daily GHA scan" \ 'https://scan.coverity.com/builds?project=neovim%2Fneovim' env: TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} diff --git a/.github/workflows/env.ps1 b/.github/workflows/env.ps1 new file mode 100644 index 0000000000..8ac267f2f9 --- /dev/null +++ b/.github/workflows/env.ps1 @@ -0,0 +1,7 @@ +$installationPath = vswhere.exe -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath +if ($installationPath -and (Test-Path "$installationPath\Common7\Tools\vsdevcmd.bat")) { + & "${env:COMSPEC}" /s /c "`"$installationPath\Common7\Tools\vsdevcmd.bat`" -arch=x64 -no_logo && set" | ForEach-Object { + $name, $value = $_ -split '=', 2 + "$name=$value" >> $env:GITHUB_ENV + } +} diff --git a/.github/workflows/env.sh b/.github/workflows/env.sh index da70d358a9..d93552fed3 100755 --- a/.github/workflows/env.sh +++ b/.github/workflows/env.sh @@ -18,7 +18,6 @@ VALGRIND_LOG=$GITHUB_WORKSPACE/build/log/valgrind-%p.log CACHE_NVIM_DEPS_DIR=$HOME/.cache/nvim-deps CACHE_MARKER=$HOME/.cache/nvim-deps/.ci_cache_marker CACHE_UNCRUSTIFY=$HOME/.cache/uncrustify -UNCRUSTIFY_VERSION=uncrustify-0.75.0 EOF DEPS_CMAKE_FLAGS= @@ -30,7 +29,6 @@ case "$FLAVOR" in BUILD_FLAGS="$BUILD_FLAGS -DPREFER_LUA=ON" cat <<EOF >> "$GITHUB_ENV" CLANG_SANITIZER=ASAN_UBSAN -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 diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index f85f9d0cda..60689029a3 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -44,6 +44,7 @@ jobs: - name: 'Request reviewers' uses: actions/github-script@v6 with: + github-token: ${{ secrets.TEAM_REVIEW }} script: | const script = require('./.github/scripts/reviews.js') await script({github, context}) diff --git a/.github/workflows/commitlint.yml b/.github/workflows/lintcommit.yml index 68be5436f6..a7a227865d 100644 --- a/.github/workflows/commitlint.yml +++ b/.github/workflows/lintcommit.yml @@ -1,8 +1,6 @@ name: "Commit Linter" on: - # 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: + pull_request: types: [opened, synchronize, reopened, ready_for_review] branches: - 'master' @@ -10,6 +8,8 @@ jobs: lint-commits: runs-on: ubuntu-latest if: github.event.pull_request.draft == false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v3 with: diff --git a/.github/workflows/news.yml b/.github/workflows/news.yml new file mode 100644 index 0000000000..46ac0ec02d --- /dev/null +++ b/.github/workflows/news.yml @@ -0,0 +1,31 @@ +name: "news.txt check" +on: + pull_request: + branches: + - 'master' +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} + - name: news.txt needs to be updated + run: | + for commit in $(git rev-list HEAD~${{ github.event.pull_request.commits }}..HEAD); do + message=$(git log -n1 --pretty=format:%s $commit) + type="$(echo "$message" | sed -E 's|([[:alpha:]]+)(\(.*\))?!?:.*|\1|')" + breaking="$(echo "$message" | sed -E 's|[[:alpha:]]+(\(.*\))?!:.*|breaking-change|')" + if [[ "$type" == "feat" ]] || [[ "$breaking" == "breaking-change" ]]; then + ! git diff HEAD~${{ github.event.pull_request.commits }}..HEAD --quiet runtime/doc/news.txt || + { + echo " + Pull request includes a new feature or a breaking change, but + news.txt hasn't been updated yet. news.txt is our primary way of + communicating changes to users so it's important to keep it up to + date." + exit 1 + } + fi + done diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1444109eae..1df33962e5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,8 +36,10 @@ jobs: id: build run: | CC=gcc-10 make CMAKE_BUILD_TYPE=${NVIM_BUILD_TYPE} CMAKE_EXTRA_FLAGS="-DCMAKE_INSTALL_PREFIX:PATH=" - 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)" + printf 'version<<END\n' >> $GITHUB_OUTPUT + ./build/bin/nvim --version | head -n 3 >> $GITHUB_OUTPUT + printf 'END\n' >> $GITHUB_OUTPUT + printf 'release=%s\n' "$(./build/bin/nvim --version | head -n 1)" >> $GITHUB_OUTPUT make DESTDIR="$GITHUB_WORKSPACE/build/release/nvim-linux64" install cd "$GITHUB_WORKSPACE/build/" cpack -C $NVIM_BUILD_TYPE @@ -150,10 +152,16 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 + - name: Set env + run: ./.github/workflows/env.ps1 - name: Build deps - run: .\ci\build.ps1 -BuildDeps + run: | + cmake -S cmake.deps -B $env:DEPS_BUILD_DIR -G Ninja -DCMAKE_BUILD_TYPE='RelWithDebInfo' + cmake --build $env:DEPS_BUILD_DIR - name: build package - run: .\ci\build.ps1 -Package + run: | + cmake -B build -G Ninja -DCMAKE_BUILD_TYPE='RelWithDebInfo' -DDEPS_PREFIX="$env:DEPS_PREFIX" + cmake --build build --target package - uses: actions/upload-artifact@v3 with: name: nvim-win64 @@ -241,6 +249,7 @@ jobs: if [ "$TAG_NAME" != "nightly" ]; then gh release create stable $PRERELEASE --notes-file "$RUNNER_TEMP/notes.md" --title "$SUBJECT" --target $GITHUB_SHA nvim-macos/* nvim-linux64/* appimage/* nvim-win64/* fi + publish-winget: needs: publish runs-on: windows-latest @@ -259,7 +268,7 @@ jobs: Invoke-WebRequest https://github.com/neovim/neovim/releases/download/nightly/nvim-win64.msi -OutFile setup.msi Install-Module -Name 'Carbon.Windows.Installer' -Force $VERSION = (Get-CMsi (Resolve-Path .\setup.msi).Path).ProductVersion - echo "::set-output name=version::$VERSION" + "version=$VERSION" >> $env:GITHUB_OUTPUT - if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name == 'nightly') name: Publish nightly uses: vedantmgoyal2009/winget-releaser@v1 diff --git a/.github/workflows/remove-reviewers-on-draft.yml b/.github/workflows/remove-reviewers.yml index f707f79737..7ab3ef568c 100644 --- a/.github/workflows/remove-reviewers-on-draft.yml +++ b/.github/workflows/remove-reviewers.yml @@ -12,6 +12,7 @@ jobs: - name: 'Remove reviewers' uses: actions/github-script@v6 with: + github-token: ${{ secrets.TEAM_REVIEW }} script: | const script = require('./.github/scripts/remove-reviewers.js') await script({github, context}) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000000..c1d3ee3ff3 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,42 @@ +name: 'Close stale issues and PRs' +on: + schedule: + - cron: '30 1 * * *' # Run every day at 01:30 + workflow_dispatch: + issue_comment: + +jobs: + close: + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v7 + with: + days-before-close: 30 + days-before-stale: -1 + stale-issue-label: needs:response + stale-pr-label: needs:response + remove-stale-when-updated: false + close-issue-message: "This issue has been closed since a request for + information has not been answered for 30 days. It can be reopened + when the requested information is provided." + close-pr-message: "This PR has been closed since a request for + changes has not been answered for 30 days. It can be reopened when + the requested changes are provided." + + remove-label: + if: github.event_name == 'issue_comment' + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/checkout@v3 + - uses: actions/github-script@v6 + with: + script: | + const script = require('./.github/scripts/unstale.js') + await script({github, context}) diff --git a/.github/workflows/vim-patches.yml b/.github/workflows/vim-patches.yml index df8c8116b5..159eb09e7c 100644 --- a/.github/workflows/vim-patches.yml +++ b/.github/workflows/vim-patches.yml @@ -2,6 +2,7 @@ name: vim-patches on: schedule: - cron: '3 3 * * *' + workflow_dispatch: jobs: update-vim-patches: @@ -24,6 +25,8 @@ jobs: path: ${{ env.VIM_SOURCE_DIR }} fetch-depth: 0 + - run: sudo apt-get install libfuse2 + - run: | gh release download -R neovim/neovim -p nvim.appimage chmod a+x nvim.appimage @@ -40,8 +43,8 @@ jobs: id: update-version run: | git checkout -b ${VERSION_BRANCH} - nvim -i NONE -u NONE --headless +'luafile scripts/vimpatch.lua' +q - printf '::set-output name=NEW_PATCHES::%s\n' $([ -z "$(git diff)" ]; echo $?) + nvim -V1 -es -i NONE +'luafile scripts/vimpatch.lua' +q + printf 'NEW_PATCHES=%s\n' $([ -z "$(git diff)" ]; echo $?) >> $GITHUB_OUTPUT - name: Automatic PR if: ${{ steps.update-version.outputs.NEW_PATCHES != 0 }} diff --git a/.gitignore b/.gitignore index be43c5cd90..57acde8722 100644 --- a/.gitignore +++ b/.gitignore @@ -74,4 +74,10 @@ tags # vim patches /vim-*.patch -/CMakeUserPresets.json +# nix build results +/result +/result-* +/contrib/result +/contrib/result-* + +CMakeUserPresets.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a25e1de8d..493c212996 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,12 @@ # Version should match the tested CMAKE_URL in .github/workflows/ci.yml. cmake_minimum_required(VERSION 3.10) + +# Can be removed once minimum version is at least 3.15 +if(POLICY CMP0092) + cmake_policy(SET CMP0092 NEW) +endif() + project(nvim C) if(POLICY CMP0075) @@ -14,7 +20,10 @@ endif() # Point CMake at any custom modules we may ship list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") -# We don't support building in-tree. +include(CheckCCompilerFlag) +include(CheckCSourceCompiles) +include(InstallHelpers) +include(LuaHelpers) # Find Lua interpreter include(PreventInTreeBuilds) include(Util) @@ -72,18 +81,7 @@ endif() list(INSERT CMAKE_PREFIX_PATH 0 ${DEPS_PREFIX}) set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:${DEPS_PREFIX}/lib/pkgconfig") -# used for check_c_compiler_flag -include(CheckCCompilerFlag) - if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - # CMake tries to treat /sw and /opt/local as extension of the system path, but - # that doesn't really work out very well. Once you have a dependency that - # resides there and have to add it as an include directory, then any other - # dependency that could be satisfied from there must be--otherwise you can end - # up with conflicting versions. So, let's make them more of a priority having - # them be included as one of the first places to look for dependencies. - list(APPEND CMAKE_PREFIX_PATH /sw /opt/local) - # If the macOS deployment target is not set manually (via $MACOSX_DEPLOYMENT_TARGET), # fall back to local system version. Needs to be done both here and in cmake.deps. if(NOT CMAKE_OSX_DEPLOYMENT_TARGET) @@ -92,14 +90,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") OUTPUT_STRIP_TRAILING_WHITESPACE) set(CMAKE_OSX_DEPLOYMENT_TARGET "${MACOS_VERSION}") endif() - message("Using deployment target ${CMAKE_OSX_DEPLOYMENT_TARGET}") - - # Work around some old, broken detection by CMake for knowing when to use the - # isystem flag. Apple's compilers have supported this for quite some time - # now. - if(CMAKE_C_COMPILER_ID MATCHES "GNU") - set(CMAKE_INCLUDE_SYSTEM_FLAG_C "-isystem ") - endif() + message(STATUS "Using deployment target ${CMAKE_OSX_DEPLOYMENT_TARGET}") endif() if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Darwin") @@ -109,8 +100,6 @@ if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Darwin") set(USE_FNAME_CASE TRUE) endif() -option(ENABLE_LIBINTL "enable libintl" ON) -option(ENABLE_LIBICONV "enable libiconv" ON) if (MINGW) # Disable LTO by default as it may not compile # See https://github.com/Alexpux/MINGW-packages/issues/3516 @@ -137,9 +126,9 @@ set(NVIM_VERSION_PATCH 0) set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers # API level -set(NVIM_API_LEVEL 10) # Bump this after any API change. +set(NVIM_API_LEVEL 11) # Bump this after any API change. set(NVIM_API_LEVEL_COMPAT 0) # Adjust this after a _breaking_ API change. -set(NVIM_API_PRERELEASE false) +set(NVIM_API_PRERELEASE true) set(NVIM_VERSION_BUILD_TYPE "${CMAKE_BUILD_TYPE}") # NVIM_VERSION_CFLAGS set further below. @@ -189,7 +178,6 @@ endif() # gcc 4.0+ sets _FORTIFY_SOURCE=2 automatically. This currently # does not work with Neovim due to some uses of dynamically-sized structures. # https://github.com/neovim/neovim/issues/223 -include(CheckCSourceCompiles) # Include the build type's default flags in the check for _FORTIFY_SOURCE, # otherwise we may incorrectly identify the level as acceptable and find out @@ -201,309 +189,8 @@ if(${INIT_FLAGS_NAME}) set(CMAKE_REQUIRED_FLAGS "${${INIT_FLAGS_NAME}}") endif() -# Include <string.h> because some toolchains define _FORTIFY_SOURCE=2 in -# internal header files, which should in turn be #included by <string.h>. -check_c_source_compiles(" -#include <string.h> - -#if defined(_FORTIFY_SOURCE) && _FORTIFY_SOURCE > 1 -#error \"_FORTIFY_SOURCE > 1\" -#endif -int -main(void) -{ - return 0; -} -" HAS_ACCEPTABLE_FORTIFY) - -if(NOT HAS_ACCEPTABLE_FORTIFY) - message(STATUS "Unsupported _FORTIFY_SOURCE found, forcing _FORTIFY_SOURCE=1") - # Extract possible prefix to _FORTIFY_SOURCE (e.g. -Wp,-D_FORTIFY_SOURCE). - STRING(REGEX MATCH "[^\ ]+-D_FORTIFY_SOURCE" _FORTIFY_SOURCE_PREFIX "${CMAKE_C_FLAGS}") - STRING(REPLACE "-D_FORTIFY_SOURCE" "" _FORTIFY_SOURCE_PREFIX "${_FORTIFY_SOURCE_PREFIX}" ) - if(NOT _FORTIFY_SOURCE_PREFIX STREQUAL "") - message(STATUS "Detected _FORTIFY_SOURCE Prefix=${_FORTIFY_SOURCE_PREFIX}") - endif() - # -U in add_definitions doesn't end up in the correct spot, so we add it to - # the flags variable instead. - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${_FORTIFY_SOURCE_PREFIX}-U_FORTIFY_SOURCE ${_FORTIFY_SOURCE_PREFIX}-D_FORTIFY_SOURCE=1") -endif() - -# Remove --sort-common from linker flags, as this seems to cause bugs (see #2641, #3374). -# TODO: Figure out the root cause. -if(CMAKE_EXE_LINKER_FLAGS MATCHES "--sort-common" OR - CMAKE_SHARED_LINKER_FLAGS MATCHES "--sort-common" OR - CMAKE_MODULE_LINKER_FLAGS MATCHES "--sort-common") - message(STATUS "Removing --sort-common from linker flags") - string(REGEX REPLACE ",--sort-common(=[^,]+)?" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") - string(REGEX REPLACE ",--sort-common(=[^,]+)?" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}") - string(REGEX REPLACE ",--sort-common(=[^,]+)?" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}") - - # If no linker flags remain for a -Wl argument, remove it. - # '-Wl$' will match LDFLAGS="-Wl,--sort-common", - # '-Wl ' will match LDFLAGS="-Wl,--sort-common -Wl,..." - string(REGEX REPLACE "-Wl($| )" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") - string(REGEX REPLACE "-Wl($| )" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}") - string(REGEX REPLACE "-Wl($| )" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}") -endif() - -check_c_source_compiles(" -#include <execinfo.h> -int main(void) -{ - void *trace[1]; - backtrace(trace, 1); - return 0; -} -" HAVE_EXECINFO_BACKTRACE) - -check_c_source_compiles(" -int main(void) -{ - int a = 42; - __builtin_add_overflow(a, a, &a); - __builtin_sub_overflow(a, a, &a); - return 0; -} -" HAVE_BUILTIN_ADD_OVERFLOW) - -option(ENABLE_COMPILER_SUGGESTIONS "Enable -Wsuggest compiler warnings" OFF) -if(MSVC) - # XXX: /W4 gives too many warnings. #3241 - add_compile_options(/W3) - add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE) - add_definitions(-DMSWIN) -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) - if(HAVE_WIMPLICIT_FALLTHROUGH_FLAG) - add_compile_options(-Wimplicit-fallthrough) - endif() - - if(ENABLE_COMPILER_SUGGESTIONS) - # 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() - 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") - add_compile_options(-Wno-c11-extensions) - endif() -endif() - -if(MINGW) - # Use POSIX compatible stdio in Mingw - add_definitions(-D__USE_MINGW_ANSI_STDIO) - add_definitions(-DMSWIN) -endif() -if(WIN32) - # Windows Vista is the minimum supported version - add_definitions(-D_WIN32_WINNT=0x0600) -endif() - -# OpenBSD's GCC (4.2.1) doesn't have -Wvla -check_c_compiler_flag(-Wvla HAS_WVLA_FLAG) -if(HAS_WVLA_FLAG) - add_compile_options(-Wvla) -endif() - -if(UNIX) - # -fstack-protector breaks non Unix builds even in Mingw-w64 - check_c_compiler_flag(-fstack-protector-strong HAS_FSTACK_PROTECTOR_STRONG_FLAG) - check_c_compiler_flag(-fstack-protector HAS_FSTACK_PROTECTOR_FLAG) - - if(HAS_FSTACK_PROTECTOR_STRONG_FLAG) - add_compile_options(-fstack-protector-strong) - link_libraries(-fstack-protector-strong) - elseif(HAS_FSTACK_PROTECTOR_FLAG) - add_compile_options(-fstack-protector --param ssp-buffer-size=4) - link_libraries(-fstack-protector --param ssp-buffer-size=4) - endif() -endif() - -check_c_compiler_flag(-fno-common HAVE_FNO_COMMON) -if (HAVE_FNO_COMMON) - add_compile_options(-fno-common) -endif() - -check_c_compiler_flag(-fdiagnostics-color=auto HAS_DIAG_COLOR_FLAG) -if(HAS_DIAG_COLOR_FLAG) - if(CMAKE_GENERATOR MATCHES "Ninja") - add_compile_options(-fdiagnostics-color=always) - else() - add_compile_options(-fdiagnostics-color=auto) - endif() -endif() - -option(CI_BUILD "CI, extra flags will be set" OFF) - -if(CI_BUILD) - message(STATUS "CI build enabled") - add_compile_options(-Werror) - if(DEFINED ENV{BUILD_UCHAR}) - # Get some test coverage for unsigned char - add_compile_options(-funsigned-char) - endif() -endif() - option(LOG_LIST_ACTIONS "Add list actions logging" OFF) -add_definitions(-DINCLUDE_GENERATED_DECLARATIONS) - -if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang") - if(CMAKE_SYSTEM_NAME STREQUAL "SunOS") - set(NO_UNDEFINED "-Wl,--no-undefined -lsocket") - elseif(NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin") - set(NO_UNDEFINED "-Wl,--no-undefined") - endif() - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${NO_UNDEFINED}") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${NO_UNDEFINED}") - - # For O_CLOEXEC, O_DIRECTORY, and O_NOFOLLOW flags on older systems - # (pre POSIX.1-2008: glibc 2.11 and earlier). #4042 - # For ptsname(). #6743 - add_definitions(-D_GNU_SOURCE) -endif() - -if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_SIZEOF_VOID_P EQUAL 8 AND NOT PREFER_LUA AND LUAJIT_VERSION LESS "2.1.0-beta3") - # Required for luajit < 2.1.0-beta3. - set(CMAKE_EXE_LINKER_FLAGS - "${CMAKE_EXE_LINKER_FLAGS} -pagezero_size 10000 -image_base 100000000") - set(CMAKE_SHARED_LINKER_FLAGS - "${CMAKE_SHARED_LINKER_FLAGS} -image_base 100000000") - set(CMAKE_MODULE_LINKER_FLAGS - "${CMAKE_MODULE_LINKER_FLAGS} -image_base 100000000") -endif() - -include_directories("${PROJECT_BINARY_DIR}/cmake.config") -include_directories("${PROJECT_SOURCE_DIR}/src") - -find_package(LibUV 1.28.0 REQUIRED) -include_directories(SYSTEM ${LIBUV_INCLUDE_DIRS}) - -find_package(Msgpack 1.0.0 REQUIRED) -include_directories(SYSTEM ${MSGPACK_INCLUDE_DIRS}) - -find_package(LibLUV 1.43.0 REQUIRED) -include_directories(SYSTEM ${LIBLUV_INCLUDE_DIRS}) - -find_package(TreeSitter REQUIRED) -include_directories(SYSTEM ${TreeSitter_INCLUDE_DIRS}) - -list(APPEND CMAKE_REQUIRED_INCLUDES "${TreeSitter_INCLUDE_DIRS}") -list(APPEND CMAKE_REQUIRED_LIBRARIES "${TreeSitter_LIBRARIES}") -check_c_source_compiles(" -#include <tree_sitter/api.h> -int -main(void) -{ - TSQueryCursor *cursor = ts_query_cursor_new(); - ts_query_cursor_set_match_limit(cursor, 32); - return 0; -} -" TS_HAS_SET_MATCH_LIMIT) -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() - -# The unit test lib requires LuaJIT; it will be skipped if LuaJIT is missing. -option(PREFER_LUA "Prefer Lua over LuaJIT in the nvim executable." OFF) - -if(PREFER_LUA) - find_package(Lua 5.1 EXACT REQUIRED) - set(LUA_PREFERRED_INCLUDE_DIRS ${LUA_INCLUDE_DIR}) - set(LUA_PREFERRED_LIBRARIES ${LUA_LIBRARIES}) - # Passive (not REQUIRED): if LUAJIT_FOUND is not set, nvim-test is skipped. - find_package(LuaJit) -else() - find_package(LuaJit REQUIRED) - set(LUA_PREFERRED_INCLUDE_DIRS ${LUAJIT_INCLUDE_DIRS}) - set(LUA_PREFERRED_LIBRARIES ${LUAJIT_LIBRARIES}) -endif() - -list(APPEND CMAKE_REQUIRED_INCLUDES "${MSGPACK_INCLUDE_DIRS}") -check_c_source_compiles(" -#include <msgpack.h> - -int -main(void) -{ - return MSGPACK_OBJECT_FLOAT32; -} -" MSGPACK_HAS_FLOAT32) -list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${MSGPACK_INCLUDE_DIRS}") -if(MSGPACK_HAS_FLOAT32) - add_definitions(-DNVIM_MSGPACK_HAS_FLOAT32) -endif() - -option(FEAT_TUI "Enable the Terminal UI" ON) - -if(FEAT_TUI) - find_package(UNIBILIUM 2.0 REQUIRED) - include_directories(SYSTEM ${UNIBILIUM_INCLUDE_DIRS}) - - list(APPEND CMAKE_REQUIRED_INCLUDES "${UNIBILIUM_INCLUDE_DIRS}") - list(APPEND CMAKE_REQUIRED_LIBRARIES "${UNIBILIUM_LIBRARIES}") - check_c_source_compiles(" - #include <unibilium.h> - - int - main(void) - { - return unibi_num_from_var(unibi_var_from_num(0)); - } - " UNIBI_HAS_VAR_FROM) - list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${UNIBILIUM_INCLUDE_DIRS}") - list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES "${UNIBILIUM_LIBRARIES}") - if(UNIBI_HAS_VAR_FROM) - add_definitions(-DNVIM_UNIBI_HAS_VAR_FROM) - endif() - - find_package(LibTermkey 0.22 REQUIRED) - include_directories(SYSTEM ${LIBTERMKEY_INCLUDE_DIRS}) -endif() - -find_package(LIBVTERM 0.3 REQUIRED) -include_directories(SYSTEM ${LIBVTERM_INCLUDE_DIRS}) - option(CLANG_ASAN_UBSAN "Enable Clang address & undefined behavior sanitizer for nvim binary." OFF) option(CLANG_MSAN "Enable Clang memory sanitizer for nvim binary." OFF) option(CLANG_TSAN "Enable Clang thread sanitizer for nvim binary." OFF) @@ -518,22 +205,6 @@ if((CLANG_ASAN_UBSAN OR CLANG_MSAN OR CLANG_TSAN) AND NOT CMAKE_C_COMPILER_ID MA message(FATAL_ERROR "Sanitizers are only supported for Clang") endif() -if(ENABLE_LIBICONV) - find_package(Iconv REQUIRED) - include_directories(SYSTEM ${Iconv_INCLUDE_DIRS}) -endif() - -if(ENABLE_LIBINTL) - # LibIntl (not Intl) selects our FindLibIntl.cmake script. #8464 - find_package(LibIntl REQUIRED) - include_directories(SYSTEM ${LibIntl_INCLUDE_DIRS}) -endif() - -# Determine platform's threading library. Set CMAKE_THREAD_PREFER_PTHREAD -# explicitly to indicate a strong preference for pthread. -set(CMAKE_THREAD_PREFER_PTHREAD ON) -find_package(Threads REQUIRED) - # Place targets in bin/ or lib/ for all build configurations set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) @@ -545,8 +216,6 @@ foreach(CFGNAME ${CMAKE_CONFIGURATION_TYPES}) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${CFGNAME} ${CMAKE_BINARY_DIR}/lib) endforeach() -# Find Lua interpreter -include(LuaHelpers) set(LUA_DEPENDENCIES lpeg mpack bit) if(NOT LUA_PRG) foreach(CURRENT_LUA_PRG luajit lua5.1 lua5.2 lua) @@ -573,6 +242,17 @@ endif() message(STATUS "Using Lua interpreter: ${LUA_PRG}") +# Some of the code generation still relies on stable table ordering in order to +# produce reproducible output - specifically the msgpack'ed data in +# funcs_metadata.generated.h and ui_events_metadata.generated.h. This should +# ideally be fixed in the generators, but until then as a workaround you may provide +# a specific lua implementation that provides the needed stability by setting LUA_GEN_PRG: +if(NOT LUA_GEN_PRG) + set(LUA_GEN_PRG "${LUA_PRG}" CACHE FILEPATH "Path to the lua used for code generation.") +endif() + +message(STATUS "Using Lua interpreter for code generation: ${LUA_GEN_PRG}") + option(COMPILE_LUA "Pre-compile Lua sources into bytecode (for sources that are included in the binary)" ON) if(COMPILE_LUA AND NOT WIN32) @@ -596,60 +276,45 @@ 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) -if(NOT BUSTED_OUTPUT_TYPE) - set(BUSTED_OUTPUT_TYPE "nvim") -endif() - # # Lint # find_program(LUACHECK_PRG luacheck) find_program(STYLUA_PRG stylua) -find_program(FLAKE8_PRG flake8) find_program(UNCRUSTIFY_PRG uncrustify) find_program(SHELLCHECK_PRG shellcheck) -add_glob_targets( +add_glob_target( REQUIRED TARGET lintlua-luacheck COMMAND ${LUACHECK_PRG} FLAGS -q GLOB_DIRS runtime/ scripts/ src/ test/ GLOB_PAT *.lua - TOUCH_STRATEGY SINGLE - ) + TOUCH_STRATEGY SINGLE) -add_glob_targets( +add_glob_target( TARGET lintlua-stylua COMMAND ${STYLUA_PRG} FLAGS --color=always --check GLOB_DIRS runtime/ GLOB_PAT *.lua - TOUCH_STRATEGY SINGLE - ) + TOUCH_STRATEGY SINGLE) add_custom_target(lintlua) add_dependencies(lintlua lintlua-luacheck lintlua-stylua) -include(InstallHelpers) -add_glob_targets( - TARGET lintpy - COMMAND ${FLAKE8_PRG} - FLAGS --max-line-length 100 - GLOB_DIRS contrib scripts src test - GLOB_PAT *.py - TOUCH_STRATEGY SINGLE - ) - -add_glob_targets( +add_glob_target( TARGET lintsh COMMAND ${SHELLCHECK_PRG} - FILES scripts/vim-patch.sh - TOUCH_STRATEGY SINGLE - ) + FLAGS -x -a + GLOB_DIRS scripts ci + GLOB_PAT *.sh + EXCLUDE + scripts/pvscheck.sh + ci/common + ci/snap + TOUCH_STRATEGY SINGLE) add_custom_target(lintcommit COMMAND ${PROJECT_BINARY_DIR}/bin/nvim -u NONE -es -c [[lua require('scripts.lintcommit').main({trace=false})]] @@ -658,7 +323,7 @@ add_custom_target(lintcommit add_dependencies(lintcommit nvim) add_custom_target(lint) -add_dependencies(lint check-single-includes lintc lintlua lintpy lintsh lintcommit lintuncrustify) +add_dependencies(lint check-single-includes lintc lintlua lintsh lintcommit) # # Format @@ -683,7 +348,6 @@ install_helper( add_subdirectory(src/nvim) get_directory_property(NVIM_VERSION_CFLAGS DIRECTORY src/nvim DEFINITION NVIM_VERSION_CFLAGS) -add_subdirectory(test/includes) add_subdirectory(cmake.config) add_subdirectory(test/functional/fixtures) # compile test programs add_subdirectory(runtime) @@ -694,19 +358,19 @@ if(WIN32) DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/nvim-qt/runtime/plugin) endif() +# Setup busted. +find_program(BUSTED_PRG NAMES busted busted.bat) +find_program(BUSTED_LUA_PRG busted-lua) +if(NOT BUSTED_OUTPUT_TYPE) + set(BUSTED_OUTPUT_TYPE "nvim") +endif() + # Setup some test-related bits. We do this after going down the tree because we # need some of the targets. if(BUSTED_PRG) - get_property(TEST_INCLUDE_DIRS DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - PROPERTY INCLUDE_DIRECTORIES) - - # When running tests from 'ninja' we need to use the - # console pool: to do so we need to use the USES_TERMINAL - # option, but this is only available in CMake 3.2 - set(TEST_TARGET_ARGS) - list(APPEND TEST_TARGET_ARGS "USES_TERMINAL") + get_target_property(TEST_INCLUDE_DIRS main_lib INTERFACE_INCLUDE_DIRECTORIES) - set(UNITTEST_PREREQS nvim-test unittest-headers) + set(UNITTEST_PREREQS nvim-test) set(FUNCTIONALTEST_PREREQS nvim printenv-test printargs-test shell-test pwsh-test streams-test tty-test ${GENERATED_HELP_TAGS}) set(BENCHMARK_PREREQS nvim tty-test) @@ -721,9 +385,10 @@ if(BUSTED_PRG) -DTEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/test -DBUILD_DIR=${CMAKE_BINARY_DIR} -DTEST_TYPE=unit + -DCIRRUS_CI=$ENV{CIRRUS_CI} -P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake DEPENDS ${UNITTEST_PREREQS} - ${TEST_TARGET_ARGS}) + USES_TERMINAL) set_target_properties(unittest PROPERTIES FOLDER test) else() message(WARNING "disabling unit tests: no Luajit FFI in ${LUA_PRG}") @@ -751,9 +416,10 @@ if(BUSTED_PRG) -DTEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/test -DBUILD_DIR=${CMAKE_BINARY_DIR} -DTEST_TYPE=functional + -DCIRRUS_CI=$ENV{CIRRUS_CI} -P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake DEPENDS ${FUNCTIONALTEST_PREREQS} - ${TEST_TARGET_ARGS}) + USES_TERMINAL) set_target_properties(functionaltest PROPERTIES FOLDER test) add_custom_target(benchmark @@ -766,9 +432,10 @@ if(BUSTED_PRG) -DTEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/test -DBUILD_DIR=${CMAKE_BINARY_DIR} -DTEST_TYPE=benchmark + -DCIRRUS_CI=$ENV{CIRRUS_CI} -P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake DEPENDS ${BENCHMARK_PREREQS} - ${TEST_TARGET_ARGS}) + USES_TERMINAL) set_target_properties(benchmark PROPERTIES FOLDER test) endif() @@ -783,9 +450,10 @@ if(BUSTED_LUA_PRG) -DTEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/test -DBUILD_DIR=${CMAKE_BINARY_DIR} -DTEST_TYPE=functional + -DCIRRUS_CI=$ENV{CIRRUS_CI} -P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake DEPENDS ${FUNCTIONALTEST_PREREQS} - ${TEST_TARGET_ARGS}) + USES_TERMINAL) set_target_properties(functionaltest-lua PROPERTIES FOLDER test) endif() diff --git a/CMakePresets.json b/CMakePresets.json index b2c93792b0..62abb2697f 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,5 +1,5 @@ { - "version": 3, + "version": 6, "configurePresets": [ { "name": "base", @@ -14,9 +14,7 @@ "cacheVariables": { "CMAKE_BUILD_TYPE": "RelWithDebInfo" }, - "inherits": [ - "base" - ] + "inherits": ["base"] }, { "name": "debug", @@ -25,9 +23,7 @@ "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }, - "inherits": [ - "base" - ] + "inherits": ["base"] }, { "name": "release", @@ -36,9 +32,7 @@ "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" }, - "inherits": [ - "base" - ] + "inherits": ["base"] }, { "name": "windows-default", @@ -54,9 +48,7 @@ }, "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { - "hostOS": [ - "Windows" - ] + "hostOS": ["Windows"] } }, "condition": { @@ -64,9 +56,16 @@ "lhs": "${hostSystemName}", "rhs": "Windows" }, - "inherits": [ - "base" - ] + "inherits": ["base"] + }, + { + "name": "iwyu", + "displayName": "IWYU", + "description": "Run include-what-you-use with the compiler", + "cacheVariables": { + "ENABLE_IWYU": "ON" + }, + "inherits": ["base"] } ], "buildPresets": [ @@ -90,6 +89,25 @@ "lhs": "${hostSystemName}", "rhs": "Windows" } + }, + { + "name": "iwyu", + "configurePreset": "iwyu" + } + ], + "workflowPresets": [ + { + "name": "iwyu", + "steps": [ + { + "type": "configure", + "name": "iwyu" + }, + { + "type": "build", + "name": "iwyu" + } + ] } ] } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 03fe48fed7..ce0f2ace05 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,10 @@ low-risk/isolated tasks: - Fix bugs found by [Clang](#clang-scan-build), [PVS](#pvs-studio) or [Coverity](#coverity). - [Improve documentation](#documenting) -- [Merge a Vim patch] (familiarity with Vim is *strongly* recommended) +- [Merge a Vim patch] (requires strong familiarity with Vim) + - NOTE: read the above link before sending improvements to "runtime files" (anything in `runtime/`). + - Vimscript and documentation files are (mostly) maintained by [Vim](https://github.com/vim/vim), not Nvim. + - Lua files are maintained by Nvim. Reporting problems ------------------ @@ -113,10 +116,7 @@ Each pull request must pass the automated builds on [Cirrus CI] and [GitHub Acti - CI builds are compiled with [`-Werror`][gcc-warnings], so compiler warnings will fail the build. -- If any tests fail, the build will fail. - See [test/README.md#running-tests][run-tests] to run tests locally. - Passing locally doesn't guarantee passing the CI build, because of the - different compilers and platforms tested against. +- If any tests fail, the build will fail. See [test/README.md#running-tests][run-tests] to run tests locally. - CI runs [ASan] and other analyzers. - To run valgrind locally: `VALGRIND=1 make test` - To run Clang ASan/UBSan locally: `CC=clang make CMAKE_FLAGS="-DCLANG_ASAN_UBSAN=ON"` @@ -124,6 +124,8 @@ Each pull request must pass the automated builds on [Cirrus CI] and [GitHub Acti neighbors_, to encourage incrementally updating the legacy style to meet our [style](#style). (See [#3174][3174] for background.) - CI for FreeBSD runs on [Cirrus CI]. +- To see CI results faster in your PR, you can temporarily set `TEST_FILE` in + [ci.yml](https://github.com/neovim/neovim/blob/e35b9020b16985eee26e942f9a3f6b045bc3809b/.github/workflows/ci.yml#L205). ### Clang scan-build @@ -244,9 +246,14 @@ You can lint a single file (but this will _not_ exclude legacy errors): For managing includes in C files, use [include-what-you-use]. - [Install include-what-you-use][include-what-you-use-install] -- Run with: +- To see which includes needs fixing use the cmake preset `iwyu`: ``` - make CMAKE_EXTRA_FLAGS=-DCMAKE_C_INCLUDE_WHAT_YOU_USE=include-what-you-use | tee iwyu.txt + cmake --workflow --preset iwyu + ``` +- There's also a make target that automatically fixes the suggestions from + IWYU: + ``` + make iwyu ``` See [#549][549] for more details. diff --git a/LICENSE.txt b/LICENSE.txt index a772829aa3..45a4a34c56 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -8,7 +8,7 @@ Neovim's license follows: ==== Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ + https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION diff --git a/MAINTAIN.md b/MAINTAIN.md index d65337f92b..95a3916535 100644 --- a/MAINTAIN.md +++ b/MAINTAIN.md @@ -64,7 +64,7 @@ Third-party dependencies ------------------------ These "bundled" dependencies can be updated by bumping their versions in `cmake.deps/CMakeLists.txt`. -Some can be auto-bumped by `scripts/bump-deps.sh`. +Some can be auto-bumped by `scripts/bump_deps.lua`. * [LuaJIT](https://github.com/LuaJIT/LuaJIT) * [Lua](https://www.lua.org/download.html) @@ -98,6 +98,36 @@ These dependencies are "vendored" (inlined), we must update the sources manually We may maintain forks, if we are waiting on upstream changes: https://github.com/neovim/neovim/wiki/Deps +CI +-------------- + +### General + +As our CI is primarily dependent on GitHub Actions at the moment, then so will +our CI strategy be. The following guidelines have worked well for us so far: + +* Never use a macOS runner if an Ubuntu or a Windows runner can be used + instead. This is because macOS runners have a [tighter restrictions on the + number of concurrent jobs](https://docs.github.com/en/actions/learn-github-actions/usage-limits-billing-and-administration#usage-limits). + +### Runner versions + +* For special-purpose jobs where the runner version doesn't really matter, + prefer `-latest` tags so we don't need to manually bump the versions. An + example of a special-purpose workflow is `labeler.yml`. + +* For our testing jobs, which is currently only `ci.yml`, prefer to use the + latest stable (i.e. non-beta) version explicitly. Avoid using the `-latest` + tags here as it makes it difficult to determine from an unrelated PR if a + failure is due to the PR itself or due to GitHub bumping the `-latest` tag + without our knowledge. There's also a high risk that automatic bumping the CI + versions will fail due to manual work being required from experience. + +* For our release job, which is `release.yml`, prefer to use the oldest stable + (i.e. non-deprecated) versions available. The reason is that we're trying to + produce images that work in the broadest number of environments, and + therefore want to use older releases. + See also -------- @@ -66,8 +66,7 @@ ifeq ($(CMAKE_GENERATOR),Ninja) endif DEPS_CMAKE_FLAGS ?= -# Back-compat: USE_BUNDLED_DEPS was the old name. -USE_BUNDLED ?= $(USE_BUNDLED_DEPS) +USE_BUNDLED ?= ifneq (,$(USE_BUNDLED)) BUNDLED_CMAKE_FLAG := -DUSE_BUNDLED=$(USE_BUNDLED) @@ -127,13 +126,21 @@ endif src/nvim/testdir/%.vim: phony_force +$(SINGLE_MAKE) -C src/nvim/testdir NVIM_PRG=$(NVIM_PRG) SCRIPTS= $(MAKEOVERRIDES) $(patsubst src/nvim/testdir/%.vim,%,$@) -functionaltest functionaltest-lua unittest benchmark: | nvim +functionaltest-lua: | nvim $(BUILD_TOOL) -C build $@ -lintlua lintsh lintpy lintuncrustify lintc lintcfull check-single-includes generated-sources lintcommit lint formatc formatlua format: | build/.ran-cmake +FORMAT=formatc formatlua format +LINT=lintlua lintsh lintc check-single-includes lintcommit lint +TEST=functionaltest unittest +generated-sources benchmark $(FORMAT) $(LINT) $(TEST): | build/.ran-cmake $(CMAKE_PRG) --build build --target $@ -test: functionaltest unittest +test: $(TEST) + +iwyu: build/.ran-cmake + cmake --workflow --fresh --preset iwyu > build/iwyu.log + iwyu-fix-includes --only_re="src/nvim" --ignore_re="src/nvim/(auto|map.h|eval/encode.c)" --safe_headers < build/iwyu.log + cmake -B build -U ENABLE_IWYU clean: +test -d build && $(BUILD_TOOL) -C build clean || true @@ -166,4 +173,4 @@ $(DEPS_BUILD_DIR)/%: phony_force $(BUILD_TOOL) -C $(DEPS_BUILD_DIR) $(patsubst $(DEPS_BUILD_DIR)/%,%,$@) endif -.PHONY: test lintlua lintpy lintsh functionaltest unittest lint lintc clean distclean nvim libnvim cmake deps install appimage checkprefix lintcommit formatc formatlua format +.PHONY: test clean distclean nvim libnvim cmake deps install appimage checkprefix benchmark $(FORMAT) $(LINT) $(TEST) @@ -1,22 +1,19 @@ <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) | -[Twitter](https://twitter.com/Neovim) + <a href="https://neovim.io/doc/">Documentation</a> | + <a href="https://app.element.io/#/room/#neovim:matrix.org">Chat</a> +</h1> -[](https://github.com/neovim/neovim/actions?query=workflow%3ACI+branch%3Amaster+event%3Apush) +[](https://github.com/neovim/neovim/actions?query=workflow%3ACI+branch%3Amaster+event%3Apush) [](https://scan.coverity.com/projects/2227) [](https://neovim.io/doc/reports/clang) [](https://neovim.io/doc/reports/pvs/PVS-studio.html.d) - [](https://repology.org/metapackage/neovim) [](https://buildd.debian.org/neovim) [](https://github.com/neovim/neovim/releases/) -[](https://snapcraft.io/nvim) -Neovim is a project that seeks to aggressively refactor Vim in order to: +Neovim is a project that seeks to aggressively refactor [Vim](https://www.vim.org/) in order to: - Simplify maintenance and encourage [contributions](CONTRIBUTING.md) - Split the work between multiple developers @@ -31,7 +28,7 @@ Features - Modern [GUIs](https://github.com/neovim/neovim/wiki/Related-projects#gui) - [API access](https://github.com/neovim/neovim/wiki/Related-projects#api-clients) - from any language including C/C++, C#, Clojure, D, Elixir, Go, Haskell, Java, + from any language including C/C++, C#, Clojure, D, Elixir, Go, Haskell, Java/Kotlin, JavaScript/Node.js, Julia, Lisp, Lua, Perl, Python, Racket, Ruby, Rust - Embedded, scriptable [terminal emulator](https://neovim.io/doc/user/nvim_terminal_emulator.html) - Asynchronous [job control](https://github.com/neovim/neovim/pull/2247) @@ -39,8 +36,7 @@ Features - [XDG base directories](https://github.com/neovim/neovim/pull/3470) support - Compatible with most Vim plugins, including Ruby and Python plugins -See [`:help nvim-features`][nvim-features] for the full list, and `[:help -news][nvim-news]` for noteworthy changes in the latest version! +See [`:help nvim-features`][nvim-features] for the full list, and [`:help news`][nvim-news] for noteworthy changes in the latest version! Install from package -------------------- @@ -53,7 +49,7 @@ Pre-built packages for Windows, macOS, and Linux are found on the Install from source ------------------- -See the [Building Neovim](https://github.com/neovim/neovim/wiki/Building-Neovim) wiki page for details. +See the [Building Neovim](https://github.com/neovim/neovim/wiki/Building-Neovim) wiki page and [supported platforms](https://neovim.io/doc/user/support.html#supported-platforms) for details. The build is CMake-based, but a Makefile is provided as a convenience. After installing the dependencies, run the following command. @@ -108,9 +104,9 @@ Apache 2.0 license, except for contributions copied from Vim (identified by the encouraged to make a donation for needy children in Uganda. Please see the kcc section of the vim docs or visit the ICCF web site, available at these URLs: - http://iccf-holland.org/ - http://www.vim.org/iccf/ - http://www.iccf.nl/ + https://iccf-holland.org/ + https://www.vim.org/iccf/ + https://www.iccf.nl/ You can also sponsor the development of Vim. Vim sponsors can vote for features. The money goes to Uganda anyway. @@ -122,7 +118,7 @@ Apache 2.0 license, except for contributions copied from Vim (identified by the [advanced UIs]: https://github.com/neovim/neovim/wiki/Related-projects#gui [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 +[Ubuntu]: https://packages.ubuntu.com/search?keywords=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 diff --git a/ci/before_cache.sh b/ci/before_cache.sh index 9bc9bb45e9..3daeb04793 100755 --- a/ci/before_cache.sh +++ b/ci/before_cache.sh @@ -4,7 +4,9 @@ set -e set -o pipefail CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source-path=SCRIPTDIR source "${CI_DIR}/common/build.sh" +# shellcheck source-path=SCRIPTDIR source "${CI_DIR}/common/suite.sh" mkdir -p "${HOME}/.cache" diff --git a/ci/before_script.sh b/ci/before_script.sh index 08e0cb9103..066789af36 100755 --- a/ci/before_script.sh +++ b/ci/before_script.sh @@ -4,6 +4,7 @@ set -e set -o pipefail CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source-path=SCRIPTDIR source "${CI_DIR}/common/build.sh" # Test some of the configuration variables. diff --git a/ci/build.ps1 b/ci/build.ps1 deleted file mode 100644 index f6ad744fa1..0000000000 --- a/ci/build.ps1 +++ /dev/null @@ -1,146 +0,0 @@ -[CmdletBinding(DefaultParameterSetName = "Build")] -param( - [Parameter(ParameterSetName="Build")][switch]$Build, - [Parameter(ParameterSetName="BuildDeps")][switch]$BuildDeps, - [Parameter(ParameterSetName="EnsureTestDeps")][switch]$EnsureTestDeps, - [Parameter(ParameterSetName="Package")][switch]$Package, - [Parameter(ParameterSetName="Test")][switch]$Test, - [Parameter(ParameterSetName="TestOld")][switch]$TestOld -) - -Set-StrictMode -Version Latest -$ErrorActionPreference = 'Stop' -$ProgressPreference = 'SilentlyContinue' - -$projectDir = [System.IO.Path]::GetFullPath("$(Get-Location)") -$buildDir = Join-Path -Path $projectDir -ChildPath "build" - -# $env:CMAKE_BUILD_TYPE is ignored by cmake when not using ninja -$cmakeBuildType = $(if ($null -ne $env:CMAKE_BUILD_TYPE) {$env:CMAKE_BUILD_TYPE} else {'RelWithDebInfo'}); -$depsCmakeVars = @{ - CMAKE_BUILD_TYPE=$cmakeBuildType; -} -$nvimCmakeVars = @{ - CMAKE_BUILD_TYPE=$cmakeBuildType; - BUSTED_OUTPUT_TYPE = 'nvim'; - DEPS_PREFIX=$(if ($null -ne $env:DEPS_PREFIX) {$env:DEPS_PREFIX} else {".deps/usr"}); -} -if ($null -eq $env:DEPS_BUILD_DIR) { - $env:DEPS_BUILD_DIR = Join-Path -Path $projectDir -ChildPath ".deps" -} -$uploadToCodeCov = $false - -function exitIfFailed() { - if ($LastExitCode -ne 0) { - exit $LastExitCode - } -} - -function convertToCmakeArgs($vars) { - return $vars.GetEnumerator() | ForEach-Object { "-D$($_.Key)=$($_.Value)" } -} - -$installationPath = vswhere.exe -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath -if ($installationPath -and (Test-Path "$installationPath\Common7\Tools\vsdevcmd.bat")) { - & "${env:COMSPEC}" /s /c "`"$installationPath\Common7\Tools\vsdevcmd.bat`" -arch=x64 -no_logo && set" | ForEach-Object { - $name, $value = $_ -split '=', 2 - Set-Content env:\"$name" $value - } -} - -function BuildDeps { - - if (Test-Path -PathType container $env:DEPS_BUILD_DIR) { - $cachedBuildTypeStr = $(Get-Content $env:DEPS_BUILD_DIR\CMakeCache.txt | Select-String -Pattern "CMAKE_BUILD_TYPE.*=($cmakeBuildType)") - if (-not $cachedBuildTypeStr) { - Write-Warning " unable to validate build type from cache dir ${env:DEPS_BUILD_DIR}" - } - } - - # we currently can't use ninja for cmake.deps, see #19405 - $depsCmakeGenerator = "Visual Studio 16 2019" - $depsCmakeGeneratorPlf = "x64" - cmake -S "$projectDir\cmake.deps" -B $env:DEPS_BUILD_DIR -G $depsCmakeGenerator -A $depsCmakeGeneratorPlf $(convertToCmakeArgs($depsCmakeVars)); exitIfFailed - - $depsCmakeNativeToolOptions= @('/verbosity:normal', '/m') - cmake --build $env:DEPS_BUILD_DIR --config $cmakeBuildType -- $depsCmakeNativeToolOptions; exitIfFailed -} - -function Build { - cmake -S $projectDir -B $buildDir $(convertToCmakeArgs($nvimCmakeVars)) -G Ninja; exitIfFailed - cmake --build $buildDir --config $cmakeBuildType; exitIfFailed -} - -function EnsureTestDeps { - & $buildDir\bin\nvim.exe "--version"; exitIfFailed - - # Ensure that the "win32" feature is set. - & $buildDir\bin\nvim -u NONE --headless -c 'exe !has(\"win32\").\"cq\"' ; exitIfFailed - - python -m pip install pynvim - # Sanity check - python -c "import pynvim; print(str(pynvim))"; exitIfFailed - - gem.cmd install --pre neovim - Get-Command -CommandType Application neovim-ruby-host.bat; exitIfFailed - - node --version - npm.cmd --version - - npm.cmd install -g neovim; exitIfFailed - Get-Command -CommandType Application neovim-node-host.cmd; exitIfFailed - npm.cmd link neovim - - if ($env:USE_LUACOV -eq 1) { - & $env:DEPS_PREFIX\luarocks\luarocks.bat install cluacov - } -} - -function Test { - # Functional tests - # The $LastExitCode from MSBuild can't be trusted - $failed = $false - - # Run only this test file: - # $env:TEST_FILE = "test\functional\foo.lua" - cmake --build $buildDir --target functionaltest 2>&1 | - ForEach-Object { $failed = $failed -or - $_ -match 'functional tests failed with error'; $_ } - - if ($failed) { - exit $LastExitCode - } - - if (-not $uploadToCodecov) { - return - } - if ($env:USE_LUACOV -eq 1) { - & $env:DEPS_PREFIX\bin\luacov.bat - } - bash -l /c/projects/neovim/ci/common/submit_coverage.sh functionaltest -} - -function TestOld { - # Old tests - # Add MSYS to path, required for e.g. `find` used in test scripts. - # But would break functionaltests, where its `more` would be used then. - $OldPath = $env:PATH - $env:PATH = "C:\msys64\usr\bin;$env:PATH" - & "C:\msys64\mingw64\bin\mingw32-make.exe" -C $(Convert-Path $projectDir\src\nvim\testdir) VERBOSE=1; exitIfFailed - $env:PATH = $OldPath - - if ($uploadToCodecov) { - bash -l /c/projects/neovim/ci/common/submit_coverage.sh oldtest - } -} - - -function Package { - cmake -S $projectDir -B $buildDir $(convertToCmakeArgs($nvimCmakeVars)) -G Ninja; exitIfFailed - cmake --build $buildDir --target package; exitIfFailed -} - -if ($PSCmdlet.ParameterSetName) { - & (Get-ChildItem "Function:$($PSCmdlet.ParameterSetName)") - exit -} diff --git a/ci/common/build.sh b/ci/common/build.sh index 6e7ea2c8f8..e30d0337b5 100644 --- a/ci/common/build.sh +++ b/ci/common/build.sh @@ -7,7 +7,7 @@ _stat() { } top_make() { - printf '%78s\n' | tr ' ' '=' + printf '%78s\n' ' ' | tr ' ' '=' ninja "$@" } @@ -33,6 +33,7 @@ build_deps() { # update CMake configuration and update to newer deps versions. cd "${DEPS_BUILD_DIR}" echo "Configuring with '${DEPS_CMAKE_FLAGS}'." + # shellcheck disable=SC2086 CC= cmake -G Ninja ${DEPS_CMAKE_FLAGS} "${CI_BUILD_DIR}/cmake.deps/" if ! top_make; then @@ -51,10 +52,10 @@ build_nvim() { mkdir -p "${BUILD_DIR}" cd "${BUILD_DIR}" - echo "Configuring with '${CMAKE_FLAGS} $@'." + echo "Configuring with '${CMAKE_FLAGS} $*'." + # shellcheck disable=SC2086 cmake -G Ninja ${CMAKE_FLAGS} "$@" "${CI_BUILD_DIR}" - echo "Building nvim." if ! top_make nvim ; then exit 1 diff --git a/ci/common/suite.sh b/ci/common/suite.sh index 0320ac15c3..c81261d2e7 100644 --- a/ci/common/suite.sh +++ b/ci/common/suite.sh @@ -9,12 +9,12 @@ fail() { local test_name="$1" local message="$2" - : ${message:=Test $test_name failed} + : "${message:=Test $test_name failed}" local full_msg="$test_name :: $message" echo "${full_msg}" >> "${FAIL_SUMMARY_FILE}" echo "Failed: $full_msg" - FAILED=1 + export FAILED=1 } ended_successfully() { diff --git a/ci/common/test.sh b/ci/common/test.sh index 7db39a0e5f..326ec162c3 100644 --- a/ci/common/test.sh +++ b/ci/common/test.sh @@ -57,6 +57,7 @@ check_core_dumps() { check_logs() { # Iterate through each log to remove an useless warning. + # shellcheck disable=SC2044 for log in $(find "${1}" -type f -name "${2}"); do sed -i "${log}" \ -e '/Warning: noted but unhandled ioctl/d' \ @@ -66,6 +67,7 @@ check_logs() { # Now do it again, but only consider files with size > 0. local err="" + # shellcheck disable=SC2044 for log in $(find "${1}" -type f -name "${2}" -size +0); do cat "${log}" err=1 @@ -97,7 +99,7 @@ unittests() {( functionaltests() {( ulimit -c unlimited || true - if ! build_make ${FUNCTIONALTEST}; then + if ! build_make "${FUNCTIONALTEST}"; then fail 'functionaltests' 'Functional tests failed' fi submit_coverage functionaltest @@ -132,7 +134,7 @@ check_runtime_files() {( 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" "$(printf "$message" "$file")" + fail "$test_name" "$(printf "%s%s" "$message" "$file")" fi done )} diff --git a/ci/run_tests.sh b/ci/run_tests.sh index da72d09506..0ef7080628 100755 --- a/ci/run_tests.sh +++ b/ci/run_tests.sh @@ -4,8 +4,11 @@ set -e set -o pipefail CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source-path=SCRIPTDIR source "${CI_DIR}/common/build.sh" +# shellcheck source-path=SCRIPTDIR source "${CI_DIR}/common/test.sh" +# shellcheck source-path=SCRIPTDIR source "${CI_DIR}/common/suite.sh" rm -f "$END_MARKER" @@ -14,16 +17,15 @@ rm -f "$END_MARKER" if (($# == 0)); then tests=('build_nvim') + # Additional threads aren't created in the unit/old 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 tests+=('unittests') fi - tests+=('functionaltests') + tests+=('oldtests') fi - tests+=('oldtests' 'install_nvim') + tests+=('functionaltests' 'install_nvim') else tests=("$@") fi diff --git a/ci/snap/.snapcraft_payload b/ci/snap/.snapcraft_payload deleted file mode 100644 index 29f895fad6..0000000000 --- a/ci/snap/.snapcraft_payload +++ /dev/null @@ -1,194 +0,0 @@ -{ - "ref": "refs/heads/master", - "before": "66b136c43c12df3dcf8f19ff48f206ad2e4f43fc", - "after": "1bf69c32217cc455603ce8aa2b5415d9717f0fa2", - "repository": { - "id": 292861950, - "node_id": "MDEwOlJlcG9zaXRvcnkyOTI4NjE5NTA=", - "name": "neovim-snap", - "full_name": "hurricanehrndz/neovim-snap", - "private": false, - "owner": { - "name": "hurricanehrndz", - "email": "hurricanehrndz@users.noreply.github.com", - "login": "hurricanehrndz", - "id": 5804237, - "node_id": "MDQ6VXNlcjU4MDQyMzc=", - "avatar_url": "https://avatars0.githubusercontent.com/u/5804237?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/hurricanehrndz", - "html_url": "https://github.com/hurricanehrndz", - "followers_url": "https://api.github.com/users/hurricanehrndz/followers", - "following_url": "https://api.github.com/users/hurricanehrndz/following{/other_user}", - "gists_url": "https://api.github.com/users/hurricanehrndz/gists{/gist_id}", - "starred_url": "https://api.github.com/users/hurricanehrndz/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/hurricanehrndz/subscriptions", - "organizations_url": "https://api.github.com/users/hurricanehrndz/orgs", - "repos_url": "https://api.github.com/users/hurricanehrndz/repos", - "events_url": "https://api.github.com/users/hurricanehrndz/events{/privacy}", - "received_events_url": "https://api.github.com/users/hurricanehrndz/received_events", - "type": "User", - "site_admin": false - }, - "html_url": "https://github.com/hurricanehrndz/neovim-snap", - "description": "snap build for neovim", - "fork": false, - "url": "https://github.com/hurricanehrndz/neovim-snap", - "forks_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/forks", - "keys_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/keys{/key_id}", - "collaborators_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/collaborators{/collaborator}", - "teams_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/teams", - "hooks_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/hooks", - "issue_events_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/issues/events{/number}", - "events_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/events", - "assignees_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/assignees{/user}", - "branches_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/branches{/branch}", - "tags_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/tags", - "blobs_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/git/blobs{/sha}", - "git_tags_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/git/tags{/sha}", - "git_refs_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/git/refs{/sha}", - "trees_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/git/trees{/sha}", - "statuses_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/statuses/{sha}", - "languages_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/languages", - "stargazers_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/stargazers", - "contributors_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/contributors", - "subscribers_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/subscribers", - "subscription_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/subscription", - "commits_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/commits{/sha}", - "git_commits_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/git/commits{/sha}", - "comments_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/comments{/number}", - "issue_comment_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/issues/comments{/number}", - "contents_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/contents/{+path}", - "compare_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/compare/{base}...{head}", - "merges_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/merges", - "archive_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/{archive_format}{/ref}", - "downloads_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/downloads", - "issues_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/issues{/number}", - "pulls_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/pulls{/number}", - "milestones_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/milestones{/number}", - "notifications_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/notifications{?since,all,participating}", - "labels_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/labels{/name}", - "releases_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/releases{/id}", - "deployments_url": "https://api.github.com/repos/hurricanehrndz/neovim-snap/deployments", - "created_at": 1599227980, - "updated_at": "2020-09-04T14:02:38Z", - "pushed_at": 1599228352, - "git_url": "git://github.com/hurricanehrndz/neovim-snap.git", - "ssh_url": "git@github.com:hurricanehrndz/neovim-snap.git", - "clone_url": "https://github.com/hurricanehrndz/neovim-snap.git", - "svn_url": "https://github.com/hurricanehrndz/neovim-snap", - "homepage": null, - "size": 0, - "stargazers_count": 0, - "watchers_count": 0, - "language": null, - "has_issues": true, - "has_projects": true, - "has_downloads": true, - "has_wiki": true, - "has_pages": false, - "forks_count": 0, - "mirror_url": null, - "archived": false, - "disabled": false, - "open_issues_count": 0, - "license": { - "key": "mit", - "name": "MIT License", - "spdx_id": "MIT", - "url": "https://api.github.com/licenses/mit", - "node_id": "MDc6TGljZW5zZTEz" - }, - "forks": 0, - "open_issues": 0, - "watchers": 0, - "default_branch": "master", - "stargazers": 0, - "master_branch": "master" - }, - "pusher": { - "name": "hurricanehrndz", - "email": "hurricanehrndz@users.noreply.github.com" - }, - "sender": { - "login": "hurricanehrndz", - "id": 5804237, - "node_id": "MDQ6VXNlcjU4MDQyMzc=", - "avatar_url": "https://avatars0.githubusercontent.com/u/5804237?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/hurricanehrndz", - "html_url": "https://github.com/hurricanehrndz", - "followers_url": "https://api.github.com/users/hurricanehrndz/followers", - "following_url": "https://api.github.com/users/hurricanehrndz/following{/other_user}", - "gists_url": "https://api.github.com/users/hurricanehrndz/gists{/gist_id}", - "starred_url": "https://api.github.com/users/hurricanehrndz/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/hurricanehrndz/subscriptions", - "organizations_url": "https://api.github.com/users/hurricanehrndz/orgs", - "repos_url": "https://api.github.com/users/hurricanehrndz/repos", - "events_url": "https://api.github.com/users/hurricanehrndz/events{/privacy}", - "received_events_url": "https://api.github.com/users/hurricanehrndz/received_events", - "type": "User", - "site_admin": false - }, - "created": false, - "deleted": false, - "forced": false, - "base_ref": null, - "compare": "https://github.com/hurricanehrndz/neovim-snap/compare/66b136c43c12...1bf69c32217c", - "commits": [ - { - "id": "1bf69c32217cc455603ce8aa2b5415d9717f0fa2", - "tree_id": "62ea83a2349be8c930c45fdc199f71b08bf5927e", - "distinct": true, - "message": "Build of latest tag", - "timestamp": "2020-09-04T14:05:40Z", - "url": "https://github.com/hurricanehrndz/neovim-snap/commit/1bf69c32217cc455603ce8aa2b5415d9717f0fa2", - "author": { - "name": "Carlos Hernandez", - "email": "carlos@techbyte.ca", - "username": "hurricanehrndz" - }, - "committer": { - "name": "Carlos Hernandez", - "email": "carlos@techbyte.ca", - "username": "hurricanehrndz" - }, - "added": [ - - ], - "removed": [ - - ], - "modified": [ - "snap/snapcraft.yaml" - ] - } - ], - "head_commit": { - "id": "1bf69c32217cc455603ce8aa2b5415d9717f0fa2", - "tree_id": "62ea83a2349be8c930c45fdc199f71b08bf5927e", - "distinct": true, - "message": "Build of latest tag", - "timestamp": "2020-09-04T14:05:40Z", - "url": "https://github.com/hurricanehrndz/neovim-snap/commit/1bf69c32217cc455603ce8aa2b5415d9717f0fa2", - "author": { - "name": "Carlos Hernandez", - "email": "carlos@techbyte.ca", - "username": "hurricanehrndz" - }, - "committer": { - "name": "Carlos Hernandez", - "email": "carlos@techbyte.ca", - "username": "hurricanehrndz" - }, - "added": [ - - ], - "removed": [ - - ], - "modified": [ - "snap/snapcraft.yaml" - ] - } -} diff --git a/ci/snap/after_success.sh b/ci/snap/after_success.sh deleted file mode 100755 index e66721a5e2..0000000000 --- a/ci/snap/after_success.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -o pipefail - - -RESULT_SNAP=$(find ./ -name "*.snap") - -sudo snap install "$RESULT_SNAP" --dangerous --classic - -/snap/bin/nvim --version - -SHA256=$(sha256sum "$RESULT_SNAP") -echo "SHA256: ${SHA256} ." diff --git a/ci/snap/deploy.sh b/ci/snap/deploy.sh deleted file mode 100755 index 1794fc61d9..0000000000 --- a/ci/snap/deploy.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -o pipefail - -SNAP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -WEBHOOK_PAYLOAD="$(cat "${SNAP_DIR}/.snapcraft_payload")" -PAYLOAD_SIG="${SECRET_SNAP_SIG}" - - -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 - git checkout "${last_committed_tag}" 2> /dev/null - last_git_release="$(git describe --first-parent 2> /dev/null | perl -lpe 's/v\d.\d.\d-//g')" - - if [[ -z "$(echo $last_snap_release | perl -ne "print if /${last_git_release}.*/")" ]]; then - return 0 - fi - return 1 -} - - -trigger_snapcraft_webhook() { - [[ -n "${PAYLOAD_SIG}" ]] || exit - echo "Triggering new snap release via webhook..." - curl -X POST \ - -H "Content-Type: application/json" \ - -H "X-Hub-Signature: sha1=${PAYLOAD_SIG}" \ - --data "${WEBHOOK_PAYLOAD}" \ - https://snapcraft.io/nvim/webhook/notify -} - - -if $(snap_release_needed); then - echo "New snap release required" - trigger_snapcraft_webhook -fi diff --git a/ci/snap/install.sh b/ci/snap/install.sh deleted file mode 100755 index 0ceb6f0422..0000000000 --- a/ci/snap/install.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -o pipefail - -sudo apt update -sudo usermod -aG lxd $USER -sudo /snap/bin/lxd.migrate -yes -sudo /snap/bin/lxd waitready -sudo /snap/bin/lxd init --auto - diff --git a/ci/snap/script.sh b/ci/snap/script.sh deleted file mode 100755 index 21d3421044..0000000000 --- a/ci/snap/script.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -o pipefail - -mkdir -p "$CI_BUILD_DIR/snaps-cache" -sg lxd -c snapcraft - diff --git a/ci/snap/travis_snapcraft.cfg b/ci/snap/travis_snapcraft.cfg Binary files differdeleted file mode 100644 index 3e6a60c30d..0000000000 --- a/ci/snap/travis_snapcraft.cfg +++ /dev/null diff --git a/cmake.config/CMakeLists.txt b/cmake.config/CMakeLists.txt index 8c86b89e43..6de86cbaf2 100644 --- a/cmake.config/CMakeLists.txt +++ b/cmake.config/CMakeLists.txt @@ -5,6 +5,26 @@ include(CheckIncludeFiles) include(CheckCSourceRuns) include(CheckCSourceCompiles) +check_c_source_compiles(" +#include <execinfo.h> +int main(void) +{ + void *trace[1]; + backtrace(trace, 1); + return 0; +} +" HAVE_EXECINFO_BACKTRACE) + +check_c_source_compiles(" +int main(void) +{ + int a = 42; + __builtin_add_overflow(a, a, &a); + __builtin_sub_overflow(a, a, &a); + return 0; +} +" HAVE_BUILTIN_ADD_OVERFLOW) + check_type_size("int" SIZEOF_INT LANGUAGE C) check_type_size("long" SIZEOF_LONG LANGUAGE C) check_type_size("intmax_t" SIZEOF_INTMAX_T LANGUAGE C) @@ -20,11 +40,6 @@ check_include_files(langinfo.h HAVE_LANGINFO_H) check_include_files(locale.h HAVE_LOCALE_H) check_include_files(pwd.h HAVE_PWD_H) check_include_files(strings.h HAVE_STRINGS_H) -check_include_files(sys/wait.h HAVE_SYS_WAIT_H) -if(NOT HAVE_SYS_WAIT_H AND UNIX) - # See if_cscope.c - message(SEND_ERROR "header sys/wait.h is required for Unix") -endif() check_include_files(sys/utsname.h HAVE_SYS_UTSNAME_H) check_include_files(termios.h HAVE_TERMIOS_H) check_include_files(sys/uio.h HAVE_SYS_UIO_H) @@ -36,11 +51,6 @@ check_function_exists(getpwent HAVE_GETPWENT) check_function_exists(getpwnam HAVE_GETPWNAM) check_function_exists(getpwuid HAVE_GETPWUID) check_function_exists(readv HAVE_READV) - -if(Iconv_FOUND) - set(HAVE_ICONV 1) -endif() - check_function_exists(opendir HAVE_OPENDIR) check_function_exists(readlink HAVE_READLINK) check_function_exists(setpgid HAVE_SETPGID) @@ -51,6 +61,26 @@ check_function_exists(strcasecmp HAVE_STRCASECMP) check_function_exists(strncasecmp HAVE_STRNCASECMP) check_function_exists(strptime HAVE_STRPTIME) +check_c_source_compiles(" +#include <sys/types.h> +#include <dirent.h> +int main(void) +{ + DIR *dir = opendir(\"dirname\"); + dirfd(dir); + return 0; +} +" HAVE_DIRFD) + +check_c_source_compiles(" +#include <sys/file.h> +int main(void) +{ + flock(10, LOCK_SH); + return 0; +} +" HAVE_FLOCK) + if(CMAKE_SYSTEM_NAME STREQUAL "SunOS") check_c_source_compiles(" #include <termios.h> diff --git a/cmake.config/config.h.in b/cmake.config/config.h.in index 59be83fb5e..c8377bf45c 100644 --- a/cmake.config/config.h.in +++ b/cmake.config/config.h.in @@ -24,7 +24,6 @@ #cmakedefine HAVE_GETPWENT #cmakedefine HAVE_GETPWNAM #cmakedefine HAVE_GETPWUID -#cmakedefine HAVE_ICONV #cmakedefine HAVE_LANGINFO_H #cmakedefine HAVE_LOCALE_H #cmakedefine HAVE_NL_LANGINFO_CODESET @@ -54,10 +53,10 @@ # undef HAVE_SYS_UIO_H # endif #endif +#cmakedefine HAVE_DIRFD +#cmakedefine HAVE_FLOCK #cmakedefine HAVE_FORKPTY -#cmakedefine FEAT_TUI - #ifndef UNIT_TESTING #cmakedefine LOG_LIST_ACTIONS #endif diff --git a/cmake.config/iwyu/gcc.libc.imp b/cmake.config/iwyu/gcc.libc.imp new file mode 100644 index 0000000000..1dd0ad42c7 --- /dev/null +++ b/cmake.config/iwyu/gcc.libc.imp @@ -0,0 +1,226 @@ +# This was initially taken from the IWYU repository: +# github.com/include-what-you-use/include-what-you-use/blob/164b8fe7597805ae55f029ecf6580dc46a74c7ed/gcc.libc.imp +# It has useful mappings that are normally enabled by default, but there are +# other default mappings that conflict with our builds. The best solution seems +# to be to disable all defaults, import the defaults from the IWYU +# repo and modify the rules that conflict with our build. +# +# TODO(dundargoc): Check if there is a way to disable specific builtin maps as +# to avoid this file entirely. + +# Mappings for GNU libc +# ( cd /usr/include && grep '^ *# *include' {sys/,net/,}* | perl -nle 'm/^([^:]+).*<([^>]+)>/ && print qq@ { include: [ "<$2>", private, "<$1>", public ] },@' | grep bits/ | sort ) +# When I saw more than one mapping for these, I typically picked +# what I thought was the "best" one. +[ + { include: [ "<bits/a.out.h>", private, "<a.out.h>", public ] }, + { include: [ "<bits/auxv.h>", private, "<sys/auxv.h>", public ] }, + { include: [ "<bits/byteswap.h>", private, "<byteswap.h>", public ] }, + { include: [ "<bits/cmathcalls.h>", private, "<complex.h>", public ] }, + { include: [ "<bits/confname.h>", private, "<unistd.h>", private ] }, + { include: [ "<bits/dirent.h>", private, "<dirent.h>", public ] }, + { include: [ "<bits/dlfcn.h>", private, "<dlfcn.h>", public ] }, + { include: [ "<bits/elfclass.h>", private, "<link.h>", public ] }, + { include: [ "<bits/endian.h>", private, "<endian.h>", public ] }, + { include: [ "<bits/environments.h>", private, "<unistd.h>", private ] }, + { include: [ "<bits/epoll.h>", private, "<sys/epoll.h>", public ] }, + { include: [ "<bits/errno.h>", private, "<errno.h>", public ] }, + { include: [ "<bits/error.h>", private, "<error.h>", public ] }, + { include: [ "<bits/eventfd.h>", private, "<sys/eventfd.h>", public ] }, + { include: [ "<bits/fcntl.h>", private, "<fcntl.h>", public ] }, + { include: [ "<bits/fcntl2.h>", private, "<fcntl.h>", public ] }, + { include: [ "<bits/fenv.h>", private, "<fenv.h>", public ] }, + { include: [ "<bits/fenvinline.h>", private, "<fenv.h>", public ] }, + { include: [ "<bits/huge_val.h>", private, "<math.h>", public ] }, + { include: [ "<bits/huge_valf.h>", private, "<math.h>", public ] }, + { include: [ "<bits/huge_vall.h>", private, "<math.h>", public ] }, + { include: [ "<bits/hwcap.h>", private, "<sys/auxv.h>", public ] }, + { include: [ "<bits/inf.h>", private, "<math.h>", public ] }, + { include: [ "<bits/inotify.h>", private, "<sys/inotify.h>", public ] }, + { include: [ "<bits/ioctl-types.h>", private, "<sys/ioctl.h>", public ] }, + { include: [ "<bits/ioctls.h>", private, "<sys/ioctl.h>", public ] }, + { include: [ "<bits/ipc.h>", private, "<sys/ipc.h>", public ] }, + { include: [ "<bits/ipctypes.h>", private, "<sys/ipc.h>", public ] }, + { include: [ "<bits/libio-ldbl.h>", private, "<libio.h>", public ] }, + { include: [ "<bits/link.h>", private, "<link.h>", public ] }, + { include: [ "<bits/locale.h>", private, "<locale.h>", public ] }, + { include: [ "<bits/math-finite.h>", private, "<math.h>", public ] }, + { include: [ "<bits/mathcalls.h>", private, "<math.h>", public ] }, + { include: [ "<bits/mathdef.h>", private, "<math.h>", public ] }, + { include: [ "<bits/mathinline.h>", private, "<math.h>", public ] }, + { include: [ "<bits/mman.h>", private, "<sys/mman.h>", public ] }, + { include: [ "<bits/mman-shared.h>", private, "<sys/mman.h>", public ] }, + { include: [ "<bits/monetary-ldbl.h>", private, "<monetary.h>", public ] }, + { include: [ "<bits/mqueue.h>", private, "<mqueue.h>", public ] }, + { include: [ "<bits/mqueue2.h>", private, "<mqueue.h>", public ] }, + { include: [ "<bits/msq.h>", private, "<sys/msg.h>", public ] }, + { include: [ "<bits/nan.h>", private, "<math.h>", public ] }, + { include: [ "<bits/netdb.h>", private, "<netdb.h>", private ] }, + { include: [ "<bits/param.h>", private, "<sys/param.h>", public ] }, + { include: [ "<bits/poll.h>", private, "<sys/poll.h>", private ] }, + { include: [ "<bits/poll2.h>", private, "<sys/poll.h>", private ] }, + { include: [ "<bits/posix1_lim.h>", private, "<limits.h>", public ] }, + { include: [ "<bits/posix2_lim.h>", private, "<limits.h>", public ] }, + { include: [ "<bits/posix_opt.h>", private, "<unistd.h>", private ] }, + { include: [ "<bits/printf-ldbl.h>", private, "<printf.h>", public ] }, + { include: [ "<bits/pthreadtypes.h>", private, "<pthread.h>", private ] }, + { include: [ "<bits/resource.h>", private, "<sys/resource.h>", public ] }, + { include: [ "<bits/sched.h>", private, "<sched.h>", public ] }, + { include: [ "<bits/select.h>", private, "<sys/select.h>", public ] }, + { include: [ "<bits/select2.h>", private, "<sys/select.h>", public ] }, + { include: [ "<bits/sem.h>", private, "<sys/sem.h>", public ] }, + { include: [ "<bits/semaphore.h>", private, "<semaphore.h>", public ] }, + { include: [ "<bits/setjmp.h>", private, "<setjmp.h>", public ] }, + { include: [ "<bits/setjmp2.h>", private, "<setjmp.h>", public ] }, + { include: [ "<bits/shm.h>", private, "<sys/shm.h>", public ] }, + { include: [ "<bits/sigaction.h>", private, "<signal.h>", public ] }, + { include: [ "<bits/sigcontext.h>", private, "<signal.h>", public ] }, + { include: [ "<bits/siginfo.h>", private, "<signal.h>", public ] }, + { include: [ "<bits/signum.h>", private, "<signal.h>", public ] }, + { include: [ "<bits/sigset.h>", private, "<signal.h>", public ] }, + { include: [ "<bits/sigstack.h>", private, "<signal.h>", public ] }, + { include: [ "<bits/sigthread.h>", private, "<signal.h>", public ] }, + { include: [ "<bits/sockaddr.h>", private, "<sys/un.h>", public ] }, + { include: [ "<bits/socket.h>", private, "<sys/socket.h>", private ] }, + { include: [ "<bits/socket2.h>", private, "<sys/socket.h>", private ] }, + { include: [ "<bits/socket_type.h>", private, "<sys/socket.h>", private ] }, + { include: [ "<bits/stab.def>", private, "<stab.h>", public ] }, + { include: [ "<bits/stat.h>", private, "<sys/stat.h>", public ] }, + { include: [ "<bits/statfs.h>", private, "<sys/statfs.h>", public ] }, + { include: [ "<bits/statvfs.h>", private, "<sys/statvfs.h>", public ] }, + { include: [ "<bits/stdio-ldbl.h>", private, "<stdio.h>", public ] }, + { include: [ "<bits/stdio-lock.h>", private, "<libio.h>", public ] }, + { include: [ "<bits/stdio.h>", private, "<stdio.h>", public ] }, + { include: [ "<bits/stdio2.h>", private, "<stdio.h>", public ] }, + { include: [ "<bits/stdio_lim.h>", private, "<stdio.h>", public ] }, + { include: [ "<bits/stdlib-bsearch.h>", private, "<stdlib.h>", public ] }, + { include: [ "<bits/stdlib-float.h>", private, "<stdlib.h>", public ] }, + { include: [ "<bits/stdlib-ldbl.h>", private, "<stdlib.h>", public ] }, + { include: [ "<bits/stdlib.h>", private, "<stdlib.h>", public ] }, + { include: [ "<bits/string.h>", private, "<string.h>", public ] }, + { include: [ "<bits/string2.h>", private, "<string.h>", public ] }, + { include: [ "<bits/string3.h>", private, "<string.h>", public ] }, + { include: [ "<bits/stropts.h>", private, "<stropts.h>", public ] }, + { include: [ "<bits/struct_stat.h>", private, "<sys/stat.h>", public ] }, + { include: [ "<bits/struct_stat.h>", private, "<ftw.h>", public ] }, + { include: [ "<bits/sys_errlist.h>", private, "<stdio.h>", public ] }, + { include: [ "<bits/syscall.h>", private, "<sys/syscall.h>", public ] }, + { include: [ "<bits/sysctl.h>", private, "<sys/sysctl.h>", public ] }, + { include: [ "<bits/syslog-ldbl.h>", private, "<sys/syslog.h>", private ] }, + { include: [ "<bits/syslog-path.h>", private, "<sys/syslog.h>", private ] }, + { include: [ "<bits/syslog.h>", private, "<sys/syslog.h>", private ] }, + { include: [ "<bits/termios.h>", private, "<termios.h>", private ] }, + { include: [ "<bits/termios-c_lflag.h>", private, "<termios.h>", private ] }, + { include: [ "<bits/termios-struct.h>", private, "<termios.h>", private ] }, + { include: [ "<bits/termios-tcflow.h>", private, "<termios.h>", private ] }, + { include: [ "<bits/time.h>", private, "<time.h>", public ] }, + { include: [ "<bits/time.h>", private, "<sys/time.h>", public ] }, + { include: [ "<bits/timerfd.h>", private, "<sys/timerfd.h>", public ] }, + { include: [ "<bits/timex.h>", private, "<sys/timex.h>", public ] }, + { include: [ "<bits/types.h>", private, "<sys/types.h>", public ] }, + { include: [ "<bits/types/siginfo_t.h>", private, "<signal.h>", public ] }, + { include: [ "<bits/types/siginfo_t.h>", private, "<sys/wait.h>", public ] }, + { include: [ "<bits/uio.h>", private, "<sys/uio.h>", public ] }, + { include: [ "<bits/unistd.h>", private, "<unistd.h>", private ] }, + { include: [ "<bits/ustat.h>", private, "<sys/ustat.h>", private ] }, + { include: [ "<bits/utmp.h>", private, "<utmp.h>", public ] }, + { include: [ "<bits/utmpx.h>", private, "<utmpx.h>", public ] }, + { include: [ "<bits/utsname.h>", private, "<sys/utsname.h>", public ] }, + { include: [ "<bits/waitflags.h>", private, "<sys/wait.h>", public ] }, + { include: [ "<bits/waitstatus.h>", private, "<sys/wait.h>", public ] }, + { include: [ "<bits/wchar-ldbl.h>", private, "<wchar.h>", public ] }, + { include: [ "<bits/wchar.h>", private, "<wchar.h>", public ] }, + { include: [ "<bits/wchar2.h>", private, "<wchar.h>", public ] }, + { include: [ "<bits/wordsize.h>", private, "<limits.h>", public ] }, + { include: [ "<bits/xopen_lim.h>", private, "<limits.h>", public ] }, + { include: [ "<bits/xtitypes.h>", private, "<stropts.h>", public ] }, + # Sometimes libc tells you what mapping to do via an '#error': + # # error "Never use <bits/dlfcn.h> directly; include <dlfcn.h> instead." + # or + # # error "Never include <bits/socket_type.h> directly; use <sys/socket.h> instead." + # ( cd /usr/include && grep -R '^ *# *error "Never use\|include' * | perl -nle 'm/<([^>]+).*directly.*<([^>]+)/ && print qq@ { include: [ "<$1>", private, "<$2>", public ] },@' | sort ) + { include: [ "<bits/a.out.h>", private, "<a.out.h>", public ] }, + { include: [ "<bits/byteswap-16.h>", private, "<byteswap.h>", public ] }, + { include: [ "<bits/byteswap.h>", private, "<byteswap.h>", public ] }, + { include: [ "<bits/cmathcalls.h>", private, "<complex.h>", public ] }, + { include: [ "<bits/confname.h>", private, "<unistd.h>", private ] }, + { include: [ "<bits/dirent.h>", private, "<dirent.h>", public ] }, + { include: [ "<bits/dlfcn.h>", private, "<dlfcn.h>", public ] }, + { include: [ "<bits/elfclass.h>", private, "<link.h>", public ] }, + { include: [ "<bits/endian.h>", private, "<endian.h>", public ] }, + { include: [ "<bits/epoll.h>", private, "<sys/epoll.h>", public ] }, + { include: [ "<bits/eventfd.h>", private, "<sys/eventfd.h>", public ] }, + { include: [ "<bits/fcntl-linux.h>", private, "<fcntl.h>", public ] }, + { include: [ "<bits/fcntl.h>", private, "<fcntl.h>", public ] }, + { include: [ "<bits/fenv.h>", private, "<fenv.h>", public ] }, + { include: [ "<bits/huge_val.h>", private, "<math.h>", public ] }, + { include: [ "<bits/huge_valf.h>", private, "<math.h>", public ] }, + { include: [ "<bits/huge_vall.h>", private, "<math.h>", public ] }, + { include: [ "<bits/in.h>", private, "<netinet/in.h>", private ] }, + { include: [ "<bits/inf.h>", private, "<math.h>", public ] }, + { include: [ "<bits/inotify.h>", private, "<sys/inotify.h>", public ] }, + { include: [ "<bits/ioctl-types.h>", private, "<sys/ioctl.h>", public ] }, + { include: [ "<bits/ioctls.h>", private, "<sys/ioctl.h>", public ] }, + { include: [ "<bits/ipc.h>", private, "<sys/ipc.h>", public ] }, + { include: [ "<bits/ipctypes.h>", private, "<sys/ipc.h>", public ] }, + { include: [ "<bits/locale.h>", private, "<locale.h>", public ] }, + { include: [ "<bits/math-finite.h>", private, "<math.h>", public ] }, + { include: [ "<bits/mathdef.h>", private, "<math.h>", public ] }, + { include: [ "<bits/mathinline.h>", private, "<math.h>", public ] }, + { include: [ "<bits/mman-linux.h>", private, "<sys/mman.h>", public ] }, + { include: [ "<bits/mman.h>", private, "<sys/mman.h>", public ] }, + { include: [ "<bits/mqueue.h>", private, "<mqueue.h>", public ] }, + { include: [ "<bits/msq.h>", private, "<sys/msg.h>", public ] }, + { include: [ "<bits/nan.h>", private, "<math.h>", public ] }, + { include: [ "<bits/param.h>", private, "<sys/param.h>", public ] }, + { include: [ "<bits/poll.h>", private, "<sys/poll.h>", private ] }, + { include: [ "<bits/predefs.h>", private, "<features.h>", public ] }, + { include: [ "<bits/resource.h>", private, "<sys/resource.h>", public ] }, + { include: [ "<bits/select.h>", private, "<sys/select.h>", public ] }, + { include: [ "<bits/semaphore.h>", private, "<semaphore.h>", public ] }, + { include: [ "<bits/sigcontext.h>", private, "<signal.h>", public ] }, + { include: [ "<bits/signalfd.h>", private, "<sys/signalfd.h>", public ] }, + { include: [ "<bits/stdlib-float.h>", private, "<stdlib.h>", public ] }, + { include: [ "<bits/string.h>", private, "<string.h>", public ] }, + { include: [ "<bits/string2.h>", private, "<string.h>", public ] }, + { include: [ "<bits/string3.h>", private, "<string.h>", public ] }, + { include: [ "<bits/timerfd.h>", private, "<sys/timerfd.h>", public ] }, + { include: [ "<bits/typesizes.h>", private, "<sys/types.h>", public ] }, + # Top-level #includes that just forward to another file: + # $ for i in /usr/include/*; do [ -f $i ] } && [ `wc -l < $i` = 1 ] } && echo $i; done + # (poll.h, syscall.h, syslog.h, ustat.h, wait.h). + # For each file, I looked at the list of canonical header files -- + # http://www.opengroup.org/onlinepubs/9699919799/idx/head.html -- + # to decide which of the two files is canonical. If neither is + # on the POSIX.1 1998 list, I just choose the top-level one. + { include: [ "<sys/poll.h>", private, "<poll.h>", public ] }, + { include: [ "<sys/syslog.h>", private, "<syslog.h>", public ] }, + { include: [ "<sys/ustat.h>", private, "<ustat.h>", public ] }, + { include: [ "<wait.h>", private, "<sys/wait.h>", public ] }, + # These are all files in bits/ that delegate to asm/ and linux/ to + # do all (or lots) of the work. Note these are private->private. + # $ for i in /usr/include/bits/*; do for dir in asm linux; do grep -H -e $dir/`basename $i` $i; done; done + { include: [ "<linux/errno.h>", private, "<bits/errno.h>", private ] }, + { include: [ "<asm/ioctls.h>", private, "<bits/ioctls.h>", private ] }, + { include: [ "<asm/socket.h>", private, "<bits/socket.h>", private ] }, + { include: [ "<linux/socket.h>", private, "<bits/socket.h>", private ] }, + # Some asm files have 32- and 64-bit variants: + # $ ls /usr/include/asm/*_{32,64}.h + { include: [ "<asm/posix_types_32.h>", private, "<asm/posix_types.h>", public ] }, + { include: [ "<asm/posix_types_64.h>", private, "<asm/posix_types.h>", public ] }, + { include: [ "<asm/unistd_32.h>", private, "<asm/unistd.h>", private ] }, + { include: [ "<asm/unistd_64.h>", private, "<asm/unistd.h>", private ] }, + # I don't know what grep would have found these. I found them + # via user report. + { include: [ "<asm/errno.h>", private, "<errno.h>", public ] }, + { include: [ "<asm/errno-base.h>", private, "<errno.h>", public ] }, + { include: [ "<asm/ptrace-abi.h>", private, "<asm/ptrace.h>", public ] }, + { include: [ "<asm/unistd.h>", private, "<sys/syscall.h>", public ] }, + { include: [ "<linux/limits.h>", private, "<limits.h>", public ] }, # PATH_MAX + { include: [ "<linux/prctl.h>", private, "<sys/prctl.h>", public ] }, + { include: [ "<sys/ucontext.h>", private, "<ucontext.h>", public ] }, + # Exports guaranteed by the C standard + { include: [ "<stdint.h>", public, "<inttypes.h>", public ] }, +] + +# vim: set ft=toml: diff --git a/cmake.config/iwyu/gcc.symbols.imp b/cmake.config/iwyu/gcc.symbols.imp new file mode 100644 index 0000000000..6066de9d09 --- /dev/null +++ b/cmake.config/iwyu/gcc.symbols.imp @@ -0,0 +1,305 @@ +# This was initially taken from the IWYU repository: +# github.com/include-what-you-use/include-what-you-use/blob/164b8fe7597805ae55f029ecf6580dc46a74c7ed/gcc.symbols.imp +# It has useful mappings that are normally enabled by default, but there are +# other default mappings that conflict with our builds. The best solution seems +# to be to disable all defaults, import the defaults from the IWYU repo and +# modify the rules that conflict with our build. +# +# TODO(dundargoc): Check if there is a way to disable specific builtin maps as +# to avoid this file entirely. + +# For library symbols that can be defined in more than one header +# file, maps from symbol-name to legitimate header files. +# This list was generated via +# grep -R '__.*_defined' /usr/include | perl -nle 'm,/usr/include/([^:]*):#\s*\S+ __(.*)_defined, and print qq@ { symbol: [ "$2", public, "<$1>", public ] },@' | sort -u +# I ignored all entries that only appeared once on the list (eg uint32_t). +# I then added in NULL, which according to [diff.null] C.2.2.3, can +# be defined in <clocale>, <cstddef>, <cstdio>, <cstdlib>, +# <cstring>, <ctime>, or <cwchar>. We also allow their C +# equivalents. +# In each case, I ordered them so <sys/types.h> was first, if it was +# an option for this type. That's the preferred #include all else +# equal. The same goes for <stdint.h>. The visibility on the +# symbol-name is ignored; by convention we always set it to private. +[ + { symbol: [ "aiocb", private, "<aio.h>", public ] }, + { symbol: [ "blkcnt_t", private, "<sys/types.h>", public ] }, + { symbol: [ "blkcnt_t", private, "<sys/stat.h>", public ] }, + { symbol: [ "blksize_t", private, "<sys/types.h>", public ] }, + { symbol: [ "blksize_t", private, "<sys/stat.h>", public ] }, + { symbol: [ "cc_t", private, "<termios.h>", private ] }, + { symbol: [ "clock_t", private, "<sys/types.h>", public ] }, + { symbol: [ "clock_t", private, "<sys/time.h>", public ] }, + { symbol: [ "clock_t", private, "<time.h>", public ] }, + { symbol: [ "clockid_t", private, "<sys/types.h>", public ] }, + { symbol: [ "clockid_t", private, "<time.h>", public ] }, + { symbol: [ "daddr_t", private, "<sys/types.h>", public ] }, + { symbol: [ "daddr_t", private, "<rpc/types.h>", public ] }, + { symbol: [ "dev_t", private, "<sys/types.h>", public ] }, + { symbol: [ "dev_t", private, "<sys/stat.h>", public ] }, + { symbol: [ "div_t", private, "<stdlib.h>", public ] }, + { symbol: [ "double_t", private, "<math.h>", public ] }, + { symbol: [ "error_t", private, "<errno.h>", public ] }, + { symbol: [ "error_t", private, "<argp.h>", public ] }, + { symbol: [ "error_t", private, "<argz.h>", public ] }, + { symbol: [ "fd_set", private, "<sys/select.h>", public ] }, + { symbol: [ "fd_set", private, "<sys/time.h>", public ] }, + { symbol: [ "fenv_t", private, "<fenv.h>", public ] }, + { symbol: [ "fexcept_t", private, "<fenv.h>", public ] }, + { symbol: [ "FILE", private, "<stdio.h>", public ] }, + { symbol: [ "FILE", private, "<wchar.h>", public ] }, + { symbol: [ "float_t", private, "<math.h>", public ] }, + { symbol: [ "fsblkcnt_t", private, "<sys/types.h>", public ] }, + { symbol: [ "fsblkcnt_t", private, "<sys/statvfs.h>", public ] }, + { symbol: [ "fsfilcnt_t", private, "<sys/types.h>", public ] }, + { symbol: [ "fsfilcnt_t", private, "<sys/statvfs.h>", public ] }, + { symbol: [ "getopt", private, "<unistd.h>", private ] }, + { symbol: [ "gid_t", private, "<sys/types.h>", public ] }, + { symbol: [ "gid_t", private, "<grp.h>", public ] }, + { symbol: [ "gid_t", private, "<pwd.h>", public ] }, + { symbol: [ "gid_t", private, "<signal.h>", public ] }, + { symbol: [ "gid_t", private, "<stropts.h>", public ] }, + { symbol: [ "gid_t", private, "<sys/ipc.h>", public ] }, + { symbol: [ "gid_t", private, "<sys/stat.h>", public ] }, + { symbol: [ "gid_t", private, "<unistd.h>", private ] }, + { symbol: [ "htonl", private, "<arpa/inet.h>", private ] }, + { symbol: [ "htons", private, "<arpa/inet.h>", private ] }, + { symbol: [ "id_t", private, "<sys/types.h>", public ] }, + { symbol: [ "id_t", private, "<sys/resource.h>", public ] }, + { symbol: [ "imaxdiv_t", private, "<inttypes.h>", public ] }, + { symbol: [ "intmax_t", private, "<stdint.h>", public ] }, + { symbol: [ "uintmax_t", private, "<stdint.h>", public ] }, + { symbol: [ "ino64_t", private, "<sys/types.h>", public ] }, + { symbol: [ "ino64_t", private, "<dirent.h>", public ] }, + { symbol: [ "ino_t", private, "<sys/types.h>", public ] }, + { symbol: [ "ino_t", private, "<dirent.h>", public ] }, + { symbol: [ "ino_t", private, "<sys/stat.h>", public ] }, + { symbol: [ "int8_t", private, "<stdint.h>", public ] }, + { symbol: [ "int16_t", private, "<stdint.h>", public ] }, + { symbol: [ "int32_t", private, "<stdint.h>", public ] }, + { symbol: [ "int64_t", private, "<stdint.h>", public ] }, + { symbol: [ "uint8_t", private, "<stdint.h>", public ] }, + { symbol: [ "uint16_t", private, "<stdint.h>", public ] }, + { symbol: [ "uint32_t", private, "<stdint.h>", public ] }, + { symbol: [ "uint64_t", private, "<stdint.h>", public ] }, + { symbol: [ "intptr_t", private, "<stdint.h>", public ] }, + { symbol: [ "uintptr_t", private, "<stdint.h>", public ] }, + { symbol: [ "iovec", private, "<sys/uio.h>", public ] }, + { symbol: [ "iovec", private, "<sys/socket.h>", private ] }, + { symbol: [ "itimerspec", private, "<time.h>", public ] }, + { symbol: [ "itimerspec", private, "<sys/timerfd.h>", public ] }, + { symbol: [ "key_t", private, "<sys/types.h>", public ] }, + { symbol: [ "key_t", private, "<sys/ipc.h>", public ] }, + { symbol: [ "lconv", private, "<locale.h>", public ] }, + { symbol: [ "ldiv_t", private, "<stdlib.h>", public ] }, + { symbol: [ "lldiv_t", private, "<stdlib.h>", public ] }, + { symbol: [ "max_align_t", private, "<stddef.h>", public ] }, + { symbol: [ "mode_t", private, "<sys/types.h>", public ] }, + { symbol: [ "mode_t", private, "<fcntl.h>", public ] }, + { symbol: [ "mode_t", private, "<ndbm.h>", public ] }, + { symbol: [ "mode_t", private, "<spawn.h>", public ] }, + { symbol: [ "mode_t", private, "<sys/ipc.h>", public ] }, + { symbol: [ "mode_t", private, "<sys/mman.h>", public ] }, + { symbol: [ "mode_t", private, "<sys/stat.h>", public ] }, + { symbol: [ "nlink_t", private, "<sys/types.h>", public ] }, + { symbol: [ "nlink_t", private, "<sys/stat.h>", public ] }, + { symbol: [ "ntohl", private, "<arpa/inet.h>", private ] }, + { symbol: [ "ntohs", private, "<arpa/inet.h>", private ] }, + { symbol: [ "off64_t", private, "<sys/types.h>", public ] }, + { symbol: [ "off64_t", private, "<unistd.h>", private ] }, + { symbol: [ "off_t", private, "<sys/types.h>", public ] }, + { symbol: [ "off_t", private, "<aio.h>", public ] }, + { symbol: [ "off_t", private, "<fcntl.h>", public ] }, + { symbol: [ "off_t", private, "<stdio.h>", public ] }, + { symbol: [ "off_t", private, "<sys/mman.h>", public ] }, + { symbol: [ "off_t", private, "<sys/stat.h>", public ] }, + { symbol: [ "off_t", private, "<unistd.h>", private ] }, + { symbol: [ "optarg", private, "<unistd.h>", private ] }, + { symbol: [ "opterr", private, "<unistd.h>", private ] }, + { symbol: [ "optind", private, "<unistd.h>", private ] }, + { symbol: [ "optopt", private, "<unistd.h>", private ] }, + { symbol: [ "pid_t", private, "<sys/types.h>", public ] }, + { symbol: [ "pid_t", private, "<fcntl.h>", public ] }, + { symbol: [ "pid_t", private, "<sched.h>", public ] }, + { symbol: [ "pid_t", private, "<signal.h>", public ] }, + { symbol: [ "pid_t", private, "<spawn.h>", public ] }, + { symbol: [ "pid_t", private, "<sys/msg.h>", public ] }, + { symbol: [ "pid_t", private, "<sys/sem.h>", public ] }, + { symbol: [ "pid_t", private, "<sys/shm.h>", public ] }, + { symbol: [ "pid_t", private, "<sys/wait.h>", public ] }, + { symbol: [ "pid_t", private, "<termios.h>", private ] }, + { symbol: [ "pid_t", private, "<time.h>", public ] }, + { symbol: [ "pid_t", private, "<unistd.h>", private ] }, + { symbol: [ "pid_t", private, "<utmpx.h>", public ] }, + { symbol: [ "ptrdiff_t", private, "<stddef.h>", public ] }, + { symbol: [ "regex_t", private, "<regex.h>", public ] }, + { symbol: [ "regmatch_t", private, "<regex.h>", public ] }, + { symbol: [ "regoff_t", private, "<regex.h>", public ] }, + { symbol: [ "sigevent", private, "<signal.h>", public ] }, + { symbol: [ "sigevent", private, "<aio.h>", public ] }, + { symbol: [ "sigevent", private, "<mqueue.h>", public ] }, + { symbol: [ "sigevent", private, "<time.h>", public ] }, + { symbol: [ "siginfo_t", private, "<signal.h>", public ] }, + { symbol: [ "siginfo_t", private, "<sys/wait.h>", public ] }, + { symbol: [ "sigset_t", private, "<signal.h>", public ] }, + { symbol: [ "sigset_t", private, "<spawn.h>", public ] }, + { symbol: [ "sigset_t", private, "<sys/select.h>", public ] }, + { symbol: [ "sigval", private, "<signal.h>", public ] }, + { symbol: [ "sockaddr", private, "<sys/socket.h>", private ] }, + { symbol: [ "socklen_t", private, "<sys/socket.h>", private ] }, + { symbol: [ "socklen_t", private, "<netdb.h>", private ] }, + { symbol: [ "ssize_t", private, "<sys/types.h>", public ] }, + { symbol: [ "ssize_t", private, "<aio.h>", public ] }, + { symbol: [ "ssize_t", private, "<monetary.h>", public ] }, + { symbol: [ "ssize_t", private, "<mqueue.h>", public ] }, + { symbol: [ "ssize_t", private, "<stdio.h>", public ] }, + { symbol: [ "ssize_t", private, "<sys/msg.h>", public ] }, + { symbol: [ "ssize_t", private, "<sys/socket.h>", private ] }, + { symbol: [ "ssize_t", private, "<sys/uio.h>", public ] }, + { symbol: [ "ssize_t", private, "<unistd.h>", private ] }, + { symbol: [ "stat", private, "<sys/stat.h>", public ] }, + { symbol: [ "stat", private, "<ftw.h>", public ] }, + { symbol: [ "suseconds_t", private, "<sys/types.h>", public ] }, + { symbol: [ "suseconds_t", private, "<sys/select.h>", public ] }, + { symbol: [ "suseconds_t", private, "<sys/time.h>", public ] }, + { symbol: [ "time_t", private, "<time.h>", public ] }, + { symbol: [ "time_t", private, "<sched.h>", public ] }, + { symbol: [ "time_t", private, "<sys/msg.h>", public ] }, + { symbol: [ "time_t", private, "<sys/select.h>", public ] }, + { symbol: [ "time_t", private, "<sys/sem.h>", public ] }, + { symbol: [ "time_t", private, "<sys/shm.h>", public ] }, + { symbol: [ "time_t", private, "<sys/stat.h>", public ] }, + { symbol: [ "time_t", private, "<sys/time.h>", public ] }, + { symbol: [ "time_t", private, "<sys/types.h>", public ] }, + { symbol: [ "time_t", private, "<utime.h>", public ] }, + { symbol: [ "timer_t", private, "<sys/types.h>", public ] }, + { symbol: [ "timer_t", private, "<time.h>", public ] }, + { symbol: [ "timespec", private, "<time.h>", public ] }, + { symbol: [ "timespec", private, "<aio.h>", public ] }, + { symbol: [ "timespec", private, "<mqueue.h>", public ] }, + { symbol: [ "timespec", private, "<sched.h>", public ] }, + { symbol: [ "timespec", private, "<signal.h>", public ] }, + { symbol: [ "timespec", private, "<sys/select.h>", public ] }, + { symbol: [ "timespec", private, "<sys/stat.h>", public ] }, + { symbol: [ "timeval", private, "<sys/time.h>", public ] }, + { symbol: [ "timeval", private, "<sys/resource.h>", public ] }, + { symbol: [ "timeval", private, "<sys/select.h>", public ] }, + { symbol: [ "timeval", private, "<utmpx.h>", public ] }, + { symbol: [ "tm", private, "<time.h>", public ] }, + { symbol: [ "u_char", private, "<sys/types.h>", public ] }, + { symbol: [ "u_char", private, "<rpc/types.h>", public ] }, + { symbol: [ "uid_t", private, "<sys/types.h>", public ] }, + { symbol: [ "uid_t", private, "<pwd.h>", public ] }, + { symbol: [ "uid_t", private, "<signal.h>", public ] }, + { symbol: [ "uid_t", private, "<stropts.h>", public ] }, + { symbol: [ "uid_t", private, "<sys/ipc.h>", public ] }, + { symbol: [ "uid_t", private, "<sys/stat.h>", public ] }, + { symbol: [ "uid_t", private, "<unistd.h>", private ] }, + { symbol: [ "useconds_t", private, "<sys/types.h>", public ] }, + { symbol: [ "useconds_t", private, "<unistd.h>", private ] }, + { symbol: [ "wchar_t", private, "<stddef.h>", public ] }, + { symbol: [ "wchar_t", private, "<stdlib.h>", public ] }, + { symbol: [ "size_t", private, "<stddef.h>", public ] }, + { symbol: [ "size_t", private, "<aio.h>", public ] }, + { symbol: [ "size_t", private, "<glob.h>", public ] }, + { symbol: [ "size_t", private, "<grp.h>", public ] }, + { symbol: [ "size_t", private, "<iconv.h>", public ] }, + { symbol: [ "size_t", private, "<monetary.h>", public ] }, + { symbol: [ "size_t", private, "<mqueue.h>", public ] }, + { symbol: [ "size_t", private, "<ndbm.h>", public ] }, + { symbol: [ "size_t", private, "<pwd.h>", public ] }, + { symbol: [ "size_t", private, "<regex.h>", public ] }, + { symbol: [ "size_t", private, "<search.h>", public ] }, + { symbol: [ "size_t", private, "<signal.h>", public ] }, + { symbol: [ "size_t", private, "<stdio.h>", public ] }, + { symbol: [ "size_t", private, "<stdlib.h>", public ] }, + { symbol: [ "size_t", private, "<string.h>", public ] }, + { symbol: [ "size_t", private, "<strings.h>", public ] }, + { symbol: [ "size_t", private, "<sys/mman.h>", public ] }, + { symbol: [ "size_t", private, "<sys/msg.h>", public ] }, + { symbol: [ "size_t", private, "<sys/sem.h>", public ] }, + { symbol: [ "size_t", private, "<sys/shm.h>", public ] }, + { symbol: [ "size_t", private, "<sys/socket.h>", private ] }, + { symbol: [ "size_t", private, "<sys/types.h>", public ] }, + { symbol: [ "size_t", private, "<sys/uio.h>", public ] }, + { symbol: [ "size_t", private, "<time.h>", public ] }, + { symbol: [ "size_t", private, "<uchar.h>", public ] }, + { symbol: [ "size_t", private, "<unistd.h>", private ] }, + { symbol: [ "size_t", private, "<wchar.h>", public ] }, + { symbol: [ "size_t", private, "<wordexp.h>", public ] }, + # Macros that can be defined in more than one file, don't have the + # same __foo_defined guard that other types do, so the grep above + # doesn't discover them. Until I figure out a better way, I just + # add them in by hand as I discover them. + { symbol: [ "EOF", private, "<stdio.h>", public ] }, + { symbol: [ "EOF", private, "<libio.h>", public ] }, + { symbol: [ "FILE", private, "<stdio.h>", public ] }, + { symbol: [ "MAP_POPULATE", private, "<sys/mman.h>", public ] }, + { symbol: [ "MAP_POPULATE", private, "<linux/mman.h>", public ] }, + { symbol: [ "MAP_STACK", private, "<sys/mman.h>", public ] }, + { symbol: [ "MAP_STACK", private, "<linux/mman.h>", public ] }, + { symbol: [ "MAXHOSTNAMELEN", private, "<sys/param.h>", public ] }, + { symbol: [ "MAXHOSTNAMELEN", private, "<protocols/timed.h>", public ] }, + { symbol: [ "SIGABRT", private, "<signal.h>", public ] }, + { symbol: [ "SIGCHLD", private, "<signal.h>", public ] }, + { symbol: [ "SIGCHLD", private, "<linux/signal.h>", public ] }, + { symbol: [ "va_list", private, "<stdarg.h>", public ] }, + { symbol: [ "va_list", private, "<stdio.h>", public ] }, + { symbol: [ "va_list", private, "<wchar.h>", public ] }, + # These are symbols that could be defined in either stdlib.h or + # malloc.h, but we always want the stdlib location. + { symbol: [ "malloc", private, "<stdlib.h>", public ] }, + { symbol: [ "calloc", private, "<stdlib.h>", public ] }, + { symbol: [ "realloc", private, "<stdlib.h>", public ] }, + { symbol: [ "free", private, "<stdlib.h>", public ] }, + # Entries for NULL + { symbol: [ "NULL", private, "<stddef.h>", public ] }, # 'canonical' location for NULL + { symbol: [ "NULL", private, "<clocale>", public ] }, + { symbol: [ "NULL", private, "<cstddef>", public ] }, + { symbol: [ "NULL", private, "<cstdio>", public ] }, + { symbol: [ "NULL", private, "<cstdlib>", public ] }, + { symbol: [ "NULL", private, "<cstring>", public ] }, + { symbol: [ "NULL", private, "<ctime>", public ] }, + { symbol: [ "NULL", private, "<cwchar>", public ] }, + { symbol: [ "NULL", private, "<locale.h>", public ] }, + { symbol: [ "NULL", private, "<stdio.h>", public ] }, + { symbol: [ "NULL", private, "<stdlib.h>", public ] }, + { symbol: [ "NULL", private, "<string.h>", public ] }, + { symbol: [ "NULL", private, "<time.h>", public ] }, + { symbol: [ "NULL", private, "<unistd.h>", private ] }, + { symbol: [ "NULL", private, "<wchar.h>", public ] }, + + # Kludge time: almost all STL types take an allocator, but they + # almost always use the default value. Usually we detect that + # and don't try to do IWYU, but sometimes it passes through. + # For instance, when adding two strings, we end up calling + # template<_CharT,_Traits,_Alloc> ... operator+( + # basic_string<_CharT,_Traits,_Alloc>, ...) + # These look like normal template args to us, so we see they're + # used and declare an iwyu dependency, even though we don't need + # to #include the traits or alloc type ourselves. The surest way + # to deal with this is to just say that everyone provides + # std::allocator. We can add more here at need. + { symbol: [ "std::allocator", private, "<memory>", public ] }, + { symbol: [ "std::allocator", private, "<string>", public ] }, + { symbol: [ "std::allocator", private, "<vector>", public ] }, + { symbol: [ "std::allocator", private, "<map>", public ] }, + { symbol: [ "std::allocator", private, "<set>", public ] }, + # A similar kludge for std::char_traits. basic_string, + # basic_ostream and basic_istream have this as a default template + # argument, and sometimes it bleeds through when clang desugars the + # string/ostream/istream type. + { symbol: [ "std::char_traits", private, "<string>", public ] }, + { symbol: [ "std::char_traits", private, "<ostream>", public ] }, + { symbol: [ "std::char_traits", private, "<istream>", public ] }, + + { symbol: [ "std::size_t", private, "<cstddef>", public ] }, + { symbol: [ "std::size_t", private, "<cstdio>", public ] }, + { symbol: [ "std::size_t", private, "<cstdlib>", public ] }, + { symbol: [ "std::size_t", private, "<cstring>", public ] }, + { symbol: [ "std::size_t", private, "<ctime>", public ] }, + { symbol: [ "std::size_t", private, "<cuchar>", public ] }, + { symbol: [ "std::size_t", private, "<cwchar>", public ] } +] + +# vim: set ft=toml: diff --git a/cmake.config/iwyu/mapping.imp b/cmake.config/iwyu/mapping.imp new file mode 100644 index 0000000000..4e55aa9875 --- /dev/null +++ b/cmake.config/iwyu/mapping.imp @@ -0,0 +1,238 @@ +[ + # Generated to normal headers: header.h.generated.h -> nvim/header.h + { include: [ '"api/autocmd.h.generated.h"', private, '"nvim/api/autocmd.h"', public ] }, + { include: [ '"api/buffer.h.generated.h"', private, '"nvim/api/buffer.h"', public ] }, + { include: [ '"api/command.h.generated.h"', private, '"nvim/api/command.h"', public ] }, + { include: [ '"api/deprecated.h.generated.h"', private, '"nvim/api/deprecated.h"', public ] }, + { include: [ '"api/extmark.h.generated.h"', private, '"nvim/api/extmark.h"', public ] }, + { include: [ '"api/options.h.generated.h"', private, '"nvim/api/options.h"', public ] }, + { include: [ '"api/private/converter.h.generated.h"', private, '"nvim/api/private/converter.h"', public ] }, + { include: [ '"api/private/dispatch.h.generated.h"', private, '"nvim/api/private/dispatch.h"', public ] }, + { include: [ '"api/private/helpers.h.generated.h"', private, '"nvim/api/private/helpers.h"', public ] }, + { include: [ '"api/tabpage.h.generated.h"', private, '"nvim/api/tabpage.h"', public ] }, + { include: [ '"api/ui.h.generated.h"', private, '"nvim/api/ui.h"', public ] }, + { include: [ '"api/vim.h.generated.h"', private, '"nvim/api/vim.h"', public ] }, + { include: [ '"api/vimscript.h.generated.h"', private, '"nvim/api/vimscript.h"', public ] }, + { include: [ '"api/win_config.h.generated.h"', private, '"nvim/api/win_config.h"', public ] }, + { include: [ '"api/window.h.generated.h"', private, '"nvim/api/window.h"', public ] }, + { include: [ '"arabic.h.generated.h"', private, '"nvim/arabic.h"', public ] }, + { include: [ '"arglist.h.generated.h"', private, '"nvim/arglist.h"', public ] }, + { include: [ '"autocmd.h.generated.h"', private, '"nvim/autocmd.h"', public ] }, + { include: [ '"buffer.h.generated.h"', private, '"nvim/buffer.h"', public ] }, + { include: [ '"buffer_updates.h.generated.h"', private, '"nvim/buffer_updates.h"', public ] }, + { include: [ '"change.h.generated.h"', private, '"nvim/change.h"', public ] }, + { include: [ '"channel.h.generated.h"', private, '"nvim/channel.h"', public ] }, + { include: [ '"charset.h.generated.h"', private, '"nvim/charset.h"', public ] }, + { include: [ '"cmdexpand.h.generated.h"', private, '"nvim/cmdexpand.h"', public ] }, + { include: [ '"cmdhist.h.generated.h"', private, '"nvim/cmdhist.h"', public ] }, + { include: [ '"context.h.generated.h"', private, '"nvim/context.h"', public ] }, + { include: [ '"cursor.h.generated.h"', private, '"nvim/cursor.h"', public ] }, + { include: [ '"cursor_shape.h.generated.h"', private, '"nvim/cursor_shape.h"', public ] }, + { include: [ '"debugger.h.generated.h"', private, '"nvim/debugger.h"', public ] }, + { include: [ '"decoration.h.generated.h"', private, '"nvim/decoration.h"', public ] }, + { include: [ '"decoration_provider.h.generated.h"', private, '"nvim/decoration_provider.h"', public ] }, + { include: [ '"diff.h.generated.h"', private, '"nvim/diff.h"', public ] }, + { include: [ '"digraph.h.generated.h"', private, '"nvim/digraph.h"', public ] }, + { include: [ '"drawline.h.generated.h"', private, '"nvim/drawline.h"', public ] }, + { include: [ '"drawscreen.h.generated.h"', private, '"nvim/drawscreen.h"', public ] }, + { include: [ '"edit.h.generated.h"', private, '"nvim/edit.h"', public ] }, + { include: [ '"eval.h.generated.h"', private, '"nvim/eval.h"', public ] }, + { include: [ '"eval/buffer.h.generated.h"', private, '"nvim/eval/buffer.h"', public ] }, + { include: [ '"eval/decode.h.generated.h"', private, '"nvim/eval/decode.h"', public ] }, + { include: [ '"eval/encode.h.generated.h"', private, '"nvim/eval/encode.h"', public ] }, + { include: [ '"eval/executor.h.generated.h"', private, '"nvim/eval/executor.h"', public ] }, + { include: [ '"eval/funcs.h.generated.h"', private, '"nvim/eval/funcs.h"', public ] }, + { include: [ '"eval/typval.h.generated.h"', private, '"nvim/eval/typval.h"', public ] }, + { include: [ '"eval/userfunc.h.generated.h"', private, '"nvim/eval/userfunc.h"', public ] }, + { include: [ '"eval/vars.h.generated.h"', private, '"nvim/eval/vars.h"', public ] }, + { include: [ '"eval/window.h.generated.h"', private, '"nvim/eval/window.h"', public ] }, + { include: [ '"event/libuv_process.h.generated.h"', private, '"nvim/event/libuv_process.h"', public ] }, + { include: [ '"event/loop.h.generated.h"', private, '"nvim/event/loop.h"', public ] }, + { include: [ '"event/multiqueue.h.generated.h"', private, '"nvim/event/multiqueue.h"', public ] }, + { include: [ '"event/process.h.generated.h"', private, '"nvim/event/process.h"', public ] }, + { include: [ '"event/rstream.h.generated.h"', private, '"nvim/event/rstream.h"', public ] }, + { include: [ '"event/signal.h.generated.h"', private, '"nvim/event/signal.h"', public ] }, + { include: [ '"event/socket.h.generated.h"', private, '"nvim/event/socket.h"', public ] }, + { include: [ '"event/stream.h.generated.h"', private, '"nvim/event/stream.h"', public ] }, + { include: [ '"event/time.h.generated.h"', private, '"nvim/event/time.h"', public ] }, + { include: [ '"event/wstream.h.generated.h"', private, '"nvim/event/wstream.h"', public ] }, + { include: [ '"ex_cmds.h.generated.h"', private, '"nvim/ex_cmds.h"', public ] }, + { include: [ '"ex_cmds2.h.generated.h"', private, '"nvim/ex_cmds2.h"', public ] }, + { include: [ '"ex_docmd.h.generated.h"', private, '"nvim/ex_docmd.h"', public ] }, + { include: [ '"ex_eval.h.generated.h"', private, '"nvim/ex_eval.h"', public ] }, + { include: [ '"ex_getln.h.generated.h"', private, '"nvim/ex_getln.h"', public ] }, + { include: [ '"ex_session.h.generated.h"', private, '"nvim/ex_session.h"', public ] }, + { include: [ '"extmark.h.generated.h"', private, '"nvim/extmark.h"', public ] }, + { include: [ '"file_search.h.generated.h"', private, '"nvim/file_search.h"', public ] }, + { include: [ '"fileio.h.generated.h"', private, '"nvim/fileio.h"', public ] }, + { include: [ '"fold.h.generated.h"', private, '"nvim/fold.h"', public ] }, + { include: [ '"garray.h.generated.h"', private, '"nvim/garray.h"', public ] }, + { include: [ '"getchar.h.generated.h"', private, '"nvim/getchar.h"', public ] }, + { include: [ '"grid.h.generated.h"', private, '"nvim/grid.h"', public ] }, + { include: [ '"hashtab.h.generated.h"', private, '"nvim/hashtab.h"', public ] }, + { include: [ '"help.h.generated.h"', private, '"nvim/help.h"', public ] }, + { include: [ '"highlight.h.generated.h"', private, '"nvim/highlight.h"', public ] }, + { include: [ '"highlight_group.h.generated.h"', private, '"nvim/highlight_group.h"', public ] }, + { include: [ '"if_cscope.h.generated.h"', private, '"nvim/if_cscope.h"', public ] }, + { include: [ '"indent.h.generated.h"', private, '"nvim/indent.h"', public ] }, + { include: [ '"indent_c.h.generated.h"', private, '"nvim/indent_c.h"', public ] }, + { include: [ '"input.h.generated.h"', private, '"nvim/input.h"', public ] }, + { include: [ '"insexpand.h.generated.h"', private, '"nvim/insexpand.h"', public ] }, + { include: [ '"keycodes.h.generated.h"', private, '"nvim/keycodes.h"', public ] }, + { include: [ '"linematch.h.generated.h"', private, '"nvim/linematch.h"', public ] }, + { include: [ '"locale.h.generated.h"', private, '"nvim/locale.h"', public ] }, + { include: [ '"log.h.generated.h"', private, '"nvim/log.h"', public ] }, + { include: [ '"lua/converter.h.generated.h"', private, '"nvim/lua/converter.h"', public ] }, + { include: [ '"lua/executor.h.generated.h"', private, '"nvim/lua/executor.h"', public ] }, + { include: [ '"lua/spell.h.generated.h"', private, '"nvim/lua/spell.h"', public ] }, + { include: [ '"lua/stdlib.h.generated.h"', private, '"nvim/lua/stdlib.h"', public ] }, + { include: [ '"lua/treesitter.h.generated.h"', private, '"nvim/lua/treesitter.h"', public ] }, + { include: [ '"lua/xdiff.h.generated.h"', private, '"nvim/lua/xdiff.h"', public ] }, + { include: [ '"main.h.generated.h"', private, '"nvim/main.h"', public ] }, + { include: [ '"mapping.h.generated.h"', private, '"nvim/mapping.h"', public ] }, + { include: [ '"mark.h.generated.h"', private, '"nvim/mark.h"', public ] }, + { include: [ '"marktree.h.generated.h"', private, '"nvim/marktree.h"', public ] }, + { include: [ '"match.h.generated.h"', private, '"nvim/match.h"', public ] }, + { include: [ '"math.h.generated.h"', private, '"nvim/math.h"', public ] }, + { include: [ '"mbyte.h.generated.h"', private, '"nvim/mbyte.h"', public ] }, + { include: [ '"memfile.h.generated.h"', private, '"nvim/memfile.h"', public ] }, + { include: [ '"memline.h.generated.h"', private, '"nvim/memline.h"', public ] }, + { include: [ '"memory.h.generated.h"', private, '"nvim/memory.h"', public ] }, + { include: [ '"menu.h.generated.h"', private, '"nvim/menu.h"', public ] }, + { include: [ '"message.h.generated.h"', private, '"nvim/message.h"', public ] }, + { include: [ '"mouse.h.generated.h"', private, '"nvim/mouse.h"', public ] }, + { include: [ '"move.h.generated.h"', private, '"nvim/move.h"', public ] }, + { include: [ '"msgpack_rpc/channel.h.generated.h"', private, '"nvim/msgpack_rpc/channel.h"', public ] }, + { include: [ '"msgpack_rpc/helpers.h.generated.h"', private, '"nvim/msgpack_rpc/helpers.h"', public ] }, + { include: [ '"msgpack_rpc/server.h.generated.h"', private, '"nvim/msgpack_rpc/server.h"', public ] }, + { include: [ '"msgpack_rpc/unpacker.h.generated.h"', private, '"nvim/msgpack_rpc/unpacker.h"', public ] }, + { include: [ '"normal.h.generated.h"', private, '"nvim/normal.h"', public ] }, + { include: [ '"ops.h.generated.h"', private, '"nvim/ops.h"', public ] }, + { include: [ '"option.h.generated.h"', private, '"nvim/option.h"', public ] }, + { include: [ '"optionstr.h.generated.h"', private, '"nvim/optionstr.h"', public ] }, + { include: [ '"os/dl.h.generated.h"', private, '"nvim/os/dl.h"', public ] }, + { include: [ '"os/fileio.h.generated.h"', private, '"nvim/os/fileio.h"', public ] }, + { include: [ '"os/fs.h.generated.h"', private, '"nvim/os/fs.h"', public ] }, + { include: [ '"os/input.h.generated.h"', private, '"nvim/os/input.h"', public ] }, + { include: [ '"os/lang.h.generated.h"', private, '"nvim/os/lang.h"', public ] }, + { include: [ '"os/process.h.generated.h"', private, '"nvim/os/process.h"', public ] }, + { include: [ '"os/pty_process_unix.h.generated.h"', private, '"nvim/os/pty_process_unix.h"', private ] }, + { include: [ '"os/shell.h.generated.h"', private, '"nvim/os/shell.h"', public ] }, + { include: [ '"os/signal.h.generated.h"', private, '"nvim/os/signal.h"', public ] }, + { include: [ '"os/time.h.generated.h"', private, '"nvim/os/time.h"', public ] }, + { include: [ '"path.h.generated.h"', private, '"nvim/path.h"', public ] }, + { include: [ '"plines.h.generated.h"', private, '"nvim/plines.h"', public ] }, + { include: [ '"popupmenu.h.generated.h"', private, '"nvim/popupmenu.h"', public ] }, + { include: [ '"profile.h.generated.h"', private, '"nvim/profile.h"', public ] }, + { include: [ '"quickfix.h.generated.h"', private, '"nvim/quickfix.h"', public ] }, + { include: [ '"rbuffer.h.generated.h"', private, '"nvim/rbuffer.h"', public ] }, + { include: [ '"regexp.h.generated.h"', private, '"nvim/regexp.h"', public ] }, + { include: [ '"runtime.h.generated.h"', private, '"nvim/runtime.h"', public ] }, + { include: [ '"screen.h.generated.h"', private, '"nvim/screen.h"', public ] }, + { include: [ '"search.h.generated.h"', private, '"nvim/search.h"', public ] }, + { include: [ '"sha256.h.generated.h"', private, '"nvim/sha256.h"', public ] }, + { include: [ '"shada.h.generated.h"', private, '"nvim/shada.h"', public ] }, + { include: [ '"sign.h.generated.h"', private, '"nvim/sign.h"', public ] }, + { include: [ '"spell.h.generated.h"', private, '"nvim/spell.h"', public ] }, + { include: [ '"spellfile.h.generated.h"', private, '"nvim/spellfile.h"', public ] }, + { include: [ '"spellsuggest.h.generated.h"', private, '"nvim/spellsuggest.h"', public ] }, + { include: [ '"state.h.generated.h"', private, '"nvim/state.h"', public ] }, + { include: [ '"statusline.h.generated.h"', private, '"nvim/statusline.h"', public ] }, + { include: [ '"strings.h.generated.h"', private, '"nvim/strings.h"', public ] }, + { include: [ '"syntax.h.generated.h"', private, '"nvim/syntax.h"', public ] }, + { include: [ '"tag.h.generated.h"', private, '"nvim/tag.h"', public ] }, + { include: [ '"terminal.h.generated.h"', private, '"nvim/terminal.h"', public ] }, + { include: [ '"testing.h.generated.h"', private, '"nvim/testing.h"', public ] }, + { include: [ '"textformat.h.generated.h"', private, '"nvim/textformat.h"', public ] }, + { include: [ '"textobject.h.generated.h"', private, '"nvim/textobject.h"', public ] }, + { include: [ '"tui/input.h.generated.h"', private, '"nvim/tui/input.h"', public ] }, + { include: [ '"tui/terminfo.h.generated.h"', private, '"nvim/tui/terminfo.h"', public ] }, + { include: [ '"tui/tui.h.generated.h"', private, '"nvim/tui/tui.h"', public ] }, + { include: [ '"ugrid.h.generated.h"', private, '"nvim/ugrid.h"', public ] }, + { include: [ '"ui.h.generated.h"', private, '"nvim/ui.h"', public ] }, + { include: [ '"ui_bridge.h.generated.h"', private, '"nvim/ui_bridge.h"', public ] }, + { include: [ '"ui_client.h.generated.h"', private, '"nvim/ui_client.h"', public ] }, + { include: [ '"ui_compositor.h.generated.h"', private, '"nvim/ui_compositor.h"', public ] }, + { include: [ '"undo.h.generated.h"', private, '"nvim/undo.h"', public ] }, + { include: [ '"usercmd.h.generated.h"', private, '"nvim/usercmd.h"', public ] }, + { include: [ '"version.h.generated.h"', private, '"nvim/version.h"', public ] }, + { include: [ '"viml/parser/expressions.h.generated.h"', private, '"nvim/viml/parser/expressions.h"', public ] }, + { include: [ '"viml/parser/parser.h.generated.h"', private, '"nvim/viml/parser/parser.h"', public ] }, + { include: [ '"window.h.generated.h"', private, '"nvim/window.h"', public ] }, + + # Generated to normal headers with a different name: header.h.generated.h -> nvim/some_other_header.h + { include: [ '"api/private/dispatch_wrappers.h.generated.h"', private, '"nvim/api/private/dispatch.h"', public ] }, + { include: [ '"auevents_enum.generated.h"', private, '"nvim/autocmd.h"', public ] }, + { include: [ '"ex_cmds_enum.generated.h"', private, '"nvim/ex_cmds_defs.h"', public ] }, + { include: [ '"keysets.h.generated.h"', private, '"nvim/api/private/helpers.h"', public ] }, + { include: [ '"keysets_defs.generated.h"', private, '"nvim/api/private/defs.h"', public ] }, + { include: [ '"nvim/os/pty_process_unix.h"', private, '"nvim/os/pty_process.h"', public ] }, + { include: [ '"nvim/os/pty_process_win.h"', private, '"nvim/os/pty_process.h"', public ] }, + { include: [ '"nvim/os/unix_defs.h"', private, '"nvim/os/os_defs.h"', public ] }, + { include: [ '"nvim/os/win_defs.h"', private, '"nvim/os/os_defs.h"', public ] }, + { include: [ '"os/env.h.generated.h"', private, '"nvim/os/os.h"', public ] }, + { include: [ '"os/fs.h.generated.h"', private, '"nvim/os/os.h"', public ] }, + { include: [ '"os/mem.h.generated.h"', private, '"nvim/os/os.h"', public ] }, + { include: [ '"os/stdpaths.h.generated.h"', private, '"nvim/os/os.h"', public ] }, + { include: [ '"os/users.h.generated.h"', private, '"nvim/os/os.h"', public ] }, + { include: [ '"regexp_bt.h.generated.h"', private, '"nvim/regexp.h"', public ] }, + { include: [ '"ui_events_call.h.generated.h"', private, '"nvim/ui.h"', public ] }, + { include: [ '"ui_events_client.h.generated.h"', private, '"nvim/ui_client.h"', public ] }, + { include: [ '"ui_events_remote.generated.h"', private, '"nvim/api/ui.h"', public ] }, + { include: [ '"ui_events_remote.h.generated.h"', private, '"nvim/api/ui.h"', public ] }, + + # Def to normal headers: nvim/header_defs.h -> nvim/header.h + # + # This is a public to public mapping, meaning that while IWYU can use the + # headers on the left, it will use the headers on the right if possible. This + # isn't explicitly mentioned in the IWYU docs, this is just my interpretation + # of its behavior. + { include: [ '"nvim/buffer_defs.h"', public, '"nvim/buffer.h"', public ] }, + { include: [ '"nvim/ex_cmds_defs.h"', public, '"nvim/ex_cmds.h"', public ] }, + { include: [ '"nvim/ex_eval_defs.h"', public, '"nvim/ex_eval.h"', public ] }, + { include: [ '"nvim/extmark_defs.h"', public, '"nvim/extmark.h"', public ] }, + { include: [ '"nvim/grid_defs.h"', public, '"nvim/grid.h"', public ] }, + { include: [ '"nvim/highlight_defs.h"', public, '"nvim/highlight.h"', public ] }, + { include: [ '"nvim/map_defs.h"', public, '"nvim/map.h"', public ] }, + { include: [ '"nvim/mark_defs.h"', public, '"nvim/mark.h"', public ] }, + { include: [ '"nvim/mbyte_defs.h"', public, '"nvim/mbyte.h"', public ] }, + { include: [ '"nvim/memfile_defs.h"', public, '"nvim/memfile.h"', public ] }, + { include: [ '"nvim/memline_defs.h"', public, '"nvim/memline.h"', public ] }, + { include: [ '"nvim/menu_defs.h"', public, '"nvim/menu.h"', public ] }, + { include: [ '"nvim/msgpack/channel_defs.h"', public, '"nvim/msgpack/channel.h"', public ] }, + { include: [ '"nvim/option_defs.h"', public, '"nvim/option.h"', public ] }, + { include: [ '"nvim/os/fs_defs.h"', public, '"nvim/os/fs.h"', public ] }, + { include: [ '"nvim/os/os_defs.h"', public, '"nvim/os/os.h"', public ] }, + { include: [ '"nvim/regexp_defs.h"', public, '"nvim/regexp.h"', public ] }, + { include: [ '"nvim/sign_defs.h"', public, '"nvim/sign.h"', public ] }, + { include: [ '"nvim/spell_defs.h"', public, '"nvim/spell.h"', public ] }, + { include: [ '"nvim/statusline_defs.h"', public, '"nvim/statusline.h"', public ] }, + { include: [ '"nvim/syntax_defs.h"', public, '"nvim/syntax.h"', public ] }, + { include: [ '"nvim/tui/input_defs.h"', public, '"nvim/tui/input.h"', public ] }, + { include: [ '"nvim/undo_defs.h"', public, '"nvim/undo.h"', public ] }, + + # Third party headers + { include: [ "<bits/types/wint_t.h>", private, "<wchar.h>", public ] }, + { include: [ '<arpa/inet.h>', private, '<uv/unix.h>', private ] }, + { include: [ '<bits/termios-c_cc.h>', private, '<termios.h>', private ] }, + { include: [ '<bits/termios-c_cflag.h>', private, '<termios.h>', private ] }, + { include: [ '<bits/termios-c_iflag.h>', private, '<termios.h>', private ] }, + { include: [ '<bits/termios-c_oflag.h>', private, '<termios.h>', private ] }, + { include: [ '<libintl.h>', private, '"nvim/gettext.h"', public ] }, + { include: [ '<netdb.h>', private, '<uv/unix.h>', private ] }, + { include: [ '<netinet/in.h>', private, '<uv/unix.h>', private ] }, + { include: [ '<pthread.h>', private, '"nvim/os/unix_defs.h"', private ] }, + { include: [ '<sys/socket.h>', private, '<uv/unix.h>', private ] }, + { include: [ '<termios.h>', private, '"nvim/os/unix_defs.h"', private ] }, + { include: [ '<unistd.h>', private, '"nvim/os/unix_defs.h"', private ] }, + { include: [ '<uv/unix.h>', private, '<uv.h>', public ] }, + + # Symbols + { symbol: [ "MAX", private, '"nvim/macros.h"', public ] }, + { symbol: [ "MIN", private, '"nvim/macros.h"', public ] }, + { symbol: [ "SEEK_END", private, '<stdio.h>', public ] }, + { symbol: [ "SEEK_SET", private, '<stdio.h>', public ] }, + { symbol: [ "time_fd", private, '"nvim/globals.h"', public ] }, +] + +# vim: set ft=toml: diff --git a/cmake.config/pathdef.c.in b/cmake.config/pathdef.c.in index 6a8a2b205a..5d6dfa6b9f 100644 --- a/cmake.config/pathdef.c.in +++ b/cmake.config/pathdef.c.in @@ -4,5 +4,5 @@ char *default_vim_dir = "${CMAKE_INSTALL_FULL_DATAROOTDIR}/nvim"; char *default_vimruntime_dir = ""; char *default_lib_dir = "${CMAKE_INSTALL_FULL_LIBDIR}/nvim"; -char_u *compiled_user = (char_u *)"${USERNAME}"; -char_u *compiled_sys = (char_u *)"${HOSTNAME}"; +char *compiled_user = "${USERNAME}"; +char *compiled_sys = "${HOSTNAME}"; diff --git a/cmake.deps/CMakeLists.txt b/cmake.deps/CMakeLists.txt index 0386432a69..a7e71ffc92 100644 --- a/cmake.deps/CMakeLists.txt +++ b/cmake.deps/CMakeLists.txt @@ -12,13 +12,21 @@ list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" "${PROJECT_SOURCE_DI include(CheckCCompilerFlag) include(Util) +set(DEPS_CMAKE_ARGS + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} + -DCMAKE_POSITION_INDEPENDENT_CODE=ON) + +set(DEPS_CMAKE_CACHE_ARGS -DCMAKE_OSX_ARCHITECTURES:STRING=${CMAKE_OSX_ARCHITECTURES}) + +set_default_buildtype() + get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(NOT isMultiConfig) - set(BUILD_TYPE_STRING -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}) + list(APPEND DEPS_CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}) endif() -set_default_buildtype() - set(DEFAULT_MAKE_CFLAGS CFLAGS+=-g) check_c_compiler_flag(-Og HAS_OG_FLAG) @@ -37,6 +45,8 @@ else() set(DEPS_INSTALL_DIR "${CMAKE_BINARY_DIR}/usr" CACHE PATH "Dependencies install directory.") endif() +list(APPEND DEPS_CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR}) + set(DEPS_BIN_DIR "${DEPS_INSTALL_DIR}/bin" CACHE PATH "Dependencies binary install directory.") set(DEPS_LIB_DIR "${DEPS_INSTALL_DIR}/lib" CACHE PATH "Dependencies library install directory.") set(DEPS_BUILD_DIR "${CMAKE_BINARY_DIR}/build" CACHE PATH "Dependencies build directory.") @@ -75,14 +85,6 @@ endif() if(UNIX) find_program(MAKE_PRG NAMES gmake make) - if(MAKE_PRG) - execute_process( - COMMAND "${MAKE_PRG}" --version - OUTPUT_VARIABLE MAKE_VERSION_INFO) - if(NOT "${OUTPUT_VARIABLE}" MATCHES ".*GNU.*") - unset(MAKE_PRG) - endif() - endif() if(NOT MAKE_PRG) message(FATAL_ERROR "GNU Make is required to build the dependencies.") else() @@ -107,27 +109,16 @@ endif() set(DEPS_C_COMPILER "${CMAKE_C_COMPILER}") -if(CMAKE_CXX_COMPILER) - set(DEPS_CXX_COMPILER "${CMAKE_CXX_COMPILER}") -endif() - if(CMAKE_OSX_SYSROOT) set(DEPS_C_COMPILER "${DEPS_C_COMPILER} -isysroot${CMAKE_OSX_SYSROOT}") - if(DEPS_CXX_COMPILER) - set(DEPS_CXX_COMPILER "${DEPS_CXX_COMPILER} -isysroot${CMAKE_OSX_SYSROOT}") - endif() endif() if(CMAKE_OSX_ARCHITECTURES) - string(REPLACE ";" "|" CMAKE_OSX_ARCHITECTURES_ALT_SEP "${CMAKE_OSX_ARCHITECTURES}") # The LuaJIT build does not like being passed multiple `-arch` flags # so we handle a universal build the old-fashioned way. set(LUAJIT_C_COMPILER "${DEPS_C_COMPILER}") foreach(ARCH IN LISTS CMAKE_OSX_ARCHITECTURES) set(DEPS_C_COMPILER "${DEPS_C_COMPILER} -arch ${ARCH}") - if(DEPS_CXX_COMPILER) - set(DEPS_CXX_COMPILER "${DEPS_CXX_COMPILER} -arch ${ARCH}") - endif() endforeach() endif() @@ -140,7 +131,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") OUTPUT_STRIP_TRAILING_WHITESPACE) set(CMAKE_OSX_DEPLOYMENT_TARGET "${MACOS_VERSION}") endif() - message("-- Using deployment target ${CMAKE_OSX_DEPLOYMENT_TARGET}") + message(STATUS "Using deployment target ${CMAKE_OSX_DEPLOYMENT_TARGET}") endif() include(ExternalProject) @@ -153,8 +144,8 @@ set(MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/c-4.0.0/m set(MSGPACK_SHA256 420fe35e7572f2a168d17e660ef981a589c9cbe77faa25eb34a520e1fcc032c8) # https://github.com/LuaJIT/LuaJIT/tree/v2.1 -set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/6c4826f12c4d33b8b978004bc681eb1eef2be977.tar.gz) -set(LUAJIT_SHA256 19a911fdd77af69e48fa50749606a9009696f543cc88e898b4480d8d3c8828f5) +set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/d0e88930ddde28ff662503f9f20facf34f7265aa.tar.gz) +set(LUAJIT_SHA256 aa354d1265814db5a1ee9dfff6049e19b148e1fd818f1ecfa4fcf2b19f6e4dd9) set(LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz) set(LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333) @@ -168,8 +159,8 @@ set(UNIBILIUM_SHA256 29815283c654277ef77a3adcc8840db79ddbb20a0f0b0c8f648bd8cd49a set(LIBTERMKEY_URL https://www.leonerd.org.uk/code/libtermkey/libtermkey-0.22.tar.gz) set(LIBTERMKEY_SHA256 6945bd3c4aaa83da83d80a045c5563da4edd7d0374c62c0d35aec09eb3014600) -set(LIBVTERM_URL https://www.leonerd.org.uk/code/libvterm/libvterm-0.3.tar.gz) -set(LIBVTERM_SHA256 61eb0d6628c52bdf02900dfd4468aa86a1a7125228bab8a67328981887483358) +set(LIBVTERM_URL https://www.leonerd.org.uk/code/libvterm/libvterm-0.3.1.tar.gz) +set(LIBVTERM_SHA256 25a8ad9c15485368dfd0a8a9dca1aec8fea5c27da3fa74ec518d5d3787f0c397) set(LUV_VERSION 1.44.2-1) set(LUV_URL https://github.com/luvit/luv/archive/80c8c00baebe3e994d1616d4b54097c2d6e14834.tar.gz) @@ -197,17 +188,17 @@ set(LIBICONV_SHA256 ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc891 set(TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/v0.20.2.tar.gz) set(TREESITTER_C_SHA256 af66fde03feb0df4faf03750102a0d265b007e5d957057b6b293c13116a70af2 ) -set(TREESITTER_LUA_URL https://github.com/MunifTanjim/tree-sitter-lua/archive/v0.0.13.tar.gz) -set(TREESITTER_LUA_SHA256 564594fe0ffd2f2fb3578a15019b723e1bc94ac82cb6a0103a6b3b9ddcc6f315) +set(TREESITTER_LUA_URL https://github.com/MunifTanjim/tree-sitter-lua/archive/v0.0.14.tar.gz) +set(TREESITTER_LUA_SHA256 930d0370dc15b66389869355c8e14305b9ba7aafd36edbfdb468c8023395016d) -set(TREESITTER_VIM_URL https://github.com/vigoux/tree-sitter-viml/archive/v0.2.0.tar.gz) -set(TREESITTER_VIM_SHA256 608dcc31a7948cb66ae7f45494620e2e9face1af75598205541f80d782ec4501) +set(TREESITTER_VIM_URL https://github.com/vigoux/tree-sitter-viml/archive/55ff1b080c09edeced9b748cf4c16d0b49d17fb9.tar.gz) +set(TREESITTER_VIM_SHA256 1b1cd39e33c8fb02fa7fe3977e844883c2a8508a7edd621f2d21e39a9aeefa92) -set(TREESITTER_HELP_URL https://github.com/neovim/tree-sitter-vimdoc/archive/v1.2.0.tar.gz) -set(TREESITTER_HELP_SHA256 63efc664dc0d7113fad7c5b437fa4b7b419eaa1760bb93ab466c0743db86aedd) +set(TREESITTER_HELP_URL https://github.com/neovim/tree-sitter-vimdoc/archive/ce20f13c3f12506185754888feaae3f2ad54c287.tar.gz) +set(TREESITTER_HELP_SHA256 2b8b166438cce66064aab56a744430b1f44871f43e47f70b51246d14bb826609) -set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.20.7.tar.gz) -set(TREESITTER_SHA256 b355e968ec2d0241bbd96748e00a9038f83968f85d822ecb9940cbe4c42e182e) +set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/eb970a83a107cbaba52e31588355c0b09b3a308a.tar.gz) +set(TREESITTER_SHA256 58063721182f182fb9ec5481794f2ba594e52d6c2497e0214570535054dfc78d) if(USE_BUNDLED_UNIBILIUM) include(BuildUnibilium) diff --git a/cmake.deps/cmake/BuildGettext.cmake b/cmake.deps/cmake/BuildGettext.cmake index aecd5da626..1f9fd38702 100644 --- a/cmake.deps/cmake/BuildGettext.cmake +++ b/cmake.deps/cmake/BuildGettext.cmake @@ -7,18 +7,13 @@ if(MSVC) URL_HASH SHA256=${GETTEXT_SHA256} DOWNLOAD_NO_PROGRESS TRUE DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/gettext - CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy + PATCH_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/cmake/GettextCMakeLists.txt - ${DEPS_BUILD_DIR}/src/gettext/CMakeLists.txt - COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/gettext - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - ${BUILD_TYPE_STRING} - -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 $<CONFIG> - INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config $<CONFIG>) + ${DEPS_BUILD_DIR}/src/gettext/CMakeLists.txt + CMAKE_ARGS ${DEPS_CMAKE_ARGS} + -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} + CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}) else() message(FATAL_ERROR "Trying to build gettext in an unsupported system ${CMAKE_SYSTEM_NAME}/${CMAKE_C_COMPILER_ID}") endif() diff --git a/cmake.deps/cmake/BuildLibiconv.cmake b/cmake.deps/cmake/BuildLibiconv.cmake index 26d9b02e77..4b9c07ed6c 100644 --- a/cmake.deps/cmake/BuildLibiconv.cmake +++ b/cmake.deps/cmake/BuildLibiconv.cmake @@ -7,16 +7,11 @@ if(MSVC) URL_HASH SHA256=${LIBICONV_SHA256} DOWNLOAD_NO_PROGRESS TRUE DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/libiconv - CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy + PATCH_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/cmake/LibiconvCMakeLists.txt - ${DEPS_BUILD_DIR}/src/libiconv/CMakeLists.txt - COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/libiconv - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - ${BUILD_TYPE_STRING} - -DCMAKE_GENERATOR=${CMAKE_GENERATOR} - -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} - BUILD_COMMAND ${CMAKE_COMMAND} --build . --config $<CONFIG> - INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config $<CONFIG>) + ${DEPS_BUILD_DIR}/src/libiconv/CMakeLists.txt + CMAKE_ARGS ${DEPS_CMAKE_ARGS} + CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}) else() message(FATAL_ERROR "Trying to build libiconv in an unsupported system ${CMAKE_SYSTEM_NAME}/${CMAKE_C_COMPILER_ID}") endif() diff --git a/cmake.deps/cmake/BuildLibtermkey.cmake b/cmake.deps/cmake/BuildLibtermkey.cmake index 1e0fb5d36a..6457a864ba 100644 --- a/cmake.deps/cmake/BuildLibtermkey.cmake +++ b/cmake.deps/cmake/BuildLibtermkey.cmake @@ -1,27 +1,3 @@ -if(WIN32) - set(LIBTERMKEY_CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_CURRENT_SOURCE_DIR}/cmake/libtermkeyCMakeLists.txt - ${DEPS_BUILD_DIR}/src/libtermkey/CMakeLists.txt - COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/libtermkey - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - ${BUILD_TYPE_STRING} - # 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}) - set(LIBTERMKEY_BUILD_COMMAND ${CMAKE_COMMAND} --build . --config $<CONFIG>) - set(LIBTERMKEY_INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config $<CONFIG>) -else() - find_package(PkgConfig REQUIRED) - - set(LIBTERMKEY_BUILD_COMMAND "" BUILD_IN_SOURCE 1) - set(LIBTERMKEY_INSTALL_COMMAND ${MAKE_PRG} CC=${DEPS_C_COMPILER} - PREFIX=${DEPS_INSTALL_DIR} PKG_CONFIG_PATH=${DEPS_LIB_DIR}/pkgconfig - CFLAGS=-fPIC LDFLAGS+=-static ${DEFAULT_MAKE_CFLAGS} install) -endif() - if(USE_EXISTING_SRC_DIR) unset(LIBTERMKEY_URL) endif() @@ -30,8 +6,13 @@ ExternalProject_Add(libtermkey URL_HASH SHA256=${LIBTERMKEY_SHA256} DOWNLOAD_NO_PROGRESS TRUE DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/libtermkey - CONFIGURE_COMMAND "${LIBTERMKEY_CONFIGURE_COMMAND}" - BUILD_COMMAND "${LIBTERMKEY_BUILD_COMMAND}" - INSTALL_COMMAND "${LIBTERMKEY_INSTALL_COMMAND}") + PATCH_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/libtermkeyCMakeLists.txt + ${DEPS_BUILD_DIR}/src/libtermkey/CMakeLists.txt + CMAKE_ARGS ${DEPS_CMAKE_ARGS} + -DCMAKE_SHARED_LIBRARY_LINK_C_FLAGS="" # Hack to avoid -rdynamic in Mingw + -DUNIBILIUM_INCLUDE_DIRS=${DEPS_INSTALL_DIR}/include + -DUNIBILIUM_LIBRARIES=${DEPS_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}unibilium${CMAKE_STATIC_LIBRARY_SUFFIX} + CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}) list(APPEND THIRD_PARTY_DEPS libtermkey) diff --git a/cmake.deps/cmake/BuildLibuv.cmake b/cmake.deps/cmake/BuildLibuv.cmake index c21e166d95..eb88458644 100644 --- a/cmake.deps/cmake/BuildLibuv.cmake +++ b/cmake.deps/cmake/BuildLibuv.cmake @@ -5,15 +5,11 @@ ExternalProject_Add(libuv URL ${LIBUV_URL} URL_HASH SHA256=${LIBUV_SHA256} DOWNLOAD_NO_PROGRESS TRUE - CMAKE_ARGS - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} + DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/libuv + CMAKE_ARGS ${DEPS_CMAKE_ARGS} -DCMAKE_INSTALL_LIBDIR=lib -DBUILD_TESTING=OFF - -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DLIBUV_BUILD_SHARED=OFF - ${BUILD_TYPE_STRING} - CMAKE_CACHE_ARGS - -DCMAKE_OSX_ARCHITECTURES:STRING=${CMAKE_OSX_ARCHITECTURES} - DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/libuv) + CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}) list(APPEND THIRD_PARTY_DEPS libuv) diff --git a/cmake.deps/cmake/BuildLibvterm.cmake b/cmake.deps/cmake/BuildLibvterm.cmake index dffa545638..1578d56fba 100644 --- a/cmake.deps/cmake/BuildLibvterm.cmake +++ b/cmake.deps/cmake/BuildLibvterm.cmake @@ -9,10 +9,8 @@ if(WIN32) -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} - -DCMAKE_GENERATOR=${CMAKE_GENERATOR}) - if(NOT MSVC) - list(APPEND LIBVTERM_CONFIGURE_COMMAND "-DCMAKE_C_FLAGS:STRING=-fPIC") - endif() + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_POSITION_INDEPENDENT_CODE=ON) set(LIBVTERM_BUILD_COMMAND ${CMAKE_COMMAND} --build . --config $<CONFIG>) set(LIBVTERM_INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config $<CONFIG>) else() @@ -35,6 +33,8 @@ ExternalProject_Add(libvterm BUILD_IN_SOURCE 1 CONFIGURE_COMMAND "${LIBVTERM_CONFIGURE_COMMAND}" BUILD_COMMAND "${LIBVTERM_BUILD_COMMAND}" - INSTALL_COMMAND "${LIBVTERM_INSTALL_COMMAND}") + INSTALL_COMMAND "${LIBVTERM_INSTALL_COMMAND}" + CMAKE_ARGS ${DEPS_CMAKE_ARGS} + CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}) list(APPEND THIRD_PARTY_DEPS libvterm) diff --git a/cmake.deps/cmake/BuildLuv.cmake b/cmake.deps/cmake/BuildLuv.cmake index 1a599a9ee2..38c0503c5b 100644 --- a/cmake.deps/cmake/BuildLuv.cmake +++ b/cmake.deps/cmake/BuildLuv.cmake @@ -1,13 +1,7 @@ -set(LUV_SRC_DIR ${DEPS_BUILD_DIR}/src/luv) set(LUV_INCLUDE_FLAGS "-I${DEPS_INSTALL_DIR}/include -I${DEPS_INSTALL_DIR}/include/luajit-2.1") -set(LUV_CONFIGURE_COMMAND_COMMON - ${CMAKE_COMMAND} ${LUV_SRC_DIR} - -DCMAKE_GENERATOR=${CMAKE_GENERATOR} - ${BUILD_TYPE_STRING} - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - -DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES_ALT_SEP} +set(LUV_CMAKE_ARGS -DLUA_BUILD_TYPE=System -DLUA_COMPAT53_DIR=${DEPS_BUILD_DIR}/src/lua-compat-5.3 -DWITH_SHARED_LIBUV=ON @@ -16,43 +10,29 @@ set(LUV_CONFIGURE_COMMAND_COMMON -DBUILD_MODULE=OFF) if(USE_BUNDLED_LUAJIT) - list(APPEND LUV_CONFIGURE_COMMAND_COMMON -DWITH_LUA_ENGINE=LuaJit) + list(APPEND LUV_CMAKE_ARGS -DWITH_LUA_ENGINE=LuaJit) elseif(USE_BUNDLED_LUA) - list(APPEND LUV_CONFIGURE_COMMAND_COMMON -DWITH_LUA_ENGINE=Lua) + list(APPEND LUV_CMAKE_ARGS -DWITH_LUA_ENGINE=Lua) else() find_package(LuaJit) if(LUAJIT_FOUND) - list(APPEND LUV_CONFIGURE_COMMAND_COMMON -DWITH_LUA_ENGINE=LuaJit) + list(APPEND LUV_CMAKE_ARGS -DWITH_LUA_ENGINE=LuaJit) else() - list(APPEND LUV_CONFIGURE_COMMAND_COMMON -DWITH_LUA_ENGINE=Lua) + list(APPEND LUV_CMAKE_ARGS -DWITH_LUA_ENGINE=Lua) endif() endif() if(USE_BUNDLED_LIBUV) - set(LUV_CONFIGURE_COMMAND_COMMON - ${LUV_CONFIGURE_COMMAND_COMMON} + list(APPEND LUV_CMAKE_ARGS -DCMAKE_PREFIX_PATH=${DEPS_INSTALL_DIR} -DLIBUV_LIBRARIES=uv_a) endif() -if(MSVC) - set(LUV_CONFIGURE_COMMAND - ${LUV_CONFIGURE_COMMAND_COMMON} - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} - "-DCMAKE_C_FLAGS:STRING=${LUV_INCLUDE_FLAGS}" - # Make sure we use the same generator, otherwise we may - # accidentally end up using different MSVC runtimes - -DCMAKE_GENERATOR=${CMAKE_GENERATOR}) -else() - set(LUV_CONFIGURE_COMMAND - ${LUV_CONFIGURE_COMMAND_COMMON} - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - "-DCMAKE_C_FLAGS:STRING=${LUV_INCLUDE_FLAGS} -fPIC") - if(CMAKE_GENERATOR MATCHES "Unix Makefiles" AND - (CMAKE_SYSTEM_NAME MATCHES ".*BSD" OR CMAKE_SYSTEM_NAME MATCHES "DragonFly")) - set(LUV_CONFIGURE_COMMAND ${LUV_CONFIGURE_COMMAND} -DCMAKE_MAKE_PROGRAM=gmake) - endif() +list(APPEND LUV_CMAKE_ARGS + "-DCMAKE_C_FLAGS:STRING=${LUV_INCLUDE_FLAGS}") +if(CMAKE_GENERATOR MATCHES "Unix Makefiles" AND + (CMAKE_SYSTEM_NAME MATCHES ".*BSD" OR CMAKE_SYSTEM_NAME MATCHES "DragonFly")) + list(APPEND LUV_CMAKE_ARGS -DCMAKE_MAKE_PROGRAM=gmake) endif() if(USE_EXISTING_SRC_DIR) @@ -77,10 +57,8 @@ ExternalProject_Add(luv-static DOWNLOAD_NO_PROGRESS TRUE DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/luv SOURCE_DIR ${DEPS_BUILD_DIR}/src/luv - CONFIGURE_COMMAND "${LUV_CONFIGURE_COMMAND}" - BUILD_COMMAND ${CMAKE_COMMAND} --build . --config $<CONFIG> - INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config $<CONFIG> - LIST_SEPARATOR |) + CMAKE_ARGS ${DEPS_CMAKE_ARGS} ${LUV_CMAKE_ARGS} + CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}) list(APPEND THIRD_PARTY_DEPS luv-static) if(USE_BUNDLED_LUAJIT) diff --git a/cmake.deps/cmake/BuildMsgpack.cmake b/cmake.deps/cmake/BuildMsgpack.cmake index b59d98159d..431420fb62 100644 --- a/cmake.deps/cmake/BuildMsgpack.cmake +++ b/cmake.deps/cmake/BuildMsgpack.cmake @@ -1,26 +1,3 @@ -set(MSGPACK_CONFIGURE_COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/msgpack - -DMSGPACK_BUILD_TESTS=OFF - -DMSGPACK_BUILD_EXAMPLES=OFF - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - -DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES_ALT_SEP} - "-DCMAKE_C_FLAGS:STRING=-fPIC" - -DCMAKE_GENERATOR=${CMAKE_GENERATOR}) - -if(MSVC) - # Same as Unix without fPIC - set(MSGPACK_CONFIGURE_COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/msgpack - -DMSGPACK_BUILD_TESTS=OFF - -DMSGPACK_BUILD_EXAMPLES=OFF - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} - ${BUILD_TYPE_STRING} - # Make sure we use the same generator, otherwise we may - # accidentally end up using different MSVC runtimes - -DCMAKE_GENERATOR=${CMAKE_GENERATOR}) -endif() - if(USE_EXISTING_SRC_DIR) unset(MSGPACK_URL) endif() @@ -29,9 +6,9 @@ ExternalProject_Add(msgpack URL_HASH SHA256=${MSGPACK_SHA256} DOWNLOAD_NO_PROGRESS TRUE DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/msgpack - CONFIGURE_COMMAND "${MSGPACK_CONFIGURE_COMMAND}" - BUILD_COMMAND ${CMAKE_COMMAND} --build . --config $<CONFIG> - INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config $<CONFIG> - LIST_SEPARATOR |) + CMAKE_ARGS ${DEPS_CMAKE_ARGS} + -DMSGPACK_BUILD_TESTS=OFF + -DMSGPACK_BUILD_EXAMPLES=OFF + CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}) list(APPEND THIRD_PARTY_DEPS msgpack) diff --git a/cmake.deps/cmake/BuildTreesitter.cmake b/cmake.deps/cmake/BuildTreesitter.cmake index c3ea02014f..d906e6aa59 100644 --- a/cmake.deps/cmake/BuildTreesitter.cmake +++ b/cmake.deps/cmake/BuildTreesitter.cmake @@ -1,22 +1,3 @@ -if(MSVC) - set(TREESITTER_CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_CURRENT_SOURCE_DIR}/cmake/TreesitterCMakeLists.txt - ${DEPS_BUILD_DIR}/src/tree-sitter/CMakeLists.txt - 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} - ${BUILD_TYPE_STRING} - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR}) - set(TREESITTER_BUILD_COMMAND ${CMAKE_COMMAND} --build . --config $<CONFIG>) - set(TREESITTER_INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config $<CONFIG>) -else() - set(TS_CFLAGS "-O3 -Wall -Wextra") - set(TREESITTER_BUILD_COMMAND ${MAKE_PRG} CC=${DEPS_C_COMPILER} CFLAGS=${TS_CFLAGS}) - set(TREESITTER_INSTALL_COMMAND - ${MAKE_PRG} CC=${DEPS_C_COMPILER} PREFIX=${DEPS_INSTALL_DIR} install) -endif() - if(USE_EXISTING_SRC_DIR) unset(TREESITTER_URL) endif() @@ -26,9 +7,10 @@ ExternalProject_Add(tree-sitter DOWNLOAD_NO_PROGRESS TRUE DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/tree-sitter INSTALL_DIR ${DEPS_INSTALL_DIR} - BUILD_IN_SOURCE 1 - CONFIGURE_COMMAND "${TREESITTER_CONFIGURE_COMMAND}" - BUILD_COMMAND "${TREESITTER_BUILD_COMMAND}" - INSTALL_COMMAND "${TREESITTER_INSTALL_COMMAND}") + PATCH_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/TreesitterCMakeLists.txt + ${DEPS_BUILD_DIR}/src/tree-sitter/CMakeLists.txt + CMAKE_ARGS ${DEPS_CMAKE_ARGS} + CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}) list(APPEND THIRD_PARTY_DEPS tree-sitter) diff --git a/cmake.deps/cmake/BuildTreesitterParsers.cmake b/cmake.deps/cmake/BuildTreesitterParsers.cmake index ead039aae6..d62b19d97d 100644 --- a/cmake.deps/cmake/BuildTreesitterParsers.cmake +++ b/cmake.deps/cmake/BuildTreesitterParsers.cmake @@ -8,14 +8,12 @@ function(BuildTSParser LANG TS_URL TS_SHA256 TS_CMAKE_FILE) URL_HASH SHA256=${TS_SHA256} DOWNLOAD_NO_PROGRESS TRUE DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/${NAME} - CMAKE_CACHE_ARGS - -DCMAKE_OSX_ARCHITECTURES:STRING=${CMAKE_OSX_ARCHITECTURES} PATCH_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/cmake/${TS_CMAKE_FILE} ${DEPS_BUILD_DIR}/src/${NAME}/CMakeLists.txt - CMAKE_ARGS - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - -DPARSERLANG=${LANG}) + CMAKE_ARGS ${DEPS_CMAKE_ARGS} + -DPARSERLANG=${LANG} + CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}) endfunction() BuildTSParser(c ${TREESITTER_C_URL} ${TREESITTER_C_SHA256} TreesitterParserCMakeLists.txt) diff --git a/cmake.deps/cmake/BuildUnibilium.cmake b/cmake.deps/cmake/BuildUnibilium.cmake index cc56499edb..9a8caf89d1 100644 --- a/cmake.deps/cmake/BuildUnibilium.cmake +++ b/cmake.deps/cmake/BuildUnibilium.cmake @@ -1,21 +1,3 @@ -if(WIN32) - set(UNIBILIUM_CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_CURRENT_SOURCE_DIR}/cmake/UnibiliumCMakeLists.txt - ${DEPS_BUILD_DIR}/src/unibilium/CMakeLists.txt - COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/unibilium - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} - ${BUILD_TYPE_STRING} - -DCMAKE_GENERATOR=${CMAKE_GENERATOR}) - set(UNIBILIUM_BUILD_COMMAND ${CMAKE_COMMAND} --build . --config $<CONFIG>) - set(UNIBILIUM_INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config $<CONFIG>) -else() - set(UNIBILIUM_BUILD_COMMAND ${MAKE_PRG} CC=${DEPS_C_COMPILER} - PREFIX=${DEPS_INSTALL_DIR} CFLAGS=-fPIC LDFLAGS+=-static - BUILD_IN_SOURCE 1) - set(UNIBILIUM_INSTALL_COMMAND ${MAKE_PRG} PREFIX=${DEPS_INSTALL_DIR} install) -endif() - if(USE_EXISTING_SRC_DIR) unset(UNIBILIUM_URL) endif() @@ -24,8 +6,7 @@ ExternalProject_Add(unibilium URL_HASH SHA256=${UNIBILIUM_SHA256} DOWNLOAD_NO_PROGRESS TRUE DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/unibilium - CONFIGURE_COMMAND "${UNIBILIUM_CONFIGURE_COMMAND}" - BUILD_COMMAND "${UNIBILIUM_BUILD_COMMAND}" - INSTALL_COMMAND "${UNIBILIUM_INSTALL_COMMAND}") + CMAKE_ARGS ${DEPS_CMAKE_ARGS} + CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}) list(APPEND THIRD_PARTY_DEPS unibilium) diff --git a/cmake.deps/cmake/GettextCMakeLists.txt b/cmake.deps/cmake/GettextCMakeLists.txt index d9f251897d..26f060ec08 100644 --- a/cmake.deps/cmake/GettextCMakeLists.txt +++ b/cmake.deps/cmake/GettextCMakeLists.txt @@ -327,3 +327,5 @@ install(TARGETS libintl msgmerge msgfmt xgettext LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +# vim: set ft=cmake: diff --git a/cmake.deps/cmake/LibiconvCMakeLists.txt b/cmake.deps/cmake/LibiconvCMakeLists.txt index b4ba6b5d7e..f6a23db864 100644 --- a/cmake.deps/cmake/LibiconvCMakeLists.txt +++ b/cmake.deps/cmake/LibiconvCMakeLists.txt @@ -95,3 +95,5 @@ install(TARGETS libcharset libiconv iconv LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +# vim: set ft=cmake: diff --git a/cmake.deps/cmake/LibvtermCMakeLists.txt b/cmake.deps/cmake/LibvtermCMakeLists.txt index d7ec9eacad..079ad28ba0 100644 --- a/cmake.deps/cmake/LibvtermCMakeLists.txt +++ b/cmake.deps/cmake/LibvtermCMakeLists.txt @@ -82,3 +82,5 @@ if(Perl_FOUND) DEPENDS ${header_path} ${perl_header_path}) endforeach() endif() + +# vim: set ft=cmake: diff --git a/cmake.deps/cmake/TreesitterCMakeLists.txt b/cmake.deps/cmake/TreesitterCMakeLists.txt index e20b47dd74..49fb19c96a 100644 --- a/cmake.deps/cmake/TreesitterCMakeLists.txt +++ b/cmake.deps/cmake/TreesitterCMakeLists.txt @@ -1,15 +1,9 @@ cmake_minimum_required(VERSION 3.10) project(tree-sitter LANGUAGES C) -file(GLOB SRC_FILES ${PROJECT_SOURCE_DIR}/lib/src/*.c) -foreach(sfile ${SRC_FILES}) - get_filename_component(f ${sfile} NAME) - if(${f} MATCHES "lib.c$") - list(REMOVE_ITEM SRC_FILES ${sfile}) - endif() -endforeach() -include_directories(${PROJECT_SOURCE_DIR}/lib/include) -add_library(tree-sitter ${SRC_FILES}) +add_library(tree-sitter lib/src/lib.c) +target_include_directories(tree-sitter + PRIVATE lib/src lib/include) install(FILES lib/include/tree_sitter/api.h @@ -17,5 +11,6 @@ install(FILES DESTINATION include/tree_sitter) include(GNUInstallDirs) -install(TARGETS tree-sitter - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) +install(TARGETS tree-sitter DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +# vim: set ft=cmake: diff --git a/cmake.deps/cmake/TreesitterParserCMakeLists.txt b/cmake.deps/cmake/TreesitterParserCMakeLists.txt index be07f00a37..9bdf500aa7 100644 --- a/cmake.deps/cmake/TreesitterParserCMakeLists.txt +++ b/cmake.deps/cmake/TreesitterParserCMakeLists.txt @@ -11,7 +11,6 @@ add_library(parser set_target_properties( parser PROPERTIES - POSITION_INDEPENDENT_CODE ON OUTPUT_NAME ${PARSERLANG} PREFIX "" ) @@ -19,3 +18,5 @@ set_target_properties( include_directories(src) install(TARGETS parser LIBRARY DESTINATION lib/nvim/parser) + +# vim: set ft=cmake: diff --git a/cmake.deps/cmake/UnibiliumCMakeLists.txt b/cmake.deps/cmake/UnibiliumCMakeLists.txt deleted file mode 100644 index 9112b416fa..0000000000 --- a/cmake.deps/cmake/UnibiliumCMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -cmake_minimum_required(VERSION 3.10) -project(unibilium LANGUAGES C) - -file(GLOB SRC_FILES ${PROJECT_SOURCE_DIR}/*.c) -add_library(unibilium ${SRC_FILES}) -set_target_properties(unibilium PROPERTIES PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/unibilium.h - VERSION "${VERSION_MAJOR}.${VERSION_MINOR}") - -if(NOT WIN32) - execute_process(COMMAND "shell ncursesw6-config --terminfo-dirs 2>/dev/null || \ - ncurses6-config --terminfo-dirs 2>/dev/null || \ - ncursesw5-config --terminfo-dirs 2>/dev/null || \ - ncurses5-config --terminfo-dirs 2>/dev/null || \ - echo '/etc/terminfo:/lib/terminfo:/usr/share/terminfo:/usr/lib/terminfo:/usr/local/share/terminfo:/usr/local/lib/terminfo'" - OUTPUT_VARIABLE TERMINFO_DIRS) -else() - set(TERMINFO_DIRS "\"\"") -endif() -target_compile_definitions(unibilium PUBLIC TERMINFO_DIRS=${TERMINFO_DIRS}) - -include(GNUInstallDirs) -install(TARGETS unibilium - PUBLIC_HEADER - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/cmake.deps/cmake/libtermkeyCMakeLists.txt b/cmake.deps/cmake/libtermkeyCMakeLists.txt index 6c02b7549d..26c9d7730b 100644 --- a/cmake.deps/cmake/libtermkeyCMakeLists.txt +++ b/cmake.deps/cmake/libtermkeyCMakeLists.txt @@ -20,15 +20,4 @@ install(TARGETS termkey ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -enable_testing() -file(GLOB TESTSOURCES "t/[0-9]*.c") -foreach(f ${TESTSOURCES}) - get_filename_component(t ${f} NAME_WE) - if(${t} STREQUAL 05read) - continue() - endif() - - add_executable("test_${t}" ${f} t/taplib.c) - target_link_libraries("test_${t}" termkey) - add_test("${t}" "test_${t}") -endforeach() +# vim: set ft=cmake: diff --git a/cmake.packaging/CMakeLists.txt b/cmake.packaging/CMakeLists.txt index 8538075388..df43f2806a 100644 --- a/cmake.packaging/CMakeLists.txt +++ b/cmake.packaging/CMakeLists.txt @@ -32,7 +32,7 @@ if(WIN32) # 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) + set(CPACK_WIX_PRODUCT_ICON ${PROJECT_SOURCE_DIR}/runtime/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. diff --git a/cmake/Download.cmake b/cmake/Download.cmake deleted file mode 100644 index 50a77816bc..0000000000 --- a/cmake/Download.cmake +++ /dev/null @@ -1,18 +0,0 @@ -file( - DOWNLOAD "${URL}" "${FILE}" - STATUS status - LOG log -) - -list(GET status 0 status_code) -list(GET status 1 status_string) - -if(NOT status_code EQUAL 0) - if(NOT ALLOW_FAILURE) - message(FATAL_ERROR "error: downloading '${URL}' failed - status_code: ${status_code} - status_string: ${status_string} - log: ${log} - ") - endif() -endif() diff --git a/cmake/FindIconv.cmake b/cmake/FindIconv.cmake index 1d0164dae9..63290fe889 100644 --- a/cmake/FindIconv.cmake +++ b/cmake/FindIconv.cmake @@ -1,3 +1,6 @@ +# TODO(dundargoc): FindIconv is shipped by default on cmake version 3.11+. This +# file can be removed once we decide to upgrade minimum cmake version. + # - Try to find iconv # Once done, this will define # diff --git a/cmake/GenerateVersion.cmake b/cmake/GenerateVersion.cmake index a7a72fe0bb..c092645140 100644 --- a/cmake/GenerateVersion.cmake +++ b/cmake/GenerateVersion.cmake @@ -1,26 +1,29 @@ -if(NVIM_VERSION_MEDIUM) - message(STATUS "USING NVIM_VERSION_MEDIUM: ${NVIM_VERSION_MEDIUM}") - return() -endif() - -set(NVIM_VERSION_MEDIUM +set(NVIM_VERSION "v${NVIM_VERSION_MAJOR}.${NVIM_VERSION_MINOR}.${NVIM_VERSION_PATCH}${NVIM_VERSION_PRERELEASE}") execute_process( - COMMAND git describe --first-parent --dirty --always + COMMAND git --git-dir=${NVIM_SOURCE_DIR}/.git --work-tree=${NVIM_SOURCE_DIR} describe --first-parent --dirty --always OUTPUT_VARIABLE GIT_TAG + OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE RES) -if(RES AND NOT RES EQUAL 0) - message(STATUS "Using NVIM_VERSION_MEDIUM: ${NVIM_VERSION_MEDIUM}") - file(WRITE "${OUTPUT}" "${NVIM_VERSION_STRING}") +if(RES) + message(STATUS "Using NVIM_VERSION: ${NVIM_VERSION}") + file(WRITE "${OUTPUT}" "") return() endif() -string(STRIP "${GIT_TAG}" GIT_TAG) -string(REGEX REPLACE "^v[0-9]+.[0-9]+.[0-9]+-" "" NVIM_VERSION_GIT "${GIT_TAG}") -set(NVIM_VERSION_MEDIUM "${NVIM_VERSION_MEDIUM}-${NVIM_VERSION_GIT}") -set(NVIM_VERSION_STRING "#define NVIM_VERSION_MEDIUM \"${NVIM_VERSION_MEDIUM}\"\n") +# `git describe` annotates the most recent tagged release; for pre-release +# builds we append that to the dev version. +if(NVIM_VERSION_PRERELEASE) + # Extract pre-release info: "v0.8.0-145-g0f9113907" => "145-g0f9113907" + string(REGEX REPLACE "^v[0-9]+.[0-9]+.[0-9]+-" "" NVIM_VERSION_GIT "${GIT_TAG}") + # Replace "-" with "+": "145-g0f9113907" => "145+g0f9113907" + string(REGEX REPLACE "^([0-9]+)-([a-z0-9]+)" "\\1+\\2" NVIM_VERSION_GIT "${NVIM_VERSION_GIT}") + set(NVIM_VERSION "${NVIM_VERSION}-${NVIM_VERSION_GIT}") +endif() + +set(NVIM_VERSION_STRING "#define NVIM_VERSION_MEDIUM \"${NVIM_VERSION}\"\n") string(SHA1 CURRENT_VERSION_HASH "${NVIM_VERSION_STRING}") if(EXISTS ${OUTPUT}) @@ -28,6 +31,9 @@ if(EXISTS ${OUTPUT}) endif() if(NOT "${NVIM_VERSION_HASH}" STREQUAL "${CURRENT_VERSION_HASH}") - message(STATUS "Using NVIM_VERSION_MEDIUM: ${NVIM_VERSION_MEDIUM}") + message(STATUS "Using NVIM_VERSION: ${NVIM_VERSION}") file(WRITE "${OUTPUT}" "${NVIM_VERSION_STRING}") + if(WIN32) + configure_file("${OUTPUT}" "${OUTPUT}" NEWLINE_STYLE UNIX) + endif() endif() diff --git a/cmake/GetCompileFlags.cmake b/cmake/GetCompileFlags.cmake index 3b027690f8..9b3c053871 100644 --- a/cmake/GetCompileFlags.cmake +++ b/cmake/GetCompileFlags.cmake @@ -1,79 +1,57 @@ function(get_compile_flags _compile_flags) - # Create template akin to CMAKE_C_COMPILE_OBJECT. - set(compile_flags "<CMAKE_C_COMPILER> <CFLAGS> <BUILD_TYPE_CFLAGS> <COMPILE_OPTIONS><COMPILE_DEFINITIONS> <INCLUDES>") + string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type) + set(compile_flags ${CMAKE_C_COMPILER} ${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${build_type}}) - string(REPLACE - "<CMAKE_C_COMPILER>" - "${CMAKE_C_COMPILER}" - compile_flags - "${compile_flags}") + # Get flags set by target_compile_options(). + get_target_property(opt main_lib INTERFACE_COMPILE_OPTIONS) + if(opt) + list(APPEND compile_flags ${opt}) + endif() - # Get flags set by add_definitions(). - get_property(compile_definitions DIRECTORY PROPERTY COMPILE_DEFINITIONS) - get_target_property(compile_definitions_target nvim COMPILE_DEFINITIONS) - if(compile_definitions_target) - list(APPEND compile_definitions ${compile_definitions_target}) - list(REMOVE_DUPLICATES compile_definitions) + get_target_property(opt nvim COMPILE_OPTIONS) + if(opt) + list(APPEND compile_flags ${opt}) endif() - # NOTE: list(JOIN) requires CMake 3.12, string(CONCAT) requires CMake 3. - string(REPLACE ";" " -D" compile_definitions "${compile_definitions}") - if(compile_definitions) - set(compile_definitions " -D${compile_definitions}") + + # Get flags set by target_compile_definitions(). + get_target_property(defs main_lib INTERFACE_COMPILE_DEFINITIONS) + if(defs) + foreach(def ${defs}) + list(APPEND compile_flags "-D${def}") + endforeach() endif() - string(REPLACE - "<COMPILE_DEFINITIONS>" - "${compile_definitions}" - compile_flags - "${compile_flags}") - # Get flags set by add_compile_options(). - get_property(compile_options DIRECTORY PROPERTY COMPILE_OPTIONS) - get_target_property(compile_options_target nvim COMPILE_OPTIONS) - if(compile_options_target) - list(APPEND compile_options ${compile_options_target}) - list(REMOVE_DUPLICATES compile_options) + get_target_property(defs nvim COMPILE_DEFINITIONS) + if(defs) + foreach(def ${defs}) + list(APPEND compile_flags "-D${def}") + endforeach() endif() - # NOTE: list(JOIN) requires CMake 3.12. - string(REPLACE ";" " " compile_options "${compile_options}") - string(REPLACE - "<COMPILE_OPTIONS>" - "${compile_options}" - compile_flags - "${compile_flags}") - # Get general C flags. - string(REPLACE - "<CFLAGS>" - "${CMAKE_C_FLAGS}" - compile_flags - "${compile_flags}") + # Get include directories. + get_target_property(dirs main_lib INTERFACE_INCLUDE_DIRECTORIES) + if(dirs) + foreach(dir ${dirs}) + list(APPEND compile_flags "-I${dir}") + endforeach() + endif() - # Get C flags specific to build type. - string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type) - string(REPLACE - "<BUILD_TYPE_CFLAGS>" - "${CMAKE_C_FLAGS_${build_type}}" - compile_flags - "${compile_flags}") + get_target_property(dirs main_lib INTERFACE_SYSTEM_INCLUDE_DIRECTORIES) + if(dirs) + foreach(dir ${dirs}) + list(APPEND compile_flags "-I${dir}") + endforeach() + endif() - # Get include directories. - get_property(include_directories_list DIRECTORY PROPERTY INCLUDE_DIRECTORIES) - list(REMOVE_DUPLICATES include_directories_list) - foreach(include_directory ${include_directories_list}) - set(include_directories "${include_directories} -I${include_directory}") - endforeach() - string(REPLACE - "<INCLUDES>" - "${include_directories}" - compile_flags - "${compile_flags}") + get_target_property(dirs nvim INCLUDE_DIRECTORIES) + if(dirs) + foreach(dir ${dirs}) + list(APPEND compile_flags "-I${dir}") + endforeach() + endif() - # Clean duplicate whitespace. - string(REPLACE - " " - " " - compile_flags - "${compile_flags}") + list(REMOVE_DUPLICATES compile_flags) + string(REPLACE ";" " " compile_flags "${compile_flags}") set(${_compile_flags} "${compile_flags}" PARENT_SCOPE) endfunction() diff --git a/cmake/RunTests.cmake b/cmake/RunTests.cmake index 2abe29b54b..c3ac5f208e 100644 --- a/cmake/RunTests.cmake +++ b/cmake/RunTests.cmake @@ -3,8 +3,8 @@ set(ENV{LC_ALL} "en_US.UTF-8") if(POLICY CMP0012) # Avoid policy warning due to CI=true. This is needed even if the main - # project has already set this policy as policy settings are reset when using - # the cmake script mode (-P). + # project has already set this policy as project settings aren't inherited + # when using cmake script mode (-P). cmake_policy(SET CMP0012 NEW) endif() @@ -15,6 +15,12 @@ set(ENV{XDG_DATA_HOME} ${BUILD_DIR}/Xtest_xdg/share) unset(ENV{XDG_DATA_DIRS}) unset(ENV{NVIM}) # Clear $NVIM in case tests are running from Nvim. #11009 +# TODO(dundargoc): The CIRRUS_CI environment variable isn't passed to here from +# the main CMakeLists.txt, so we have to manually pass it to this script and +# re-set the environment variable. Investigate if we can avoid manually setting +# it like with the GITHUB_CI environment variable. +set(ENV{CIRRUS_CI} ${CIRRUS_CI}) + if(NOT DEFINED ENV{NVIM_LOG_FILE}) set(ENV{NVIM_LOG_FILE} ${BUILD_DIR}/.nvimlog) endif() diff --git a/cmake/Util.cmake b/cmake/Util.cmake index 343a729305..e15b44d29a 100644 --- a/cmake/Util.cmake +++ b/cmake/Util.cmake @@ -49,12 +49,13 @@ # simple be added to FILES # GLOB_DIRS - The directories to recursively search for files with extension # GLOB_PAT -# -function(add_glob_targets) +# EXCLUDE - List of paths to skip (regex). Works on both directories and +# files. +function(add_glob_target) cmake_parse_arguments(ARG "REQUIRED" "TARGET;COMMAND;GLOB_PAT;TOUCH_STRATEGY" - "FLAGS;FILES;GLOB_DIRS" + "FLAGS;FILES;GLOB_DIRS;EXCLUDE" ${ARGN} ) @@ -72,10 +73,19 @@ function(add_glob_targets) endif() foreach(gd ${ARG_GLOB_DIRS}) - file(GLOB_RECURSE globfiles ${PROJECT_SOURCE_DIR}/${gd}/${ARG_GLOB_PAT}) + file(GLOB_RECURSE globfiles_unnormalized ${PROJECT_SOURCE_DIR}/${gd}/${ARG_GLOB_PAT}) + set(globfiles) + foreach(f ${globfiles_unnormalized}) + file(TO_CMAKE_PATH "${f}" f) + list(APPEND globfiles ${f}) + endforeach() list(APPEND ARG_FILES ${globfiles}) endforeach() + foreach(exclude_pattern ${ARG_EXCLUDE}) + list(FILTER ARG_FILES EXCLUDE REGEX ${exclude_pattern}) + endforeach() + if(NOT ARG_TOUCH_STRATEGY) set(ARG_TOUCH_STRATEGY PER_FILE) endif() diff --git a/contrib/flake.lock b/contrib/flake.lock index 48f5f0115a..c6dfb4642b 100644 --- a/contrib/flake.lock +++ b/contrib/flake.lock @@ -2,11 +2,11 @@ "nodes": { "flake-utils": { "locked": { - "lastModified": 1659877975, - "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", "owner": "numtide", "repo": "flake-utils", - "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", "type": "github" }, "original": { @@ -17,11 +17,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1662019588, - "narHash": "sha256-oPEjHKGGVbBXqwwL+UjsveJzghWiWV0n9ogo1X6l4cw=", + "lastModified": 1671983799, + "narHash": "sha256-Z2Ro6hFPZHkBqkVXY5/aBUzxi5xizQGvuHQ9+T5B/ks=", "owner": "nixos", "repo": "nixpkgs", - "rev": "2da64a81275b68fdad38af669afeda43d401e94b", + "rev": "fad51abd42ca17a60fc1d4cb9382e2d79ae31836", "type": "github" }, "original": { diff --git a/contrib/flake.nix b/contrib/flake.nix index cd6242a8d6..0898c943d7 100644 --- a/contrib/flake.nix +++ b/contrib/flake.nix @@ -8,24 +8,14 @@ outputs = { self, nixpkgs, flake-utils }: { - overlay = final: prev: rec { - neovim-unwrapped = prev.neovim-unwrapped.override ({ - libvterm-neovim = prev.libvterm-neovim.overrideAttrs (old: { - version = "0.3"; - src = builtins.fetchTarball { - url = "https://www.leonerd.org.uk/code/libvterm/libvterm-0.3.tar.gz"; - sha256 = "0zg6sn5brwrnqaab883pdj0l2swk5askbbwbdam0zq55ikbrzgar"; - }; - }); - }); + overlay = final: prev: { - neovim = final.neovim-unwrapped.overrideAttrs (oa: { - version = "master"; + neovim = final.neovim-unwrapped.overrideAttrs (oa: rec { + version = self.shortRev or "dirty"; src = ../.; - - buildInputs = oa.buildInputs - ++ final.lib.optionals final.stdenv.isDarwin - (with final.darwin.apple_sdk.frameworks; [ CoreServices ]); + preConfigure = '' + sed -i cmake.config/versiondef.h.in -e 's/@NVIM_VERSION_PRERELEASE@/-dev-${version}/' + ''; }); # a development binary to help debug issues @@ -34,7 +24,7 @@ final.llvmPackages_latest.stdenv else final.stdenv; - in ((neovim.override { + in (final.neovim.override { lua = final.luajit; inherit stdenv; }).overrideAttrs (oa: { @@ -46,13 +36,13 @@ cmakeFlags = oa.cmakeFlags ++ [ "-DMIN_LOG_LEVEL=0" ]; disallowedReferences = [ ]; - })); + }); # for neovim developers, beware of the slow binary - neovim-developer = let luacheck = final.luaPackages.luacheck; - in (neovim-debug.override ({ + neovim-developer = let inherit (final.luaPackages) luacheck; + in (final.neovim-debug.override { doCheck = final.stdenv.isLinux; - })).overrideAttrs (oa: { + }).overrideAttrs (oa: { cmakeFlags = oa.cmakeFlags ++ [ "-DLUACHECK_PRG=${luacheck}/bin/luacheck" "-DMIN_LOG_LEVEL=0" @@ -75,7 +65,6 @@ pythonEnv = pkgs.python3.withPackages (ps: [ ps.msgpack - ps.flake8 # for 'make pylint' ]); in { packages = with pkgs; { @@ -84,11 +73,6 @@ }; checks = { - pylint = pkgs.runCommand "pylint" { - nativeBuildInputs = [ pythonEnv ]; - preferLocalBuild = true; - } "make -C ${./..} pylint > $out"; - shlint = pkgs.runCommand "shlint" { nativeBuildInputs = [ pkgs.shellcheck ]; preferLocalBuild = true; @@ -122,15 +106,10 @@ # ASAN_OPTIONS=detect_leaks=1 export ASAN_OPTIONS="log_path=./test.log:abort_on_error=1" export UBSAN_OPTIONS=print_stacktrace=1 - mkdir -p build/runtime/parser - # nvim looks into CMAKE_INSTALL_DIR. Hack to avoid errors - # when running the functionaltests - mkdir -p outputs/out/share/nvim/syntax - touch outputs/out/share/nvim/syntax/syntax.vim # for treesitter functionaltests mkdir -p runtime/parser - cp -f ${pkgs.tree-sitter.builtGrammars.tree-sitter-c}/parser runtime/parser/c.so + cp -f ${pkgs.vimPlugins.nvim-treesitter.builtGrammars.c}/parser runtime/parser/c.so ''; }); }; diff --git a/contrib/luarc.json b/contrib/luarc.json index 8d76d1261d..ebad0581b9 100644 --- a/contrib/luarc.json +++ b/contrib/luarc.json @@ -10,13 +10,17 @@ "before_each", "after_each", "setup", - "teardown" + "teardown", + "finally", + "lfs" ] }, "workspace": { - "library": { - "runtime/lua": true - }, + "library": [ + "runtime/lua", + "${3rd}/lfs/library" + ], + "checkThirdParty": false, "maxPreload": 2000, "preloadFileSize": 1000 }, diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 6b926e9fc1..581a4545db 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -63,7 +63,7 @@ foreach(DF ${DOCFILES}) endforeach() add_custom_command(OUTPUT ${GENERATED_HELP_TAGS} - COMMAND ${CMAKE_COMMAND} -E remove doc/* + COMMAND ${CMAKE_COMMAND} -E remove_directory doc COMMAND ${CMAKE_COMMAND} -E copy_directory ${PROJECT_SOURCE_DIR}/runtime/doc doc COMMAND "${PROJECT_BINARY_DIR}/bin/nvim" @@ -110,12 +110,16 @@ if(NOT APPLE) install_helper( FILES ${CMAKE_CURRENT_SOURCE_DIR}/nvim.desktop DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications) - - install_helper( - FILES ${CMAKE_CURRENT_SOURCE_DIR}/nvim.png - DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps) endif() +install_helper( + FILES ${CMAKE_CURRENT_SOURCE_DIR}/nvim.png + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps) + +install_helper( + FILES ${CMAKE_CURRENT_SOURCE_DIR}/neovim.ico + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/nvim/runtime) + globrecurse_wrapper(RUNTIME_PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR} *.awk *.sh *.bat) foreach(PROG ${RUNTIME_PROGRAMS}) diff --git a/runtime/autoload/ccomplete.lua b/runtime/autoload/ccomplete.lua new file mode 100644 index 0000000000..f4a3eabd9a --- /dev/null +++ b/runtime/autoload/ccomplete.lua @@ -0,0 +1,857 @@ +---------------------------------------- +-- This file is generated via github.com/tjdevries/vim9jit +-- For any bugs, please first consider reporting there. +---------------------------------------- + +-- Ignore "value assigned to a local variable is unused" because +-- we can't guarantee that local variables will be used by plugins +-- luacheck: ignore 311 + +local vim9 = require('_vim9script') +local M = {} +local prepended = nil +local grepCache = nil +local Complete = nil +local GetAddition = nil +local Tag2item = nil +local Dict2info = nil +local ParseTagline = nil +local Tagline2item = nil +local Tagcmd2extra = nil +local Nextitem = nil +local StructMembers = nil +local SearchMembers = nil +-- vim9script + +-- # Vim completion script +-- # Language: C +-- # Maintainer: Bram Moolenaar <Bram@vim.org> +-- # Rewritten in Vim9 script by github user lacygoill +-- # Last Change: 2022 Jan 31 + +prepended = '' +grepCache = vim.empty_dict() + +-- # This function is used for the 'omnifunc' option. + +Complete = function(findstart, abase) + findstart = vim9.bool(findstart) + if vim9.bool(findstart) then + -- # Locate the start of the item, including ".", "->" and "[...]". + local line = vim9.fn.getline('.') + local start = vim9.fn.charcol('.') - 1 + local lastword = -1 + while start > 0 do + if vim9.ops.RegexpMatches(vim9.index(line, vim9.ops.Minus(start, 1)), '\\w') then + start = start - 1 + elseif + vim9.bool(vim9.ops.RegexpMatches(vim9.index(line, vim9.ops.Minus(start, 1)), '\\.')) + then + if lastword == -1 then + lastword = start + end + start = start - 1 + elseif + vim9.bool( + start > 1 + and vim9.index(line, vim9.ops.Minus(start, 2)) == '-' + and vim9.index(line, vim9.ops.Minus(start, 1)) == '>' + ) + then + if lastword == -1 then + lastword = start + end + start = vim9.ops.Minus(start, 2) + elseif vim9.bool(vim9.index(line, vim9.ops.Minus(start, 1)) == ']') then + -- # Skip over [...]. + local n = 0 + start = start - 1 + while start > 0 do + start = start - 1 + if vim9.index(line, start) == '[' then + if n == 0 then + break + end + n = n - 1 + elseif vim9.bool(vim9.index(line, start) == ']') then + n = n + 1 + end + end + else + break + end + end + + -- # Return the column of the last word, which is going to be changed. + -- # Remember the text that comes before it in prepended. + if lastword == -1 then + prepended = '' + return vim9.fn.byteidx(line, start) + end + prepended = vim9.slice(line, start, vim9.ops.Minus(lastword, 1)) + return vim9.fn.byteidx(line, lastword) + end + + -- # Return list of matches. + + local base = prepended .. abase + + -- # Don't do anything for an empty base, would result in all the tags in the + -- # tags file. + if base == '' then + return {} + end + + -- # init cache for vimgrep to empty + grepCache = {} + + -- # Split item in words, keep empty word after "." or "->". + -- # "aa" -> ['aa'], "aa." -> ['aa', ''], "aa.bb" -> ['aa', 'bb'], etc. + -- # We can't use split, because we need to skip nested [...]. + -- # "aa[...]" -> ['aa', '[...]'], "aa.bb[...]" -> ['aa', 'bb', '[...]'], etc. + local items = {} + local s = 0 + local arrays = 0 + while 1 do + local e = vim9.fn.charidx(base, vim9.fn.match(base, '\\.\\|->\\|\\[', s)) + if e < 0 then + if s == 0 or vim9.index(base, vim9.ops.Minus(s, 1)) ~= ']' then + vim9.fn.add(items, vim9.slice(base, s, nil)) + end + break + end + if s == 0 or vim9.index(base, vim9.ops.Minus(s, 1)) ~= ']' then + vim9.fn.add(items, vim9.slice(base, s, vim9.ops.Minus(e, 1))) + end + if vim9.index(base, e) == '.' then + -- # skip over '.' + s = vim9.ops.Plus(e, 1) + elseif vim9.bool(vim9.index(base, e) == '-') then + -- # skip over '->' + s = vim9.ops.Plus(e, 2) + else + -- # Skip over [...]. + local n = 0 + s = e + e = e + 1 + while e < vim9.fn.strcharlen(base) do + if vim9.index(base, e) == ']' then + if n == 0 then + break + end + n = n - 1 + elseif vim9.bool(vim9.index(base, e) == '[') then + n = n + 1 + end + e = e + 1 + end + e = e + 1 + vim9.fn.add(items, vim9.slice(base, s, vim9.ops.Minus(e, 1))) + arrays = arrays + 1 + s = e + end + end + + -- # Find the variable items[0]. + -- # 1. in current function (like with "gd") + -- # 2. in tags file(s) (like with ":tag") + -- # 3. in current file (like with "gD") + local res = {} + if vim9.fn.searchdecl(vim9.index(items, 0), false, true) == 0 then + -- # Found, now figure out the type. + -- # TODO: join previous line if it makes sense + local line = vim9.fn.getline('.') + local col = vim9.fn.charcol('.') + if vim9.fn.stridx(vim9.slice(line, nil, vim9.ops.Minus(col, 1)), ';') >= 0 then + -- # Handle multiple declarations on the same line. + local col2 = vim9.ops.Minus(col, 1) + while vim9.index(line, col2) ~= ';' do + col2 = col2 - 1 + end + line = vim9.slice(line, vim9.ops.Plus(col2, 1), nil) + col = vim9.ops.Minus(col, col2) + end + if vim9.fn.stridx(vim9.slice(line, nil, vim9.ops.Minus(col, 1)), ',') >= 0 then + -- # Handle multiple declarations on the same line in a function + -- # declaration. + local col2 = vim9.ops.Minus(col, 1) + while vim9.index(line, col2) ~= ',' do + col2 = col2 - 1 + end + if + vim9.ops.RegexpMatches( + vim9.slice(line, vim9.ops.Plus(col2, 1), vim9.ops.Minus(col, 1)), + ' *[^ ][^ ]* *[^ ]' + ) + then + line = vim9.slice(line, vim9.ops.Plus(col2, 1), nil) + col = vim9.ops.Minus(col, col2) + end + end + if vim9.fn.len(items) == 1 then + -- # Completing one word and it's a local variable: May add '[', '.' or + -- # '->'. + local match = vim9.index(items, 0) + local kind = 'v' + if vim9.fn.match(line, '\\<' .. match .. '\\s*\\[') > 0 then + match = match .. '[' + else + res = Nextitem(vim9.slice(line, nil, vim9.ops.Minus(col, 1)), { '' }, 0, true) + if vim9.fn.len(res) > 0 then + -- # There are members, thus add "." or "->". + if vim9.fn.match(line, '\\*[ \\t(]*' .. match .. '\\>') > 0 then + match = match .. '->' + else + match = match .. '.' + end + end + end + res = { { ['match'] = match, ['tagline'] = '', ['kind'] = kind, ['info'] = line } } + elseif vim9.bool(vim9.fn.len(items) == vim9.ops.Plus(arrays, 1)) then + -- # Completing one word and it's a local array variable: build tagline + -- # from declaration line + local match = vim9.index(items, 0) + local kind = 'v' + local tagline = '\t/^' .. line .. '$/' + res = { { ['match'] = match, ['tagline'] = tagline, ['kind'] = kind, ['info'] = line } } + else + -- # Completing "var.", "var.something", etc. + res = + Nextitem(vim9.slice(line, nil, vim9.ops.Minus(col, 1)), vim9.slice(items, 1, nil), 0, true) + end + end + + if vim9.fn.len(items) == 1 or vim9.fn.len(items) == vim9.ops.Plus(arrays, 1) then + -- # Only one part, no "." or "->": complete from tags file. + local tags = {} + if vim9.fn.len(items) == 1 then + tags = vim9.fn.taglist('^' .. base) + else + tags = vim9.fn.taglist('^' .. vim9.index(items, 0) .. '$') + end + + vim9.fn_mut('filter', { + vim9.fn_mut('filter', { + tags, + function(_, v) + return vim9.ternary(vim9.fn.has_key(v, 'kind'), function() + return v.kind ~= 'm' + end, true) + end, + }, { replace = 0 }), + function(_, v) + return vim9.ops.Or( + vim9.ops.Or( + vim9.prefix['Bang'](vim9.fn.has_key(v, 'static')), + vim9.prefix['Bang'](vim9.index(v, 'static')) + ), + vim9.fn.bufnr('%') == vim9.fn.bufnr(vim9.index(v, 'filename')) + ) + end, + }, { replace = 0 }) + + res = vim9.fn.extend( + res, + vim9.fn.map(tags, function(_, v) + return Tag2item(v) + end) + ) + end + + if vim9.fn.len(res) == 0 then + -- # Find the variable in the tags file(s) + local diclist = vim9.fn.filter( + vim9.fn.taglist('^' .. vim9.index(items, 0) .. '$'), + function(_, v) + return vim9.ternary(vim9.fn.has_key(v, 'kind'), function() + return v.kind ~= 'm' + end, true) + end + ) + + res = {} + + for _, i in vim9.iter(vim9.fn.range(vim9.fn.len(diclist))) do + -- # New ctags has the "typeref" field. Patched version has "typename". + if vim9.bool(vim9.fn.has_key(vim9.index(diclist, i), 'typename')) then + res = vim9.fn.extend( + res, + StructMembers( + vim9.index(vim9.index(diclist, i), 'typename'), + vim9.slice(items, 1, nil), + true + ) + ) + elseif vim9.bool(vim9.fn.has_key(vim9.index(diclist, i), 'typeref')) then + res = vim9.fn.extend( + res, + StructMembers( + vim9.index(vim9.index(diclist, i), 'typeref'), + vim9.slice(items, 1, nil), + true + ) + ) + end + + -- # For a variable use the command, which must be a search pattern that + -- # shows the declaration of the variable. + if vim9.index(vim9.index(diclist, i), 'kind') == 'v' then + local line = vim9.index(vim9.index(diclist, i), 'cmd') + if vim9.slice(line, nil, 1) == '/^' then + local col = + vim9.fn.charidx(line, vim9.fn.match(line, '\\<' .. vim9.index(items, 0) .. '\\>')) + res = vim9.fn.extend( + res, + Nextitem( + vim9.slice(line, 2, vim9.ops.Minus(col, 1)), + vim9.slice(items, 1, nil), + 0, + true + ) + ) + end + end + end + end + + if vim9.fn.len(res) == 0 and vim9.fn.searchdecl(vim9.index(items, 0), true) == 0 then + -- # Found, now figure out the type. + -- # TODO: join previous line if it makes sense + local line = vim9.fn.getline('.') + local col = vim9.fn.charcol('.') + res = + Nextitem(vim9.slice(line, nil, vim9.ops.Minus(col, 1)), vim9.slice(items, 1, nil), 0, true) + end + + -- # If the last item(s) are [...] they need to be added to the matches. + local last = vim9.fn.len(items) - 1 + local brackets = '' + while last >= 0 do + if vim9.index(vim9.index(items, last), 0) ~= '[' then + break + end + brackets = vim9.index(items, last) .. brackets + last = last - 1 + end + + return vim9.fn.map(res, function(_, v) + return Tagline2item(v, brackets) + end) +end +M['Complete'] = Complete + +GetAddition = function(line, match, memarg, bracket) + bracket = vim9.bool(bracket) + -- # Guess if the item is an array. + if vim9.bool(vim9.ops.And(bracket, vim9.fn.match(line, match .. '\\s*\\[') > 0)) then + return '[' + end + + -- # Check if the item has members. + if vim9.fn.len(SearchMembers(memarg, { '' }, false)) > 0 then + -- # If there is a '*' before the name use "->". + if vim9.fn.match(line, '\\*[ \\t(]*' .. match .. '\\>') > 0 then + return '->' + else + return '.' + end + end + return '' +end + +Tag2item = function(val) + -- # Turn the tag info "val" into an item for completion. + -- # "val" is is an item in the list returned by taglist(). + -- # If it is a variable we may add "." or "->". Don't do it for other types, + -- # such as a typedef, by not including the info that GetAddition() uses. + local res = vim9.convert.decl_dict({ ['match'] = vim9.index(val, 'name') }) + + res[vim9.index_expr('extra')] = + Tagcmd2extra(vim9.index(val, 'cmd'), vim9.index(val, 'name'), vim9.index(val, 'filename')) + + local s = Dict2info(val) + if s ~= '' then + res[vim9.index_expr('info')] = s + end + + res[vim9.index_expr('tagline')] = '' + if vim9.bool(vim9.fn.has_key(val, 'kind')) then + local kind = vim9.index(val, 'kind') + res[vim9.index_expr('kind')] = kind + if kind == 'v' then + res[vim9.index_expr('tagline')] = '\t' .. vim9.index(val, 'cmd') + res[vim9.index_expr('dict')] = val + elseif vim9.bool(kind == 'f') then + res[vim9.index_expr('match')] = vim9.index(val, 'name') .. '(' + end + end + + return res +end + +Dict2info = function(dict) + -- # Use all the items in dictionary for the "info" entry. + local info = '' + + for _, k in vim9.iter(vim9.fn_mut('sort', { vim9.fn.keys(dict) }, { replace = 0 })) do + info = info .. k .. vim9.fn['repeat'](' ', 10 - vim9.fn.strlen(k)) + if k == 'cmd' then + info = info + .. vim9.fn.substitute( + vim9.fn.matchstr(vim9.index(dict, 'cmd'), '/^\\s*\\zs.*\\ze$/'), + '\\\\\\(.\\)', + '\\1', + 'g' + ) + else + local dictk = vim9.index(dict, k) + if vim9.fn.typename(dictk) ~= 'string' then + info = info .. vim9.fn.string(dictk) + else + info = info .. dictk + end + end + info = info .. '\n' + end + + return info +end + +ParseTagline = function(line) + -- # Parse a tag line and return a dictionary with items like taglist() + local l = vim9.fn.split(line, '\t') + local d = vim.empty_dict() + if vim9.fn.len(l) >= 3 then + d[vim9.index_expr('name')] = vim9.index(l, 0) + d[vim9.index_expr('filename')] = vim9.index(l, 1) + d[vim9.index_expr('cmd')] = vim9.index(l, 2) + local n = 2 + if vim9.ops.RegexpMatches(vim9.index(l, 2), '^/') then + -- # Find end of cmd, it may contain Tabs. + while n < vim9.fn.len(l) and vim9.ops.NotRegexpMatches(vim9.index(l, n), '/;"$') do + n = n + 1 + d[vim9.index_expr('cmd')] = vim9.index(d, 'cmd') .. ' ' .. vim9.index(l, n) + end + end + + for _, i in vim9.iter(vim9.fn.range(vim9.ops.Plus(n, 1), vim9.fn.len(l) - 1)) do + if vim9.index(l, i) == 'file:' then + d[vim9.index_expr('static')] = 1 + elseif vim9.bool(vim9.ops.NotRegexpMatches(vim9.index(l, i), ':')) then + d[vim9.index_expr('kind')] = vim9.index(l, i) + else + d[vim9.index_expr(vim9.fn.matchstr(vim9.index(l, i), '[^:]*'))] = + vim9.fn.matchstr(vim9.index(l, i), ':\\zs.*') + end + end + end + + return d +end + +Tagline2item = function(val, brackets) + -- # Turn a match item "val" into an item for completion. + -- # "val['match']" is the matching item. + -- # "val['tagline']" is the tagline in which the last part was found. + local line = vim9.index(val, 'tagline') + local add = GetAddition(line, vim9.index(val, 'match'), { val }, brackets == '') + local res = vim9.convert.decl_dict({ ['word'] = vim9.index(val, 'match') .. brackets .. add }) + + if vim9.bool(vim9.fn.has_key(val, 'info')) then + -- # Use info from Tag2item(). + res[vim9.index_expr('info')] = vim9.index(val, 'info') + else + -- # Parse the tag line and add each part to the "info" entry. + local s = Dict2info(ParseTagline(line)) + if s ~= '' then + res[vim9.index_expr('info')] = s + end + end + + if vim9.bool(vim9.fn.has_key(val, 'kind')) then + res[vim9.index_expr('kind')] = vim9.index(val, 'kind') + elseif vim9.bool(add == '(') then + res[vim9.index_expr('kind')] = 'f' + else + local s = vim9.fn.matchstr(line, '\\t\\(kind:\\)\\=\\zs\\S\\ze\\(\\t\\|$\\)') + if s ~= '' then + res[vim9.index_expr('kind')] = s + end + end + + if vim9.bool(vim9.fn.has_key(val, 'extra')) then + res[vim9.index_expr('menu')] = vim9.index(val, 'extra') + return res + end + + -- # Isolate the command after the tag and filename. + local s = vim9.fn.matchstr( + line, + '[^\\t]*\\t[^\\t]*\\t\\zs\\(/^.*$/\\|[^\\t]*\\)\\ze\\(;"\\t\\|\\t\\|$\\)' + ) + if s ~= '' then + res[vim9.index_expr('menu')] = Tagcmd2extra( + s, + vim9.index(val, 'match'), + vim9.fn.matchstr(line, '[^\\t]*\\t\\zs[^\\t]*\\ze\\t') + ) + end + return res +end + +Tagcmd2extra = function(cmd, name, fname) + -- # Turn a command from a tag line to something that is useful in the menu + local x = '' + if vim9.ops.RegexpMatches(cmd, '^/^') then + -- # The command is a search command, useful to see what it is. + x = vim9.fn.substitute( + vim9.fn.substitute( + vim9.fn.matchstr(cmd, '^/^\\s*\\zs.*\\ze$/'), + '\\<' .. name .. '\\>', + '@@', + '' + ), + '\\\\\\(.\\)', + '\\1', + 'g' + ) .. ' - ' .. fname + elseif vim9.bool(vim9.ops.RegexpMatches(cmd, '^\\d*$')) then + -- # The command is a line number, the file name is more useful. + x = fname .. ' - ' .. cmd + else + -- # Not recognized, use command and file name. + x = cmd .. ' - ' .. fname + end + return x +end + +Nextitem = function(lead, items, depth, all) + all = vim9.bool(all) + -- # Find composing type in "lead" and match items[0] with it. + -- # Repeat this recursively for items[1], if it's there. + -- # When resolving typedefs "depth" is used to avoid infinite recursion. + -- # Return the list of matches. + + -- # Use the text up to the variable name and split it in tokens. + local tokens = vim9.fn.split(lead, '\\s\\+\\|\\<') + + -- # Try to recognize the type of the variable. This is rough guessing... + local res = {} + + local body = function(_, tidx) + -- # Skip tokens starting with a non-ID character. + if vim9.ops.NotRegexpMatches(vim9.index(tokens, tidx), '^\\h') then + return vim9.ITER_CONTINUE + end + + -- # Recognize "struct foobar" and "union foobar". + -- # Also do "class foobar" when it's C++ after all (doesn't work very well + -- # though). + if + ( + vim9.index(tokens, tidx) == 'struct' + or vim9.index(tokens, tidx) == 'union' + or vim9.index(tokens, tidx) == 'class' + ) and vim9.ops.Plus(tidx, 1) < vim9.fn.len(tokens) + then + res = StructMembers( + vim9.index(tokens, tidx) .. ':' .. vim9.index(tokens, vim9.ops.Plus(tidx, 1)), + items, + all + ) + return vim9.ITER_BREAK + end + + -- # TODO: add more reserved words + if + vim9.fn.index( + { 'int', 'short', 'char', 'float', 'double', 'static', 'unsigned', 'extern' }, + vim9.index(tokens, tidx) + ) >= 0 + then + return vim9.ITER_CONTINUE + end + + -- # Use the tags file to find out if this is a typedef. + local diclist = vim9.fn.taglist('^' .. vim9.index(tokens, tidx) .. '$') + + local body = function(_, tagidx) + local item = vim9.convert.decl_dict(vim9.index(diclist, tagidx)) + + -- # New ctags has the "typeref" field. Patched version has "typename". + if vim9.bool(vim9.fn.has_key(item, 'typeref')) then + res = vim9.fn.extend(res, StructMembers(vim9.index(item, 'typeref'), items, all)) + return vim9.ITER_CONTINUE + end + if vim9.bool(vim9.fn.has_key(item, 'typename')) then + res = vim9.fn.extend(res, StructMembers(vim9.index(item, 'typename'), items, all)) + return vim9.ITER_CONTINUE + end + + -- # Only handle typedefs here. + if vim9.index(item, 'kind') ~= 't' then + return vim9.ITER_CONTINUE + end + + -- # Skip matches local to another file. + if + vim9.bool( + vim9.ops.And( + vim9.ops.And(vim9.fn.has_key(item, 'static'), vim9.index(item, 'static')), + vim9.fn.bufnr('%') ~= vim9.fn.bufnr(vim9.index(item, 'filename')) + ) + ) + then + return vim9.ITER_CONTINUE + end + + -- # For old ctags we recognize "typedef struct aaa" and + -- # "typedef union bbb" in the tags file command. + local cmd = vim9.index(item, 'cmd') + local ei = vim9.fn.charidx(cmd, vim9.fn.matchend(cmd, 'typedef\\s\\+')) + if ei > 1 then + local cmdtokens = vim9.fn.split(vim9.slice(cmd, ei, nil), '\\s\\+\\|\\<') + if vim9.fn.len(cmdtokens) > 1 then + if + vim9.index(cmdtokens, 0) == 'struct' + or vim9.index(cmdtokens, 0) == 'union' + or vim9.index(cmdtokens, 0) == 'class' + then + local name = '' + -- # Use the first identifier after the "struct" or "union" + + for _, ti in vim9.iter(vim9.fn.range((vim9.fn.len(cmdtokens) - 1))) do + if vim9.ops.RegexpMatches(vim9.index(cmdtokens, ti), '^\\w') then + name = vim9.index(cmdtokens, ti) + break + end + end + + if name ~= '' then + res = vim9.fn.extend( + res, + StructMembers(vim9.index(cmdtokens, 0) .. ':' .. name, items, all) + ) + end + elseif vim9.bool(depth < 10) then + -- # Could be "typedef other_T some_T". + res = vim9.fn.extend( + res, + Nextitem(vim9.index(cmdtokens, 0), items, vim9.ops.Plus(depth, 1), all) + ) + end + end + end + + return vim9.ITER_DEFAULT + end + + for _, tagidx in vim9.iter(vim9.fn.range(vim9.fn.len(diclist))) do + local nvim9_status, nvim9_ret = body(_, tagidx) + if nvim9_status == vim9.ITER_BREAK then + break + elseif nvim9_status == vim9.ITER_RETURN then + return nvim9_ret + end + end + + if vim9.fn.len(res) > 0 then + return vim9.ITER_BREAK + end + + return vim9.ITER_DEFAULT + end + + for _, tidx in vim9.iter(vim9.fn.range(vim9.fn.len(tokens))) do + local nvim9_status, nvim9_ret = body(_, tidx) + if nvim9_status == vim9.ITER_BREAK then + break + elseif nvim9_status == vim9.ITER_RETURN then + return nvim9_ret + end + end + + return res +end + +StructMembers = function(atypename, items, all) + all = vim9.bool(all) + + -- # Search for members of structure "typename" in tags files. + -- # Return a list with resulting matches. + -- # Each match is a dictionary with "match" and "tagline" entries. + -- # When "all" is true find all, otherwise just return 1 if there is any member. + + -- # Todo: What about local structures? + local fnames = vim9.fn.join(vim9.fn.map(vim9.fn.tagfiles(), function(_, v) + return vim9.fn.escape(v, ' \\#%') + end)) + if fnames == '' then + return {} + end + + local typename = atypename + local qflist = {} + local cached = 0 + local n = '' + if vim9.bool(vim9.prefix['Bang'](all)) then + n = '1' + if vim9.bool(vim9.fn.has_key(grepCache, typename)) then + qflist = vim9.index(grepCache, typename) + cached = 1 + end + else + n = '' + end + if vim9.bool(vim9.prefix['Bang'](cached)) then + while 1 do + vim.api.nvim_command( + 'silent! keepjumps noautocmd ' + .. n + .. 'vimgrep ' + .. '/\\t' + .. typename + .. '\\(\\t\\|$\\)/j ' + .. fnames + ) + + qflist = vim9.fn.getqflist() + if vim9.fn.len(qflist) > 0 or vim9.fn.match(typename, '::') < 0 then + break + end + -- # No match for "struct:context::name", remove "context::" and try again. + typename = vim9.fn.substitute(typename, ':[^:]*::', ':', '') + end + + if vim9.bool(vim9.prefix['Bang'](all)) then + -- # Store the result to be able to use it again later. + grepCache[vim9.index_expr(typename)] = qflist + end + end + + -- # Skip over [...] items + local idx = 0 + local target = '' + while 1 do + if idx >= vim9.fn.len(items) then + target = '' + break + end + if vim9.index(vim9.index(items, idx), 0) ~= '[' then + target = vim9.index(items, idx) + break + end + idx = idx + 1 + end + -- # Put matching members in matches[]. + local matches = {} + + for _, l in vim9.iter(qflist) do + local memb = vim9.fn.matchstr(vim9.index(l, 'text'), '[^\\t]*') + if vim9.ops.RegexpMatches(memb, '^' .. target) then + -- # Skip matches local to another file. + if + vim9.fn.match(vim9.index(l, 'text'), '\tfile:') < 0 + or vim9.fn.bufnr('%') + == vim9.fn.bufnr(vim9.fn.matchstr(vim9.index(l, 'text'), '\\t\\zs[^\\t]*')) + then + local item = + vim9.convert.decl_dict({ ['match'] = memb, ['tagline'] = vim9.index(l, 'text') }) + + -- # Add the kind of item. + local s = + vim9.fn.matchstr(vim9.index(l, 'text'), '\\t\\(kind:\\)\\=\\zs\\S\\ze\\(\\t\\|$\\)') + if s ~= '' then + item[vim9.index_expr('kind')] = s + if s == 'f' then + item[vim9.index_expr('match')] = memb .. '(' + end + end + + vim9.fn.add(matches, item) + end + end + end + + if vim9.fn.len(matches) > 0 then + -- # Skip over next [...] items + idx = idx + 1 + while 1 do + if idx >= vim9.fn.len(items) then + return matches + end + if vim9.index(vim9.index(items, idx), 0) ~= '[' then + break + end + idx = idx + 1 + end + + -- # More items following. For each of the possible members find the + -- # matching following members. + return SearchMembers(matches, vim9.slice(items, idx, nil), all) + end + + -- # Failed to find anything. + return {} +end + +SearchMembers = function(matches, items, all) + all = vim9.bool(all) + + -- # For matching members, find matches for following items. + -- # When "all" is true find all, otherwise just return 1 if there is any member. + local res = {} + + for _, i in vim9.iter(vim9.fn.range(vim9.fn.len(matches))) do + local typename = '' + local line = '' + if vim9.bool(vim9.fn.has_key(vim9.index(matches, i), 'dict')) then + if vim9.bool(vim9.fn.has_key(vim9.index(vim9.index(matches, i), 'dict'), 'typename')) then + typename = vim9.index(vim9.index(vim9.index(matches, i), 'dict'), 'typename') + elseif vim9.bool(vim9.fn.has_key(vim9.index(vim9.index(matches, i), 'dict'), 'typeref')) then + typename = vim9.index(vim9.index(vim9.index(matches, i), 'dict'), 'typeref') + end + line = '\t' .. vim9.index(vim9.index(vim9.index(matches, i), 'dict'), 'cmd') + else + line = vim9.index(vim9.index(matches, i), 'tagline') + local eb = vim9.fn.matchend(line, '\\ttypename:') + local e = vim9.fn.charidx(line, eb) + if e < 0 then + eb = vim9.fn.matchend(line, '\\ttyperef:') + e = vim9.fn.charidx(line, eb) + end + if e > 0 then + -- # Use typename field + typename = vim9.fn.matchstr(line, '[^\\t]*', eb) + end + end + + if typename ~= '' then + res = vim9.fn.extend(res, StructMembers(typename, items, all)) + else + -- # Use the search command (the declaration itself). + local sb = vim9.fn.match(line, '\\t\\zs/^') + local s = vim9.fn.charidx(line, sb) + if s > 0 then + local e = vim9.fn.charidx( + line, + vim9.fn.match(line, '\\<' .. vim9.index(vim9.index(matches, i), 'match') .. '\\>', sb) + ) + if e > 0 then + res = + vim9.fn.extend(res, Nextitem(vim9.slice(line, s, vim9.ops.Minus(e, 1)), items, 0, all)) + end + end + end + if vim9.bool(vim9.ops.And(vim9.prefix['Bang'](all), vim9.fn.len(res) > 0)) then + break + end + end + + return res +end + +-- #}}}1 + +-- # vim: noet sw=2 sts=2 +return M diff --git a/runtime/autoload/ccomplete.vim b/runtime/autoload/ccomplete.vim index 95a20e16b0..d7e0ba4ac5 100644 --- a/runtime/autoload/ccomplete.vim +++ b/runtime/autoload/ccomplete.vim @@ -1,639 +1,8 @@ -" Vim completion script -" Language: C -" Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2020 Nov 14 - -let s:cpo_save = &cpo -set cpo&vim - -" This function is used for the 'omnifunc' option. -func ccomplete#Complete(findstart, base) - if a:findstart - " Locate the start of the item, including ".", "->" and "[...]". - let line = getline('.') - let start = col('.') - 1 - let lastword = -1 - while start > 0 - if line[start - 1] =~ '\w' - let start -= 1 - elseif line[start - 1] =~ '\.' - if lastword == -1 - let lastword = start - endif - let start -= 1 - elseif start > 1 && line[start - 2] == '-' && line[start - 1] == '>' - if lastword == -1 - let lastword = start - endif - let start -= 2 - elseif line[start - 1] == ']' - " Skip over [...]. - let n = 0 - let start -= 1 - while start > 0 - let start -= 1 - if line[start] == '[' - if n == 0 - break - endif - let n -= 1 - elseif line[start] == ']' " nested [] - let n += 1 - endif - endwhile - else - break - endif - endwhile - - " Return the column of the last word, which is going to be changed. - " Remember the text that comes before it in s:prepended. - if lastword == -1 - let s:prepended = '' - return start - endif - let s:prepended = strpart(line, start, lastword - start) - return lastword - endif - - " Return list of matches. - - let base = s:prepended . a:base - - " Don't do anything for an empty base, would result in all the tags in the - " tags file. - if base == '' - return [] - endif - - " init cache for vimgrep to empty - let s:grepCache = {} - - " Split item in words, keep empty word after "." or "->". - " "aa" -> ['aa'], "aa." -> ['aa', ''], "aa.bb" -> ['aa', 'bb'], etc. - " We can't use split, because we need to skip nested [...]. - " "aa[...]" -> ['aa', '[...]'], "aa.bb[...]" -> ['aa', 'bb', '[...]'], etc. - let items = [] - let s = 0 - let arrays = 0 - while 1 - let e = match(base, '\.\|->\|\[', s) - if e < 0 - if s == 0 || base[s - 1] != ']' - call add(items, strpart(base, s)) - endif - break - endif - if s == 0 || base[s - 1] != ']' - call add(items, strpart(base, s, e - s)) - endif - if base[e] == '.' - let s = e + 1 " skip over '.' - elseif base[e] == '-' - let s = e + 2 " skip over '->' - else - " Skip over [...]. - let n = 0 - let s = e - let e += 1 - while e < len(base) - if base[e] == ']' - if n == 0 - break - endif - let n -= 1 - elseif base[e] == '[' " nested [...] - let n += 1 - endif - let e += 1 - endwhile - let e += 1 - call add(items, strpart(base, s, e - s)) - let arrays += 1 - let s = e - endif - endwhile - - " Find the variable items[0]. - " 1. in current function (like with "gd") - " 2. in tags file(s) (like with ":tag") - " 3. in current file (like with "gD") - let res = [] - if searchdecl(items[0], 0, 1) == 0 - " Found, now figure out the type. - " TODO: join previous line if it makes sense - let line = getline('.') - let col = col('.') - if stridx(strpart(line, 0, col), ';') != -1 - " Handle multiple declarations on the same line. - let col2 = col - 1 - while line[col2] != ';' - let col2 -= 1 - endwhile - let line = strpart(line, col2 + 1) - let col -= col2 - endif - if stridx(strpart(line, 0, col), ',') != -1 - " Handle multiple declarations on the same line in a function - " declaration. - let col2 = col - 1 - while line[col2] != ',' - let col2 -= 1 - endwhile - if strpart(line, col2 + 1, col - col2 - 1) =~ ' *[^ ][^ ]* *[^ ]' - let line = strpart(line, col2 + 1) - let col -= col2 - endif - endif - if len(items) == 1 - " Completing one word and it's a local variable: May add '[', '.' or - " '->'. - let match = items[0] - let kind = 'v' - if match(line, '\<' . match . '\s*\[') > 0 - let match .= '[' - else - let res = s:Nextitem(strpart(line, 0, col), [''], 0, 1) - if len(res) > 0 - " There are members, thus add "." or "->". - if match(line, '\*[ \t(]*' . match . '\>') > 0 - let match .= '->' - else - let match .= '.' - endif - endif - endif - let res = [{'match': match, 'tagline' : '', 'kind' : kind, 'info' : line}] - elseif len(items) == arrays + 1 - " Completing one word and it's a local array variable: build tagline - " from declaration line - let match = items[0] - let kind = 'v' - let tagline = "\t/^" . line . '$/' - let res = [{'match': match, 'tagline' : tagline, 'kind' : kind, 'info' : line}] - else - " Completing "var.", "var.something", etc. - let res = s:Nextitem(strpart(line, 0, col), items[1:], 0, 1) - endif - endif - - if len(items) == 1 || len(items) == arrays + 1 - " Only one part, no "." or "->": complete from tags file. - if len(items) == 1 - let tags = taglist('^' . base) - else - let tags = taglist('^' . items[0] . '$') - endif - - " Remove members, these can't appear without something in front. - call filter(tags, 'has_key(v:val, "kind") ? v:val["kind"] != "m" : 1') - - " Remove static matches in other files. - call filter(tags, '!has_key(v:val, "static") || !v:val["static"] || bufnr("%") == bufnr(v:val["filename"])') - - call extend(res, map(tags, 's:Tag2item(v:val)')) - endif - - if len(res) == 0 - " Find the variable in the tags file(s) - let diclist = taglist('^' . items[0] . '$') - - " Remove members, these can't appear without something in front. - call filter(diclist, 'has_key(v:val, "kind") ? v:val["kind"] != "m" : 1') - - let res = [] - for i in range(len(diclist)) - " New ctags has the "typeref" field. Patched version has "typename". - if has_key(diclist[i], 'typename') - call extend(res, s:StructMembers(diclist[i]['typename'], items[1:], 1)) - elseif has_key(diclist[i], 'typeref') - call extend(res, s:StructMembers(diclist[i]['typeref'], items[1:], 1)) - endif - - " For a variable use the command, which must be a search pattern that - " shows the declaration of the variable. - if diclist[i]['kind'] == 'v' - let line = diclist[i]['cmd'] - if line[0] == '/' && line[1] == '^' - let col = match(line, '\<' . items[0] . '\>') - call extend(res, s:Nextitem(strpart(line, 2, col - 2), items[1:], 0, 1)) - endif - endif - endfor - endif - - if len(res) == 0 && searchdecl(items[0], 1) == 0 - " Found, now figure out the type. - " TODO: join previous line if it makes sense - let line = getline('.') - let col = col('.') - let res = s:Nextitem(strpart(line, 0, col), items[1:], 0, 1) - endif - - " If the last item(s) are [...] they need to be added to the matches. - let last = len(items) - 1 - let brackets = '' - while last >= 0 - if items[last][0] != '[' - break - endif - let brackets = items[last] . brackets - let last -= 1 - endwhile - - return map(res, 's:Tagline2item(v:val, brackets)') -endfunc - -func s:GetAddition(line, match, memarg, bracket) - " Guess if the item is an array. - if a:bracket && match(a:line, a:match . '\s*\[') > 0 - return '[' - endif - - " Check if the item has members. - if len(s:SearchMembers(a:memarg, [''], 0)) > 0 - " If there is a '*' before the name use "->". - if match(a:line, '\*[ \t(]*' . a:match . '\>') > 0 - return '->' - else - return '.' - endif - endif - return '' -endfunc - -" Turn the tag info "val" into an item for completion. -" "val" is is an item in the list returned by taglist(). -" If it is a variable we may add "." or "->". Don't do it for other types, -" such as a typedef, by not including the info that s:GetAddition() uses. -func s:Tag2item(val) - let res = {'match': a:val['name']} - - let res['extra'] = s:Tagcmd2extra(a:val['cmd'], a:val['name'], a:val['filename']) - - let s = s:Dict2info(a:val) - if s != '' - let res['info'] = s - endif - - let res['tagline'] = '' - if has_key(a:val, "kind") - let kind = a:val['kind'] - let res['kind'] = kind - if kind == 'v' - let res['tagline'] = "\t" . a:val['cmd'] - let res['dict'] = a:val - elseif kind == 'f' - let res['match'] = a:val['name'] . '(' - endif - endif - - return res -endfunc - -" Use all the items in dictionary for the "info" entry. -func s:Dict2info(dict) - let info = '' - for k in sort(keys(a:dict)) - let info .= k . repeat(' ', 10 - len(k)) - if k == 'cmd' - let info .= substitute(matchstr(a:dict['cmd'], '/^\s*\zs.*\ze$/'), '\\\(.\)', '\1', 'g') - else - let info .= a:dict[k] - endif - let info .= "\n" - endfor - return info -endfunc - -" Parse a tag line and return a dictionary with items like taglist() -func s:ParseTagline(line) - let l = split(a:line, "\t") - let d = {} - if len(l) >= 3 - let d['name'] = l[0] - let d['filename'] = l[1] - let d['cmd'] = l[2] - let n = 2 - if l[2] =~ '^/' - " Find end of cmd, it may contain Tabs. - while n < len(l) && l[n] !~ '/;"$' - let n += 1 - let d['cmd'] .= " " . l[n] - endwhile - endif - for i in range(n + 1, len(l) - 1) - if l[i] == 'file:' - let d['static'] = 1 - elseif l[i] !~ ':' - let d['kind'] = l[i] - else - let d[matchstr(l[i], '[^:]*')] = matchstr(l[i], ':\zs.*') - endif - endfor - endif - - return d -endfunc - -" Turn a match item "val" into an item for completion. -" "val['match']" is the matching item. -" "val['tagline']" is the tagline in which the last part was found. -func s:Tagline2item(val, brackets) - let line = a:val['tagline'] - let add = s:GetAddition(line, a:val['match'], [a:val], a:brackets == '') - let res = {'word': a:val['match'] . a:brackets . add } - - if has_key(a:val, 'info') - " Use info from Tag2item(). - let res['info'] = a:val['info'] - else - " Parse the tag line and add each part to the "info" entry. - let s = s:Dict2info(s:ParseTagline(line)) - if s != '' - let res['info'] = s - endif - endif - - if has_key(a:val, 'kind') - let res['kind'] = a:val['kind'] - elseif add == '(' - let res['kind'] = 'f' - else - let s = matchstr(line, '\t\(kind:\)\=\zs\S\ze\(\t\|$\)') - if s != '' - let res['kind'] = s - endif - endif - - if has_key(a:val, 'extra') - let res['menu'] = a:val['extra'] - return res - endif - - " Isolate the command after the tag and filename. - let s = matchstr(line, '[^\t]*\t[^\t]*\t\zs\(/^.*$/\|[^\t]*\)\ze\(;"\t\|\t\|$\)') - if s != '' - let res['menu'] = s:Tagcmd2extra(s, a:val['match'], matchstr(line, '[^\t]*\t\zs[^\t]*\ze\t')) - endif - return res -endfunc - -" Turn a command from a tag line to something that is useful in the menu -func s:Tagcmd2extra(cmd, name, fname) - if a:cmd =~ '^/^' - " The command is a search command, useful to see what it is. - let x = matchstr(a:cmd, '^/^\s*\zs.*\ze$/') - let x = substitute(x, '\<' . a:name . '\>', '@@', '') - let x = substitute(x, '\\\(.\)', '\1', 'g') - let x = x . ' - ' . a:fname - elseif a:cmd =~ '^\d*$' - " The command is a line number, the file name is more useful. - let x = a:fname . ' - ' . a:cmd - else - " Not recognized, use command and file name. - let x = a:cmd . ' - ' . a:fname - endif - return x -endfunc - -" Find composing type in "lead" and match items[0] with it. -" Repeat this recursively for items[1], if it's there. -" When resolving typedefs "depth" is used to avoid infinite recursion. -" Return the list of matches. -func s:Nextitem(lead, items, depth, all) - - " Use the text up to the variable name and split it in tokens. - let tokens = split(a:lead, '\s\+\|\<') - - " Try to recognize the type of the variable. This is rough guessing... - let res = [] - for tidx in range(len(tokens)) - - " Skip tokens starting with a non-ID character. - if tokens[tidx] !~ '^\h' - continue - endif - - " Recognize "struct foobar" and "union foobar". - " Also do "class foobar" when it's C++ after all (doesn't work very well - " though). - if (tokens[tidx] == 'struct' || tokens[tidx] == 'union' || tokens[tidx] == 'class') && tidx + 1 < len(tokens) - let res = s:StructMembers(tokens[tidx] . ':' . tokens[tidx + 1], a:items, a:all) - break - endif - - " TODO: add more reserved words - if index(['int', 'short', 'char', 'float', 'double', 'static', 'unsigned', 'extern'], tokens[tidx]) >= 0 - continue - endif - - " Use the tags file to find out if this is a typedef. - let diclist = taglist('^' . tokens[tidx] . '$') - for tagidx in range(len(diclist)) - let item = diclist[tagidx] - - " New ctags has the "typeref" field. Patched version has "typename". - if has_key(item, 'typeref') - call extend(res, s:StructMembers(item['typeref'], a:items, a:all)) - continue - endif - if has_key(item, 'typename') - call extend(res, s:StructMembers(item['typename'], a:items, a:all)) - continue - endif - - " Only handle typedefs here. - if item['kind'] != 't' - continue - endif - - " Skip matches local to another file. - if has_key(item, 'static') && item['static'] && bufnr('%') != bufnr(item['filename']) - continue - endif - - " For old ctags we recognize "typedef struct aaa" and - " "typedef union bbb" in the tags file command. - let cmd = item['cmd'] - let ei = matchend(cmd, 'typedef\s\+') - if ei > 1 - let cmdtokens = split(strpart(cmd, ei), '\s\+\|\<') - if len(cmdtokens) > 1 - if cmdtokens[0] == 'struct' || cmdtokens[0] == 'union' || cmdtokens[0] == 'class' - let name = '' - " Use the first identifier after the "struct" or "union" - for ti in range(len(cmdtokens) - 1) - if cmdtokens[ti] =~ '^\w' - let name = cmdtokens[ti] - break - endif - endfor - if name != '' - call extend(res, s:StructMembers(cmdtokens[0] . ':' . name, a:items, a:all)) - endif - elseif a:depth < 10 - " Could be "typedef other_T some_T". - call extend(res, s:Nextitem(cmdtokens[0], a:items, a:depth + 1, a:all)) - endif - endif - endif - endfor - if len(res) > 0 - break - endif - endfor - - return res -endfunc - - -" Search for members of structure "typename" in tags files. -" Return a list with resulting matches. -" Each match is a dictionary with "match" and "tagline" entries. -" When "all" is non-zero find all, otherwise just return 1 if there is any -" member. -func s:StructMembers(typename, items, all) - " Todo: What about local structures? - let fnames = join(map(tagfiles(), 'escape(v:val, " \\#%")')) - if fnames == '' - return [] - endif - - let typename = a:typename - let qflist = [] - let cached = 0 - if a:all == 0 - let n = '1' " stop at first found match - if has_key(s:grepCache, a:typename) - let qflist = s:grepCache[a:typename] - let cached = 1 - endif - else - let n = '' - endif - if !cached - while 1 - exe 'silent! keepj noautocmd ' . n . 'vimgrep /\t' . typename . '\(\t\|$\)/j ' . fnames - - let qflist = getqflist() - if len(qflist) > 0 || match(typename, "::") < 0 - break - endif - " No match for "struct:context::name", remove "context::" and try again. - let typename = substitute(typename, ':[^:]*::', ':', '') - endwhile - - if a:all == 0 - " Store the result to be able to use it again later. - let s:grepCache[a:typename] = qflist - endif - endif - - " Skip over [...] items - let idx = 0 - while 1 - if idx >= len(a:items) - let target = '' " No further items, matching all members - break - endif - if a:items[idx][0] != '[' - let target = a:items[idx] - break - endif - let idx += 1 - endwhile - " Put matching members in matches[]. - let matches = [] - for l in qflist - let memb = matchstr(l['text'], '[^\t]*') - if memb =~ '^' . target - " Skip matches local to another file. - if match(l['text'], "\tfile:") < 0 || bufnr('%') == bufnr(matchstr(l['text'], '\t\zs[^\t]*')) - let item = {'match': memb, 'tagline': l['text']} - - " Add the kind of item. - let s = matchstr(l['text'], '\t\(kind:\)\=\zs\S\ze\(\t\|$\)') - if s != '' - let item['kind'] = s - if s == 'f' - let item['match'] = memb . '(' - endif - endif - - call add(matches, item) - endif - endif - endfor - - if len(matches) > 0 - " Skip over next [...] items - let idx += 1 - while 1 - if idx >= len(a:items) - return matches " No further items, return the result. - endif - if a:items[idx][0] != '[' - break - endif - let idx += 1 - endwhile - - " More items following. For each of the possible members find the - " matching following members. - return s:SearchMembers(matches, a:items[idx :], a:all) - endif - - " Failed to find anything. - return [] -endfunc - -" For matching members, find matches for following items. -" When "all" is non-zero find all, otherwise just return 1 if there is any -" member. -func s:SearchMembers(matches, items, all) - let res = [] - for i in range(len(a:matches)) - let typename = '' - if has_key(a:matches[i], 'dict') - if has_key(a:matches[i].dict, 'typename') - let typename = a:matches[i].dict['typename'] - elseif has_key(a:matches[i].dict, 'typeref') - let typename = a:matches[i].dict['typeref'] - endif - let line = "\t" . a:matches[i].dict['cmd'] - else - let line = a:matches[i]['tagline'] - let e = matchend(line, '\ttypename:') - if e < 0 - let e = matchend(line, '\ttyperef:') - endif - if e > 0 - " Use typename field - let typename = matchstr(line, '[^\t]*', e) - endif - endif - - if typename != '' - call extend(res, s:StructMembers(typename, a:items, a:all)) - else - " Use the search command (the declaration itself). - let s = match(line, '\t\zs/^') - if s > 0 - let e = match(line, '\<' . a:matches[i]['match'] . '\>', s) - if e > 0 - call extend(res, s:Nextitem(strpart(line, s, e - s), a:items, 0, a:all)) - endif - endif - endif - if a:all == 0 && len(res) > 0 - break - endif - endfor - return res -endfunc - -let &cpo = s:cpo_save -unlet s:cpo_save - -" vim: noet sw=2 sts=2 +" Generated vim file by vim9jit. Please do not edit +let s:path = expand("<script>") +let s:lua_path = fnamemodify(s:path, ":r") . ".lua" +let s:nvim_module = luaeval('require("_vim9script").autoload(_A)', s:lua_path) + +function! ccomplete#Complete(findstart, abase) abort + return s:nvim_module.Complete(a:findstart, a:abase) +endfunction diff --git a/runtime/autoload/dist/ft.vim b/runtime/autoload/dist/ft.vim deleted file mode 100644 index 7333e5a7e7..0000000000 --- a/runtime/autoload/dist/ft.vim +++ /dev/null @@ -1,1090 +0,0 @@ -" Vim functions for file type detection -" -" Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2022 Apr 13 - -" These functions are moved here from runtime/filetype.vim to make startup -" faster. - -" Line continuation is used here, remove 'C' from 'cpoptions' -let s:cpo_save = &cpo -set cpo&vim - -func dist#ft#Check_inp() - if getline(1) =~ '^\*' - setf abaqus - else - let n = 1 - if line("$") > 500 - let nmax = 500 - else - let nmax = line("$") - endif - while n <= nmax - if getline(n) =~? "^header surface data" - setf trasys - break - endif - let n = n + 1 - endwhile - endif -endfunc - -" This function checks for the kind of assembly that is wanted by the user, or -" can be detected from the first five lines of the file. -func dist#ft#FTasm() - " make sure b:asmsyntax exists - if !exists("b:asmsyntax") - let b:asmsyntax = "" - endif - - if b:asmsyntax == "" - call dist#ft#FTasmsyntax() - endif - - " if b:asmsyntax still isn't set, default to asmsyntax or GNU - if b:asmsyntax == "" - if exists("g:asmsyntax") - let b:asmsyntax = g:asmsyntax - else - let b:asmsyntax = "asm" - endif - endif - - exe "setf " . fnameescape(b:asmsyntax) -endfunc - -func dist#ft#FTasmsyntax() - " see if file contains any asmsyntax=foo overrides. If so, change - " b:asmsyntax appropriately - let head = " ".getline(1)." ".getline(2)." ".getline(3)." ".getline(4). - \" ".getline(5)." " - let match = matchstr(head, '\sasmsyntax=\zs[a-zA-Z0-9]\+\ze\s') - if match != '' - let b:asmsyntax = match - elseif ((head =~? '\.title') || (head =~? '\.ident') || (head =~? '\.macro') || (head =~? '\.subtitle') || (head =~? '\.library')) - let b:asmsyntax = "vmasm" - endif -endfunc - -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*\%(' .. - \ '#\s*\a\+\|' .. - \ 'option\s\+\%(byval\|dynamic\|escape\|\%(no\)\=gosub\|nokeyword\|private\|static\)\>\|' .. - \ '\%(''\|rem\)\s*\$lang\>\|' .. - \ 'def\%(byte\|longint\|short\|ubyte\|uint\|ulongint\|ushort\)\>' .. - \ '\)' - 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\)\>\)' - - for lnum in range(1, min([line("$"), 100])) - let line = getline(lnum) - if line =~ s:ft_visual_basic_content - setf vb - return - elseif line =~ fb_preproc || line =~ fb_comment || line =~ fb_keywords - setf freebasic - return - elseif line =~ qb64_preproc - setf qb64 - return - endif - endfor - setf basic -endfunc - -func dist#ft#FTbtm() - if exists("g:dosbatch_syntax_for_btm") && g:dosbatch_syntax_for_btm - setf dosbatch - else - setf btm - endif -endfunc - -func dist#ft#BindzoneCheck(default) - if getline(1).getline(2).getline(3).getline(4) =~ '^; <<>> DiG [0-9.]\+.* <<>>\|$ORIGIN\|$TTL\|IN\s\+SOA' - setf bindzone - elseif a:default != '' - exe 'setf ' . a: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#FTcls() - if exists("g:filetype_cls") - exe "setf " .. g:filetype_cls - return - endif - - if getline(1) =~ '^%' - setf tex - elseif getline(1)[0] == '#' && getline(1) =~ 'rexx' - setf rexx - elseif getline(1) == 'VERSION 1.0 CLASS' - setf vb - else - setf st - endif -endfunc - -func dist#ft#FTlpc() - if exists("g:lpc_syntax_for_c") - let lnum = 1 - while lnum <= 12 - if getline(lnum) =~# '^\(//\|inherit\|private\|protected\|nosave\|string\|object\|mapping\|mixed\)' - setf lpc - return - endif - let lnum = lnum + 1 - endwhile - endif - setf c -endfunc - -func dist#ft#FTheader() - if match(getline(1, min([line("$"), 200])), '^@\(interface\|end\|class\)') > -1 - if exists("g:c_syntax_for_h") - setf objc - else - setf objcpp - endif - elseif exists("g:c_syntax_for_h") - setf c - elseif exists("g:ch_syntax_for_h") - setf ch - else - setf cpp - endif -endfunc - -" This function checks if one of the first ten lines start with a '@'. In -" that case it is probably a change file. -" If the first line starts with # or ! it's probably a ch file. -" If a line has "main", "include", "//" or "/*" it's probably ch. -" Otherwise CHILL is assumed. -func dist#ft#FTchange() - let lnum = 1 - while lnum <= 10 - if getline(lnum)[0] == '@' - setf change - return - endif - if lnum == 1 && (getline(1)[0] == '#' || getline(1)[0] == '!') - setf ch - return - endif - if getline(lnum) =~ "MODULE" - setf chill - return - endif - if getline(lnum) =~ 'main\s*(\|#\s*include\|//' - setf ch - return - endif - let lnum = lnum + 1 - endwhile - setf chill -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, '{'. - " If not found, assume SGML. - let lnum = 1 - while lnum < 6 - let line = getline(lnum) - if line =~ '^\s*[#{]' - setf cl - return - elseif line !~ '^\s*$' - " Not a blank line, not a comment, and not a block start, - " so doesn't look like valid cl code. - break - endif - let lnum = lnum + 1 - endw - setf dtd -endfunc - -func dist#ft#ExCheck() - let lines = getline(1, min([line("$"), 100])) - if exists('g:filetype_euphoria') - exe 'setf ' . g:filetype_euphoria - elseif match(lines, '^--\|^ifdef\>\|^include\>') > -1 - setf euphoria3 - else - setf elixir - endif -endfunc - -func dist#ft#EuphoriaCheck() - if exists('g:filetype_euphoria') - exe 'setf ' . g:filetype_euphoria - else - setf euphoria3 - endif -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. - setf d - elseif match(lines, '^#!\S\+dtrace\|#pragma\s\+D\s\+option\|:\S\{-}:\S\{-}:') > -1 - setf dtrace - else - setf d - endif -endfunc - -func dist#ft#FTe() - if exists('g:filetype_euphoria') - exe 'setf ' . g:filetype_euphoria - else - let n = 1 - while n < 100 && n <= line("$") - if getline(n) =~ "^\\s*\\(<'\\|'>\\)\\s*$" - setf specman - return - endif - let n = n + 1 - endwhile - setf eiffel - 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 - while n < 10 && n <= line("$") - if getline(n) =~ '\<DTD\s\+XHTML\s' - setf xhtml - return - endif - if getline(n) =~ '{%\s*\(extends\|block\|load\)\>\|{#\s\+' - setf htmldjango - return - endif - let n = n + 1 - endwhile - setf FALLBACK html -endfunc - -" Distinguish between standard IDL and MS-IDL -func dist#ft#FTidl() - let n = 1 - while n < 50 && n <= line("$") - if getline(n) =~ '^\s*import\s\+"\(unknwn\|objidl\)\.idl"' - setf msidl - return - endif - let n = n + 1 - endwhile - setf idl -endfunc - -" Distinguish between "default", Prolog and Cproto prototype file. */ -func dist#ft#ProtoCheck(default) - " Cproto files have a comment in the first line and a function prototype in - " the second line, it always ends in ";". Indent files may also have - " comments, thus we can't match comments to see the difference. - " IDL files can have a single ';' in the second line, require at least one - " chacter before the ';'. - if getline(2) =~ '.;$' - setf cpp - else - " recognize Prolog by specific text in the first non-empty line - " require a blank after the '%' because Perl uses "%list" and "%translate" - let l = getline(nextnonblank(1)) - if l =~ '\<prolog\>' || l =~ '^\s*\(%\+\(\s\|$\)\|/\*\)' || l =~ ':-' - setf prolog - else - exe 'setf ' .. a:default - endif - endif -endfunc - -func dist#ft#FTm() - if exists("g:filetype_m") - exe "setf " . g:filetype_m - return - endif - - " 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 - let line = getline(n) - if line =~ '^\s*/\*' - " /* ... */ is a comment in Objective C and Murphi, so we can't conclude - " it's either of them yet, but track this as a hint in case we don't see - " anything more definitive. - let saw_comment = 1 - endif - if line =~ '^\s*//' || line =~ '^\s*@import\>' || line =~ objc_preprocessor - setf objc - return - endif - if line =~ '^\s*\%(#\|%!\)' || line =~ '^\s*unwind_protect\>' || - \ line =~ '\%(^\|;\)\s*' .. octave_block_terminators - setf octave - return - endif - " TODO: could be Matlab or Octave - if line =~ '^\s*%' - setf matlab - return - endif - if line =~ '^\s*(\*' - setf mma - return - endif - if line =~ '^\c\s*\(\(type\|var\)\>\|--\)' - setf murphi - return - endif - let n = n + 1 - endwhile - - if saw_comment - " We didn't see anything definitive, but this looks like either Objective C - " or Murphi based on the comment leader. Assume the former as it is more - " common. - setf objc - else - " Default is Matlab - setf matlab - endif -endfunc - -func dist#ft#FTmms() - let n = 1 - while n < 20 - let line = getline(n) - if line =~ '^\s*\(%\|//\)' || line =~ '^\*' - setf mmix - return - endif - if line =~ '^\s*#' - setf make - return - endif - let n = n + 1 - endwhile - setf mmix -endfunc - -" This function checks if one of the first five lines start with a dot. In -" that case it is probably an nroff file: 'filetype' is set and 1 is returned. -func dist#ft#FTnroff() - if getline(1)[0] . getline(2)[0] . getline(3)[0] . getline(4)[0] . getline(5)[0] =~ '\.' - setf nroff - return 1 - endif - return 0 -endfunc - -func dist#ft#FTmm() - let n = 1 - while n < 20 - let line = getline(n) - if line =~ '^\s*\(#\s*\(include\|import\)\>\|@import\>\|/\*\)' - setf objcpp - return - endif - let n = n + 1 - endwhile - setf nroff -endfunc - -" Returns true if file content looks like LambdaProlog module -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 - else - " recognize Prolog by specific text in the first non-empty line - " require a blank after the '%' because Perl uses "%list" and "%translate" - let l = getline(nextnonblank(1)) - if l =~ '\<prolog\>' || l =~ '^\s*\(%\+\(\s\|$\)\|/\*\)' || l =~ ':-' - setf prolog - else - setf perl - endif - endif -endfunc - -func dist#ft#FTinc() - if exists("g:filetype_inc") - exe "setf " . g:filetype_inc - else - let lines = getline(1).getline(2).getline(3) - if lines =~? "perlscript" - setf aspperl - elseif lines =~ "<%" - setf aspvbs - elseif lines =~ "<?" - setf php - " Pascal supports // comments but they're vary rarely used for file - " headers so assume POV-Ray - elseif lines =~ '^\s*\%({\|(\*\)' || lines =~? s:ft_pascal_keywords - setf pascal - elseif lines =~# '\<\%(require\|inherit\)\>' || lines =~# '[A-Z][A-Za-z0-9_:${}]*\s\+\%(??\|[?:+]\)\?= ' - setf bitbake - else - call dist#ft#FTasmsyntax() - if exists("b:asmsyntax") - exe "setf " . fnameescape(b:asmsyntax) - else - setf pov - endif - endif - endif -endfunc - -func dist#ft#FTprogress_cweb() - if exists("g:filetype_w") - exe "setf " . g:filetype_w - return - endif - if getline(1) =~ '&ANALYZE' || getline(3) =~ '&GLOBAL-DEFINE' - setf progress - else - setf cweb - endif -endfunc - -func dist#ft#FTprogress_asm() - if exists("g:filetype_i") - exe "setf " . g:filetype_i - return - endif - " This function checks for an assembly comment the first ten lines. - " If not found, assume Progress. - let lnum = 1 - while lnum <= 10 && lnum < line('$') - let line = getline(lnum) - if line =~ '^\s*;' || line =~ '^\*' - call dist#ft#FTasm() - return - elseif line !~ '^\s*$' || line =~ '^/\*' - " Not an empty line: Doesn't look like valid assembly code. - " Or it looks like a Progress /* comment - break - endif - let lnum = lnum + 1 - endw - setf progress -endfunc - -let s:ft_pascal_comments = '^\s*\%({\|(\*\|//\)' -let s:ft_pascal_keywords = '^\s*\%(program\|unit\|library\|uses\|begin\|procedure\|function\|const\|type\|var\)\>' - -func dist#ft#FTprogress_pascal() - if exists("g:filetype_p") - exe "setf " . g:filetype_p - return - endif - " This function checks for valid Pascal syntax in the first ten lines. - " Look for either an opening comment or a program start. - " If not found, assume Progress. - let lnum = 1 - while lnum <= 10 && lnum < line('$') - let line = getline(lnum) - if line =~ s:ft_pascal_comments || line =~? s:ft_pascal_keywords - setf pascal - return - elseif line !~ '^\s*$' || line =~ '^/\*' - " Not an empty line: Doesn't look like valid Pascal code. - " Or it looks like a Progress /* comment - break - endif - let lnum = lnum + 1 - endw - setf progress -endfunc - -func dist#ft#FTpp() - if exists("g:filetype_pp") - exe "setf " . g:filetype_pp - else - let line = getline(nextnonblank(1)) - if line =~ s:ft_pascal_comments || line =~? s:ft_pascal_keywords - setf pascal - else - setf puppet - endif - 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("$") - - for n in range(1, max) - " Rebol is easy to recognize, check for that first - if getline(n) =~? '\<REBOL\>' - setf rebol - return - endif - endfor - - for n in range(1, max) - " R has # comments - if getline(n) =~ '^\s*#' - setf r - return - endif - " Rexx has /* comments */ - if getline(n) =~ '^\s*/\*' - setf rexx - return - endif - endfor - - " Nothing recognized, use user default or assume Rexx - if exists("g:filetype_r") - exe "setf " . g:filetype_r - else - " Rexx used to be the default, but R appears to be much more popular. - setf r - endif -endfunc - -func dist#ft#McSetf() - " Rely on the file to start with a comment. - " MS message text files use ';', Sendmail files use '#' or 'dnl' - for lnum in range(1, min([line("$"), 20])) - let line = getline(lnum) - if line =~ '^\s*\(#\|dnl\)' - setf m4 " Sendmail .mc file - return - elseif line =~ '^\s*;' - setf msmessages " MS Message text file - return - endif - endfor - setf m4 " Default: Sendmail .mc file -endfunc - -" Called from filetype.vim and scripts.vim. -func dist#ft#SetFileTypeSH(name) - if did_filetype() - " Filetype was already detected - return - endif - if expand("<amatch>") =~ g:ft_ignore_pat - return - endif - if a:name =~ '\<csh\>' - " Some .sh scripts contain #!/bin/csh. - call dist#ft#SetFileTypeShell("csh") - return - elseif a:name =~ '\<tcsh\>' - " Some .sh scripts contain #!/bin/tcsh. - call dist#ft#SetFileTypeShell("tcsh") - return - elseif a:name =~ '\<zsh\>' - " Some .sh scripts contain #!/bin/zsh. - call dist#ft#SetFileTypeShell("zsh") - return - elseif a:name =~ '\<ksh\>' - let b:is_kornshell = 1 - if exists("b:is_bash") - unlet b:is_bash - endif - if exists("b:is_sh") - unlet b:is_sh - endif - elseif exists("g:bash_is_sh") || a:name =~ '\<bash\>' || a:name =~ '\<bash2\>' - let b:is_bash = 1 - if exists("b:is_kornshell") - unlet b:is_kornshell - endif - if exists("b:is_sh") - unlet b:is_sh - endif - elseif a:name =~ '\<sh\>' - let b:is_sh = 1 - if exists("b:is_kornshell") - unlet b:is_kornshell - endif - if exists("b:is_bash") - unlet b:is_bash - endif - endif - call dist#ft#SetFileTypeShell("sh") -endfunc - -" For shell-like file types, check for an "exec" command hidden in a comment, -" as used for Tcl. -" Also called from scripts.vim, thus can't be local to this script. -func dist#ft#SetFileTypeShell(name) - if did_filetype() - " Filetype was already detected - return - endif - if expand("<amatch>") =~ g:ft_ignore_pat - return - endif - let l = 2 - while l < 20 && l < line("$") && getline(l) =~ '^\s*\(#\|$\)' - " Skip empty and comment lines. - let l = l + 1 - endwhile - if l < line("$") && getline(l) =~ '\s*exec\s' && getline(l - 1) =~ '^\s*#.*\\$' - " Found an "exec" line after a comment with continuation - let n = substitute(getline(l),'\s*exec\s\+\([^ ]*/\)\=', '', '') - if n =~ '\<tclsh\|\<wish' - setf tcl - return - endif - endif - exe "setf " . a:name -endfunc - -func dist#ft#CSH() - if did_filetype() - " Filetype was already detected - return - endif - if exists("g:filetype_csh") - call dist#ft#SetFileTypeShell(g:filetype_csh) - elseif &shell =~ "tcsh" - call dist#ft#SetFileTypeShell("tcsh") - else - call dist#ft#SetFileTypeShell("csh") - endif -endfunc - -let s:ft_rules_udev_rules_pattern = '^\s*\cudev_rules\s*=\s*"\([^"]\{-1,}\)/*".*' -func dist#ft#FTRules() - let path = expand('<amatch>:p') - if path =~ '/\(etc/udev/\%(rules\.d/\)\=.*\.rules\|\%(usr/\)\=lib/udev/\%(rules\.d/\)\=.*\.rules\)$' - setf udevrules - return - endif - if path =~ '^/etc/ufw/' - setf conf " Better than hog - return - endif - if path =~ '^/\(etc\|usr/share\)/polkit-1/rules\.d' - setf javascript - return - endif - try - let config_lines = readfile('/etc/udev/udev.conf') - catch /^Vim\%((\a\+)\)\=:E484/ - setf hog - return - endtry - let dir = expand('<amatch>:p:h') - for line in config_lines - if line =~ s:ft_rules_udev_rules_pattern - let udev_rules = substitute(line, s:ft_rules_udev_rules_pattern, '\1', "") - if dir == udev_rules - setf udevrules - endif - break - endif - endfor - setf hog -endfunc - -func dist#ft#SQL() - if exists("g:filetype_sql") - exe "setf " . g:filetype_sql - else - setf 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 -" file. -" (Slow test) If a file contains a 'use' statement then it is almost certainly -" a Perl file. -func dist#ft#FTperl() - let dirname = expand("%:p:h:t") - if expand("%:e") == 't' && (dirname == 't' || dirname == 'xt') - setf perl - return 1 - endif - if getline(1)[0] == '#' && getline(1) =~ 'perl' - setf perl - return 1 - endif - let save_cursor = getpos('.') - call cursor(1,1) - let has_use = search('^use\s\s*\k', 'c', 30) > 0 - call setpos('.', save_cursor) - if has_use - setf perl - return 1 - endif - return 0 -endfunc - -" LambdaProlog and Standard ML signature files -func dist#ft#FTsig() - if exists("g:filetype_sig") - exe "setf " .. g:filetype_sig - return - endif - - let lprolog_comment = '^\s*\%(/\*\|%\)' - let lprolog_keyword = '^\s*sig\s\+\a' - let sml_comment = '^\s*(\*' - let sml_keyword = '^\s*\%(signature\|structure\)\s\+\a' - - let line = getline(nextnonblank(1)) - - if line =~ lprolog_comment || line =~# lprolog_keyword - setf lprolog - elseif line =~ sml_comment || line =~# sml_keyword - setf sml - endif -endfunc - -" This function checks the first 100 lines of files matching "*.sil" to -" resolve detection between Swift Intermediate Language and SILE. -func dist#ft#FTsil() - for lnum in range(1, [line('$'), 100]->min()) - let line = getline(lnum) - if line =~ '^\s*[\\%]' - setf sile - return - elseif line =~ '^\s*\S' - setf sil - return - endif - endfor - " no clue, default to "sil" - setf sil -endfunc - -func dist#ft#FTsys() - if exists("g:filetype_sys") - exe "setf " .. g:filetype_sys - elseif 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. -" 3. Default to "plain" or to g:tex_flavor, can be set in user's vimrc. -func dist#ft#FTtex() - let firstline = getline(1) - if firstline =~ '^%&\s*\a\+' - let format = tolower(matchstr(firstline, '\a\+')) - let format = substitute(format, 'pdf', '', '') - if format == 'tex' - let format = 'latex' - elseif format == 'plaintex' - let format = 'plain' - endif - elseif expand('%') =~ 'tex/context/.*/.*.tex' - let format = 'context' - else - " Default value, may be changed later: - let format = exists("g:tex_flavor") ? g:tex_flavor : 'plain' - " Save position, go to the top of the file, find first non-comment line. - let save_cursor = getpos('.') - call cursor(1,1) - let firstNC = search('^\s*[^[:space:]%]', 'c', 1000) - 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 . '\)', - \ 'cnp', firstNC + 1000) - if kwline == 1 " lpat matched - let format = 'latex' - elseif kwline == 2 " cpat matched - let format = 'context' - endif " If neither matched, keep default set above. - " let lline = search('^\s*\\\%(' . lpat . '\)', 'cn', firstNC + 1000) - " let cline = search('^\s*\\\%(' . cpat . '\)', 'cn', firstNC + 1000) - " if cline > 0 - " let format = 'context' - " endif - " if lline > 0 && (cline == 0 || cline > lline) - " let format = 'tex' - " endif - endif " firstNC - call setpos('.', save_cursor) - endif " firstline =~ '^%&\s*\a\+' - - " Translation from formats to file types. TODO: add AMSTeX, RevTex, others? - if format == 'plain' - setf plaintex - elseif format == 'context' - setf context - else " probably LaTeX - setf tex - endif - return -endfunc - -func dist#ft#FTxml() - let n = 1 - while n < 100 && n <= line("$") - let line = getline(n) - " DocBook 4 or DocBook 5. - let is_docbook4 = line =~ '<!DOCTYPE.*DocBook' - let is_docbook5 = line =~ ' xmlns="http://docbook.org/ns/docbook"' - if is_docbook4 || is_docbook5 - let b:docbk_type = "xml" - if is_docbook5 - let b:docbk_ver = 5 - else - let b:docbk_ver = 4 - endif - setf docbk - return - endif - if line =~ 'xmlns:xbl="http://www.mozilla.org/xbl"' - setf xbl - return - endif - let n += 1 - endwhile - setf xml -endfunc - -func dist#ft#FTy() - let n = 1 - while n < 100 && n <= line("$") - let line = getline(n) - if line =~ '^\s*%' - setf yacc - return - endif - if getline(n) =~ '^\s*\(#\|class\>\)' && getline(n) !~ '^\s*#\s*include' - setf racc - return - endif - let n = n + 1 - endwhile - setf yacc -endfunc - -func dist#ft#Redif() - let lnum = 1 - while lnum <= 5 && lnum < line('$') - if getline(lnum) =~ "^\ctemplate-type:" - setf redif - return - endif - let lnum = lnum + 1 - 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 - -let s:ft_krl_header = '\&\w+' -" Determine if a *.src file is Kuka Robot Language -func dist#ft#FTsrc() - let ft_krl_def_or_deffct = '%(global\s+)?def%(fct)?>' - if exists("g:filetype_src") - exe "setf " .. g:filetype_src - elseif getline(nextnonblank(1)) =~? '\v^\s*%(' .. s:ft_krl_header .. '|' .. ft_krl_def_or_deffct .. ')' - setf krl - endif -endfunc - -" Determine if a *.dat file is Kuka Robot Language -func dist#ft#FTdat() - let ft_krl_defdat = 'defdat>' - if exists("g:filetype_dat") - exe "setf " .. g:filetype_dat - elseif getline(nextnonblank(1)) =~? '\v^\s*%(' .. s:ft_krl_header .. '|' .. ft_krl_defdat .. ')' - setf krl - endif -endfunc - -" Restore 'cpoptions' -let &cpo = s:cpo_save -unlet s:cpo_save diff --git a/runtime/autoload/health.vim b/runtime/autoload/health.vim index a693868381..5fd4627b11 100644 --- a/runtime/autoload/health.vim +++ b/runtime/autoload/health.vim @@ -5,8 +5,13 @@ function! health#check(plugin_names) abort \ ? s:discover_healthchecks() \ : s:get_healthcheck(a:plugin_names) - " create scratch-buffer - execute 'tab sbuffer' nvim_create_buf(v:true, v:true) + " Create buffer and open in a tab, unless this is the default buffer when Nvim starts. + let emptybuf = (bufnr('$') == 1 && empty(getline(1)) && 1 == line('$')) + execute (emptybuf ? 'buffer' : 'tab sbuffer') nvim_create_buf(v:true, v:true) + if bufexists('health://') + bwipe health:// + endif + file health:// setfiletype checkhealth if empty(healthchecks) @@ -38,7 +43,7 @@ function! health#check(plugin_names) abort \ name, v:throwpoint, v:exception)) endif endtry - let header = [name. ': ' . func, repeat('=', 72)] + let header = [repeat('=', 78), name .. ': ' .. func, ''] " remove empty line after header from report_start let s:output = s:output[0] == '' ? s:output[1:] : s:output let s:output = header + s:output + [''] @@ -47,8 +52,7 @@ function! health#check(plugin_names) abort endfor endif - " needed for plasticboy/vim-markdown, because it uses fdm=expr - normal! zR + " Clear the 'Running healthchecks...' message. redraw|echo '' endfunction @@ -58,7 +62,7 @@ endfunction " Starts a new report. function! health#report_start(name) abort - call s:collect_output("\n## " . a:name) + call s:collect_output(printf("\n%s ~", a:name)) endfunction " Indents lines *except* line 1 of a string if it contains newlines. @@ -81,7 +85,7 @@ endfunction " Format a message for a specific report item. " a:1: Optional advice (string or list) function! s:format_report_message(status, msg, ...) abort " {{{ - let output = ' - ' . a:status . ': ' . s:indent_after_line1(a:msg, 4) + let output = '- ' .. a:status .. (empty(a:status) ? '' : ' ') .. s:indent_after_line1(a:msg, 2) " Optional parameters if a:0 > 0 @@ -92,9 +96,9 @@ function! s:format_report_message(status, msg, ...) abort " {{{ " Report each suggestion if !empty(advice) - let output .= "\n - ADVICE:" + let output .= "\n - ADVICE:" for suggestion in advice - let output .= "\n - " . s:indent_after_line1(suggestion, 10) + let output .= "\n - " . s:indent_after_line1(suggestion, 6) endfor endif endif @@ -102,9 +106,9 @@ function! s:format_report_message(status, msg, ...) abort " {{{ return s:help_to_link(output) endfunction " }}} -" Use {msg} to report information in the current section +" Reports a message as a listitem in the current section. function! health#report_info(msg) abort " {{{ - call s:collect_output(s:format_report_message('INFO', a:msg)) + call s:collect_output(s:format_report_message('', a:msg)) endfunction " }}} " Reports a successful healthcheck. diff --git a/runtime/autoload/health/nvim.vim b/runtime/autoload/health/nvim.vim deleted file mode 100644 index 9b387095ee..0000000000 --- a/runtime/autoload/health/nvim.vim +++ /dev/null @@ -1,284 +0,0 @@ -let s:suggest_faq = 'https://github.com/neovim/neovim/wiki/FAQ' - -function! s:check_config() abort - let ok = v:true - call health#report_start('Configuration') - - let vimrc = empty($MYVIMRC) ? stdpath('config').'/init.vim' : $MYVIMRC - if !filereadable(vimrc) - let ok = v:false - let has_vim = filereadable(expand('~/.vimrc')) - call health#report_warn((-1 == getfsize(vimrc) ? 'Missing' : 'Unreadable').' user config file: '.vimrc, - \[ has_vim ? ':help nvim-from-vim' : ':help init.vim' ]) - endif - - " If $VIM is empty we don't care. Else make sure it is valid. - if !empty($VIM) && !filereadable($VIM.'/runtime/doc/nvim.txt') - let ok = v:false - call health#report_error('$VIM is invalid: '.$VIM) - endif - - if exists('$NVIM_TUI_ENABLE_CURSOR_SHAPE') - let ok = v:false - call health#report_warn('$NVIM_TUI_ENABLE_CURSOR_SHAPE is ignored in Nvim 0.2+', - \ [ "Use the 'guicursor' option to configure cursor shape. :help 'guicursor'", - \ 'https://github.com/neovim/neovim/wiki/Following-HEAD#20170402' ]) - endif - - if v:ctype ==# 'C' - let ok = v:false - call health#report_error('Locale does not support UTF-8. Unicode characters may not display correctly.' - \ .printf("\n$LANG=%s $LC_ALL=%s $LC_CTYPE=%s", $LANG, $LC_ALL, $LC_CTYPE), - \ [ 'If using tmux, try the -u option.', - \ 'Ensure that your terminal/shell/tmux/etc inherits the environment, or set $LANG explicitly.' , - \ 'Configure your system locale.' ]) - endif - - if &paste - let ok = v:false - call health#report_error("'paste' is enabled. This option is only for pasting text.\nIt should not be set in your config.", - \ [ 'Remove `set paste` from your init.vim, if applicable.', - \ 'Check `:verbose set paste?` to see if a plugin or script set the option.', ]) - endif - - let writeable = v:true - let shadafile = empty(&shada) ? &shada : substitute(matchstr( - \ split(&shada, ',')[-1], '^n.\+'), '^n', '', '') - let shadafile = empty(&shadafile) ? empty(shadafile) ? - \ stdpath('state').'/shada/main.shada' : expand(shadafile) - \ : &shadafile ==# 'NONE' ? '' : &shadafile - if !empty(shadafile) && empty(glob(shadafile)) - " Since this may be the first time neovim has been run, we will try to - " create a shada file - try - wshada - catch /.*/ - let writeable = v:false - endtry - endif - if !writeable || (!empty(shadafile) && - \ (!filereadable(shadafile) || !filewritable(shadafile))) - let ok = v:false - call health#report_error('shada file is not '. - \ ((!writeable || filereadable(shadafile)) ? - \ 'writeable' : 'readable').":\n".shadafile) - endif - - if ok - call health#report_ok('no issues found') - endif -endfunction - -" Load the remote plugin manifest file and check for unregistered plugins -function! s:check_rplugin_manifest() abort - call health#report_start('Remote Plugins') - let existing_rplugins = {} - - for item in remote#host#PluginsForHost('python') - let existing_rplugins[item.path] = 'python' - endfor - - for item in remote#host#PluginsForHost('python3') - let existing_rplugins[item.path] = 'python3' - endfor - - let require_update = 0 - - for path in map(split(&runtimepath, ','), 'resolve(v:val)') - let python_glob = glob(path.'/rplugin/python*', 1, 1) - if empty(python_glob) - continue - endif - - let python_dir = python_glob[0] - let python_version = fnamemodify(python_dir, ':t') - - for script in glob(python_dir.'/*.py', 1, 1) - \ + glob(python_dir.'/*/__init__.py', 1, 1) - let contents = join(readfile(script)) - if contents =~# '\<\%(from\|import\)\s\+neovim\>' - if script =~# '[\/]__init__\.py$' - let script = tr(fnamemodify(script, ':h'), '\', '/') - endif - - if !has_key(existing_rplugins, script) - let msg = printf('"%s" is not registered.', fnamemodify(path, ':t')) - if python_version ==# 'pythonx' - if !has('python3') - let msg .= ' (python3 not available)' - endif - elseif !has(python_version) - let msg .= printf(' (%s not available)', python_version) - else - let require_update = 1 - endif - - call health#report_warn(msg) - endif - - break - endif - endfor - endfor - - if require_update - call health#report_warn('Out of date', ['Run `:UpdateRemotePlugins`']) - else - call health#report_ok('Up to date') - endif -endfunction - -function! s:check_performance() abort - call health#report_start('Performance') - - " check buildtype - let buildtype = matchstr(execute('version'), '\v\cbuild type:?\s*[^\n\r\t ]+') - if empty(buildtype) - call health#report_error('failed to get build type from :version') - elseif buildtype =~# '\v(MinSizeRel|Release|RelWithDebInfo)' - call health#report_ok(buildtype) - else - call health#report_info(buildtype) - call health#report_warn( - \ 'Non-optimized '.(has('debug')?'(DEBUG) ':'').'build. Nvim will be slower.', - \ ['Install a different Nvim package, or rebuild with `CMAKE_BUILD_TYPE=RelWithDebInfo`.', - \ s:suggest_faq]) - endif - - " check for slow shell invocation - let slow_cmd_time = 1.5 - let start_time = reltime() - call system('echo') - let elapsed_time = reltimefloat(reltime(start_time)) - if elapsed_time > slow_cmd_time - call health#report_warn( - \ 'Slow shell invocation (took '.printf('%.2f', elapsed_time).' seconds).') - endif -endfunction - -function! s:get_tmux_option(option) abort - let cmd = 'tmux show-option -qvg '.a:option " try global scope - let out = system(split(cmd)) - let val = substitute(out, '\v(\s|\r|\n)', '', 'g') - if v:shell_error - call health#report_error('command failed: '.cmd."\n".out) - return 'error' - elseif empty(val) - let cmd = 'tmux show-option -qvgs '.a:option " try session scope - let out = system(split(cmd)) - let val = substitute(out, '\v(\s|\r|\n)', '', 'g') - if v:shell_error - call health#report_error('command failed: '.cmd."\n".out) - return 'error' - endif - endif - return val -endfunction - -function! s:check_tmux() abort - if empty($TMUX) || !executable('tmux') - return - endif - call health#report_start('tmux') - - " check escape-time - let suggestions = ["set escape-time in ~/.tmux.conf:\nset-option -sg escape-time 10", - \ s:suggest_faq] - let tmux_esc_time = s:get_tmux_option('escape-time') - if tmux_esc_time !=# 'error' - if empty(tmux_esc_time) - call health#report_error('`escape-time` is not set', suggestions) - elseif tmux_esc_time > 300 - call health#report_error( - \ '`escape-time` ('.tmux_esc_time.') is higher than 300ms', suggestions) - else - call health#report_ok('escape-time: '.tmux_esc_time) - endif - endif - - " check focus-events - let suggestions = ["(tmux 1.9+ only) Set `focus-events` in ~/.tmux.conf:\nset-option -g focus-events on"] - let tmux_focus_events = s:get_tmux_option('focus-events') - call health#report_info('Checking stuff') - if tmux_focus_events !=# 'error' - if empty(tmux_focus_events) || tmux_focus_events !=# 'on' - call health#report_warn( - \ "`focus-events` is not enabled. |'autoread'| may not work.", suggestions) - else - call health#report_ok('focus-events: '.tmux_focus_events) - endif - endif - - " check default-terminal and $TERM - call health#report_info('$TERM: '.$TERM) - let cmd = 'tmux show-option -qvg default-terminal' - let out = system(split(cmd)) - let tmux_default_term = substitute(out, '\v(\s|\r|\n)', '', 'g') - if empty(tmux_default_term) - let cmd = 'tmux show-option -qvgs default-terminal' - let out = system(split(cmd)) - let tmux_default_term = substitute(out, '\v(\s|\r|\n)', '', 'g') - endif - - if v:shell_error - call health#report_error('command failed: '.cmd."\n".out) - elseif tmux_default_term !=# $TERM - call health#report_info('default-terminal: '.tmux_default_term) - call health#report_error( - \ '$TERM differs from the tmux `default-terminal` setting. Colors might look wrong.', - \ ['$TERM may have been set by some rc (.bashrc, .zshrc, ...).']) - elseif $TERM !~# '\v(tmux-256color|screen-256color)' - call health#report_error( - \ '$TERM should be "screen-256color" or "tmux-256color" in tmux. Colors might look wrong.', - \ ["Set default-terminal in ~/.tmux.conf:\nset-option -g default-terminal \"screen-256color\"", - \ s:suggest_faq]) - endif - - " check for RGB capabilities - let info = system(['tmux', 'server-info']) - let has_tc = stridx(info, " Tc: (flag) true") != -1 - let has_rgb = stridx(info, " RGB: (flag) true") != -1 - if !has_tc && !has_rgb - call health#report_warn( - \ "Neither Tc nor RGB capability set. True colors are disabled. |'termguicolors'| won't work properly.", - \ ["Put this in your ~/.tmux.conf and replace XXX by your $TERM outside of tmux:\nset-option -sa terminal-overrides ',XXX:RGB'", - \ "For older tmux versions use this instead:\nset-option -ga terminal-overrides ',XXX:Tc'"]) - endif -endfunction - -function! s:check_terminal() abort - if !executable('infocmp') - return - endif - call health#report_start('terminal') - let cmd = 'infocmp -L' - let out = system(split(cmd)) - let kbs_entry = matchstr(out, 'key_backspace=[^,[:space:]]*') - let kdch1_entry = matchstr(out, 'key_dc=[^,[:space:]]*') - - if v:shell_error - \ && (!has('win32') - \ || empty(matchstr(out, - \ 'infocmp: couldn''t open terminfo file .\+' - \ ..'\%(conemu\|vtpcon\|win32con\)'))) - call health#report_error('command failed: '.cmd."\n".out) - else - call health#report_info('key_backspace (kbs) terminfo entry: ' - \ .(empty(kbs_entry) ? '? (not found)' : kbs_entry)) - call health#report_info('key_dc (kdch1) terminfo entry: ' - \ .(empty(kbs_entry) ? '? (not found)' : kdch1_entry)) - endif - for env_var in ['XTERM_VERSION', 'VTE_VERSION', 'TERM_PROGRAM', 'COLORTERM', 'SSH_TTY'] - if exists('$'.env_var) - call health#report_info(printf("$%s='%s'", env_var, eval('$'.env_var))) - endif - endfor -endfunction - -function! health#nvim#check() abort - call s:check_config() - call s:check_performance() - call s:check_rplugin_manifest() - call s:check_terminal() - call s:check_tmux() -endfunction diff --git a/runtime/autoload/netrw.vim b/runtime/autoload/netrw.vim index ef0282848f..2fcf0b32c7 100644 --- a/runtime/autoload/netrw.vim +++ b/runtime/autoload/netrw.vim @@ -1751,8 +1751,10 @@ fun! s:NetrwOptionsRestore(vt) " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." a:vt=".a:vt,'~'.expand("<slnum>")) if !exists("{a:vt}netrw_optionsave") " call Decho("case ".a:vt."netrw_optionsave : doesn't exist",'~'.expand("<slnum>")) -" call Decho("..doing filetype detect anyway") - filetype detect + if !isdirectory(expand('%')) +" call Decho("..doing filetype detect anyway") + filetype detect + endif " call Decho("..settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." a:vt=".a:vt,'~'.expand("<slnum>")) " call Decho("..ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("<slnum>")) " call Dret("s:NetrwOptionsRestore : ".a:vt."netrw_optionsave doesn't exist") @@ -1859,9 +1861,11 @@ fun! s:NetrwOptionsRestore(vt) " were having their filetype detect-generated settings overwritten by " NetrwOptionRestore. if &ft != "netrw" -" call Decho("before: filetype detect (ft=".&ft.")",'~'.expand("<slnum>")) - filetype detect -" call Decho("after : filetype detect (ft=".&ft.")",'~'.expand("<slnum>")) + if !isdirectory(expand('%')) +" call Decho("before: filetype detect (ft=".&ft.")",'~'.expand("<slnum>")) + filetype detect +" call Decho("after : filetype detect (ft=".&ft.")",'~'.expand("<slnum>")) + endif endif " call Decho("(s:NetrwOptionsRestore) lines=".&lines) " call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." a:vt=".a:vt,'~'.expand("<slnum>")) @@ -6442,7 +6446,6 @@ fun! s:NetrwMaps(islocal) " if !hasmapto('<Plug>NetrwMarkFileGrep') |nmap <buffer> <silent> <nowait> mg <Plug>NetrwMarkFileGrep|endif " if !hasmapto('<Plug>NetrwMarkHideSfx') |nmap <buffer> <silent> <nowait> mh <Plug>NetrwMarkHideSfx|endif " if !hasmapto('<Plug>NetrwMarkFileMove') |nmap <buffer> <silent> <nowait> mm <Plug>NetrwMarkFileMove|endif -" if !hasmapto('<Plug>NetrwMarkFilePrint') |nmap <buffer> <silent> <nowait> mp <Plug>NetrwMarkFilePrint|endif " if !hasmapto('<Plug>NetrwMarkFileRegexp') |nmap <buffer> <silent> <nowait> mr <Plug>NetrwMarkFileRegexp|endif " if !hasmapto('<Plug>NetrwMarkFileSource') |nmap <buffer> <silent> <nowait> ms <Plug>NetrwMarkFileSource|endif " if !hasmapto('<Plug>NetrwMarkFileTag') |nmap <buffer> <silent> <nowait> mT <Plug>NetrwMarkFileTag|endif @@ -6505,7 +6508,6 @@ fun! s:NetrwMaps(islocal) nnoremap <buffer> <silent> <nowait> mg :<c-u>call <SID>NetrwMarkFileGrep(1)<cr> nnoremap <buffer> <silent> <nowait> mh :<c-u>call <SID>NetrwMarkHideSfx(1)<cr> nnoremap <buffer> <silent> <nowait> mm :<c-u>call <SID>NetrwMarkFileMove(1)<cr> - nnoremap <buffer> <silent> <nowait> mp :<c-u>call <SID>NetrwMarkFilePrint(1)<cr> nnoremap <buffer> <silent> <nowait> mr :<c-u>call <SID>NetrwMarkFileRegexp(1)<cr> nnoremap <buffer> <silent> <nowait> ms :<c-u>call <SID>NetrwMarkFileSource(1)<cr> nnoremap <buffer> <silent> <nowait> mT :<c-u>call <SID>NetrwMarkFileTag(1)<cr> @@ -6618,7 +6620,6 @@ fun! s:NetrwMaps(islocal) nnoremap <buffer> <silent> <nowait> mg :<c-u>call <SID>NetrwMarkFileGrep(0)<cr> nnoremap <buffer> <silent> <nowait> mh :<c-u>call <SID>NetrwMarkHideSfx(0)<cr> nnoremap <buffer> <silent> <nowait> mm :<c-u>call <SID>NetrwMarkFileMove(0)<cr> - nnoremap <buffer> <silent> <nowait> mp :<c-u>call <SID>NetrwMarkFilePrint(0)<cr> nnoremap <buffer> <silent> <nowait> mr :<c-u>call <SID>NetrwMarkFileRegexp(0)<cr> nnoremap <buffer> <silent> <nowait> ms :<c-u>call <SID>NetrwMarkFileSource(0)<cr> nnoremap <buffer> <silent> <nowait> mT :<c-u>call <SID>NetrwMarkFileTag(0)<cr> @@ -7836,46 +7837,6 @@ fun! s:NetrwMarkFileMove(islocal) endfun " --------------------------------------------------------------------- -" s:NetrwMarkFilePrint: (invoked by mp) This function prints marked files {{{2 -" using the hardcopy command. Local marked-file list only. -fun! s:NetrwMarkFilePrint(islocal) -" call Dfunc("s:NetrwMarkFilePrint(islocal=".a:islocal.")") - let curbufnr= bufnr("%") - - " sanity check - if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr}) - NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66) -" call Dret("s:NetrwMarkFilePrint") - return - endif -" call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}),'~'.expand("<slnum>")) - let curdir= s:NetrwGetCurdir(a:islocal) - - if exists("s:netrwmarkfilelist_{curbufnr}") - let netrwmarkfilelist = s:netrwmarkfilelist_{curbufnr} - call s:NetrwUnmarkList(curbufnr,curdir) - for fname in netrwmarkfilelist - if a:islocal - if g:netrw_keepdir - let fname= s:ComposePath(curdir,fname) - endif - else - let fname= curdir.fname - endif - 1split - " the autocmds will handle both local and remote files -" call Decho("exe sil e ".escape(fname,' '),'~'.expand("<slnum>")) - exe "sil NetrwKeepj e ".fnameescape(fname) -" call Decho("hardcopy",'~'.expand("<slnum>")) - hardcopy - q - endfor - 2match none - endif -" call Dret("s:NetrwMarkFilePrint") -endfun - -" --------------------------------------------------------------------- " s:NetrwMarkFileRegexp: (invoked by mr) This function is used to mark {{{2 " files when given a regexp (for which a prompt is " issued) (matches to name of files). diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim index 991bed6bbd..98c80f1843 100644 --- a/runtime/autoload/provider/clipboard.vim +++ b/runtime/autoload/provider/clipboard.vim @@ -97,18 +97,24 @@ function! provider#clipboard#Executable() abort let s:copy['*'] = ['wl-copy', '--foreground', '--primary', '--type', 'text/plain'] let s:paste['*'] = ['wl-paste', '--no-newline', '--primary'] return 'wl-copy' - elseif !empty($DISPLAY) && executable('xclip') - let s:copy['+'] = ['xclip', '-quiet', '-i', '-selection', 'clipboard'] - let s:paste['+'] = ['xclip', '-o', '-selection', 'clipboard'] - let s:copy['*'] = ['xclip', '-quiet', '-i', '-selection', 'primary'] - let s:paste['*'] = ['xclip', '-o', '-selection', 'primary'] - return 'xclip' + elseif !empty($WAYLAND_DISPLAY) && executable('waycopy') && executable('waypaste') + let s:copy['+'] = ['waycopy', '-t', 'text/plain'] + let s:paste['+'] = ['waypaste', '-t', 'text/plain'] + let s:copy['*'] = s:copy['+'] + let s:paste['*'] = s:paste['+'] + return 'wayclip' elseif !empty($DISPLAY) && executable('xsel') && s:cmd_ok('xsel -o -b') let s:copy['+'] = ['xsel', '--nodetach', '-i', '-b'] let s:paste['+'] = ['xsel', '-o', '-b'] let s:copy['*'] = ['xsel', '--nodetach', '-i', '-p'] let s:paste['*'] = ['xsel', '-o', '-p'] return 'xsel' + elseif !empty($DISPLAY) && executable('xclip') + let s:copy['+'] = ['xclip', '-quiet', '-i', '-selection', 'clipboard'] + let s:paste['+'] = ['xclip', '-o', '-selection', 'clipboard'] + let s:copy['*'] = ['xclip', '-quiet', '-i', '-selection', 'primary'] + let s:paste['*'] = ['xclip', '-o', '-selection', 'primary'] + return 'xclip' elseif executable('lemonade') let s:copy['+'] = ['lemonade', 'copy'] let s:paste['+'] = ['lemonade', 'paste'] @@ -139,7 +145,12 @@ function! provider#clipboard#Executable() abort let s:paste['*'] = s:paste['+'] return 'termux-clipboard' elseif !empty($TMUX) && executable('tmux') - let s:copy['+'] = ['tmux', 'load-buffer', '-'] + let ver = matchlist(systemlist(['tmux', '-V'])[0], '\vtmux %(next-)?(\d+)\.(\d+)') + if len(ver) >= 3 && (ver[1] > 3 || (ver[1] == 3 && ver[2] >= 2)) + let s:copy['+'] = ['tmux', 'load-buffer', '-w', '-'] + else + let s:copy['+'] = ['tmux', 'load-buffer', '-'] + endif let s:paste['+'] = ['tmux', 'save-buffer', '-'] let s:copy['*'] = s:copy['+'] let s:paste['*'] = s:paste['+'] diff --git a/runtime/autoload/provider/node.vim b/runtime/autoload/provider/node.vim index 45b1dd4fd7..87af0094fe 100644 --- a/runtime/autoload/provider/node.vim +++ b/runtime/autoload/provider/node.vim @@ -71,13 +71,11 @@ function! provider#node#Detect() abort let yarn_opts = deepcopy(s:NodeHandler) let yarn_opts.entry_point = '/node_modules/neovim/bin/cli.js' " `yarn global dir` is slow (> 250ms), try the default path first - " XXX: The following code is not portable " https://github.com/yarnpkg/yarn/issues/2049#issuecomment-263183768 - if has('unix') - let yarn_default_path = $HOME . '/.config/yarn/global/' . yarn_opts.entry_point - if filereadable(yarn_default_path) - return [yarn_default_path, ''] - endif + let yarn_config_dir = has('win32') ? '/AppData/Local/Yarn/Data' : '/.config/yarn' + let yarn_default_path = $HOME . yarn_config_dir . '/global/' . yarn_opts.entry_point + if filereadable(yarn_default_path) + return [yarn_default_path, ''] endif let yarn_opts.job_id = jobstart('yarn global dir', yarn_opts) endif diff --git a/runtime/autoload/tohtml.vim b/runtime/autoload/tohtml.vim index 66f1cb46cb..4ae17815ba 100644 --- a/runtime/autoload/tohtml.vim +++ b/runtime/autoload/tohtml.vim @@ -712,6 +712,9 @@ func! tohtml#GetUserSettings() "{{{ call tohtml#GetOption(user_settings, 'no_foldcolumn', user_settings.ignore_folding) call tohtml#GetOption(user_settings, 'hover_unfold', 0 ) call tohtml#GetOption(user_settings, 'no_pre', 0 ) + call tohtml#GetOption(user_settings, 'no_doc', 0 ) + call tohtml#GetOption(user_settings, 'no_links', 0 ) + call tohtml#GetOption(user_settings, 'no_modeline', 0 ) call tohtml#GetOption(user_settings, 'no_invalid', 0 ) call tohtml#GetOption(user_settings, 'whole_filler', 0 ) call tohtml#GetOption(user_settings, 'use_xhtml', 0 ) @@ -752,7 +755,7 @@ func! tohtml#GetUserSettings() "{{{ " pre_wrap doesn't do anything if not using pre or not using CSS if user_settings.no_pre || !user_settings.use_css - let user_settings.pre_wrap=0 + let user_settings.pre_wrap = 0 endif "}}} diff --git a/runtime/autoload/tutor.vim b/runtime/autoload/tutor.vim index abf5c5e2c8..4da4213826 100644 --- a/runtime/autoload/tutor.vim +++ b/runtime/autoload/tutor.vim @@ -104,7 +104,7 @@ function! tutor#CheckLine(line) if exists('b:tutor_metadata') && has_key(b:tutor_metadata, 'expect') let bufn = bufnr('%') let ctext = getline(a:line) - let signs = sign_getplaced('.', {'lnum': a:line})[0].signs + let signs = sign_getplaced(bufn, {'lnum': a:line})[0].signs if !empty(signs) call sign_unplace('', {'id': signs[0].id}) endif diff --git a/runtime/autoload/zig/fmt.vim b/runtime/autoload/zig/fmt.vim new file mode 100644 index 0000000000..b78c1994dd --- /dev/null +++ b/runtime/autoload/zig/fmt.vim @@ -0,0 +1,100 @@ +" Adapted from fatih/vim-go: autoload/go/fmt.vim +" +" Copyright 2011 The Go Authors. All rights reserved. +" Use of this source code is governed by a BSD-style +" license that can be found in the LICENSE file. +" +" Upstream: https://github.com/ziglang/zig.vim + +function! zig#fmt#Format() abort + " Save cursor position and many other things. + let view = winsaveview() + + if !executable('zig') + echohl Error | echomsg "no zig binary found in PATH" | echohl None + return + endif + + let cmdline = 'zig fmt --stdin --ast-check' + let current_buf = bufnr('') + + " The formatted code is output on stdout, the errors go on stderr. + if exists('*systemlist') + silent let out = systemlist(cmdline, current_buf) + else + silent let out = split(system(cmdline, current_buf)) + endif + if len(out) == 1 + if out[0] == "error: unrecognized parameter: '--ast-check'" + let cmdline = 'zig fmt --stdin' + if exists('*systemlist') + silent let out = systemlist(cmdline, current_buf) + else + silent let out = split(system(cmdline, current_buf)) + endif + endif + endif + let err = v:shell_error + + + if err == 0 + " remove undo point caused via BufWritePre. + try | silent undojoin | catch | endtry + + " Replace the file content with the formatted version. + if exists('*deletebufline') + call deletebufline(current_buf, len(out), line('$')) + else + silent execute ':' . len(out) . ',' . line('$') . ' delete _' + endif + call setline(1, out) + + " No errors detected, close the loclist. + call setloclist(0, [], 'r') + lclose + elseif get(g:, 'zig_fmt_parse_errors', 1) + let errors = s:parse_errors(expand('%'), out) + + call setloclist(0, [], 'r', { + \ 'title': 'Errors', + \ 'items': errors, + \ }) + + let max_win_height = get(g:, 'zig_fmt_max_window_height', 5) + " Prevent the loclist from becoming too long. + let win_height = min([max_win_height, len(errors)]) + " Open the loclist, but only if there's at least one error to show. + execute 'silent! lwindow ' . win_height + endif + + call winrestview(view) + + if err != 0 + echohl Error | echomsg "zig fmt returned error" | echohl None + return + endif + + " Run the syntax highlighter on the updated content and recompute the folds if + " needed. + syntax sync fromstart +endfunction + +" parse_errors parses the given errors and returns a list of parsed errors +function! s:parse_errors(filename, lines) abort + " list of errors to be put into location list + let errors = [] + for line in a:lines + let tokens = matchlist(line, '^\(.\{-}\):\(\d\+\):\(\d\+\)\s*\(.*\)') + if !empty(tokens) + call add(errors,{ + \"filename": a:filename, + \"lnum": tokens[2], + \"col": tokens[3], + \"text": tokens[4], + \ }) + endif + endfor + + return errors +endfunction +" vim: sw=2 ts=2 et diff --git a/runtime/colors/blue.vim b/runtime/colors/blue.vim index 652046b561..aa99bacd3b 100644 --- a/runtime/colors/blue.vim +++ b/runtime/colors/blue.vim @@ -13,10 +13,14 @@ set background=dark hi clear let g:colors_name = 'blue' -let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +let s:t_Co = &t_Co if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#000000', '#cd0000', '#00cd00', '#cdcd00', '#0000ee', '#cd00cd', '#00cdcd', '#e5e5e5', '#7f7f7f', '#ff0000', '#00ff00', '#ffff00', '#5c5cff', '#ff00ff', '#00ffff', '#ffffff'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi Normal guifg=#ffd700 guibg=#000087 gui=NONE cterm=NONE hi CursorLine guifg=NONE guibg=#005faf gui=NONE cterm=NONE diff --git a/runtime/colors/darkblue.vim b/runtime/colors/darkblue.vim index 4ce8687415..c7bba4471e 100644 --- a/runtime/colors/darkblue.vim +++ b/runtime/colors/darkblue.vim @@ -13,10 +13,14 @@ set background=dark hi clear let g:colors_name = 'darkblue' -let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +let s:t_Co = &t_Co if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#000000', '#8b0000', '#90f020', '#ffa500', '#00008b', '#8b008b', '#008b8b', '#c0c0c0', '#808080', '#ffa0a0', '#90f020', '#ffff60', '#0030ff', '#ff00ff', '#90fff0', '#ffffff'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi! link Terminal Normal hi! link CursorColumn CursorLine diff --git a/runtime/colors/delek.vim b/runtime/colors/delek.vim index 38f21d7156..d9db90f2c5 100644 --- a/runtime/colors/delek.vim +++ b/runtime/colors/delek.vim @@ -13,10 +13,14 @@ set background=light hi clear let g:colors_name = 'delek' -let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +let s:t_Co = &t_Co if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#ffffff', '#0000ff', '#00cd00', '#cd00cd', '#008b8b', '#0000ff', '#ff1493', '#bcbcbc', '#ee0000', '#0000ff', '#00cd00', '#cd00cd', '#008b8b', '#0000ff', '#ff1493', '#000000'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi! link Terminal Normal hi! link LineNrAbove LineNr diff --git a/runtime/colors/desert.vim b/runtime/colors/desert.vim index 4bfdf7eabd..0b56740664 100644 --- a/runtime/colors/desert.vim +++ b/runtime/colors/desert.vim @@ -13,10 +13,14 @@ set background=dark hi clear let g:colors_name = 'desert' -let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +let s:t_Co = &t_Co if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#7f7f8c', '#cd5c5c', '#9acd32', '#bdb76b', '#75a0ff', '#eeee00', '#cd853f', '#666666', '#8a7f7f', '#ff0000', '#89fb98', '#f0e68c', '#6dceeb', '#ffde9b', '#ffa0a0', '#c2bfa5'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi! link Terminal Normal hi! link LineNrAbove LineNr diff --git a/runtime/colors/elflord.vim b/runtime/colors/elflord.vim index a9bdac7a1d..4a33e33eec 100644 --- a/runtime/colors/elflord.vim +++ b/runtime/colors/elflord.vim @@ -12,7 +12,7 @@ set background=dark hi clear let g:colors_name = 'elflord' -let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +let s:t_Co = &t_Co hi! link Terminal Normal hi! link Boolean Constant @@ -48,6 +48,10 @@ hi! link PopupNotification Todo if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#000000', '#cd0000', '#00cd00', '#cdcd00', '#0000ee', '#cd00cd', '#00cdcd', '#e5e5e5', '#7f7f7f', '#ff0000', '#00ff00', '#ffff00', '#5c5cff', '#ff00ff', '#00ffff', '#ffffff'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi Normal guifg=#00ffff guibg=#000000 gui=NONE cterm=NONE hi QuickFixLine guifg=#ffffff guibg=#2e8b57 gui=NONE cterm=NONE diff --git a/runtime/colors/evening.vim b/runtime/colors/evening.vim index 23ff8421e8..70ae55aa8d 100644 --- a/runtime/colors/evening.vim +++ b/runtime/colors/evening.vim @@ -13,10 +13,14 @@ set background=dark hi clear let g:colors_name = 'evening' -let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +let s:t_Co = &t_Co if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#000000', '#cd0000', '#00cd00', '#cdcd00', '#0087ff', '#cd00cd', '#00cdcd', '#e5e5e5', '#7f7f7f', '#ff0000', '#00ff00', '#ffff00', '#5c5cff', '#ff00ff', '#00ffff', '#ffffff'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi! link VertSplit StatusLineNC hi! link StatusLineTerm StatusLine diff --git a/runtime/colors/habamax.vim b/runtime/colors/habamax.vim index 049413beef..f6aa5609b1 100644 --- a/runtime/colors/habamax.vim +++ b/runtime/colors/habamax.vim @@ -13,10 +13,14 @@ set background=dark hi clear let g:colors_name = 'habamax' -let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +let s:t_Co = &t_Co if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#1c1c1c', '#d75f5f', '#87af87', '#afaf87', '#5f87af', '#af87af', '#5f8787', '#9e9e9e', '#767676', '#d7875f', '#afd7af', '#d7d787', '#87afd7', '#d7afd7', '#87afaf', '#bcbcbc'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi! link Terminal Normal hi! link StatuslineTerm Statusline diff --git a/runtime/colors/industry.vim b/runtime/colors/industry.vim index 3f6726038e..f09786000d 100644 --- a/runtime/colors/industry.vim +++ b/runtime/colors/industry.vim @@ -13,10 +13,14 @@ set background=dark hi clear let g:colors_name = 'industry' -let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +let s:t_Co = &t_Co if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#303030', '#870000', '#5fd75f', '#afaf00', '#87afff', '#af00af', '#00afaf', '#6c6c6c', '#444444', '#ff0000', '#00ff00', '#ffff00', '#005fff', '#ff00ff', '#00ffff', '#ffffff'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi Normal guifg=#dadada guibg=#000000 gui=NONE cterm=NONE hi EndOfBuffer guifg=#444444 guibg=#000000 gui=NONE cterm=NONE diff --git a/runtime/colors/koehler.vim b/runtime/colors/koehler.vim index f414eeb3e6..67719123a2 100644 --- a/runtime/colors/koehler.vim +++ b/runtime/colors/koehler.vim @@ -12,7 +12,7 @@ set background=dark hi clear let g:colors_name = 'koehler' -let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +let s:t_Co = &t_Co hi! link Terminal Normal hi! link Boolean Constant @@ -54,6 +54,10 @@ hi! link PopupNotification Todo if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#000000', '#cd0000', '#00cd00', '#cdcd00', '#0000ee', '#cd00cd', '#00cdcd', '#e5e5e5', '#7f7f7f', '#ff0000', '#00ff00', '#ffff00', '#5c5cff', '#ff00ff', '#00ffff', '#ffffff'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi Normal guifg=#ffffff guibg=#000000 gui=NONE cterm=NONE hi ColorColumn guifg=NONE guibg=#8b0000 gui=NONE cterm=NONE diff --git a/runtime/colors/lunaperche.vim b/runtime/colors/lunaperche.vim index 159e04cc0b..2954f622aa 100644 --- a/runtime/colors/lunaperche.vim +++ b/runtime/colors/lunaperche.vim @@ -11,7 +11,7 @@ hi clear let g:colors_name = 'lunaperche' -let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +let s:t_Co = &t_Co hi! link helpVim Title hi! link helpHeader Title @@ -119,6 +119,10 @@ hi! link PopupNotification Todo if &background ==# 'dark' if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#000000', '#af5f5f', '#5faf5f', '#af875f', '#5f87af', '#d787d7', '#5fafaf', '#c6c6c6', '#767676', '#ff5f5f', '#5fd75f', '#ffd787', '#5fafff', '#ff87ff', '#5fd7d7', '#ffffff'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi Normal guifg=#c6c6c6 guibg=#000000 gui=NONE cterm=NONE hi Statusline guifg=#c6c6c6 guibg=#000000 gui=bold,reverse cterm=bold,reverse @@ -205,6 +209,10 @@ else " Light background if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#000000', '#af0000', '#008700', '#af5f00', '#005fd7', '#af00af', '#005f5f', '#808080', '#767676', '#d70000', '#87d787', '#ffd787', '#0087d7', '#ff00ff', '#008787', '#ffffff'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi Normal guifg=#000000 guibg=#ffffff gui=NONE cterm=NONE hi Statusline guifg=#ffffff guibg=#000000 gui=bold cterm=bold diff --git a/runtime/colors/morning.vim b/runtime/colors/morning.vim index a7aec49808..5c6a617137 100644 --- a/runtime/colors/morning.vim +++ b/runtime/colors/morning.vim @@ -13,10 +13,14 @@ set background=light hi clear let g:colors_name = 'morning' -let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +let s:t_Co = &t_Co if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#e4e4e4', '#a52a2a', '#ff00ff', '#6a0dad', '#008787', '#2e8b57', '#6a5acd', '#bcbcbc', '#0000ff', '#a52a2a', '#ff00ff', '#6a0dad', '#008787', '#2e8b57', '#6a5acd', '#000000'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi! link Terminal Normal hi! link LineNrAbove LineNr diff --git a/runtime/colors/murphy.vim b/runtime/colors/murphy.vim index 60a6aed428..47d7dbe22e 100644 --- a/runtime/colors/murphy.vim +++ b/runtime/colors/murphy.vim @@ -13,10 +13,14 @@ set background=dark hi clear let g:colors_name = 'murphy' -let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +let s:t_Co = &t_Co if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#303030', '#ffa700', '#005f00', '#ffd7af', '#87afff', '#ffafaf', '#00afaf', '#bcbcbc', '#444444', '#ff0000', '#00875f', '#ffff00', '#005fff', '#ff00ff', '#00ffff', '#ffffff'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi! link Terminal Normal hi! link LineNrAbove LineNr diff --git a/runtime/colors/pablo.vim b/runtime/colors/pablo.vim index dc3e496853..8766cc4776 100644 --- a/runtime/colors/pablo.vim +++ b/runtime/colors/pablo.vim @@ -12,10 +12,14 @@ set background=dark hi clear let g:colors_name = 'pablo' -let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +let s:t_Co = &t_Co if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#000000', '#cd0000', '#00cd00', '#cdcd00', '#0000ee', '#cd00cd', '#00cdcd', '#e5e5e5', '#7f7f7f', '#ff0000', '#00ff00', '#ffff00', '#5c5cff', '#ff00ff', '#00ffff', '#ffffff'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi! link Terminal Normal hi! link StatusLineTerm StatusLine diff --git a/runtime/colors/peachpuff.vim b/runtime/colors/peachpuff.vim index 130eceeb18..0bab72dace 100644 --- a/runtime/colors/peachpuff.vim +++ b/runtime/colors/peachpuff.vim @@ -13,10 +13,14 @@ set background=light hi clear let g:colors_name = 'peachpuff' -let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +let s:t_Co = &t_Co if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#ffdab9', '#a52a2a', '#c00058', '#cd00cd', '#008b8b', '#2e8b57', '#6a5acd', '#737373', '#406090', '#a52a2a', '#c00058', '#cd00cd', '#008b8b', '#2e8b57', '#6a5acd', '#000000'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi! link Terminal Normal hi! link LineNrAbove LineNr diff --git a/runtime/colors/quiet.vim b/runtime/colors/quiet.vim index aecc69619b..d286839250 100644 --- a/runtime/colors/quiet.vim +++ b/runtime/colors/quiet.vim @@ -11,7 +11,7 @@ hi clear let g:colors_name = 'quiet' -let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +let s:t_Co = &t_Co hi! link Terminal Normal hi! link StatusLineTerm StatusLine @@ -49,6 +49,10 @@ hi! link debugPC CursorLine if &background ==# 'dark' if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#080808', '#d7005f', '#00af5f', '#d78700', '#0087d7', '#d787d7', '#00afaf', '#dadada', '#707070', '#ff005f', '#00d75f', '#ffaf00', '#5fafff', '#ff87ff', '#00d7d7', '#ffffff'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi Normal guifg=#dadada guibg=#080808 gui=NONE cterm=NONE hi ColorColumn guifg=NONE guibg=#1c1c1c gui=NONE cterm=NONE @@ -114,6 +118,10 @@ else " Light background if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#080808', '#af0000', '#005f00', '#af5f00', '#005faf', '#870087', '#008787', '#d7d7d7', '#626262', '#d70000', '#008700', '#d78700', '#0087d7', '#af00af', '#00afaf', '#ffffff'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi Normal guifg=#080808 guibg=#d7d7d7 gui=NONE cterm=NONE hi ColorColumn guifg=NONE guibg=#e4e4e4 gui=NONE cterm=NONE diff --git a/runtime/colors/ron.vim b/runtime/colors/ron.vim index ea37b646a6..d3a692a69f 100644 --- a/runtime/colors/ron.vim +++ b/runtime/colors/ron.vim @@ -12,7 +12,7 @@ set background=dark hi clear let g:colors_name = 'ron' -let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +let s:t_Co = &t_Co hi! link Terminal Normal hi! link Boolean Constant @@ -51,6 +51,10 @@ hi! link PopupNotification Todo if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#000000', '#cd0000', '#00cd00', '#cdcd00', '#0000ee', '#cd00cd', '#00cdcd', '#e5e5e5', '#7f7f7f', '#ff0000', '#00ff00', '#ffff00', '#5c5cff', '#ff00ff', '#00ffff', '#ffffff'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi Normal guifg=#00ffff guibg=#000000 gui=NONE cterm=NONE hi ColorColumn guifg=NONE guibg=#cd0000 gui=NONE cterm=NONE diff --git a/runtime/colors/shine.vim b/runtime/colors/shine.vim index f9b1e39324..b30ac415d0 100644 --- a/runtime/colors/shine.vim +++ b/runtime/colors/shine.vim @@ -13,10 +13,14 @@ set background=light hi clear let g:colors_name = 'shine' -let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +let s:t_Co = &t_Co if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#000000', '#8b0000', '#006400', '#ffff00', '#00008b', '#6a0dad', '#008b8b', '#dadada', '#767676', '#ffafaf', '#90ee90', '#ffff60', '#add8e6', '#ff00ff', '#00ffff', '#ffffff'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi! link Terminal Normal hi! link LineNrAbove LineNr diff --git a/runtime/colors/slate.vim b/runtime/colors/slate.vim index 04fbc6d6c8..6da572d9a7 100644 --- a/runtime/colors/slate.vim +++ b/runtime/colors/slate.vim @@ -13,10 +13,14 @@ set background=dark hi clear let g:colors_name = 'slate' -let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +let s:t_Co = &t_Co if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#000000', '#cd0000', '#00cd00', '#cdcd00', '#0000ee', '#cd00cd', '#00cdcd', '#e5e5e5', '#7f7f7f', '#ff0000', '#00ff00', '#ffff00', '#5c5cff', '#ff00ff', '#00ffff', '#ffffff'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi! link Terminal Normal hi! link LineNrAbove LineNr diff --git a/runtime/colors/torte.vim b/runtime/colors/torte.vim index 01d5f0b4e0..bec681bb4e 100644 --- a/runtime/colors/torte.vim +++ b/runtime/colors/torte.vim @@ -13,10 +13,14 @@ set background=dark hi clear let g:colors_name = 'torte' -let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +let s:t_Co = &t_Co if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#000000', '#cd0000', '#00cd00', '#cdcd00', '#0000ee', '#cd00cd', '#00cdcd', '#e5e5e5', '#7f7f7f', '#ff0000', '#00ff00', '#ffff00', '#5c5cff', '#ff00ff', '#00ffff', '#ffffff'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi! link Terminal Normal hi! link LineNrAbove LineNr diff --git a/runtime/colors/zellner.vim b/runtime/colors/zellner.vim index ab794c0193..af48ef86dc 100644 --- a/runtime/colors/zellner.vim +++ b/runtime/colors/zellner.vim @@ -13,10 +13,14 @@ set background=light hi clear let g:colors_name = 'zellner' -let s:t_Co = exists('&t_Co') && !has('gui_running') ? (&t_Co ? &t_Co : 0) : -1 +let s:t_Co = &t_Co if (has('termguicolors') && &termguicolors) || has('gui_running') let g:terminal_ansi_colors = ['#ffffff', '#a52a2a', '#ff00ff', '#a020f0', '#0000ff', '#0000ff', '#ff00ff', '#a9a9a9', '#ff0000', '#a52a2a', '#ff00ff', '#a020f0', '#0000ff', '#0000ff', '#ff00ff', '#000000'] + " Nvim uses g:terminal_color_{0-15} instead + for i in range(g:terminal_ansi_colors->len()) + let g:terminal_color_{i} = g:terminal_ansi_colors[i] + endfor endif hi! link Terminal Normal hi! link LineNrAbove LineNr diff --git a/runtime/compiler/dotnet.vim b/runtime/compiler/dotnet.vim new file mode 100644 index 0000000000..ac64084663 --- /dev/null +++ b/runtime/compiler/dotnet.vim @@ -0,0 +1,39 @@ +" Vim compiler file +" Compiler: dotnet build (.NET CLI) +" Maintainer: Nick Jensen <nickspoon@gmail.com> +" Last Change: 2022-12-06 +" License: Vim (see :h license) +" Repository: https://github.com/nickspoons/vim-cs + +if exists("current_compiler") + finish +endif +let current_compiler = "dotnet" + +if exists(":CompilerSet") != 2 " older Vim always used :setlocal + command -nargs=* CompilerSet setlocal <args> +endif + +let s:cpo_save = &cpo +set cpo&vim + +if get(g:, "dotnet_errors_only", v:false) + CompilerSet makeprg=dotnet\ build\ -nologo + \\ -consoleloggerparameters:NoSummary + \\ -consoleloggerparameters:ErrorsOnly +else + CompilerSet makeprg=dotnet\ build\ -nologo\ -consoleloggerparameters:NoSummary +endif + +if get(g:, "dotnet_show_project_file", v:true) + CompilerSet errorformat=%E%f(%l\\,%c):\ %trror\ %m, + \%W%f(%l\\,%c):\ %tarning\ %m, + \%-G%.%# +else + CompilerSet errorformat=%E%f(%l\\,%c):\ %trror\ %m\ [%.%#], + \%W%f(%l\\,%c):\ %tarning\ %m\ [%.%#], + \%-G%.%# +endif + +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/runtime/compiler/zig.vim b/runtime/compiler/zig.vim new file mode 100644 index 0000000000..2cc6831329 --- /dev/null +++ b/runtime/compiler/zig.vim @@ -0,0 +1,28 @@ +" Vim compiler file +" Compiler: Zig Compiler +" Upstream: https://github.com/ziglang/zig.vim + +if exists("current_compiler") + finish +endif +let current_compiler = "zig" + +let s:save_cpo = &cpo +set cpo&vim + +if exists(":CompilerSet") != 2 + command -nargs=* CompilerSet setlocal <args> +endif + +" a subcommand must be provided for the this compiler (test, build-exe, etc) +if has('patch-7.4.191') + CompilerSet makeprg=zig\ \$*\ \%:S +else + CompilerSet makeprg=zig\ \$*\ \"%\" +endif + +" TODO: improve errorformat as needed. + +let &cpo = s:save_cpo +unlet s:save_cpo +" vim: tabstop=8 shiftwidth=4 softtabstop=4 expandtab diff --git a/runtime/compiler/zig_build.vim b/runtime/compiler/zig_build.vim new file mode 100644 index 0000000000..0441267b64 --- /dev/null +++ b/runtime/compiler/zig_build.vim @@ -0,0 +1,29 @@ +" Vim compiler file +" Compiler: Zig Compiler (zig build) +" Upstream: https://github.com/ziglang/zig.vim + +if exists('current_compiler') + finish +endif +runtime compiler/zig.vim +let current_compiler = 'zig_build' + +let s:save_cpo = &cpo +set cpo&vim + + +if exists(':CompilerSet') != 2 + command -nargs=* CompilerSet setlocal <args> +endif + +if exists('g:zig_build_makeprg_params') + execute 'CompilerSet makeprg=zig\ build\ '.escape(g:zig_build_makeprg_params, ' \|"').'\ $*' +else + CompilerSet makeprg=zig\ build\ $* +endif + +" TODO: anything to add to errorformat for zig build specifically? + +let &cpo = s:save_cpo +unlet s:save_cpo +" vim: tabstop=8 shiftwidth=4 softtabstop=4 expandtab diff --git a/runtime/compiler/zig_build_exe.vim b/runtime/compiler/zig_build_exe.vim new file mode 100644 index 0000000000..20f0bb3366 --- /dev/null +++ b/runtime/compiler/zig_build_exe.vim @@ -0,0 +1,27 @@ +" Vim compiler file +" Compiler: Zig Compiler (zig build-exe) +" Upstream: https://github.com/ziglang/zig.vim + +if exists('current_compiler') + finish +endif +runtime compiler/zig.vim +let current_compiler = 'zig_build_exe' + +let s:save_cpo = &cpo +set cpo&vim + + +if exists(':CompilerSet') != 2 + command -nargs=* CompilerSet setlocal <args> +endif + +if has('patch-7.4.191') + CompilerSet makeprg=zig\ build-exe\ \%:S\ \$* +else + CompilerSet makeprg=zig\ build-exe\ \"%\"\ \$* +endif + +let &cpo = s:save_cpo +unlet s:save_cpo +" vim: tabstop=8 shiftwidth=4 softtabstop=4 expandtab diff --git a/runtime/compiler/zig_test.vim b/runtime/compiler/zig_test.vim new file mode 100644 index 0000000000..a82d2a6378 --- /dev/null +++ b/runtime/compiler/zig_test.vim @@ -0,0 +1,27 @@ +" Vim compiler file +" Compiler: Zig Compiler (zig test) +" Upstream: https://github.com/ziglang/zig.vim + +if exists('current_compiler') + finish +endif +runtime compiler/zig.vim +let current_compiler = 'zig_test' + +let s:save_cpo = &cpo +set cpo&vim + + +if exists(':CompilerSet') != 2 + command -nargs=* CompilerSet setlocal <args> +endif + +if has('patch-7.4.191') + CompilerSet makeprg=zig\ test\ \%:S\ \$* +else + CompilerSet makeprg=zig\ test\ \"%\"\ \$* +endif + +let &cpo = s:save_cpo +unlet s:save_cpo +" vim: tabstop=8 shiftwidth=4 softtabstop=4 expandtab diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index f92ef26399..0e1cc3c28c 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -51,7 +51,7 @@ 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 |api-client|. Here's a Ruby script that prints "hello world!" in the current Nvim instance: -> +>ruby #!/usr/bin/env ruby # Requires msgpack-rpc: gem install msgpack-rpc # @@ -79,7 +79,7 @@ functions can be called interactively: < You can also embed Nvim via |jobstart()|, and communicate using |rpcrequest()| and |rpcnotify()|: -> +>vim let nvim = jobstart(['nvim', '--embed'], {'rpc': v:true}) echo rpcrequest(nvim, 'nvim_eval', '"Hello " . "world!"') call jobstop(nvim) @@ -201,9 +201,9 @@ any of these approaches: Example (requires Python "pyyaml" and "msgpack-python" modules): > 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. > + 3. Use the |api_info()| Vimscript function. >vim :lua print(vim.inspect(vim.fn.api_info())) -< Example using |filter()| to exclude non-deprecated API functions: > +< Example using |filter()| to exclude non-deprecated API functions: >vim :new|put =map(filter(api_info().functions, '!has_key(v:val,''deprecated_since'')'), 'v:val.name') ============================================================================== @@ -361,10 +361,10 @@ callbacks. These callbacks are called frequently in various contexts; receive parameters ("lines", {buf}, {changedtick}, {firstline}, {lastline}, {new_lastline}, {old_byte_size} [, {old_utf32_size}, {old_utf16_size}]). Unlike remote channel events the text contents are not passed. The new text can -be accessed inside the callback as - - `vim.api.nvim_buf_get_lines(buf, firstline, new_lastline, true)` +be accessed inside the callback as >lua + vim.api.nvim_buf_get_lines(buf, firstline, new_lastline, true) +< {old_byte_size} is the total size of the replaced region {firstline} to {lastline} in bytes, including the final newline after {lastline}. if `utf_sizes` is set to true in |nvim_buf_attach()| keyword args, then the @@ -400,7 +400,7 @@ performance can be improved by calling |nvim_buf_add_highlight()| as an asynchronous notification, after first (synchronously) requesting a source id. Example using the Python API client (|pynvim|): -> +>python src = vim.new_highlight_source() buf = vim.current.buffer for i in range(5): @@ -414,7 +414,7 @@ clear highlights from a specific source, in a specific line range or the entire buffer by passing in the line range 0, -1 (the latter is the default in python as used above). -Example using the API from Vimscript: > +Example using the API from Vimscript: >vim call nvim_buf_set_lines(0, 0, 0, v:true, ["test text"]) let src = nvim_buf_add_highlight(0, 0, "String", 1, 0, 4) @@ -438,7 +438,7 @@ Two ways to create a floating window: To close it use |nvim_win_close()| or a command such as |:close|. To check whether a window is floating, check whether the `relative` option in -its config is non-empty: > +its config is non-empty: >lua if vim.api.nvim_win_get_config(window_id).relative ~= '' then -- window with this window_id is floating @@ -456,7 +456,7 @@ Currently, floating windows don't support some widgets like scrollbar. The output of |:mksession| does not include commands for restoring floating windows. -Example: create a float with scratch buffer: > +Example: create a float with scratch buffer: >vim let buf = nvim_create_buf(v:false, v:true) call nvim_buf_set_lines(buf, 0, -1, v:true, ["test", "text"]) @@ -468,7 +468,7 @@ Example: create a float with scratch buffer: > < ============================================================================== -Extended marks *api-extended-marks* *extmarks* +Extended marks *api-extended-marks* *extmarks* *extmark* Extended marks (extmarks) represent buffer annotations that track text changes in the buffer. They can represent cursors, folds, misspelled words, anything @@ -510,19 +510,20 @@ Let's set an extmark at the first row (row=0) and third column (column=2). 01 2345678 0 ex|ample.. ^ extmark position - +< +>vim let g:mark_ns = nvim_create_namespace('myplugin') let g:mark_id = nvim_buf_set_extmark(0, g:mark_ns, 0, 2, {}) < -We can get the mark by its id: > +We can get the mark by its id: >vim echo nvim_buf_get_extmark_by_id(0, g:mark_ns, g:mark_id, {}) - => [0, 2] + " => [0, 2] -We can get all marks in a buffer by |namespace| (or by a range): > +We can get all marks in a buffer by |namespace| (or by a range): >vim echo nvim_buf_get_extmarks(0, g:mark_ns, 0, -1, {}) - => [[1, 0, 2]] + " => [[1, 0, 2]] Deleting all surrounding text does NOT remove an extmark! To remove extmarks use |nvim_buf_del_extmark()|. Deleting "x" in our example: > @@ -530,9 +531,10 @@ use |nvim_buf_del_extmark()|. Deleting "x" in our example: > 0 12345678 0 e|ample.. ^ extmark position - +< +>vim echo nvim_buf_get_extmark_by_id(0, g:mark_ns, g:mark_id, {}) - => [0, 1] + " => [0, 1] < Note: Extmark "gravity" decides how it will shift after a text edit. See |nvim_buf_set_extmark()| @@ -715,7 +717,7 @@ nvim_del_var({name}) *nvim_del_var()* Parameters: ~ • {name} Variable name -nvim_echo({chunks}, {history}, {opts}) *nvim_echo()* +nvim_echo({chunks}, {history}, {*opts}) *nvim_echo()* Echo a message. Parameters: ~ @@ -723,7 +725,11 @@ nvim_echo({chunks}, {history}, {opts}) *nvim_echo()* chunk with specified highlight. `hl_group` element can be omitted for no highlight. • {history} if true, add to |message-history|. - • {opts} Optional parameters. Reserved for future use. + • {opts} Optional parameters. + • verbose: Message was printed as a result of 'verbose' + option if Nvim was invoked with -V3log_file, the message + will be redirected to the log_file and suppressed from + direct output. nvim_err_write({str}) *nvim_err_write()* Writes a message to the Vim error buffer. Does not append "\n", the @@ -801,7 +807,7 @@ nvim_feedkeys({keys}, {mode}, {escape_ks}) *nvim_feedkeys()* with escape_ks=false) to replace |keycodes|, then pass the result to nvim_feedkeys(). - Example: > + Example: >vim :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true) :call nvim_feedkeys(key, 'n', v:false) < @@ -809,7 +815,7 @@ nvim_feedkeys({keys}, {mode}, {escape_ks}) *nvim_feedkeys()* Parameters: ~ • {keys} to be typed • {mode} behavior flags, see |feedkeys()| - • {escape_ks} If true, escape K_SPECIAL bytes in `keys` This should be + • {escape_ks} If true, escape K_SPECIAL bytes in `keys`. This should be false if you already used |nvim_replace_termcodes()|, and true otherwise. @@ -857,7 +863,7 @@ nvim_get_color_by_name({name}) *nvim_get_color_by_name()* Returns the 24-bit RGB value of a |nvim_get_color_map()| color name or "#rrggbb" hexadecimal string. - Example: > + Example: >vim :echo nvim_get_color_by_name("Pink") :echo nvim_get_color_by_name("#cbcbcb") < @@ -1127,7 +1133,7 @@ nvim_list_uis() *nvim_list_uis()* • "width" Requested width of the UI • "rgb" true if the UI uses RGB colors (false implies |cterm-colors|) • "ext_..." Requested UI extensions, see |ui-option| - • "chan" Channel id of remote UI (not present for TUI) + • "chan" |channel-id| of remote UI nvim_list_wins() *nvim_list_wins()* Gets the current list of window handles. @@ -1251,19 +1257,21 @@ nvim_replace_termcodes({str}, {from_part}, {do_lt}, {special}) *nvim_select_popupmenu_item()* nvim_select_popupmenu_item({item}, {insert}, {finish}, {opts}) - Selects an item in the completion popupmenu. + Selects an item in the completion popup menu. - If |ins-completion| is not active this API call is silently ignored. - Useful for an external UI using |ui-popupmenu| to control the popupmenu - with the mouse. Can also be used in a mapping; use <cmd> |:map-cmd| to - ensure the mapping doesn't end completion mode. + If neither |ins-completion| nor |cmdline-completion| popup menu is active + this API call is silently ignored. Useful for an external UI using + |ui-popupmenu| to control the popup menu with the mouse. Can also be used + in a mapping; use <Cmd> |:map-cmd| or a Lua mapping to ensure the mapping + doesn't end completion mode. Parameters: ~ • {item} Index (zero-based) of the item to select. Value of -1 selects nothing and restores the original text. - • {insert} Whether the selection should be inserted in the buffer. - • {finish} Finish the completion and dismiss the popupmenu. Implies - `insert`. + • {insert} For |ins-completion|, whether the selection should be + inserted in the buffer. Ignored for |cmdline-completion|. + • {finish} Finish the completion and dismiss the popup menu. Implies + {insert}. • {opts} Optional parameters. Reserved for future use. *nvim_set_client_info()* @@ -1440,11 +1448,11 @@ nvim_set_keymap({mode}, {lhs}, {rhs}, {*opts}) *nvim_set_keymap()* Unlike |:map|, leading/trailing whitespace is accepted as part of the {lhs} or {rhs}. Empty {rhs} is |<Nop>|. |keycodes| are replaced as usual. - Example: > + Example: >vim call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true}) < - is equivalent to: > + is equivalent to: >vim nmap <nowait> <Space><NL> <Nop> < @@ -1742,7 +1750,7 @@ nvim_create_user_command({name}, {command}, {*opts}) {command} is the replacement text or Lua function to execute. - Example: > + Example: >vim :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {}) :SayHello Hello world! @@ -1755,6 +1763,7 @@ nvim_create_user_command({name}, {command}, {*opts}) 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: + • name: (string) Command name • args: (string) The args passed to the command, if any |<args>| • fargs: (table) The args split by unescaped whitespace @@ -1831,7 +1840,8 @@ nvim_parse_cmd({str}, {opts}) *nvim_parse_cmd()* cannot take a register. • bang: (boolean) Whether command contains a |<bang>| (!) modifier. • args: (array) Command arguments. - • addr: (string) Value of |:command-addr|. Uses short name. + • addr: (string) Value of |:command-addr|. Uses short name or "line" + for -addr=lines. • nargs: (string) Value of |:command-nargs|. • nextcmd: (string) Next command if there are multiple commands separated by a |:bar|. Empty if there isn't a next command. @@ -2022,7 +2032,7 @@ whether a buffer is loaded. nvim_buf_attach({buffer}, {send_buffer}, {opts}) *nvim_buf_attach()* Activates buffer-update events on a channel, or as Lua callbacks. - Example (Lua): capture buffer updates in a global `events` variable (use "print(vim.inspect(events))" to see its contents): > + Example (Lua): capture buffer updates in a global `events` variable (use "print(vim.inspect(events))" to see its contents): >lua events = {} vim.api.nvim_buf_attach(0, false, { on_lines=function(...) table.insert(events, {...}) end}) @@ -2479,7 +2489,7 @@ nvim_buf_add_highlight({buffer}, {ns_id}, {hl_group}, {line}, {col_start}, *nvim_buf_clear_namespace()* nvim_buf_clear_namespace({buffer}, {ns_id}, {line_start}, {line_end}) - Clears namespaced objects (highlights, extmarks, virtual text) from a + Clears |namespace|d objects (highlights, |extmarks|, virtual text) from a region. Lines are 0-indexed. |api-indexing| To clear the namespace in the entire @@ -2493,7 +2503,7 @@ nvim_buf_clear_namespace({buffer}, {ns_id}, {line_start}, {line_end}) clear to end of buffer. nvim_buf_del_extmark({buffer}, {ns_id}, {id}) *nvim_buf_del_extmark()* - Removes an extmark. + Removes an |extmark|. Parameters: ~ • {buffer} Buffer handle, or 0 for current buffer @@ -2505,7 +2515,7 @@ nvim_buf_del_extmark({buffer}, {ns_id}, {id}) *nvim_buf_del_extmark()* *nvim_buf_get_extmark_by_id()* nvim_buf_get_extmark_by_id({buffer}, {ns_id}, {id}, {opts}) - Gets the position (0-indexed) of an extmark. + Gets the position (0-indexed) of an |extmark|. Parameters: ~ • {buffer} Buffer handle, or 0 for current buffer @@ -2519,34 +2529,32 @@ nvim_buf_get_extmark_by_id({buffer}, {ns_id}, {id}, {opts}) *nvim_buf_get_extmarks()* nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts}) - Gets extmarks in "traversal order" from a |charwise| region defined by + Gets |extmarks| in "traversal order" from a |charwise| region defined by buffer positions (inclusive, 0-indexed |api-indexing|). Region can be given as (row,col) tuples, or valid extmark ids (whose positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1) - respectively, thus the following are equivalent: -> - nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) - nvim_buf_get_extmarks(0, my_ns, [0,0], [-1,-1], {}) + respectively, thus the following are equivalent: >lua + vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) + vim.api.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 position.) - Example: -> - local a = vim.api - 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, {}) - -- Create new extmark at line 3, column 1. - 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. - local all = a.nvim_buf_get_extmarks(0, ns, 0, -1, {}) - print(vim.inspect(ms)) + Example: >lua + local a = vim.api + 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, {}) + -- Create new extmark at line 3, column 1. + local m2 = a.nvim_buf_set_extmark(0, ns, 2, 0, {}) + -- 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. + local all = a.nvim_buf_get_extmarks(0, ns, 0, -1, {}) + print(vim.inspect(ms)) < Parameters: ~ @@ -2566,7 +2574,7 @@ nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts}) *nvim_buf_set_extmark()* nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {*opts}) - Creates or updates an extmark. + Creates or updates an |extmark|. By default a new extmark is created when no id is passed in, but it is also possible to create a new mark by passing in a previously unused id or @@ -2679,7 +2687,7 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {*opts}) Id of the created/updated extmark nvim_create_namespace({name}) *nvim_create_namespace()* - Creates a new *namespace* or gets an existing one. + Creates a new namespace or gets an existing one. *namespace* Namespaces are used for buffer highlights and virtual text, see |nvim_buf_add_highlight()| and |nvim_buf_set_extmark()|. @@ -2695,19 +2703,19 @@ nvim_create_namespace({name}) *nvim_create_namespace()* Namespace id nvim_get_namespaces() *nvim_get_namespaces()* - Gets existing, non-anonymous namespaces. + Gets existing, non-anonymous |namespace|s. Return: ~ dict that maps from names to namespace ids. *nvim_set_decoration_provider()* nvim_set_decoration_provider({ns_id}, {*opts}) - Set or change decoration provider for a namespace + Set or change decoration provider for a |namespace| This is a very general purpose interface for having lua callbacks being triggered during the redraw code. - The expected usage is to set extmarks for the currently redrawn buffer. + The expected usage is to set |extmarks| for the currently redrawn buffer. |nvim_buf_set_extmark()| can be called to add marks on a per-window or per-lines basis. Use the `ephemeral` key to only use the mark for the current screen redraw (the callback will be called again for the next @@ -2964,12 +2972,12 @@ nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()* could let floats hover outside of the main window like a tooltip, but this should not be used to specify arbitrary WM screen positions. - Example (Lua): window-relative float > + Example (Lua): window-relative float >lua vim.api.nvim_open_win(0, false, {relative='win', row=3, col=3, width=12, height=3}) < - Example (Lua): buffer-relative float (travels as buffer is scrolled) > + Example (Lua): buffer-relative float (travels as buffer is scrolled) >lua vim.api.nvim_open_win(0, false, {relative='win', width=12, height=3, bufpos={100,10}}) < @@ -2987,6 +2995,7 @@ nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()* • "win" Window given by the `win` field, or current window. • "cursor" Cursor position in current window. + • "mouse" Mouse position • win: |window-ID| for relative="win". • anchor: Decides which corner of the float to place at @@ -3034,10 +3043,10 @@ nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()* Disables 'number', 'relativenumber', 'cursorline', 'cursorcolumn', 'foldcolumn', 'spell' and 'list' options. 'signcolumn' is changed to `auto` and - 'colorcolumn' is cleared. The end-of-buffer region is - hidden by setting `eob` flag of 'fillchars' to a space - char, and clearing the |hl-EndOfBuffer| region in - 'winhighlight'. + 'colorcolumn' is cleared. 'statuscolumn' is changed to + empty. The end-of-buffer region is hidden by setting + `eob` flag of 'fillchars' to a space char, and clearing + the |hl-EndOfBuffer| region in 'winhighlight'. • border: Style of (optional) window border. This can either be a string or an array. The string values are @@ -3063,9 +3072,14 @@ nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()* borders but not horizontal ones. By default, `FloatBorder` highlight is used, which links to `WinSeparator` when not defined. It could also be - specified by character: [ {"+", "MyCorner"}, {"x", - "MyBorder"} ]. - + specified by character: [ ["+", "MyCorner"], ["x", + "MyBorder"] ]. + + • title: Title (optional) in window border, String or list. + List is [text, highlight] tuples. if is string the default + highlight group is `FloatTitle`. + • title_pos: Title position must set with title option. + value can be of `left` `center` `right` default is left. • noautocmd: If true then no buffer-related autocommand events such as |BufEnter|, |BufLeave| or |BufWinEnter| may fire from calling this function. @@ -3200,7 +3214,7 @@ nvim_clear_autocmds({*opts}) *nvim_clear_autocmds()* nvim_create_augroup({name}, {*opts}) *nvim_create_augroup()* Create or get an autocommand group |autocmd-groups|. - To get an existing group id, do: > + To get an existing group id, do: >lua local id = vim.api.nvim_create_augroup("MyGroup", { clear = false }) @@ -3219,84 +3233,54 @@ nvim_create_augroup({name}, {*opts}) *nvim_create_augroup()* |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" + Creates an |autocommand| event handler, defined by `callback` (Lua function or Vimscript function name string) or `command` (Ex command string). + Example using Lua callback: >lua vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { pattern = {"*.c", "*.h"}, - callback = myluafun, -- Or myvimfun + callback = function(ev) + print(string.format('event fired: s', vim.inspect(ev))) + end }) < - Lua functions receive a table with information about the autocmd event as - an argument. To use a function which itself accepts another (optional) - parameter, wrap the function in a lambda: -> - -- Lua function with an optional parameter. - -- The autocmd callback would pass a table as argument but this - -- function expects number|nil - local myluafun = function(bufnr) bufnr = bufnr or vim.api.nvim_get_current_buf() end - - vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { - pattern = {"*.c", "*.h"}, - callback = function() myluafun() end, - }) -< - - Example using command: > + Example using an Ex command as the handler: >lua 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" } -< - - Example values for event: > - "BufWritePre" - {"CursorHold", "BufWritePre", "BufWritePost"} + Note: `pattern` is NOT automatically expanded (unlike with |:autocmd|), thus names like + "$HOME" and "~" must be expanded explicitly: >lua + pattern = vim.fn.expand("~") .. "/some/path/*.py" < 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 + • {event} (string|array) Event(s) that will trigger the handler + (`callback` or `command`). + • {opts} Options dict: + • group (string|integer) optional: autocommand group name or + id to match against. + • pattern (string|array) optional: pattern(s) to match + literally |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>| + • desc (string) optional: description (for documentation and + troubleshooting). + • callback (function|string) optional: Lua function (or + Vimscript function name, if string) called when the + event(s) is triggered. Lua callback can return true to + delete the autocommand, and receives a table argument with + these keys: + • id: (number) autocommand id + • event: (string) name of the triggered event + |autocmd-events| + • group: (number|nil) autocommand group id, if any + • match: (string) expanded value of |<amatch>| + • buf: (number) expanded value of |<abuf>| + • file: (string) expanded value of |<afile>| • data: (any) arbitrary data passed to |nvim_exec_autocmds()| @@ -3308,7 +3292,7 @@ nvim_create_autocmd({event}, {*opts}) *nvim_create_autocmd()* autocommands |autocmd-nested|. Return: ~ - Integer id of the created autocommand. + Autocommand id (number) See also: ~ |autocommand| @@ -3378,7 +3362,7 @@ nvim_exec_autocmds({event}, {*opts}) *nvim_exec_autocmds()* 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: > + These examples will get autocommands matching ALL the given criteria: >lua -- Matches all criteria autocommands = vim.api.nvim_get_autocmds({ group = "MyGroup", @@ -3488,6 +3472,12 @@ nvim_ui_pum_set_height({height}) *nvim_ui_pum_set_height()* Parameters: ~ • {height} Popupmenu height, must be greater than zero. +nvim_ui_set_focus({gained}) *nvim_ui_set_focus()* + Tells the nvim server if focus was gained or lost by the GUI. + + Attributes: ~ + |RPC| only + nvim_ui_set_option({name}, {value}) *nvim_ui_set_option()* TODO: Documentation diff --git a/runtime/doc/arabic.txt b/runtime/doc/arabic.txt index 0df861111c..0a80edb981 100644 --- a/runtime/doc/arabic.txt +++ b/runtime/doc/arabic.txt @@ -14,8 +14,9 @@ It is best to view this file with these settings within VIM's GUI: > :set arabicshape +------------------------------------------------------------------------------ Introduction ------------- + Arabic is a rather demanding language in which a number of special features are required. Characters are right-to-left oriented and ought to appear as such on the screen (i.e. from right to left). @@ -34,8 +35,9 @@ The commands, prompts and help files are not in Arabic, therefore the user interface remains the standard Vi interface. +------------------------------------------------------------------------------ Highlights ----------- + o Editing left-to-right files as in the original Vim hasn't changed. o Viewing and editing files in right-to-left windows. File @@ -64,8 +66,8 @@ o Proper Bidirectional functionality is possible given Vim is started within a Bidi capable terminal emulator. +------------------------------------------------------------------------------ Arabic Fonts *arabicfonts* ------------- Vim requires monospaced fonts of which there are many out there. Arabic requires ISO-8859-6 as well as Presentation Form-B fonts @@ -75,8 +77,8 @@ Do an Internet search or check www.arabeyes.org for further info on where to obtain the necessary Arabic fonts. +------------------------------------------------------------------------------ Font Installation ------------------ o Installation of fonts for X Window systems (Unix/Linux) @@ -88,8 +90,9 @@ o Installation of fonts for X Window systems (Unix/Linux) % xset +fp path_name_of_arabic_fonts_directory +------------------------------------------------------------------------------ Usage ------ + Prior to the actual usage of Arabic within Vim, a number of settings need to be accounted for and invoked. @@ -259,8 +262,8 @@ o Enable Arabic settings [short-cut] ':set arabicshape' to your vimrc file. +------------------------------------------------------------------------------ Keymap/Keyboard *arabickeymap* ---------------- The character/letter encoding used in Vim is the standard UTF-8. It is widely discouraged that any other encoding be used or even @@ -276,7 +279,7 @@ o Keyboard + CTRL-^ in insert/replace mode toggles between Arabic/Latin mode + Keyboard mapping is based on the Microsoft's Arabic keymap (the - de facto standard in the Arab world): + de facto standard in the Arab world): > +---------------------------------------------------------------------+ |! |@ |# |$ |% |^ |& |* |( |) |_ |+ || |~ ّ | @@ -291,17 +294,18 @@ o Keyboard |Z ~ |X ْ |C { |V } |B لآ |N آ |M ' |< , |> . |? ؟ | |z ئ |x ء |c ؤ |v ر |b لا |n ى |m ة |, و |. ز |/ ظ | +-------------------------------------------------+ +< +------------------------------------------------------------------------------ Restrictions ------------- o Vim in its GUI form does not currently support Bi-directionality (i.e. the ability to see both Arabic and Latin intermixed within the same line). +------------------------------------------------------------------------------ Known Bugs ----------- There is one known minor bug, diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index e27f191e0d..8cc4754880 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -681,16 +681,12 @@ FuncUndefined When a user function is used but it isn't UIEnter After a UI connects via |nvim_ui_attach()|, or after builtin TUI is started, after |VimEnter|. Sets these |v:event| keys: - chan: 0 for builtin TUI - 1 for |--embed| - |channel-id| of the UI otherwise + chan: |channel-id| of the UI *UILeave* UILeave After a UI disconnects from Nvim, or after builtin TUI is stopped, after |VimLeave|. Sets these |v:event| keys: - chan: 0 for builtin TUI - 1 for |--embed| - |channel-id| of the UI otherwise + chan: |channel-id| of the UI *InsertChange* InsertChange When typing <Insert> while in Insert or Replace mode. The |v:insertmode| variable @@ -806,7 +802,7 @@ OptionSet After setting an option (except during QuickFixCmdPre Before a quickfix command is run (|:make|, |:lmake|, |:grep|, |:lgrep|, |:grepadd|, |:lgrepadd|, |:vimgrep|, |:lvimgrep|, - |:vimgrepadd|, |:lvimgrepadd|, |:cscope|, + |:vimgrepadd|, |:lvimgrepadd|, |:cfile|, |:cgetfile|, |:caddfile|, |:lfile|, |:lgetfile|, |:laddfile|, |:helpgrep|, |:lhelpgrep|, |:cexpr|, |:cgetexpr|, @@ -823,8 +819,8 @@ QuickFixCmdPre Before a quickfix command is run (|:make|, QuickFixCmdPost Like QuickFixCmdPre, but after a quickfix command is run, before jumping to the first location. For |:cfile| and |:lfile| commands - it is run after error file is read and before - moving to the first error. + it is run after the error file is read and + before moving to the first error. See |QuickFixCmdPost-example|. *QuitPre* QuitPre When using `:quit`, `:wq` or `:qall`, before @@ -996,6 +992,10 @@ TextChangedP After a change was made to the text in the current buffer in Insert mode, only when the popup menu is visible. Otherwise the same as TextChanged. + *TextChangedT* +TextChangedT After a change was made to the text in the + current buffer in |Terminal-mode|. Otherwise + the same as TextChanged. *TextYankPost* TextYankPost Just after a |yank| or |deleting| command, but not if the black hole register |quote_| is used nor @@ -1061,8 +1061,9 @@ VimResume After Nvim resumes from |suspend| state. *VimSuspend* VimSuspend Before Nvim enters |suspend| state. *WinClosed* -WinClosed After closing a window. The pattern is - matched against the |window-ID|. Both +WinClosed When closing a window, just before it is + removed from the window layout. The pattern + is matched against the |window-ID|. Both <amatch> and <afile> are set to the |window-ID|. After WinLeave. Non-recursive (event cannot trigger itself). @@ -1091,23 +1092,48 @@ WinNew When a new window was created. Not done for Before WinEnter. *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 After any window in the current tab page + scrolled the text (horizontally or vertically) + or changed width or height. See + |win-scrolled-resized|. + + The pattern is matched against the |window-ID| + of the first window that scrolled or resized. + Both <amatch> and <afile> are set to the + |window-ID|. + + |v:event| is set with information about size + and scroll changes. |WinScrolled-event| + + Only starts triggering after startup finished + and the first screen redraw was done. + Does not trigger when defining the first + WinScrolled or WinResized event, but may + trigger when adding more. + + Non-recursive: the event will not trigger + while executing commands for the WinScrolled + event. However, if the command causes a + window to scroll or change size, then another WinScrolled event will be triggered later. - Does not trigger when the command is added, - only after the first scroll or resize. + + + *WinResized* +WinResized After a window in the current tab page changed + width or height. + See |win-scrolled-resized|. + + |v:event| is set with information about size + changes. |WinResized-event| + + Same behavior as |WinScrolled| for the + pattern, triggering and recursiveness. ============================================================================== 6. Patterns *autocmd-pattern* *{aupat}* -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: > +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 diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 344abe557c..4d2c85b134 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -4,7 +4,7 @@ VIM REFERENCE MANUAL by Bram Moolenaar -Builtin functions *builtin-functions* +Builtin functions *vimscript-functions* *builtin-functions* For functions grouped by what they are used for see |function-list|. @@ -68,8 +68,8 @@ 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} +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 @@ -78,13 +78,13 @@ 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} charclass({string}) Number character class of {string} -charcol({expr}) Number column number of cursor or mark +charcol({expr} [, {winid}]) 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 +col({expr} [, {winid}]) 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 @@ -96,8 +96,6 @@ 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| @@ -136,7 +134,8 @@ exists({expr}) Number |TRUE| if {expr} exists exp({expr}) Float exponential of {expr} expand({expr} [, {nosuf} [, {list}]]) any expand special keywords in {expr} -expandcmd({expr}) String expand {expr} like with `:edit` +expandcmd({string} [, {options}]) + String expand {string} like with `:edit` extend({expr1}, {expr2} [, {expr3}]) List/Dict insert items of {expr2} into {expr1} feedkeys({string} [, {mode}]) Number add key sequence to typeahead buffer @@ -171,8 +170,10 @@ 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} +getbufoneline({buf}, {lnum}) String line {lnum} of buffer {buf} getbufvar({buf}, {varname} [, {def}]) any variable {varname} in buffer {buf} +getcellwidths() List get character cell width overrides getchangelist([{buf}]) List list of change list items getchar([expr]) Number or String get one character from the user @@ -222,6 +223,7 @@ gettabvar({nr}, {varname} [, {def}]) gettabwinvar({tabnr}, {winnr}, {name} [, {def}]) any {name} in {winnr} in tab page {tabnr} gettagstack([{nr}]) Dict get the tag stack of window {nr} +gettext({text}) String lookup translation of {text} getwininfo([{winid}]) List list of info about each window getwinpos([{timeout}]) List X and Y coord in pixels of the Vim window getwinposx() Number X coord in pixels of Vim window @@ -317,9 +319,9 @@ matchfuzzypos({list}, {str} [, {dict}]) 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} + String {count}th match of {pat} in {expr} matchstrpos({expr}, {pat} [, {start} [, {count}]]) - List {count}'th match of {pat} in {expr} + 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} menu_info({name} [, {mode}]) Dict get menu item information @@ -350,6 +352,7 @@ pyxeval({expr}) any evaluate |python_x| expression rand([{expr}]) Number get pseudo-random number range({expr} [, {max} [, {stride}]]) List items from {expr} to {max} +readblob({fname}) Blob read a |Blob| from {fname} readdir({dir} [, {expr}]) List file names in {dir} selected by {expr} readfile({fname} [, {type} [, {max}]]) List get list of lines from file {fname} @@ -466,10 +469,11 @@ str2list({expr} [, {utf8}]) List convert each character of {expr} to ASCII/UTF-8 value str2nr({expr} [, {base} [, {quoted}]]) Number convert String to Number +strcharlen({expr}) Number character length of the String {expr} strcharpart({str}, {start} [, {len}]) String {len} characters of {str} at character {start} -strchars({expr} [, {skipcc}]) Number character length of the String {expr} +strchars({expr} [, {skipcc}]) Number character count of the String {expr} strdisplaywidth({expr} [, {col}]) Number display length of the String {expr} strftime({format} [, {time}]) String format time with a specified format strgetchar({str}, {index}) Number get char {index} from {str} @@ -636,6 +640,7 @@ append({lnum}, {text}) *append()* text line below line {lnum} in the current buffer. Otherwise append {text} as one text line below line {lnum} in the current buffer. + Any type of item is accepted and converted to a String. {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), @@ -654,9 +659,10 @@ appendbufline({buf}, {lnum}, {text}) *appendbufline()* For the use of {buf}, see |bufname()|. - {lnum} is used like with |append()|. Note that using |line()| - would use the current buffer, not the one appending to. - Use "$" to append at the end of the buffer. + {lnum} is the line number to append below. Note that using + |line()| would use the current buffer, not the one appending + to. Use "$" to append at the end of the buffer. Other string + values are not supported. On success 0 is returned, on failure 1 is returned. @@ -917,7 +923,8 @@ bufwinid({buf}) *bufwinid()* echo "A window containing buffer 1 is " .. (bufwinid(1)) < - Only deals with the current tab page. + Only deals with the current tab page. See |win_findbuf()| for + finding more. Can also be used as a |method|: > FindBuffer()->bufwinid() @@ -950,7 +957,7 @@ byte2line({byte}) *byte2line()* GetOffset()->byte2line() byteidx({expr}, {nr}) *byteidx()* - Return byte index of the {nr}'th character in the String + 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 @@ -1085,8 +1092,8 @@ charclass({string}) *charclass()* Returns 0 if {string} is not a |String|. - *charcol()* -charcol({expr}) Same as |col()| but returns the character index of the column +charcol({expr} [, {winid}]) *charcol()* + Same as |col()| but returns the character index of the column position given with {expr} instead of the byte position. Example: @@ -1168,8 +1175,8 @@ clearmatches([{win}]) *clearmatches()* 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 +col({expr} [, {winid}) *col()* + 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 @@ -1184,6 +1191,8 @@ col({expr}) The result is a Number, which is the byte index of the column 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. + With the optional {winid} argument the values are obtained for + that window instead of the current window. To get the line number use |line()|. To get both use |getpos()|. For the screen column position use |virtcol()|. For the @@ -1194,16 +1203,15 @@ col({expr}) The result is a Number, which is the byte index of the column col("$") length of cursor line plus one col("'t") column of mark t col("'" .. markname) column of mark markname -< The first column is 1. Returns 0 if {expr} is invalid. +< The first column is 1. Returns 0 if {expr} is invalid or when + the window with ID {winid} is not found. 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> + line. Also, when using a <Cmd> mapping the cursor isn't + moved, this can be used to obtain the column in Insert mode: > + :imap <F2> <Cmd>echo col(".").."\n"<CR> < Can also be used as a |method|: > GetPos()->col() @@ -1436,47 +1444,6 @@ count({comp}, {expr} [, {ic} [, {start}]]) *count()* 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|). @@ -1519,9 +1486,10 @@ cursor({list}) |setcursorcharpos()|. Does not change the jumplist. + {lnum} is used like with |getline()|, except that if {lnum} is + zero, the cursor will stay in the current line. 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. @@ -1541,7 +1509,7 @@ 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-debug|. - {Sends a SIGINT to a process {pid} other than MS-Windows} + (Sends a SIGINT to a process {pid} other than MS-Windows) Returns |TRUE| if successfully interrupted the program. Otherwise returns |FALSE|. @@ -1630,9 +1598,9 @@ dictwatcheradd({dict}, {pattern}, {callback}) *dictwatcheradd()* 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 + 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: @@ -2084,18 +2052,27 @@ expand({string} [, {nosuf} [, {list}]]) *expand()* Can also be used as a |method|: > Getpattern()->expand() -expandcmd({string}) *expandcmd()* +expandcmd({string} [, {options}]) *expandcmd()* Expand special items in String {string} like what is done for an Ex command such as `:edit`. This expands special keywords, like with |expand()|, and environment variables, anywhere in {string}. "~user" and "~/path" are only expanded at the start. + + The following items are supported in the {options} Dict + argument: + errmsg If set to TRUE, error messages are displayed + if an error is encountered during expansion. + By default, error messages are not displayed. + Returns the expanded string. If an error is encountered during expansion, the unmodified {string} is returned. + Example: > :echo expandcmd('make %<.o') -< make /path/runtime/doc/builtin.o ~ - + make /path/runtime/doc/builtin.o + :echo expandcmd('make %<.o', {'errmsg': v:true}) +< Can also be used as a |method|: > GetCommand()->expandcmd() < @@ -2321,7 +2298,7 @@ flatten({list} [, {maxdepth}]) *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. + {expr} must evaluate to a |Float| or a |Number|. Returns 0 if {expr} is not 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 @@ -2710,11 +2687,13 @@ getbufinfo([{dict}]) 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. + |List| with only the line {lnum} is returned. See + `getbufoneline()` for only getting the line. For the use of {buf}, see |bufname()| above. @@ -2737,6 +2716,11 @@ getbufline({buf}, {lnum} [, {end}]) < Can also be used as a |method|: > GetBufnr()->getbufline(lnum) +< + *getbufoneline()* +getbufoneline({buf}, {lnum}) + Just like `getbufline()` but only get one line and return it + as a string. getbufvar({buf}, {varname} [, {def}]) *getbufvar()* The result is the value of option or local buffer variable @@ -2762,6 +2746,13 @@ getbufvar({buf}, {varname} [, {def}]) *getbufvar()* < Can also be used as a |method|: > GetBufnr()->getbufvar(varname) < +getcellwidths() *getcellwidths()* + Returns a |List| of cell widths of character ranges overridden + by |setcellwidths()|. The format is equal to the argument of + |setcellwidths()|. If no character ranges have their cell + widths overridden, an empty List is returned. + + getchangelist([{buf}]) *getchangelist()* Returns the |changelist| for the buffer {buf}. For the use of {buf}, see |bufname()| above. If buffer {buf} doesn't @@ -2975,12 +2966,12 @@ getcompletion({pat}, {type} [, {filtered}]) *getcompletion()* arglist file names in argument list augroup autocmd groups buffer buffer names - behave :behave suboptions + behave |:behave| suboptions + breakpoint |:breakadd| and |:breakdel| 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 @@ -2992,7 +2983,7 @@ getcompletion({pat}, {type} [, {filtered}]) *getcompletion()* function function name help help subjects highlight highlight groups - history :history suboptions + history |:history| suboptions locale locale names (as output of locale -a) mapclear buffer argument mapping mapping name @@ -3000,6 +2991,7 @@ getcompletion({pat}, {type} [, {filtered}]) *getcompletion()* messages |:messages| suboptions option options packadd optional package |pack-add| names + scriptnames sourced script names |:scriptnames| shellcmd Shell command sign |:sign| suboptions syntax syntax file names |'syntax'| @@ -3017,6 +3009,13 @@ getcompletion({pat}, {type} [, {filtered}]) *getcompletion()* is applied to filter the results. Otherwise all the matches are returned. The 'wildignorecase' option always applies. + If the 'wildoptions' option contains "fuzzy", then fuzzy + matching is used to get the completion matches. Otherwise + regular expression matching is used. Thus this function + follows the user preference, what happens on the command line. + If you do not want this you can make 'wildoptions' empty + before calling getcompletion() and restore it afterwards. + If {type} is "cmdline", then the |cmdline-completion| result is returned. For example, to complete the possible values after a ":call" command: > @@ -3225,7 +3224,8 @@ getline({lnum} [, {end}]) < Can also be used as a |method|: > ComputeLnum()->getline() -< To get lines from another buffer see |getbufline()| +< To get lines from another buffer see |getbufline()| and + |getbufoneline()| getloclist({nr} [, {what}]) *getloclist()* Returns a |List| with all the entries in the location list for @@ -3627,6 +3627,19 @@ gettagstack([{winnr}]) *gettagstack()* Can also be used as a |method|: > GetWinnr()->gettagstack() + +gettext({text}) *gettext()* + Translate String {text} if possible. + This is mainly for use in the distributed Vim scripts. When + generating message translations the {text} is extracted by + xgettext, the translator can add the translated message in the + .po file and Vim will lookup the translation when gettext() is + called. + For {text} double quoted strings are preferred, because + xgettext does not understand escaping in single quoted + strings. + + getwininfo([{winid}]) *getwininfo()* Returns information about windows as a |List| with Dictionaries. @@ -4960,7 +4973,7 @@ match({expr}, {pat} [, {start} [, {count}]]) *match()* 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 + 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) @@ -5190,7 +5203,7 @@ matchfuzzy({list}, {str} [, {dict}]) *matchfuzzy()* :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']. > +< results in `['two one', 'one two']` . > :echo ['one two', 'two one']->matchfuzzy('two one', \ {'matchseq': 1}) < results in ['two one']. @@ -6067,6 +6080,14 @@ rand([{expr}]) *rand()* Can also be used as a |method|: > seed->rand() < + +readblob({fname}) *readblob()* + Read file {fname} in binary mode and return a |Blob|. + When the file can't be opened an error message is given and + the result is an empty |Blob|. + Also see |readfile()| and |writefile()|. + + *readdir()* readdir({directory} [, {expr}]) Return a list with file and directory names in {directory}. @@ -6101,6 +6122,7 @@ readdir({directory} [, {expr}]) Can also be used as a |method|: > GetDirName()->readdir() < + *readfile()* readfile({fname} [, {type} [, {max}]]) Read file {fname} and return a |List|, each line of the file @@ -6112,8 +6134,6 @@ readfile({fname} [, {type} [, {max}]]) - When the last line ends in a NL an extra empty list item is added. - No CR characters are removed. - When {type} contains "B" a |Blob| is returned with the binary - data of the file unmodified. Otherwise: - CR characters that appear before a NL are removed. - Whether the last line ends in a NL or not does not matter. @@ -6130,6 +6150,9 @@ readfile({fname} [, {type} [, {max}]]) Note that without {max} the whole file is read into memory. Also note that there is no recognition of encoding. Read a file into a buffer if you need to. + Deprecated (use |readblob()| instead): When {type} contains + "B" a |Blob| is returned with the binary data of the file + unmodified. When the file can't be opened an error message is given and the result is an empty list. Also see |writefile()|. @@ -6677,7 +6700,7 @@ searchcount([{options}]) *searchcount()* pos |List| `[lnum, col, off]` value when recomputing the result. this changes "current" result - value. see |cursor()|, |getpos() + value. see |cursor()|, |getpos()| (default: cursor's position) Can also be used as a |method|: > @@ -6833,18 +6856,24 @@ serverstart([{address}]) *serverstart()* |RPC| messages. Clients can send |API| commands to the returned address to control Nvim. - Returns the address string (may differ from the requested - {address}). - - - If {address} contains a colon ":" it is interpreted as - a TCP/IPv4/IPv6 address where the last ":" separates host - and port (empty or zero assigns a random port). - - Else it is interpreted as a named pipe or Unix domain socket - path. If there are no slashes it is treated as a name and - appended to a generated path. - - If {address} is empty it generates a path. - - Example named pipe: > + Returns the address string (which may differ from the + {address} argument, see below). + + - If {address} has a colon (":") it is a TCP/IPv4/IPv6 address + where the last ":" separates host and port (empty or zero + assigns a random port). + - Else {address} is the path to a named pipe (except on Windows). + - If {address} has no slashes ("/") it is treated as the + "name" part of a generated path in this format: > + stdpath("run").."/{name}.{pid}.{counter}" +< - If {address} is omitted the name is "nvim". > + :echo serverstart() + => /tmp/nvim.bram/oknANW/nvim.15430.5 + +< Example bash command to list all Nvim servers: > + ls ${XDG_RUNTIME_DIR:-${TMPDIR}nvim.${USER}}/*/nvim.*.0 + +< Example named pipe: > if has('win32') echo serverstart('\\.\pipe\nvim-pipe-1234') else @@ -6909,29 +6938,38 @@ setbufvar({buf}, {varname}, {val}) *setbufvar()* setcellwidths({list}) *setcellwidths()* Specify overrides for cell widths of character ranges. This - tells Vim how wide characters are, counted in screen cells. - This overrides 'ambiwidth'. Example: > - setcellwidths([[0xad, 0xad, 1], - \ [0x2194, 0x2199, 2]]) - -< *E1109* *E1110* *E1111* *E1112* *E1113* *E1114* - The {list} argument is a list of lists with each three - numbers. These three numbers are [low, high, width]. "low" - and "high" can be the same, in which case this refers to one - character. Otherwise it is the range of characters from "low" - to "high" (inclusive). "width" is either 1 or 2, indicating - the character width in screen cells. + tells Vim how wide characters are when displayed in the + terminal, counted in screen cells. The values override + 'ambiwidth'. Example: > + call setcellwidths([ + \ [0x111, 0x111, 1], + \ [0x2194, 0x2199, 2], + \ ]) + +< The {list} argument is a List of Lists with each three + numbers: [{low}, {high}, {width}]. *E1109* *E1110* + {low} and {high} can be the same, in which case this refers to + one character. Otherwise it is the range of characters from + {low} to {high} (inclusive). *E1111* *E1114* + Only characters with value 0x80 and higher can be used. + + {width} must be either 1 or 2, indicating the character width + in screen cells. *E1112* An error is given if the argument is invalid, also when a - range overlaps with another. - Only characters with value 0x100 and higher can be used. + range overlaps with another. *E1113* If the new value causes 'fillchars' or 'listchars' to become invalid it is rejected and an error is given. - To clear the overrides pass an empty list: > - setcellwidths([]); + To clear the overrides pass an empty {list}: > + call setcellwidths([]) + < You can use the script $VIMRUNTIME/tools/emoji_list.vim to see - the effect for known emoji characters. + the effect for known emoji characters. Move the cursor + through the text to check if the cell widths of your terminal + match with what Vim knows about each emoji. If it doesn't + look right you need to adjust the {list} argument. + setcharpos({expr}, {list}) *setcharpos()* Same as |setpos()| but uses the specified column number as the @@ -7052,6 +7090,8 @@ setline({lnum}, {text}) *setline()* {lnum} is used like with |getline()|. When {lnum} is just below the last line the {text} will be added below the last line. + {text} can be any type or a List of any type, each item is + converted to a String. If this succeeds, FALSE is returned. If this fails (most likely because {lnum} is invalid) TRUE is returned. @@ -7094,8 +7134,8 @@ setloclist({nr}, {list} [, {action} [, {what}]]) *setloclist()* GetLoclist()->setloclist(winnr) setmatches({list} [, {win}]) *setmatches()* - Restores a list of matches saved by |getmatches() for the - current window|. Returns 0 if successful, otherwise -1. All + Restores a list of matches saved by |getmatches()| for the + current window. Returns 0 if successful, otherwise -1. All current matches are cleared before the list is restored. See example for |getmatches()|. If {win} is specified, use the window with this number or @@ -7275,6 +7315,7 @@ setqflist({list} [, {action} [, {what}]]) *setqflist()* *setreg()* setreg({regname}, {value} [, {options}]) Set the register {regname} to {value}. + If {regname} is "" or "@", the unnamed register '"' is used. The {regname} argument is a string. {value} may be any value returned by |getreg()| or @@ -7848,6 +7889,21 @@ str2nr({string} [, {base}]) *str2nr()* Can also be used as a |method|: > GetText()->str2nr() + +strcharlen({string}) *strcharlen()* + The result is a Number, which is the number of characters + in String {string}. Composing characters are ignored. + |strchars()| can count the number of characters, counting + composing characters separately. + + Returns 0 if {string} is empty or on error. + + Also see |strlen()|, |strdisplaywidth()| and |strwidth()|. + + Can also be used as a |method|: > + GetText()->strcharlen() + + strcharpart({src}, {start} [, {len}]) *strcharpart()* Like |strpart()| but using character index and length instead of byte index and length. Composing characters are counted @@ -7862,12 +7918,14 @@ strcharpart({src}, {start} [, {len}]) *strcharpart()* Can also be used as a |method|: > GetText()->strcharpart(5) + strchars({string} [, {skipcc}]) *strchars()* The result is a Number, which is the number of characters in String {string}. When {skipcc} is omitted or zero, composing characters are counted separately. When {skipcc} set to 1, Composing characters are ignored. + |strcharlen()| always does this. Returns zero on error. @@ -7962,7 +8020,7 @@ stridx({haystack}, {needle} [, {start}]) *stridx()* 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 @@ -8106,7 +8164,7 @@ strwidth({string}) *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} + 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. @@ -8270,6 +8328,7 @@ synIDattr({synID}, {what} [, {mode}]) *synIDattr()* "underdotted" "1" if dotted underlined "underdashed" "1" if dashed underlined "strikethrough" "1" if struckthrough + "altfont" "1" if alternative font "nocombine" "1" if nocombine Returns an empty string on error. @@ -8932,7 +8991,12 @@ win_execute({id}, {command} [, {silent}]) *win_execute()* have unexpected side effects. Use |:noautocmd| if needed. Example: > call win_execute(winid, 'syntax enable') -< +< Doing the same with `setwinvar()` would not trigger + autocommands and not actually show syntax highlighting. + + When window {id} does not exist then no error is given and + an empty string is returned. + Can also be used as a |method|, the base is passed as the second argument: > GetCommand()->win_execute(winid) @@ -9013,6 +9077,7 @@ win_move_separator({nr}, {offset}) *win_move_separator()* FALSE otherwise. This will fail for the rightmost window and a full-width window, since it has no separator on the right. + Only works for the current tab page. *E1308* Can also be used as a |method|: > GetWinnr()->win_move_separator(offset) @@ -9027,6 +9092,7 @@ win_move_statusline({nr}, {offset}) *win_move_statusline()* 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. + Only works for the current tab page. Can also be used as a |method|: > GetWinnr()->win_move_statusline(offset) diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt index bed5cb26d7..990ba3d8fd 100644 --- a/runtime/doc/change.txt +++ b/runtime/doc/change.txt @@ -204,7 +204,6 @@ gR Enter Virtual Replace mode: Each character you type *v_S* {Visual}["x]S Delete the highlighted lines [into register x] and start insert (for {Visual} see |Visual-mode|). - *v_R* {Visual}["x]R Currently just like {Visual}["x]S. In a next version it might work differently. @@ -441,13 +440,13 @@ steps to make a numbered list. SHIFTING LINES LEFT OR RIGHT *shift-left-right* *<* -<{motion} Shift {motion} lines one 'shiftwidth' leftwards. + <{motion} Shift {motion} lines one 'shiftwidth' leftwards. If the 'shiftwidth' option is set to zero, the amount of indent is calculated at the first non-blank character in the line. *<<* -<< Shift [count] lines one 'shiftwidth' leftwards. + << Shift [count] lines one 'shiftwidth' leftwards. *v_<* {Visual}[count]< Shift the highlighted lines [count] 'shiftwidth' diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt index f4a17b1842..1c52b2d692 100644 --- a/runtime/doc/channel.txt +++ b/runtime/doc/channel.txt @@ -91,7 +91,7 @@ only bytes can be written to Nvim's own stderr. There are two ways to deal with this: - 1. To wait for the entire output, use |channel-buffered| mode. - - 2. To read line-by-line, use the following code: > + - 2. To read line-by-line, use the following code: >vim let s:lines = [''] func! s:on_event(job_id, data, event) dict let eof = (a:data == ['']) @@ -108,7 +108,7 @@ callbacks. Data can be sent to the channel using the |chansend()| function. Here is a simple example, echoing some data through a cat-process: -> +>vim function! s:OnEvent(id, data, event) dict let str = join(a:data, "\n") echomsg str @@ -119,7 +119,7 @@ simple example, echoing some data through a cat-process: Here is a example of setting a buffer to the result of grep, but only after all data has been processed: -> +>vim function! s:OnEvent(id, data, event) dict call nvim_buf_set_lines(2, 0, -1, v:true, a:data) endfunction @@ -142,7 +142,7 @@ However, change of PTY size can be signaled to the slave using |jobresize()|. See also |terminal-emulator|. Terminal characteristics (termios) for |:terminal| and PTY channels are copied -from the host TTY, or if Nvim is |--headless| it uses default values: > +from the host TTY, or if Nvim is |--headless| it uses default values: >vim :echo system('nvim --headless +"te stty -a" +"sleep 1" +"1,/^$/print" +q') ============================================================================== @@ -163,7 +163,7 @@ used as a channel. See also |--embed|. Call |stdioopen()| during |startup| to open the stdio channel as |channel-id| 1. Nvim's stderr is always available as |v:stderr|, a write-only bytes channel. -Example: > +Example: >vim func! OnEvent(id, data, event) if a:data == [""] quit @@ -172,7 +172,7 @@ Example: > endfunc call stdioopen({'on_stdin': 'OnEvent'}) < -Put this in `uppercase.vim` and run: > +Put this in `uppercase.vim` and run: >bash nvim --headless --cmd "source uppercase.vim" ============================================================================== @@ -223,7 +223,7 @@ start of the line. Here is an example for Unix. It starts a shell in the background and prompts for the next shell command. Output from the shell is displayed above the -prompt. > +prompt. >vim " Function handling a line of text that has been typed. func TextEntered(text) diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index b1013420fa..b4923b0d70 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -524,7 +524,6 @@ that see the '"' as part of their argument: :cexpr (and the like) :cdo (and the like) :command - :cscope (and the like) :debug :display :echo (and the like) @@ -566,7 +565,6 @@ followed by another Vim command: :cdo :cfdo :command - :cscope :debug :eval :folddoopen @@ -575,7 +573,6 @@ followed by another Vim command: :global :help :helpgrep - :lcscope :ldo :lfdo :lhelpgrep @@ -586,7 +583,6 @@ followed by another Vim command: :python :registers :read ! - :scscope :sign :terminal :vglobal @@ -694,7 +690,9 @@ Line numbers may be specified with: *:range* *{address}* 'T position of mark T (uppercase); when the mark is in another file it cannot be used in a range /{pattern}[/] the next line where {pattern} matches *:/* + also see |:range-pattern| below ?{pattern}[?] the previous line where {pattern} matches *:?* + also see |:range-pattern| below \/ the next line where the previously used search pattern matches \? the previous line where the previously used search @@ -702,11 +700,49 @@ Line numbers may be specified with: *:range* *{address}* \& the next line where the previously used substitute pattern matches + *:range-offset* 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. If there is nothing before the '+' or '-' then the current line is used. - + *:range-closed-fold* +When a line number after the comma is in a closed fold it is adjusted to the +last line of the fold, thus the whole fold is included. + +When a number is added this is done after the adjustment to the last line of +the fold. This means these lines are additionally included in the range. For +example: > + :3,4+2print +On this text: + 1 one ~ + 2 two ~ + 3 three ~ + 4 four FOLDED ~ + 5 five FOLDED ~ + 6 six ~ + 7 seven ~ + 8 eight ~ +Where lines four and five are a closed fold, ends up printing lines 3 to 7. +The 7 comes from the "4" in the range, which is adjusted to the end of the +closed fold, which is 5, and then the offset 2 is added. + +An example for subtracting (which isn't very useful): > + :2,4-1print +On this text: + 1 one ~ + 2 two ~ + 3 three FOLDED~ + 4 four FOLDED ~ + 5 five FOLDED ~ + 6 six FOLDED ~ + 7 seven ~ + 8 eight ~ +Where lines three to six are a closed fold, ends up printing lines 2 to 6. +The 6 comes from the "4" in the range, which is adjusted to the end of the +closed fold, which is 6, and then 1 is subtracted, then this is still in the +closed fold and the last line of that fold is used, which is 6. + + *:range-pattern* The "/" and "?" after {pattern} are required to separate the pattern from anything that follows. @@ -762,7 +798,7 @@ always be swapped then. Count and Range *N:* -When giving a count before entering ":", this is translated into: +When giving a count before entering ":", this is translated into: > :.,.+(count - 1) In words: The "count" lines at and after the cursor. Example: To delete three lines: > diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index 8fcd0fc1d0..1bdd13ac0c 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -30,7 +30,7 @@ ENVIRONMENT VARIABLES - detect a parent Nvim (use |$NVIM| instead) - Ignored if --listen is given. - Unset by |terminal| and |jobstart()| unless explicitly given by the "env" - option. Example: > + option. Example: >vim call jobstart(['foo'], { 'env': { 'NVIM_LISTEN_ADDRESS': v:servername } }) < @@ -107,7 +107,7 @@ internally and are no longer exposed as part of the API. Instead, use - *vim.lsp.diagnostic.set_virtual_text()* LSP FUNCTIONS -- *vim.lsp.range_code_action* Use |vim.lsp.buf.code_action()| with +- *vim.lsp.buf.range_code_action()* Use |vim.lsp.buf.code_action()| with the `range` parameter. - *vim.lsp.util.diagnostics_to_items()* Use |vim.diagnostic.toqflist()| instead. - *vim.lsp.util.set_qflist()* Use |setqflist()| instead. @@ -128,11 +128,6 @@ NORMAL COMMANDS OPTIONS - *cpo-<* *:menu-<special>* *:menu-special* *:map-<special>* *:map-special* `<>` notation is always enabled. -- *'cscopeverbose'* Enabled by default. Use |:silent| instead. -- *'exrc'* *'ex'* Security risk: downloaded files could include - a malicious .nvimrc or .exrc file. See 'secure'. - Recommended alternative: define an autocommand in your - |vimrc| to set options for a matching directory. - 'gdefault' Enables the |:substitute| flag 'g' by default. - *'fe'* 'fenc'+'enc' before Vim 6.0; no longer used. - *'highlight'* *'hl'* Names of builtin |highlight-groups| cannot be changed. diff --git a/runtime/doc/dev_style.txt b/runtime/doc/dev_style.txt index 77253e7831..b96b01dbff 100644 --- a/runtime/doc/dev_style.txt +++ b/runtime/doc/dev_style.txt @@ -8,7 +8,7 @@ Nvim style guide *dev-style* This is style guide for developers working on Nvim's source code. -License: CC-By 3.0 http://creativecommons.org/licenses/by/3.0/ +License: CC-By 3.0 https://creativecommons.org/licenses/by/3.0/ Type |gO| to see the table of contents. @@ -38,7 +38,7 @@ All header files should have `#define` guards to prevent multiple inclusion. The format of the symbol name should be `NVIM_<DIRECTORY>_<FILE>_H`. In foo/bar.h: -> +>c #ifndef NVIM_FOO_BAR_H #define NVIM_FOO_BAR_H @@ -71,7 +71,7 @@ C99 allows you to declare variables anywhere in a function. Declare them in as local a scope as possible, and as close to the first use as possible. This makes it easier for the reader to find the declaration and see what type the variable is and what it was initialized to. In particular, initialization -should be used instead of declaration and assignment, e.g. > +should be used instead of declaration and assignment, e.g. >c int i; i = f(); // BAD: initialization separate from declaration. @@ -110,7 +110,7 @@ Variable-length arrays can cause hard to detect stack overflows. Postincrement and Postdecrement ~ -Use postfix form (`i++`) in statements. > +Use postfix form (`i++`) in statements. >c for (int i = 0; i < 3; i++) { } int j = ++i; // OK: ++i is used as an expression. @@ -136,7 +136,7 @@ Use `const` pointers whenever possible. Avoid `const` on non-pointer parameter d before the "noun" (`int`). That said, while we encourage putting `const` first, we do not require it. - But be consistent with the code around you! > + But be consistent with the code around you! >c void foo(const char *p, int i); } @@ -176,21 +176,14 @@ Type unsigned signed Booleans ~ -Use `bool` to represent boolean values. > +Use `bool` to represent boolean values. >c int loaded = 1; // BAD: loaded should have type bool. -Variable declarations ~ - -Declare only one variable per line. > - - int i, j = 1 - - Conditions ~ -Don't use "yoda-conditions". Use at most one assignment per condition. > +Don't use "yoda-conditions". Use at most one assignment per condition. >c if (1 == x) { @@ -203,7 +196,7 @@ Function declarations ~ Every function must not have a separate declaration. -Function declarations are created by the gendeclarations.lua script. > +Function declarations are created by the gendeclarations.lua script. >c static void f(void); @@ -216,7 +209,7 @@ Function declarations are created by the gendeclarations.lua script. > General translation unit layout ~ The definitions of public functions precede the definitions of static -functions. > +functions. >c <HEADER> @@ -237,7 +230,7 @@ if .c file does not contain any static functions. Included file name consists of the .c file name without extension, preceded by the directory name relative to src/nvim. Name of the file containing static functions declarations ends with `.c.generated.h`, `*.h.generated.h` files -contain only non-static function declarations. > +contain only non-static function declarations. >c // src/nvim/foo.c file #include <stddef.h> @@ -281,7 +274,7 @@ comparisons, and structure alignment. `#pragma pack()` and `__declspec(align())`. - Use the `LL` or `ULL` suffixes as needed to create 64-bit constants. For - example: > + example: >c int64_t my_value = 0x123456789LL; uint64_t my_mask = 3ULL << 48; @@ -295,7 +288,7 @@ Use `sizeof(varname)` when you take the size of a particular variable. `sizeof(varname)` will update appropriately if someone changes the variable type either now or later. You may use `sizeof(type)` for code unrelated to any particular variable, such as code that manages an external or internal data -format where a variable of an appropriate C type is not convenient. > +format where a variable of an appropriate C type is not convenient. >c Struct data; memset(&data, 0, sizeof(data)); @@ -331,7 +324,7 @@ Give as descriptive a name as possible, within reason. Do not worry about saving horizontal space as it is far more important to make your code immediately understandable by a new reader. Do not use abbreviations that are ambiguous or unfamiliar to readers outside your project, and do not abbreviate -by deleting letters within a word. > +by deleting letters within a word. >c int price_count_reader; // No abbreviation. int num_errors; // "num" is a widespread convention. @@ -368,7 +361,7 @@ Typedef-ed structs and enums start with a capital letter and have a capital letter for each new word, with no underscores: `MyExcitingStruct`. Non-Typedef-ed structs and enums are all lowercase with underscores between -words: `struct my_exciting_struct` . > +words: `struct my_exciting_struct` . >c struct my_struct { ... @@ -383,7 +376,7 @@ instance: `my_exciting_local_variable`. Common Variable names ~ - For example: > + For example: >c string table_name; // OK: uses underscore. string tablename; // OK: all lowercase. @@ -393,7 +386,7 @@ instance: `my_exciting_local_variable`. Struct Variables ~ - Data members in structs should be named like regular variables. > + Data members in structs should be named like regular variables. >c struct url_table_properties { string name; @@ -413,7 +406,7 @@ Use a `k` followed by mixed case: `kDaysInAWeek`. All compile-time constants, whether they are declared locally or globally, follow a slightly different naming convention from other variables. Use a `k` -followed by words with uppercase first letters: > +followed by words with uppercase first letters: >c const int kDaysInAWeek = 7; @@ -423,7 +416,7 @@ Function names are all lowercase, with underscores between words. For instance: `my_exceptional_function()`. All functions in the same header file should have a common prefix. -In `os_unix.h`: > +In `os_unix.h`: >c void unix_open(const char *path); void unix_user_id(void); @@ -436,7 +429,7 @@ normal operation. Enumerator Names ~ -Enumerators should be named like constants: `kEnumName`. > +Enumerators should be named like constants: `kEnumName`. >c enum url_table_errors { kOK = 0, @@ -447,7 +440,7 @@ Enumerators should be named like constants: `kEnumName`. > Macro Names ~ -They're like this: `MY_MACRO_THAT_SCARES_CPP_DEVELOPERS`. > +They're like this: `MY_MACRO_THAT_SCARES_CPP_DEVELOPERS`. >c #define ROUND(x) ... #define PI_ROUNDED 5.0 @@ -468,7 +461,7 @@ Nvim uses Doxygen comments. Comment Style ~ -Use the `//`-style syntax only. > +Use the `//`-style syntax only. >c // This is a comment spanning // multiple lines @@ -496,7 +489,7 @@ Start each file with a description of its contents. mention in the `.c` that the documentation is in the `.h` file. Do not duplicate comments in both the `.h` and the `.c`. Duplicated - comments diverge. > + comments diverge. >c /// A brief description of this file. /// @@ -507,7 +500,7 @@ Start each file with a description of its contents. Struct Comments ~ Every struct definition should have accompanying comments that describes what -it is for and how it should be used. > +it is for and how it should be used. >c /// Window info stored with a buffer. /// @@ -529,7 +522,7 @@ it is for and how it should be used. > }; If the field comments are short, you can also put them next to the field. But -be consistent within one struct, and follow the necessary doxygen style. > +be consistent within one struct, and follow the necessary doxygen style. >c struct wininfo_S { WinInfo *wi_next; ///< Next entry or NULL for last entry. @@ -567,8 +560,7 @@ of a function describe operation. - If the function allocates memory that the caller must free. - Whether any of the arguments can be a null pointer. - If there are any performance implications of how a function is used. - - If the function is re-entrant. What are its synchronization assumptions? - > + - If the function is re-entrant. What are its synchronization assumptions? >c /// Brief description of the function. /// /// Detailed description. @@ -596,7 +588,7 @@ of a function describe operation. Note you should not just repeat the comments given with the function declaration, in the `.h` file or wherever. It's okay to recapitulate briefly what the function does, but the focus of the comments should be on - how it does it. > + how it does it. >c // Note that we don't use Doxygen comments here. Iterator *get_iterator(void *arg1, void *arg2) @@ -614,7 +606,7 @@ comments are required. Global Variables ~ All global variables should have a comment describing what they are and - what they are used for. For example: > + what they are used for. For example: >c /// The total number of tests cases that we run /// through in this regression test. @@ -630,7 +622,7 @@ interesting, or important parts of your code. Also, lines that are non-obvious should get a comment at the end of the line. These end-of-line comments should be separated from the code by 2 - spaces. Example: > + spaces. Example: >c // If we have enough memory, mmap the data portion too. mmap_budget = max<int64>(0, mmap_budget - index_->length()); @@ -643,7 +635,7 @@ interesting, or important parts of your code. function returns. If you have several comments on subsequent lines, it can often be more - readable to line them up: > + readable to line them up: >c do_something(); // Comment here so the comments line up. do_something_else_that_is_longer(); // Comment here so there are two spaces between @@ -659,7 +651,7 @@ interesting, or important parts of your code. When you pass in a null pointer, boolean, or literal integer values to functions, you should consider adding a comment about what they are, or make your code self-documenting by using constants. For example, compare: - > + >c bool success = calculate_something(interesting_value, 10, @@ -667,7 +659,7 @@ interesting, or important parts of your code. NULL); // What are these arguments?? < - versus: > + versus: >c bool success = calculate_something(interesting_value, 10, // Default base value. @@ -675,7 +667,7 @@ interesting, or important parts of your code. NULL); // No callback. < - Or alternatively, constants or self-describing variables: > + Or alternatively, constants or self-describing variables: >c const int kDefaultBaseValue = 10; const bool kFirstTimeCalling = false; @@ -690,7 +682,7 @@ interesting, or important parts of your code. Note that you should never describe the code itself. Assume that the person reading the code knows C better than you do, even though he or she - does not know what you are trying to do: > + does not know what you are trying to do: >c // Now go through the b array and make sure that if i occurs, // the next element is i+1. @@ -725,7 +717,7 @@ about the problem referenced by the `TODO`. The main purpose is to have a consistent `TODO` format that can be searched to find the person who can provide more details upon request. A `TODO` is not a commitment that the person referenced will fix the problem. Thus when you create a `TODO`, it is -almost always your name that is given. > +almost always your name that is given. >c // TODO(kl@gmail.com): Use a "*" here for concatenation operator. // TODO(Zeke): change this to use relations. @@ -789,72 +781,23 @@ example, `"\uFEFF"`, is the Unicode zero-width no-break space character, which would be invisible if included in the source as straight UTF-8. -Function Declarations and Definitions ~ - -Return type on the same line as function name, parameters on the same line if -they fit. - -Functions look like this: > - - ReturnType function_name(Type par_name1, Type par_name2) - { - do_something(); - ... - } - -If you have too much text to fit on one line: > - - ReturnType really_long_function_name(Type par_name1, Type par_name2, - Type par_name3) - { - do_something(); - ... - } - -or if you cannot fit even the first parameter (but only then): > - - ReturnType really_really_really_long_function_name( - Type par_name1, // 4 space indent - Type par_name2, - Type par_name3) - { - do_something(); // 2 space indent - ... - } - -Some points to note: - -- The open parenthesis is always on the same line as the function name. -- There is never a space between the function name and the open parenthesis. -- There is never a space between the parentheses and the parameters. -- The open curly brace is always on the next line. -- The close curly brace is always on the last line by itself. -- There should be a space between the close parenthesis and the open curly - brace. -- All parameters should be named, with identical names in the declaration and - implementation. -- All parameters should be aligned if possible. -- Default indentation is 2 spaces. -- Wrapped parameters have a 4 space indent. - - Function Calls ~ On one line if it fits; otherwise, wrap arguments at the parenthesis. -Function calls have the following format: > +Function calls have the following format: >c bool retval = do_something(argument1, argument2, argument3); If the arguments do not all fit on one line, they should be broken up onto multiple lines, with each subsequent line aligned with the first argument. Do -not add spaces after the open paren or before the close paren: > +not add spaces after the open paren or before the close paren: >c bool retval = do_something(averyveryveryverylongargument1, argument2, argument3); If the function has many arguments, consider having one per line if this makes -the code more readable: > +the code more readable: >c bool retval = do_something(argument1, argument2, @@ -862,7 +805,7 @@ the code more readable: > argument4); Arguments may optionally all be placed on subsequent lines, with one line per -argument: > +argument: >c if (...) { ... @@ -886,7 +829,7 @@ place but with one space after the `{` and one space before the `}` If the braced list follows a name (e.g. a type or variable name), format as if the `{}` were the parentheses of a function call with that name. If there is -no name, assume a zero-length name. > +no name, assume a zero-length name. >c struct my_struct m = { // Here, you could also break before {. superlongvariablename1, @@ -896,18 +839,6 @@ no name, assume a zero-length name. > interiorwrappinglist2 } }; -Conditionals ~ - -Don't use spaces inside parentheses. > - - if (condition) { // no spaces inside parentheses - ... // 2 space indent. - } else if (...) { // The else goes on the same line as the closing brace. - ... - } else { - ... - } - Loops and Switch Statements ~ Annotate non-trivial fall-through between cases. @@ -915,7 +846,7 @@ Annotate non-trivial fall-through between cases. If not conditional on an enumerated value, switch statements should always have a `default` case (in the case of an enumerated value, the compiler will warn you if any values are not handled). If the default case should never -execute, simply `assert`: > +execute, simply `assert`: >c switch (var) { case 0: @@ -928,45 +859,12 @@ execute, simply `assert`: > assert(false); } -Pointer Expressions ~ - -No spaces around period or arrow. Pointer operators do not have trailing -spaces. - -The following are examples of correctly-formatted pointer and reference -expressions: > - - x = *p; - p = &x; - x = r.y; - x = r->y; - -Note that: - - - There are no spaces around the period or arrow when accessing a member. - - Pointer operators have no space after the * or &. - -Boolean Expressions ~ - -When you have a boolean expression that is longer than the standard line -length, keep operators at the start of the line. > - - if (this_one_thing > this_other_thing - && a_third_thing == a_fourth_thing - && yet_another && last_one) { - ... - } - -Also note that you should always use the punctuation operators, such as `&&` -and `~`, rather than the word operators, such as `and` and `compl`. - - Return Values ~ Do not needlessly surround the `return` expression with parentheses. Use parentheses in `return expr`; only where you would use them in `x = -expr;`. > +expr;`. >c return result; return (some_long_condition && another_condition); @@ -980,12 +878,12 @@ Horizontal Whitespace ~ Use of horizontal whitespace depends on location. General ~ -> +>c int x[] = { 0 }; // Spaces inside braces for braced-init-list. < Variables ~ -> +>c int long_variable = 0; // Don't align assignments. int i = 1; @@ -1002,14 +900,12 @@ Use of horizontal whitespace depends on location. Operators ~ -> +>c x = 0; // Assignment operators always have spaces around // them. x = -5; // No spaces separating unary operators and their x++; // arguments. if (x && !y) - ... - i = (int)d; // No spaces after a cast operator. < Vertical Whitespace ~ diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt index 1ba6ae757b..ff48ae3e26 100644 --- a/runtime/doc/develop.txt +++ b/runtime/doc/develop.txt @@ -28,11 +28,9 @@ The Neo bits of Nvim should make it a better Vim, without becoming a completely different editor. - In matters of taste, prefer Vim/Unix tradition. If there is no relevant Vim/Unix tradition, consider the "common case". -- A feature that people do not know about is a useless feature. Don't add - obscure features, or at least add hints in documentation that they exist. -- There is no limit to the features that can be added. Selecting new features - is based on (1) what users ask for, (2) how much effort it takes to - implement and (3) someone actually implementing it. +- There is no limit to the features that can be added. Select new features + based on (1) what users ask for, (2) how much effort it takes to implement + and (3) someone actually implementing it. - Backwards compatibility is a feature. The RPC API in particular should never break. @@ -48,7 +46,7 @@ NVIM IS... WELL DOCUMENTED *design-documented* NVIM IS... FAST AND SMALL *design-speed-size* -Keep Nvim small and fast. +Keep Nvim small and fast. This directly affects versatility and usability. - Computers are becoming faster and bigger each year. Vim can grow too, but no faster than computers are growing. Keep Vim usable on older systems. - Many users start Vim from a shell very often. Startup time must be short. @@ -57,7 +55,8 @@ Keep Nvim small and fast. - Don't forget that some people use Vim over a slow connection. Minimize the communication overhead. - Vim is a component among other components. Don't turn it into a massive - application, but have it work well together with other programs. + application, but have it work well together with other programs + ("composability"). NVIM IS... MAINTAINABLE *design-maintain* @@ -119,7 +118,7 @@ reflects whether Python support is working. *provider-reload* Sometimes a GUI or other application may want to force a provider to "reload". To reload a provider, undefine its "loaded" flag, then use -|:runtime| to reload it: > +|:runtime| to reload it: >vim :unlet g:loaded_clipboard_provider :runtime autoload/provider/clipboard.vim @@ -185,6 +184,7 @@ Docstring format: - Limited markdown is supported. - List-items start with `-` (useful to nest or "indent") - Use `<pre>` for code samples. + Code samples can be annotated as `vim` or `lua` Example: the help for |nvim_open_win()| is generated from a docstring defined in src/nvim/api/win_config.c like this: > @@ -193,7 +193,7 @@ in src/nvim/api/win_config.c like this: > /// ... /// /// Example (Lua): window-relative float - /// <pre> + /// <pre>lua /// vim.api.nvim_open_win(0, false, /// {relative='win', row=3, col=3, width=12, height=3}) /// </pre> @@ -223,6 +223,7 @@ Docstring format: - Limited markdown is supported. - List-items start with `-` (useful to nest or "indent") - Use `<pre>` for code samples. + Code samples can be annotated as `vim` or `lua` Example: the help for |vim.paste()| is generated from a docstring decorating vim.paste in runtime/lua/vim/_editor.lua like this: > @@ -231,7 +232,7 @@ vim.paste in runtime/lua/vim/_editor.lua like this: > --- (such as the |TUI|) pastes text into the editor. --- --- Example: To remove ANSI color codes when pasting: - --- <pre> + --- <pre>lua --- vim.paste = (function() --- local overridden = vim.paste --- ... @@ -248,13 +249,25 @@ vim.paste in runtime/lua/vim/_editor.lua like this: > LUA *dev-lua* - Keep the core Lua modules |lua-stdlib| simple. Avoid elaborate OOP or - pseudo-OOP designs. Plugin authors just want functions to call, they don't - want to learn a big, fancy inheritance hierarchy. Thus avoid specialized - objects; tables or values are usually better. + pseudo-OOP designs. Plugin authors just want functions to call, not a big, + fancy inheritance hierarchy. +- Avoid requiring or returning special objects in the Nvim stdlib. Plain + tables or values are easier to serialize, easier to construct from literals, + easier to inspect and print, and inherently compatible with all Lua plugins. + (This guideline doesn't apply to opaque, non-data objects like `vim.cmd`.) API *dev-api* +- Avoid "mutually exclusive" parameters--via constraints or limitations, if + necessary. For example nvim_create_autocmd() has mutually exclusive + "callback" and "command" args; but the "command" arg could be eliminated by + simply not supporting Vimscript function names, and treating a string + "callback" arg as an Ex command (which can call Vimscript functions). The + "buffer" arg could also be eliminated by treating a number "pattern" as + a buffer number. + + *dev-api-naming* Use this format to name new RPC |API| functions: nvim_{thing}_{action}_{arbitrary-qualifiers} diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index 828093ddd4..7066a3739a 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -68,11 +68,11 @@ The "severity" key in a diagnostic is one of the values defined in Functions that take a severity as an optional parameter (e.g. |vim.diagnostic.get()|) accept one of two forms: -1. A single |vim.diagnostic.severity| value: > +1. A single |vim.diagnostic.severity| value: >lua vim.diagnostic.get(0, { severity = vim.diagnostic.severity.WARN }) -2. A table with a "min" or "max" key (or both): > +2. A table with a "min" or "max" key (or both): >lua vim.diagnostic.get(0, { severity = { min = vim.diagnostic.severity.WARN } }) @@ -107,7 +107,7 @@ Nvim provides these handlers by default: "virtual_text", "signs", and *diagnostic-handlers-example* The example below creates a new handler that notifies the user of diagnostics -with |vim.notify()|: > +with |vim.notify()|: >lua -- It's good practice to namespace custom handlers to avoid collisions vim.diagnostic.handlers["my/notify"] = { @@ -135,7 +135,7 @@ In this example, there is nothing to do when diagnostics are hidden, so we omit the "hide" function. Existing handlers can be overridden. For example, use the following to only -show a sign for the highest severity diagnostic on a given line: > +show a sign for the highest severity diagnostic on a given line: >lua -- Create a custom namespace. This will aggregate signs from all other -- namespaces and only show the one with the highest severity on a @@ -185,7 +185,7 @@ 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 -highlights), use the |:highlight| command: > +highlights), use the |:highlight| command: >vim highlight DiagnosticError guifg="BrightRed" < @@ -209,6 +209,11 @@ DiagnosticHint Used as the base highlight group. Other Diagnostic highlights link to this by default (except Underline) + *hl-DiagnosticOk* +DiagnosticOk + Used as the base highlight group. + Other Diagnostic highlights link to this by default (except Underline) + *hl-DiagnosticVirtualTextError* DiagnosticVirtualTextError Used for "Error" diagnostic virtual text. @@ -225,6 +230,10 @@ DiagnosticVirtualTextInfo DiagnosticVirtualTextHint Used for "Hint" diagnostic virtual text. + *hl-DiagnosticVirtualTextOk* +DiagnosticVirtualTextOk + Used for "Ok" diagnostic virtual text. + *hl-DiagnosticUnderlineError* DiagnosticUnderlineError Used to underline "Error" diagnostics. @@ -241,6 +250,10 @@ DiagnosticUnderlineInfo DiagnosticUnderlineHint Used to underline "Hint" diagnostics. + *hl-DiagnosticUnderlineOk* +DiagnosticUnderlineOk + Used to underline "Ok" diagnostics. + *hl-DiagnosticFloatingError* DiagnosticFloatingError Used to color "Error" diagnostic messages in diagnostics float. @@ -258,6 +271,10 @@ DiagnosticFloatingInfo DiagnosticFloatingHint Used to color "Hint" diagnostic messages in diagnostics float. + *hl-DiagnosticFloatingOk* +DiagnosticFloatingOk + Used to color "Ok" diagnostic messages in diagnostics float. + *hl-DiagnosticSignError* DiagnosticSignError Used for "Error" signs in sign column. @@ -274,12 +291,16 @@ DiagnosticSignInfo DiagnosticSignHint Used for "Hint" signs in sign column. + *hl-DiagnosticSignOk* +DiagnosticSignOk + Used for "Ok" signs in sign column. + ============================================================================== SIGNS *diagnostic-signs* Signs are defined for each diagnostic severity. The default text for each sign is the first letter of the severity name (for example, "E" for ERROR). Signs -can be customized using the following: > +can be customized using the following: >vim sign define DiagnosticSignError text=E texthl=DiagnosticSignError linehl= numhl= sign define DiagnosticSignWarn text=W texthl=DiagnosticSignWarn linehl= numhl= @@ -299,7 +320,7 @@ DiagnosticChanged After diagnostics have changed. When used from Lua, the new diagnostics are passed to the autocmd callback in the "data" table. -Example: > +Example: >lua vim.api.nvim_create_autocmd('DiagnosticChanged', { callback = function(args) @@ -320,12 +341,12 @@ config({opts}, {namespace}) *vim.diagnostic.config()* |vim.diagnostic.show()|). Ephemeral configuration has highest priority, followed by namespace configuration, and finally global configuration. - For example, if a user enables virtual text globally with > + For example, if a user enables virtual text globally with >lua vim.diagnostic.config({ virtual_text = true }) < - and a diagnostic producer sets diagnostics with > + and a diagnostic producer sets diagnostics with >lua vim.diagnostic.set(ns, 0, diagnostics, { virtual_text = false }) < @@ -365,16 +386,21 @@ config({opts}, {namespace}) *vim.diagnostic.config()* the beginning of the virtual text. • prefix: (string) Prepend diagnostic message with prefix. + • suffix: (string or function) Append diagnostic + message with suffix. If a function, it must have the + signature (diagnostic) -> string, where {diagnostic} + is of type |diagnostic-structure|. This can be used + to render an LSP diagnostic error code. • 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: > + the text used to display the diagnostic. Example: >lua - function(diagnostic) - if diagnostic.severity == vim.diagnostic.severity.ERROR then - return string.format("E: %s", diagnostic.message) + function(diagnostic) + if diagnostic.severity == vim.diagnostic.severity.ERROR then + return string.format("E: %s", diagnostic.message) + end + return diagnostic.message end - return diagnostic.message - end < • signs: (default true) Use signs for diagnostics. @@ -426,7 +452,7 @@ fromqflist({list}) *vim.diagnostic.fromqflist()* |getloclist()|. Return: ~ - array of diagnostics |diagnostic-structure| + Diagnostic [] array of |diagnostic-structure| get({bufnr}, {opts}) *vim.diagnostic.get()* Get current diagnostics. @@ -441,7 +467,7 @@ get({bufnr}, {opts}) *vim.diagnostic.get()* • severity: See |diagnostic-severity|. Return: ~ - (table) A list of diagnostic items |diagnostic-structure|. + Diagnostic [] table A list of diagnostic items |diagnostic-structure|. get_namespace({namespace}) *vim.diagnostic.get_namespace()* Get namespace metadata. @@ -462,37 +488,39 @@ get_next({opts}) *vim.diagnostic.get_next()* Get the next diagnostic closest to the cursor position. Parameters: ~ - • {opts} (table) See |vim.diagnostic.goto_next()| + • {opts} (table|nil) See |vim.diagnostic.goto_next()| Return: ~ - (table) Next diagnostic + Diagnostic|nil Next diagnostic get_next_pos({opts}) *vim.diagnostic.get_next_pos()* Return the position of the next diagnostic in the current buffer. Parameters: ~ - • {opts} (table) See |vim.diagnostic.goto_next()| + • {opts} (table|nil) See |vim.diagnostic.goto_next()| Return: ~ - (table) Next diagnostic position as a (row, col) tuple. + table|false Next diagnostic position as a (row, col) tuple or false if + no next diagnostic. get_prev({opts}) *vim.diagnostic.get_prev()* Get the previous diagnostic closest to the cursor position. Parameters: ~ - • {opts} (table) See |vim.diagnostic.goto_next()| + • {opts} nil|table See |vim.diagnostic.goto_next()| Return: ~ - (table) Previous diagnostic + Diagnostic|nil Previous diagnostic get_prev_pos({opts}) *vim.diagnostic.get_prev_pos()* Return the position of the previous diagnostic in the current buffer. Parameters: ~ - • {opts} (table) See |vim.diagnostic.goto_next()| + • {opts} (table|nil) See |vim.diagnostic.goto_next()| Return: ~ - (table) Previous diagnostic position as a (row, col) tuple. + table|false Previous diagnostic position as a (row, col) tuple or + false if there is no prior diagnostic goto_next({opts}) *vim.diagnostic.goto_next()* Move to the next diagnostic. @@ -519,7 +547,7 @@ goto_prev({opts}) *vim.diagnostic.goto_prev()* Move to the previous diagnostic in the current buffer. Parameters: ~ - • {opts} (table) See |vim.diagnostic.goto_next()| + • {opts} (table|nil) See |vim.diagnostic.goto_next()| hide({namespace}, {bufnr}) *vim.diagnostic.hide()* Hide currently displayed diagnostics. @@ -532,11 +560,23 @@ hide({namespace}, {bufnr}) *vim.diagnostic.hide()* |vim.diagnostic.disable()|. Parameters: ~ - • {namespace} (number|nil) Diagnostic namespace. When omitted, hide - diagnostics from all namespaces. + • {namespace} (number|nil) Diagnostic namespace. When omitted, hide diagnostics from all + namespaces. • {bufnr} (number|nil) Buffer number, or 0 for current buffer. When omitted, hide diagnostics in all buffers. +is_disabled({bufnr}, {namespace}) *vim.diagnostic.is_disabled()* + Check whether diagnostics are disabled in a given buffer. + + Parameters: ~ + • {bufnr} (number|nil) Buffer number, or 0 for current buffer. + • {namespace} (number|nil) Diagnostic namespace. When omitted, checks if all diagnostics are + disabled in {bufnr}. Otherwise, only checks if + diagnostics from {namespace} are disabled. + + Return: ~ + (boolean) + *vim.diagnostic.match()* match({str}, {pat}, {groups}, {severity_map}, {defaults}) Parse a diagnostic from a string. @@ -546,12 +586,12 @@ match({str}, {pat}, {groups}, {severity_map}, {defaults}) WARNING filename:27:3: Variable 'foo' does not exist < - This can be parsed into a diagnostic |diagnostic-structure| with: > + This can be parsed into a diagnostic |diagnostic-structure| with: >lua - local s = "WARNING filename:27:3: Variable 'foo' does not exist" - local pattern = "^(%w+) %w+:(%d+):(%d+): (.+)$" - local groups = { "severity", "lnum", "col", "message" } - vim.diagnostic.match(s, pattern, groups, { WARNING = vim.diagnostic.WARN }) + local s = "WARNING filename:27:3: Variable 'foo' does not exist" + local pattern = "^(%w+) %w+:(%d+):(%d+): (.+)$" + local groups = { "severity", "lnum", "col", "message" } + vim.diagnostic.match(s, pattern, groups, { WARNING = vim.diagnostic.WARN }) < Parameters: ~ @@ -566,8 +606,8 @@ match({str}, {pat}, {groups}, {severity_map}, {defaults}) default to 0 and "severity" defaults to ERROR. Return: ~ - diagnostic |diagnostic-structure| or `nil` if {pat} fails to match - {str}. + Diagnostic|nil: |diagnostic-structure| or `nil` if {pat} fails to + match {str}. open_float({opts}, {...}) *vim.diagnostic.open_float()* Show diagnostics in a floating window. @@ -618,9 +658,12 @@ open_float({opts}, {...}) *vim.diagnostic.open_float()* {prefix} is a string, it is prepended to each diagnostic in the window with no highlight. Overrides the setting from |vim.diagnostic.config()|. + • suffix: Same as {prefix}, but appends the text to the + diagnostic instead of prepending it. Overrides the setting + from |vim.diagnostic.config()|. Return: ~ - tuple ({float_bufnr}, {win_id}) + number|nil, number|nil: ({float_bufnr}, {win_id}) reset({namespace}, {bufnr}) *vim.diagnostic.reset()* Remove all diagnostics from the given namespace. @@ -631,8 +674,8 @@ reset({namespace}, {bufnr}) *vim.diagnostic.reset()* re-displayed, use |vim.diagnostic.hide()|. Parameters: ~ - • {namespace} (number|nil) Diagnostic namespace. When omitted, remove - diagnostics from all namespaces. + • {namespace} (number|nil) Diagnostic namespace. When omitted, remove diagnostics from all + namespaces. • {bufnr} (number|nil) Remove diagnostics for the given buffer. When omitted, diagnostics are removed for all buffers. @@ -680,8 +723,8 @@ show({namespace}, {bufnr}, {diagnostics}, {opts}) Display diagnostics for the given namespace and buffer. Parameters: ~ - • {namespace} (number|nil) Diagnostic namespace. When omitted, show - diagnostics from all namespaces. + • {namespace} (number|nil) Diagnostic namespace. When omitted, show diagnostics from all + namespaces. • {bufnr} (number|nil) Buffer number, or 0 for current buffer. When omitted, show diagnostics in all buffers. • {diagnostics} (table|nil) The diagnostics to display. When omitted, @@ -701,6 +744,6 @@ toqflist({diagnostics}) *vim.diagnostic.toqflist()* • {diagnostics} (table) List of diagnostics |diagnostic-structure|. Return: ~ - array of quickfix list items |setqflist-what| + table[] of quickfix list items |setqflist-what| vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl: diff --git a/runtime/doc/diff.txt b/runtime/doc/diff.txt index 9c5792dd43..382d025d3c 100644 --- a/runtime/doc/diff.txt +++ b/runtime/doc/diff.txt @@ -137,6 +137,10 @@ Otherwise they are set to their default value: 'foldmethod' "manual" 'foldcolumn' 0 +'foldenable' will most-likely be reset to off. That is when 'foldmethod' is +restored to "manual". The folds themselves are not cleared but they should +not show up, resetting 'foldenable' is the best way to do that. + ============================================================================== 2. Viewing diffs *view-diffs* @@ -388,6 +392,11 @@ mode, so that a CTRL-Z doesn't end the text on DOS. The `redraw!` command may not be needed, depending on whether executing a shell command shows something on the display or not. +If the 'diffexpr' expression starts with s: or |<SID>|, then it is replaced +with the script ID (|local-function|). Example: > + set diffexpr=s:MyDiffExpr() + set diffexpr=<SID>SomeDiffExpr() +< *E810* *E97* Vim will do a test if the diff output looks alright. If it doesn't, you will get an error message. Possible causes: @@ -401,7 +410,7 @@ to see more messages. The self-installing Vim for MS-Windows includes a diff program. If you don't have it you might want to download a diff.exe. For example from -http://gnuwin32.sourceforge.net/packages/diffutils.htm. +https://gnuwin32.sourceforge.net/packages/diffutils.htm. USING PATCHES *diff-patchexpr* @@ -439,4 +448,9 @@ evaluating 'patchexpr'. This hopefully avoids that files in the current directory are accidentally patched. Vim will also delete files starting with v:fname_in and ending in ".rej" and ".orig". +If the 'patchexpr' expression starts with s: or |<SID>|, then it is replaced +with the script ID (|local-function|). Example: > + set patchexpr=s:MyPatchExpr() + set patchexpr=<SID>SomePatchExpr() +< vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/digraph.txt b/runtime/doc/digraph.txt index eb3de0111f..ce0a929bc1 100644 --- a/runtime/doc/digraph.txt +++ b/runtime/doc/digraph.txt @@ -156,7 +156,7 @@ These are the RFC1345 digraphs for the one-byte characters. See the output of ":digraphs" for the others. EURO - + *euro* *euro-digraph* Exception: RFC1345 doesn't specify the euro sign. In Vim the digraph =e was added for this. Note the difference between latin1, where the digraph Cu is used for the currency sign, and latin9 (iso-8859-15), where the digraph =e is diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index 21a30ca429..f77db5fab3 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -345,9 +345,9 @@ escaped with a backslash. Wildcards in {file} are expanded, but as with file completion, 'wildignore' and 'suffixes' apply. Which wildcards are supported depends on the system. These are the common ones: - ? matches one character - * matches anything, including nothing - ** matches anything, including nothing, recurses into directories + `?` matches one character + `*` matches anything, including nothing + `**` matches anything, including nothing, recurses into directories [abc] match 'a', 'b' or 'c' To avoid the special meaning of the wildcards prepend a backslash. However, @@ -406,7 +406,7 @@ external command, by putting an equal sign right after the first backtick, e.g.: > :e `=tempname()` The expression can contain just about anything, thus this can also be used to -avoid the special meaning of '"', '|', '%' and '#'. However, 'wildignore' +avoid the special meaning of '"', "|", '%' and '#'. However, 'wildignore' does apply like to other wildcards. Environment variables in the expression are expanded when evaluating the @@ -423,9 +423,8 @@ Note that such expressions are only supported in places where a filename is expected as an argument to an Ex-command. *++opt* *[++opt]* -The [++opt] argument can be used to force the value of 'fileformat', -'fileencoding' or 'binary' to a value for one command, and to specify the -behavior for bad characters. The form is: > +The [++opt] argument can be used to set some options for one command, and to +specify the behavior for bad characters. The form is: > ++{optname} Or: > ++{optname}={value} @@ -436,11 +435,11 @@ Where {optname} is one of: *++ff* *++enc* *++bin* *++nobin* *++edit* bin or binary sets 'binary' nobin or nobinary resets 'binary' bad specifies behavior for bad characters - edit for |:read| only: keep option values as if editing - a file + edit for |:read|: keeps options as if editing a file + p for |:write|: creates the file's parent directory -{value} cannot contain white space. It can be any valid value for these -options. Examples: > +{value} cannot contain whitespace. It can be any valid value for the options. +Examples: > :e ++ff=unix This edits the same file again with 'fileformat' set to "unix". > @@ -450,9 +449,24 @@ This writes the current buffer to "newfile" in latin1 format. The message given when writing a file will show "[converted]" when 'fileencoding' or the value specified with ++enc differs from 'encoding'. -There may be several ++opt arguments, separated by white space. They must all +There may be several ++opt arguments, separated by whitespace. They must all appear before any |+cmd| argument. + *++p* +The "++p" flag creates the parent directory of the file if it does not exist. +For example if you edit "foo/bar/file.txt", the ":write ++p" command creates +"foo/bar/" if necessary before writing the file. > + + :edit foo/bar/file.txt + :write ++p + +If you want :write (without "++p") to always create missing parent +directories, add this autocmd to your config: > + + " Auto-create parent directories (except for URIs "://"). + au BufWritePre,FileWritePre * if @% !~# '\(://\)' | call mkdir(expand('<afile>:p:h'), 'p') | endif +< + *++bad* The argument of "++bad=" specifies what happens with characters that can't be converted and illegal bytes. It can be one of three things: @@ -545,6 +559,44 @@ Before editing binary, executable or Vim script files you should set the option. This will avoid the use of 'fileformat'. Without this you risk that single <NL> characters are unexpectedly replaced with <CR><NL>. +END OF LINE AND END OF FILE *eol-and-eof* + +Vim has several options to control the file format: + 'fileformat' the <EOL> style: Unix, DOS, Mac + 'endofline' whether the last line ends with a <EOL> + 'endoffile' whether the file ends with a CTRL-Z + 'fixendofline' whether to fix eol and eof + +The first three values are normally detected automatically when reading the +file and are used when writing the text to a file. While editing the buffer +it looks like every line has a line ending and the CTRL-Z isn't there (an +exception is when 'binary' is set, it works differently then). + +The 'fixendofline' option can be used to choose what to write. You can also +change the option values to write the file differently than how it was read. + +Here are some examples how to use them. + +If you want files in Unix format (every line NL terminated): > + setl ff=unix fixeol +You should probably do this on any Unix-like system. Also modern MS-Windows +systems tend to work well with this. It is recommended to always use this +format for Vim scripts. + +If you want to use an old MS-DOS file in a modern environment, fixing line +endings and dropping CTRL-Z, but keeping the <CR><NL> style <EOL>: > + setl ff=dos fixeol +This is useful for many MS-Windows programs, they regularly expect the +<CR><NL> line endings. + +If you want to drop the final <EOL> and add a final CTRL-Z (e.g. for an old +system like CP/M): > + setl ff=dos nofixeol noeol eof + +If you want to preserve the fileformat exactly as-is, including any final +<EOL> and final CTRL-Z: > + setl nofixeol + ============================================================================== 3. The argument list *argument-list* *arglist* @@ -842,7 +894,7 @@ changed. This is done for all *.c files. Example: > :args *.[ch] :argdo %s/\<my_foo\>/My_Foo/ge | update -This changes the word "my_foo" to "My_Foo" in all *.c and *.h files. The "e" +This changes the word "my_foo" to "My_Foo" in all "*.c" and "*.h" files. The "e" flag is used for the ":substitute" command to avoid an error for files where "my_foo" isn't used. ":update" writes the file only if changes were made. @@ -855,11 +907,13 @@ Note: When the 'write' option is off, you are not able to write any file. *E502* *E503* *E504* *E505* *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 - another reason why the file can't be written. - For ++opt see |++opt|, but only ++bin, ++nobin, ++ff - and ++enc are effective. + the normal way to save changes to a file. Fails when + 'readonly' is set or when there is another reason why + the file can't be written, such as when the parent + directory doesn't exist (use |++p| to avoid that). + For ++opt see |++opt|, but only ++p, ++bin, ++nobin, + ++ff and ++enc are effective. + :w[rite]! [++opt] Like ":write", but forcefully write when 'readonly' is set or there is another reason why writing was @@ -1222,8 +1276,8 @@ unmodified. For MS-Windows you can modify the filters that are used in the browse dialog. By setting the g:browsefilter or b:browsefilter variables, you can change the filters globally or locally to the buffer. The variable is set to -a string in the format "{filter label}\t{pattern};{pattern}\n" where {filter -label} is the text that appears in the "Files of Type" comboBox, and {pattern} +a string in the format "{filter label}\t{pattern};{pattern}\n" where "{filter +label}" is the text that appears in the "Files of Type" comboBox, and {pattern} is the pattern which filters the filenames. Several patterns can be given, separated by ';'. @@ -1271,6 +1325,7 @@ exist, the next-higher scope in the hierarchy applies. :cd[!] {path} Change the current directory to {path}. If {path} is relative, it is searched for in the directories listed in |'cdpath'|. + Clear any window-local directory. Does not change the meaning of an already opened file, because its full path name is remembered. Files from the |arglist| may change though! @@ -1521,8 +1576,8 @@ which is slightly different. There are three different types of searching: 1) Downward search: *starstar* - Downward search uses the wildcards '*', '**' and possibly others - supported by your operating system. '*' and '**' are handled inside Vim, + Downward search uses the wildcards "*", "**" and possibly others + supported by your operating system. "*" and "**" are handled inside Vim, so they work on all operating systems. Note that "**" only acts as a special wildcard when it is at the start of a name. @@ -1530,12 +1585,12 @@ There are three different types of searching: search pattern this would be ".*". Note that the "." is not used for file searching. - '**' is more sophisticated: + "**" is more sophisticated: - It ONLY matches directories. - It matches up to 30 directories deep by default, so you can use it to search an entire directory tree - The maximum number of levels matched can be given by appending a number - to '**'. + to "**". Thus '/usr/**2' can match: > /usr /usr/include @@ -1546,14 +1601,14 @@ There are three different types of searching: .... < It does NOT match '/usr/include/g++/std' as this would be three levels. - The allowed number range is 0 ('**0' is removed) to 100 + The allowed number range is 0 ("**0" is removed) to 100 If the given number is smaller than 0 it defaults to 30, if it's bigger than 100 then 100 is used. The system also has a limit on the path length, usually 256 or 1024 bytes. - - '**' can only be at the end of the path or be followed by a path + - "**" can only be at the end of the path or be followed by a path separator or by a number and a path separator. - You can combine '*' and '**' in any order: > + You can combine "*" and "**" in any order: > /usr/**/sys/* /usr/*tory/sys/** /usr/**2/sys/* @@ -1610,4 +1665,32 @@ There are three different types of searching: currently work with 'path' items that contain a URL or use the double star with depth limiter (/usr/**2) or upward search (;) notations. +============================================================================== +12. Trusted Files *trust* + +Nvim has the ability to execute arbitrary code through the 'exrc' option. In +order to prevent executing code from untrusted sources, Nvim has the concept of +"trusted files". An untrusted file will not be executed without the user's +consent, and a user can permanently mark a file as trusted or untrusted using +the |:trust| command or the |vim.secure.read()| function. + + *:trust* *E5570* +:trust [++deny] [++remove] [{file}] + + Manage files in the trust database. Without any options + or arguments, :trust adds the file associated with the + current buffer to the trust database, along with the + SHA256 hash of its contents. + + [++deny] marks the file associated with the current + buffer (or {file}, if given) as denied; no prompts will + be displayed to the user and the file will never be + executed. + + [++remove] removes the file associated with the current + buffer (or {file}, if given) from the trust database. + Future attempts to read the file in a secure setting + (i.e. with 'exrc' or |vim.secure.read()|) will prompt + the user if the file is trusted. + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/editorconfig.txt b/runtime/doc/editorconfig.txt new file mode 100644 index 0000000000..04a057e5ff --- /dev/null +++ b/runtime/doc/editorconfig.txt @@ -0,0 +1,91 @@ +*editorconfig.txt* Nvim + + + NVIM REFERENCE MANUAL + + +EditorConfig integration *editorconfig* + +Nvim natively supports EditorConfig. When a file is opened, Nvim searches +upward through all of the parent directories of that file looking for +".editorconfig" files. Each of these is parsed and any properties that match +the opened file are applied. + +For more information on EditorConfig, see https://editorconfig.org/. + + *g:editorconfig* *b:editorconfig* +EditorConfig integration can be disabled globally by adding >lua + + vim.g.editorconfig = false +< +to the user's |init.lua| file (or the Vimscript equivalent to |init.vim|). It +can also be disabled per-buffer by setting the |b:editorconfig| buffer-local +variable to `false`. + +When Nvim finds a valid .editorconfig file it will store the applied +properties in the buffer variable |b:editorconfig| if it was not already set to +`false` by the user. + + *editorconfig-properties* +The following properties are supported by default: + + *editorconfig_root* +root If "true", then stop searching for .editorconfig files + in parent directories. This property must be at the + top-level of the .editorconfig file (i.e. it must not + be within a glob section). + + *editorconfig_charset* +charset One of "utf-8", "utf-8-bom", "latin1", "utf-16be", or + "utf-16le". Sets the 'fileencoding' and 'bomb' + options. + + *editorconfig_end_of_line* +end_of_line One of "lf", "crlf", or "cr". These correspond to + setting 'fileformat' to "unix", "dos", or "mac", + respectively. + + *editorconfig_indent_style* +indent_style One of "tab" or "space". Sets the 'expandtab' option. + + *editorconfig_indent_size* +indent_size A number indicating the size of a single indent. + Alternatively, use the value "tab" to use the value of + the tab_width property. Sets the 'shiftwidth' and + 'softtabstop'. + + *editorconfig_insert_final_newline* +insert_final_newline "true" or "false" to ensure the file always has a + trailing newline as its last byte. Sets the + 'fixendofline' and 'endofline' options. + + *editorconfig_max_line_length* +max_line_length A number indicating the maximum length of a single + line. Sets the 'textwidth' option. + + *editorconfig_tab_width* +tab_width The display size of a single tab character. Sets the + 'tabstop' option. + + *editorconfig_trim_trailing_whitespace* +trim_trailing_whitespace + When "true", trailing whitespace is automatically + removed when the buffer is written. + + *editorconfig-custom-properties* +New properties can be added by adding a new entry to the "properties" table. +The table key is a property name and the value is a callback function which +accepts the number of the buffer to be modified, the value of the property +in the .editorconfig file, and (optionally) a table containing all of the +other properties and their values (useful for properties which depend on other +properties). The value is always a string and must be coerced if necessary. +Example: >lua + + require('editorconfig').properties.foo = function(bufnr, val, opts) + if opts.charset and opts.charset ~= "utf-8" then + error("foo can only be set when charset is utf-8", 0) + end + vim.b[bufnr].foo = val + end +< + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 6a9fb6d03c..58759a6053 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -4,7 +4,7 @@ VIM REFERENCE MANUAL by Bram Moolenaar -Expression evaluation *expression* *expr* *E15* *eval* +Expression evaluation *vimscript* *expression* *expr* *E15* *eval* Using expressions is introduced in chapter 41 of the user manual |usr_41.txt|. @@ -229,13 +229,13 @@ is not available it returns zero or the default value you specify: > List concatenation ~ - + *list-concatenation* Two lists can be concatenated with the "+" operator: > :let longlist = mylist + [5, 6] :let mylist += [7, 8] -To prepend or append an item turn the item into a list by putting [] around -it. To change a list in-place see |list-modification| below. +To prepend or append an item, turn the item into a list by putting [] around +it. To change a list in-place, refer to |list-modification| below. Sublist ~ @@ -840,8 +840,8 @@ Example: > All expressions within one level are parsed from left to right. +------------------------------------------------------------------------------ expr1 *expr1* *ternary* *E109* ------ expr2 ? expr1 : expr1 @@ -867,8 +867,8 @@ You should always put a space before the ':', otherwise it can be mistaken for use in a variable such as "a:1". +------------------------------------------------------------------------------ expr2 and expr3 *expr2* *expr3* ---------------- expr3 || expr3 .. logical OR *expr-barbar* expr4 && expr4 .. logical AND *expr-&&* @@ -906,8 +906,8 @@ This is valid whether "b" has been defined or not. The second clause will only be evaluated if "b" has been defined. +------------------------------------------------------------------------------ expr4 *expr4* ------ expr5 {cmp} expr5 @@ -1010,8 +1010,9 @@ can be matched like an ordinary character. Examples: "foo\nbar" =~ "\\n" evaluates to 0 +------------------------------------------------------------------------------ expr5 and expr6 *expr5* *expr6* ---------------- + expr6 + expr6 Number addition, |List| or |Blob| concatenation *expr-+* expr6 - expr6 Number subtraction *expr--* expr6 . expr6 String concatenation *expr-.* @@ -1064,8 +1065,9 @@ None of these work for |Funcref|s. . and % do not work for Float. *E804* +------------------------------------------------------------------------------ expr7 *expr7* ------ + ! expr7 logical NOT *expr-!* - expr7 unary minus *expr-unary--* + expr7 unary plus *expr-unary-+* @@ -1082,8 +1084,9 @@ These three can be repeated and mixed. Examples: --9 == 9 +------------------------------------------------------------------------------ expr8 *expr8* ------ + This expression is either |expr9| or a sequence of the alternatives below, in any order. E.g., these are all possible: expr8[expr1].name @@ -1234,8 +1237,9 @@ When using the lambda form there must be no white space between the } and the *expr9* +------------------------------------------------------------------------------ number ------- + number number constant *expr-number* *0x* *hex-number* *0o* *octal-number* *binary-number* @@ -1297,9 +1301,9 @@ function. Example: > < 7.853981633974483e-01 - +------------------------------------------------------------------------------ string *string* *String* *expr-string* *E114* ------- + "string" string constant *expr-quote* Note that double quotes are used. @@ -1338,16 +1342,17 @@ encodings. Use "\u00ff" to store character 255 correctly as UTF-8. Note that "\000" and "\x00" force the end of the string. +------------------------------------------------------------------------------ blob-literal *blob-literal* *E973* ------------- Hexadecimal starting with 0z or 0Z, with an arbitrary number of bytes. The sequence must be an even number of hex characters. Example: > :let b = 0zFF00ED015DAF +------------------------------------------------------------------------------ literal-string *literal-string* *E115* ---------------- + 'string' string constant *expr-'* Note that single quotes are used. @@ -1361,8 +1366,9 @@ to be doubled. These two commands are equivalent: > if a =~ '\s*' +------------------------------------------------------------------------------ option *expr-option* *E112* *E113* ------- + &option option value, local value if possible &g:option global option value &l:option local option value @@ -1376,8 +1382,9 @@ and there is no buffer-local or window-local value, the global value is used anyway. +------------------------------------------------------------------------------ register *expr-register* *@r* --------- + @r contents of register 'r' The result is the contents of the named register, as a single string. @@ -1394,8 +1401,9 @@ nesting *expr-nesting* *E110* (expr1) nested expression +------------------------------------------------------------------------------ environment variable *expr-env* --------------------- + $VAR environment variable The String value of any environment variable. When it is not defined, the @@ -1420,20 +1428,23 @@ The first one probably doesn't echo anything, the second echoes the $shell variable (if your shell supports it). +------------------------------------------------------------------------------ internal variable *expr-variable* ------------------ + variable internal variable See below |internal-variables|. +------------------------------------------------------------------------------ function call *expr-function* *E116* *E118* *E119* *E120* -------------- + function(expr1, ...) function call See below |functions|. +------------------------------------------------------------------------------ lambda expression *expr-lambda* *lambda* ------------------ + {args -> expr1} lambda expression A lambda expression creates a new unnamed function which returns the result of @@ -1524,7 +1535,7 @@ specified by what is prepended: |tabpage-variable| t: Local to the current tab page. |global-variable| g: Global. |local-variable| l: Local to a function. -|script-variable| s: Local to a |:source|'ed Vim script. +|script-variable| s: Local to a |:source|d Vim script. |function-argument| a: Function argument (only inside a function). |vim-variable| v: Global, predefined by Vim. @@ -1707,17 +1718,13 @@ v:charconvert_to Only valid while evaluating the 'charconvert' option. *v:cmdarg* *cmdarg-variable* -v:cmdarg This variable is used for two purposes: - 1. The extra arguments given to a file read/write command. - Currently these are "++enc=" and "++ff=". This variable is - set before an autocommand event for a file read/write - command is triggered. There is a leading space to make it - possible to append this variable directly after the - read/write command. Note: The "+cmd" argument isn't - included here, because it will be executed anyway. - 2. When printing a PostScript file with ":hardcopy" this is - the argument for the ":hardcopy" command. This can be used - in 'printexpr'. +v:cmdarg + The extra arguments ("++p", "++enc=", "++ff=") given to a file + read/write command. This is set before an autocommand event + for a file read/write command is triggered. There is a + leading space to make it possible to append this variable + directly after the read/write command. Note: "+cmd" isn't + included here, because it will be executed anyway. *v:collate* *collate-variable* v:collate The current locale setting for collation order of the runtime @@ -1780,7 +1787,7 @@ v:exiting Exit code, or |v:null| before invoking the |VimLeavePre| and |VimLeave| autocmds. See |:q|, |:x| and |:cquit|. Example: > :au VimLeave * echo "Exit value is " .. v:exiting - +< *v:echospace* *echospace-variable* v:echospace Number of screen cells that can be used for an `:echo` message in the last screen line before causing the |hit-enter-prompt|. @@ -1915,17 +1922,16 @@ v:fname_in The name of the input file. Valid while evaluating: 'charconvert' file to be converted 'diffexpr' original file 'patchexpr' original file - 'printexpr' file to be printed And set to the swap file name for |SwapExists|. *v:fname_out* *fname_out-variable* v:fname_out The name of the output file. Only valid while evaluating: option used for ~ - 'charconvert' resulting converted file (*) + 'charconvert' resulting converted file [1] 'diffexpr' output of diff 'patchexpr' resulting patched file - (*) When doing conversion for a write command (e.g., ":w + [1] When doing conversion for a write command (e.g., ":w file") it will be equal to v:fname_in. When doing conversion for a read command (e.g., ":e file") it will be a temporary file and different from v:fname_in. @@ -1995,10 +2001,10 @@ v:lc_time The current locale setting for time messages of the runtime command. See |multi-lang|. *v:lnum* *lnum-variable* -v:lnum Line number for the 'foldexpr' |fold-expr|, 'formatexpr' and - 'indentexpr' expressions, tab page number for 'guitablabel' - and 'guitabtooltip'. Only valid while one of these - expressions is being evaluated. Read-only when in the +v:lnum Line number for the 'foldexpr' |fold-expr|, 'formatexpr', + 'indentexpr' and 'statuscolumn' expressions, tab page number + for 'guitablabel' and 'guitabtooltip'. Only valid while one of + these expressions is being evaluated. Read-only when in the |sandbox|. *v:lua* *lua-variable* @@ -2132,6 +2138,10 @@ v:register The name of the register in effect for the current normal mode '*' or '+'. Also see |getreg()| and |setreg()| + *v:relnum* *relnum-variable* +v:relnum Relative line number for the 'statuscolumn' expression. + Read-only. + *v:scrollstart* *scrollstart-variable* v:scrollstart String describing the script or function that caused the screen to scroll up. It's only set when it is empty, thus the @@ -2141,9 +2151,11 @@ v:scrollstart String describing the script or function that caused the hit-enter prompt. *v:servername* *servername-variable* -v:servername Primary listen-address of the current Nvim instance, the first - item returned by |serverlist()|. Can be set by |--listen| or - |$NVIM_LISTEN_ADDRESS| (deprecated) at startup. +v:servername Primary listen-address of Nvim, the first item returned by + |serverlist()|. Usually this is the named pipe created by Nvim + at |startup| or given by |--listen| (or the deprecated + |$NVIM_LISTEN_ADDRESS| env var). + See also |serverstart()| |serverstop()|. Read-only. @@ -2283,6 +2295,13 @@ v:version Vim version number: major version times 100 plus minor :if has("nvim-0.2.1") < + *v:virtnum* *virtnum-variable* +v:virtnum Virtual line number for the 'statuscolumn' expression. + Negative when drawing the status column for virtual lines, zero + when drawing an actual buffer line, and positive when drawing + the wrapped part of a buffer line. + Read-only. + *v:vim_did_enter* *vim_did_enter-variable* v:vim_did_enter 0 during startup, 1 just before |VimEnter|. Read-only. @@ -2662,6 +2681,8 @@ text... [depth] is relevant when locking a |List| or |Dictionary|. It specifies how deep the locking goes: + 0 Lock the variable {name} but not its + value. 1 Lock the |List| or |Dictionary| itself, cannot add or remove items, but can still change their values. @@ -2675,7 +2696,14 @@ text... |Dictionary|, one level deeper. The default [depth] is 2, thus when {name} is a |List| or |Dictionary| the values cannot be changed. - *E743* + + Example with [depth] 0: > + let mylist = [1, 2, 3] + lockvar 0 mylist + let mylist[0] = 77 " OK + call add(mylist, 4] " OK + let mylist = [7, 8, 9] " Error! +< *E743* For unlimited depth use [!] and omit [depth]. However, there is a maximum depth of 100 to catch loops. @@ -2696,6 +2724,8 @@ text... Unlock the internal variable {name}. Does the opposite of |:lockvar|. + No error is given if {name} does not exist. + :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. @@ -3175,7 +3205,7 @@ this pending exception or command is discarded. For examples see |throw-catch| and |try-finally|. -NESTING OF TRY CONDITIONALS *try-nesting* +NESTING OF TRY CONDITIONALS *try-nesting* Try conditionals can be nested arbitrarily. That is, a complete try conditional can be put into the try block, a catch clause, or the finally diff --git a/runtime/doc/filetype.txt b/runtime/doc/filetype.txt index ac54a6b6ca..a53c287d48 100644 --- a/runtime/doc/filetype.txt +++ b/runtime/doc/filetype.txt @@ -148,6 +148,7 @@ variables can be used to overrule the filetype used for certain extensions: *.fs g:filetype_fs |ft-forth-syntax| *.i g:filetype_i |ft-progress-syntax| *.inc g:filetype_inc + *.lsl g:filetype_lsl *.m g:filetype_m |ft-mathematica-syntax| *.mod g:filetype_mod *.p g:filetype_p |ft-pascal-syntax| @@ -175,15 +176,13 @@ 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 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. The following explains the legacy Vim mechanism (enabled -if |g:do_legacy_filetype| is set). For Nvim's default mechanism, see -|vim.filetype.add()|. +to add it. The recommended way is to use |vim.filetype.add()| to add it to +Nvim's builtin filetype detection mechanism. If you want to handle the +detection manually, proceed as follows: A. If you want to overrule all default file type checks. This works by writing one file for each filetype. The disadvantage is that - there can be many files. The advantage is that you can simply drop this + there can be many files. The advantage is that you can simply drop this file in the right directory to make it work. *ftdetect* 1. Create your user runtime directory. You would normally use the first @@ -273,28 +272,14 @@ D. If your filetype can only be detected by inspecting the contents of the means that your rules override the default rules in $VIMRUNTIME/scripts.vim. - *remove-filetype* -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 -to add/remove the same filetypes, consider writing the filetype.vim and -scripts.vim files in a runtime directory that is used for everybody. Check -the 'runtimepath' for a directory to use. If there isn't one, set -'runtimepath' in the |system-vimrc|. Be careful to keep the default -directories! - - *g:do_legacy_filetype* -To disable Nvim's default filetype detection and revert to Vim's legacy -filetype detection, add the following to your |init.vim|: > - let g:do_legacy_filetype = 1 -< *g:did_load_filetypes* + *remove-filetype* +If a file type is detected that is wrong for you, you can set 'filetype' to +a non-existing name such as `ignored` to avoid that it will be set later anyway. + + *g:did_load_filetypes* The builtin filetype detection provided by Nvim can be disabled by setting -the `did_load_filetypes` global variable. If this variable exists, neither -the default `$VIMRUNTIME/filetype.lua` nor the legacy `$VIMRUNTIME/filetype.vim` -will run. +the `did_load_filetypes` global variable. If this variable exists, the default +`$VIMRUNTIME/filetype.lua` will not run. *plugin-details* The "plugin" directory can be in any of the directories in the 'runtimepath' @@ -549,7 +534,7 @@ used. For example, to set the dialect to a default of "fblite" but still allow for any #lang directive overrides, use the following command: > - let g:freebasic_lang = "fblite" + let g:freebasic_lang = "fblite" GIT COMMIT *ft-gitcommit-plugin* diff --git a/runtime/doc/fold.txt b/runtime/doc/fold.txt index e97a0a6459..35a3be35fb 100644 --- a/runtime/doc/fold.txt +++ b/runtime/doc/fold.txt @@ -116,6 +116,11 @@ method can be very slow! Try to avoid the "=", "a" and "s" return values, since Vim often has to search backwards for a line for which the fold level is defined. This can be slow. +If the 'foldexpr' expression starts with s: or |<SID>|, then it is replaced +with the script ID (|local-function|). Examples: > + set foldexpr=s:MyFoldExpr() + set foldexpr=<SID>SomeFoldExpr() +< An example of using "a1" and "s1": For a multi-line C comment, a line containing "/*" would return "a1" to start a fold, and a line containing "*/" would return "s1" to end the fold after that line: > @@ -521,6 +526,11 @@ The resulting line is truncated to fit in the window, it never wraps. When there is room after the text, it is filled with the character specified by 'fillchars'. +If the 'foldtext' expression starts with s: or |<SID>|, then it is replaced +with the script ID (|local-function|). Examples: > + set foldtext=s:MyFoldText() + set foldtext=<SID>SomeFoldText() +< Note that backslashes need to be used for characters that the ":set" command handles differently: Space, backslash and double-quote. |option-backslash| @@ -576,6 +586,11 @@ line is folded, it cannot be displayed there. Many movement commands handle a sequence of folded lines like an empty line. For example, the "w" command stops once in the first column. +When starting a search in a closed fold it will not find a match in the +current fold. It's like a forward search always starts from the end of the +closed fold, while a backwards search starts from the start of the closed +fold. + When in Insert mode, the cursor line is never folded. That allows you to see what you type! diff --git a/runtime/doc/ft_ada.txt b/runtime/doc/ft_ada.txt index e4ac37a86e..3321228009 100644 --- a/runtime/doc/ft_ada.txt +++ b/runtime/doc/ft_ada.txt @@ -486,25 +486,25 @@ You can optionally install the following extra plug-ins. They work well with Ada and enhance the ability of the Ada mode: backup.vim - http://www.vim.org/scripts/script.php?script_id=1537 + https://www.vim.org/scripts/script.php?script_id=1537 Keeps as many backups as you like so you don't have to. rainbow_parenthesis.vim - http://www.vim.org/scripts/script.php?script_id=1561 + https://www.vim.org/scripts/script.php?script_id=1561 Very helpful since Ada uses only '(' and ')'. nerd_comments.vim - http://www.vim.org/scripts/script.php?script_id=1218 + https://www.vim.org/scripts/script.php?script_id=1218 Excellent commenting and uncommenting support for almost any programming language. matchit.vim - http://www.vim.org/scripts/script.php?script_id=39 + https://www.vim.org/scripts/script.php?script_id=39 '%' jumping for any language. The normal '%' jump only works for '{}' style languages. The Ada mode will set the needed search patterns. taglist.vim - http://www.vim.org/scripts/script.php?script_id=273 + https://www.vim.org/scripts/script.php?script_id=273 Source code explorer sidebar. There is a patch for Ada available. The GNU Ada Project distribution (http://gnuada.sourceforge.net) of Vim diff --git a/runtime/doc/ft_rust.txt b/runtime/doc/ft_rust.txt index 3a0bf2293e..a5d5b558dc 100644 --- a/runtime/doc/ft_rust.txt +++ b/runtime/doc/ft_rust.txt @@ -1,70 +1,70 @@ -*ft_rust.txt* For Vim version 8.1. Last change: 2017 Nov 02 +*ft_rust.txt* Nvim This is documentation for the Rust filetype plugin. ============================================================================== -CONTENTS *rust* +CONTENTS *rust* -1. Introduction |rust-intro| -2. Settings |rust-settings| -3. Commands |rust-commands| -4. Mappings |rust-mappings| +1. Introduction |rust-intro| +2. Settings |rust-settings| +3. Commands |rust-commands| +4. Mappings |rust-mappings| ============================================================================== -INTRODUCTION *rust-intro* +INTRODUCTION *rust-intro* This plugin provides syntax and supporting functionality for the Rust filetype. ============================================================================== -SETTINGS *rust-settings* +SETTINGS *rust-settings* This plugin has a few variables you can define in your vimrc that change the behavior of the plugin. - *g:rustc_path* + *g:rustc_path* g:rustc_path~ Set this option to the path to rustc for use in the |:RustRun| and |:RustExpand| commands. If unset, "rustc" will be located in $PATH: > let g:rustc_path = $HOME .. "/bin/rustc" < - *g:rustc_makeprg_no_percent* + *g:rustc_makeprg_no_percent* g:rustc_makeprg_no_percent~ Set this option to 1 to have 'makeprg' default to "rustc" instead of "rustc %": > let g:rustc_makeprg_no_percent = 1 < - *g:rust_conceal* + *g:rust_conceal* g:rust_conceal~ Set this option to turn on the basic |conceal| support: > let g:rust_conceal = 1 < - *g:rust_conceal_mod_path* + *g:rust_conceal_mod_path* g:rust_conceal_mod_path~ Set this option to turn on |conceal| for the path connecting token "::": > let g:rust_conceal_mod_path = 1 < - *g:rust_conceal_pub* + *g:rust_conceal_pub* g:rust_conceal_pub~ Set this option to turn on |conceal| for the "pub" token: > let g:rust_conceal_pub = 1 < - *g:rust_recommended_style* + *g:rust_recommended_style* g:rust_recommended_style~ - Set this option to enable vim indentation and textwidth settings to - conform to style conventions of the rust standard library (i.e. use 4 - spaces for indents and sets 'textwidth' to 99). This option is enabled + Set this option to enable vim indentation and textwidth settings to + conform to style conventions of the rust standard library (i.e. use 4 + spaces for indents and sets 'textwidth' to 99). This option is enabled by default. To disable it: > let g:rust_recommended_style = 0 < - *g:rust_fold* + *g:rust_fold* g:rust_fold~ Set this option to turn on |folding|: > let g:rust_fold = 1 @@ -76,39 +76,39 @@ g:rust_fold~ 2 Braced blocks are folded. 'foldlevel' is left at the global value (all folds are closed by default). - *g:rust_bang_comment_leader* + *g:rust_bang_comment_leader* g:rust_bang_comment_leader~ Set this option to 1 to preserve the leader on multi-line doc comments using the /*! syntax: > let g:rust_bang_comment_leader = 1 < - *g:ftplugin_rust_source_path* + *g:ftplugin_rust_source_path* g:ftplugin_rust_source_path~ Set this option to a path that should be prepended to 'path' for Rust source files: > let g:ftplugin_rust_source_path = $HOME .. '/dev/rust' < - *g:rustfmt_command* + *g:rustfmt_command* g:rustfmt_command~ Set this option to the name of the "rustfmt" executable in your $PATH. If not specified it defaults to "rustfmt" : > let g:rustfmt_command = 'rustfmt' < - *g:rustfmt_autosave* + *g:rustfmt_autosave* g:rustfmt_autosave~ Set this option to 1 to run |:RustFmt| automatically when saving a buffer. If not specified it defaults to 0 : > let g:rustfmt_autosave = 0 < - *g:rustfmt_fail_silently* + *g:rustfmt_fail_silently* g:rustfmt_fail_silently~ Set this option to 1 to prevent "rustfmt" from populating the |location-list| with errors. If not specified it defaults to 0: > let g:rustfmt_fail_silently = 0 < - *g:rustfmt_options* + *g:rustfmt_options* g:rustfmt_options~ Set this option to a string of options to pass to "rustfmt". The write-mode is already set to "overwrite". If not specified it @@ -116,13 +116,13 @@ g:rustfmt_options~ let g:rustfmt_options = '' < - *g:rust_playpen_url* + *g:rust_playpen_url* g:rust_playpen_url~ Set this option to override the URL for the playpen to use: > let g:rust_playpen_url = 'https://play.rust-lang.org/' < - *g:rust_shortener_url* + *g:rust_shortener_url* g:rust_shortener_url~ Set this option to override the URL for the URL shortener: > let g:rust_shortener_url = 'https://is.gd/' @@ -130,9 +130,9 @@ g:rust_shortener_url~ ============================================================================== -COMMANDS *rust-commands* +COMMANDS *rust-commands* -:RustRun [args] *:RustRun* +:RustRun [args] *:RustRun* :RustRun! [rustc-args] [--] [args] Compiles and runs the current file. If it has unsaved changes, it will be saved first using |:update|. If the current file is @@ -150,7 +150,7 @@ COMMANDS *rust-commands* If |g:rustc_path| is defined, it is used as the path to rustc. Otherwise it is assumed rustc can be found in $PATH. -:RustExpand [args] *:RustExpand* +:RustExpand [args] *:RustExpand* :RustExpand! [TYPE] [args] Expands the current file using --pretty and displays the results in a new split. If the current file has unsaved @@ -169,7 +169,7 @@ COMMANDS *rust-commands* If |g:rustc_path| is defined, it is used as the path to rustc. Otherwise it is assumed rustc can be found in $PATH. -:RustEmitIr [args] *:RustEmitIr* +:RustEmitIr [args] *:RustEmitIr* Compiles the current file to LLVM IR and displays the results in a new split. If the current file has unsaved changes, it will be saved first using |:update|. If the current file is an @@ -180,7 +180,7 @@ COMMANDS *rust-commands* If |g:rustc_path| is defined, it is used as the path to rustc. Otherwise it is assumed rustc can be found in $PATH. -:RustEmitAsm [args] *:RustEmitAsm* +:RustEmitAsm [args] *:RustEmitAsm* Compiles the current file to assembly and displays the results in a new split. If the current file has unsaved changes, it will be saved first using |:update|. If the current file is an @@ -191,7 +191,7 @@ COMMANDS *rust-commands* If |g:rustc_path| is defined, it is used as the path to rustc. Otherwise it is assumed rustc can be found in $PATH. -:RustPlay *:RustPlay* +:RustPlay *:RustPlay* This command will only work if you have web-api.vim installed (available at https://github.com/mattn/webapi-vim). It sends the current selection, or if nothing is selected, the entirety of the @@ -204,7 +204,7 @@ COMMANDS *rust-commands* |g:rust_shortener_url| is the base URL for the shortener, by default "https://is.gd/" -:RustFmt *:RustFmt* +:RustFmt *:RustFmt* Runs |g:rustfmt_command| on the current buffer. If |g:rustfmt_options| is set then those will be passed to the executable. @@ -214,12 +214,12 @@ COMMANDS *rust-commands* |g:rustfmt_command|. If |g:rustfmt_fail_silently| is set to 1 then it will not populate the |location-list|. -:RustFmtRange *:RustFmtRange* +:RustFmtRange *:RustFmtRange* Runs |g:rustfmt_command| with selected range. See |:RustFmt| for any other information. ============================================================================== -MAPPINGS *rust-mappings* +MAPPINGS *rust-mappings* This plugin defines mappings for |[[| and |]]| to support hanging indents. diff --git a/runtime/doc/ft_sql.txt b/runtime/doc/ft_sql.txt index 03d9082aab..21a244e67a 100644 --- a/runtime/doc/ft_sql.txt +++ b/runtime/doc/ft_sql.txt @@ -37,9 +37,10 @@ The SQL ftplugin provides a number of options to assist with file navigation. +------------------------------------------------------------------------------ 1.1 Matchit *sql-matchit* ------------ -The matchit plugin (http://www.vim.org/scripts/script.php?script_id=39) + +The matchit plugin (https://www.vim.org/scripts/script.php?script_id=39) provides many additional features and can be customized for different languages. The matchit plugin is configured by defining a local buffer variable, b:match_words. Pressing the % key while on various @@ -85,8 +86,9 @@ The following keywords are supported: > returns +------------------------------------------------------------------------------ 1.2 Text Object Motions *sql-object-motions* ------------------------ + Vim has a number of predefined keys for working with text |object-motions|. This filetype plugin attempts to translate these keys to maps which make sense for the SQL language. @@ -99,8 +101,9 @@ file): > [] move backwards to the previous 'end' +------------------------------------------------------------------------------ 1.3 Predefined Object Motions *sql-predefined-objects* ------------------------------ + Most relational databases support various standard features, tables, indices, triggers and stored procedures. Each vendor also has a variety of proprietary objects. The next set of maps have been created to help move between these @@ -166,8 +169,9 @@ with comments: > +------------------------------------------------------------------------------ 1.4 Macros *sql-macros* ----------- + Vim's feature to find macro definitions, |'define'|, is supported using this regular expression: > \c\<\(VARIABLE\|DECLARE\|IN\|OUT\|INOUT\)\> @@ -230,8 +234,9 @@ The majority of people work with only one vendor's database product, it would be nice to specify a default in your |init.vim|. +------------------------------------------------------------------------------ 2.1 SQLSetType *sqlsettype* *SQLSetType* --------------- + For the people that work with many different databases, it is nice to be able to flip between the various vendors rules (indent, syntax) on a per buffer basis, at any time. The ftplugin/sql.vim file defines this function: > @@ -259,8 +264,9 @@ of available Vim script names: > :SQL<Tab><space><Tab> +------------------------------------------------------------------------------ 2.2 SQLGetType *sqlgettype* *SQLGetType* --------------- + At anytime you can determine which SQL dialect you are using by calling the SQLGetType command. The ftplugin/sql.vim file defines this function: > SQLGetType @@ -269,8 +275,9 @@ This will echo: > Current SQL dialect in use:sqlanywhere +------------------------------------------------------------------------------ 2.3 SQL Dialect Default *sql-type-default* ------------------------ + As mentioned earlier, the default syntax rules for Vim is based on Oracle (PL/SQL). You can override this default by placing one of the following in your |init.vim|: > @@ -296,7 +303,7 @@ exist. 3. Adding new SQL Dialects *sql-adding-dialects* If you begin working with a SQL dialect which does not have any customizations -available with the default Vim distribution you can check http://www.vim.org +available with the default Vim distribution you can check https://www.vim.org to see if any customization currently exist. If not, you can begin by cloning an existing script. Read |filetype-plugins| for more details. @@ -325,8 +332,9 @@ highlight rules. The dynamic mode populates the popups with data retrieved directly from a database. This includes, table lists, column lists, procedures names and more. +------------------------------------------------------------------------------ 4.1 Static Mode *sql-completion-static* ---------------- + The static popups created contain items defined by the active syntax rules while editing a file with a filetype of SQL. The plugin defines (by default) various maps to help the user refine the list of items to be displayed. @@ -399,11 +407,12 @@ Here are some examples of the entries which are pulled from the syntax files: > - Integer, Char, Varchar, Date, DateTime, Timestamp, ... +------------------------------------------------------------------------------ 4.2 Dynamic Mode *sql-completion-dynamic* ----------------- + Dynamic mode populates the popups with data directly from a database. In order for the dynamic feature to be enabled you must have the dbext.vim -plugin installed, (http://vim.sourceforge.net/script.php?script_id=356). +plugin installed, (https://vim.sourceforge.net/script.php?script_id=356). Dynamic mode is used by several features of the SQL completion plugin. After installing the dbext plugin see the dbext-tutorial for additional @@ -448,8 +457,8 @@ necessary to clear the plugins cache. The default map for this is: > imap <buffer> <C-C>R <C-\><C-O>:call sqlcomplete#Map('ResetCache')<CR><C-X><C-O> +------------------------------------------------------------------------------ 4.3 SQL Tutorial *sql-completion-tutorial* ----------------- This tutorial is designed to take you through the common features of the SQL completion plugin so that: > @@ -462,8 +471,8 @@ First, create a new buffer: > :e tutorial.sql -Static features ---------------- +Static features ~ + To take you through the various lists, simply enter insert mode, hit: <C-C>s (show SQL statements) At this point, you can page down through the list until you find "select". @@ -484,10 +493,10 @@ depending on the syntax file you are using. The SQL Anywhere syntax file DECLARE customer_id <C-C>T <-- Choose a type from the list -Dynamic features ----------------- +Dynamic features ~ + To take advantage of the dynamic features you must first install the -dbext.vim plugin (http://vim.sourceforge.net/script.php?script_id=356). It +dbext.vim plugin (https://vim.sourceforge.net/script.php?script_id=356). It also comes with a tutorial. From the SQL completion plugin's perspective, the main feature dbext provides is a connection to a database. dbext connection profiles are the most efficient mechanism to define connection @@ -597,8 +606,8 @@ Similar to the table list, <C-C>v, will display a list of views in the database. +------------------------------------------------------------------------------ 4.4 Completion Customization *sql-completion-customization* ----------------------------- The SQL completion plugin can be customized through various options set in your |init.vim|: > @@ -657,14 +666,14 @@ your |init.vim|: > option. > +------------------------------------------------------------------------------ 4.5 SQL Maps *sql-completion-maps* ------------- The default SQL maps have been described in other sections of this document in greater detail. Here is a list of the maps with a brief description of each. -Static Maps ------------ +Static Maps ~ + These are maps which use populate the completion list using Vim's syntax highlighting rules. > <C-C>a @@ -680,8 +689,8 @@ highlighting rules. > <C-C>s < - Displays all SQL syntax items defined as 'sqlStatement'. > -Dynamic Maps ------------- +Dynamic Maps ~ + These are maps which use populate the completion list using the dbext.vim plugin. > <C-C>t @@ -713,8 +722,8 @@ plugin. > < - This maps removes all cached items and forces the SQL completion to regenerate the list of items. -Customizing Maps ----------------- +Customizing Maps ~ + You can create as many additional key maps as you like. Generally, the maps will be specifying different syntax highlight groups. @@ -733,8 +742,8 @@ chosen since it will work on both Windows and *nix platforms. On the windows platform you can also use <C-Space> or ALT keys. +------------------------------------------------------------------------------ 4.6 Using with other filetypes *sql-completion-filetypes* ------------------------------- Many times SQL can be used with different filetypes. For example Perl, Java, PHP, Javascript can all interact with a database. Often you need both the SQL @@ -746,8 +755,8 @@ This can be enabled easily with the following steps (assuming a Perl file): > 2. :set filetype=sql 3. :set ft=perl -Step 1 ------- +Step 1 ~ + Begins by editing a Perl file. Vim automatically sets the filetype to "perl". By default, Vim runs the appropriate filetype file ftplugin/perl.vim. If you are using the syntax completion plugin by following @@ -755,8 +764,8 @@ the directions at |ft-syntax-omni| then the |'omnifunc'| option has been set to "syntax#Complete". Pressing <C-X><C-O> will display the omni popup containing the syntax items for Perl. -Step 2 ------- +Step 2 ~ + Manually setting the filetype to "sql" will also fire the appropriate filetype files ftplugin/sql.vim. This file will define a number of buffer specific maps for SQL completion, see |sql-completion-maps|. Now these maps have @@ -767,8 +776,8 @@ begin with <C-C>, the maps will toggle the |'omnifunc'| when in use. So you can use <C-X><C-O> to continue using the completion for Perl (using the syntax completion plugin) and <C-C> to use the SQL completion features. -Step 3 ------- +Step 3 ~ + Setting the filetype back to Perl sets all the usual "perl" related items back as they were. diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt index 8f09e5225f..1fce9fa491 100644 --- a/runtime/doc/gui.txt +++ b/runtime/doc/gui.txt @@ -44,8 +44,8 @@ Scrollbars *gui-scrollbars* There are vertical scrollbars and a horizontal scrollbar. You may configure which ones appear with the 'guioptions' option. -The interface looks like this (with ":set guioptions=mlrb"): - +The interface looks like this (with `:set guioptions=mlrb`): +> +------------------------------+ ` | File Edit Help | <- Menu bar (m) ` +-+--------------------------+-+ ` @@ -66,7 +66,7 @@ The interface looks like this (with ":set guioptions=mlrb"): +-+--------------------------+-+ ` | |< #### >| | <- Bottom ` +-+--------------------------+-+ scrollbar (b) ` - +< Any of the scrollbar or menu components may be turned off by not putting the appropriate letter in the 'guioptions' string. The bottom scrollbar is only useful when 'nowrap' is set. @@ -123,7 +123,7 @@ message). Keep Shift pressed to change to the directory instead. If Vim happens to be editing a command line, the names of the dropped files and directories will be inserted at the cursor. This allows you to use these names with any Ex command. Special characters (space, tab, double quote and -'|'; backslash on non-MS-Windows systems) will be escaped. +"|"; backslash on non-MS-Windows systems) will be escaped. ============================================================================== Menus *menus* @@ -450,8 +450,8 @@ The default "PopUp" menu is: > anoremenu PopUp.Paste "+gP vnoremenu PopUp.Paste "+P vnoremenu PopUp.Delete "_x - nnoremenu PopUp.Select\ All> ggVG - vnoremenu PopUp.Select\ All> gg0oG$ + nnoremenu PopUp.Select\ All ggVG + vnoremenu PopUp.Select\ All gg0oG$ inoremenu PopUp.Select\ All <C-Home><C-O>VG anoremenu PopUp.-1- <Nop> anoremenu PopUp.How-to\ disable\ mouse <Cmd>help disable-mouse<CR> @@ -569,14 +569,14 @@ Tooltips & Menu tips See section |42.4| in the user manual. *:tmenu* -:tm[enu] {menupath} {rhs} Define a tip for a menu or tool. {only in - X11 and Win32 GUI} +:tm[enu] {menupath} {rhs} Define a tip for a menu or tool. (only in + X11 and Win32 GUI) -:tm[enu] [menupath] List menu tips. {only in X11 and Win32 GUI} +:tm[enu] [menupath] List menu tips. (only in X11 and Win32 GUI) *:tunmenu* :tu[nmenu] {menupath} Remove a tip for a menu or tool. - {only in X11 and Win32 GUI} + (only in X11 and Win32 GUI) Note: To create menus for terminal mode, use |:tlmenu| instead. diff --git a/runtime/doc/hebrew.txt b/runtime/doc/hebrew.txt index 2f4b137bd3..76266d777f 100644 --- a/runtime/doc/hebrew.txt +++ b/runtime/doc/hebrew.txt @@ -11,8 +11,9 @@ Lottem. <alottem at gmail dot com> Ron Aaron <ron at ronware dot org> is currently helping support these features. +------------------------------------------------------------------------------ Introduction ------------- + Hebrew-specific options are 'hkmap', 'hkmapp' 'keymap'=hebrew and 'aleph'. Hebrew-useful options are 'delcombine', 'allowrevins', 'revins', 'rightleft' and 'rightleftcmd'. @@ -22,8 +23,9 @@ from right to left instead of the usual left to right. This is useful primarily when editing Hebrew or other Middle-Eastern languages. See |rileft.txt| for further details. +------------------------------------------------------------------------------ Details --------------- + + Options: + 'rightleft' ('rl') sets window orientation to right-to-left. This means that the logical text 'ABC' will be displayed as 'CBA', and will start @@ -31,7 +33,7 @@ Details + 'hkmap' ('hk') sets keyboard mapping to Hebrew, in insert/replace modes. + 'aleph' ('al'), numeric, holds the decimal code of Aleph, for keyboard mapping. - + 'hkmapp' ('hkp') sets keyboard mapping to 'phonetic hebrew' + + 'hkmapp' ('hkp') sets keyboard mapping to "phonetic hebrew" NOTE: these three ('hkmap', 'hkmapp' and 'aleph') are obsolete. You should use ":set keymap=hebrewp" instead. @@ -51,7 +53,7 @@ Details ('deco' does nothing if UTF8 encoding is not active). + Vim arguments: - + 'vim -H file' starts editing a Hebrew file, i.e. 'rightleft' and 'hkmap' + + `vim -H file` starts editing a Hebrew file, i.e. 'rightleft' and 'hkmap' are set. + Keyboard: @@ -116,8 +118,9 @@ when exiting 'revins' via CTRL-_, the cursor moves to the end of the typed text (if possible). +------------------------------------------------------------------------------ Pasting when in a rightleft window ----------------------------------- + When cutting text with the mouse and pasting it in a rightleft window the text will be reversed, because the characters come from the cut buffer from the left to the right, while inserted in the file from the right to @@ -125,8 +128,9 @@ the left. In order to avoid it, toggle 'revins' (by typing CTRL-? or CTRL-_) before pasting. +------------------------------------------------------------------------------ Hebrew characters and the 'isprint' variable --------------------------------------------- + Sometimes Hebrew character codes are in the non-printable range defined by the 'isprint' variable. For example in the Linux console, the Hebrew font encoding starts from 128, while the default 'isprint' variable is @,161-255. diff --git a/runtime/doc/help.txt b/runtime/doc/help.txt index a1bedfa500..07f898f99c 100644 --- a/runtime/doc/help.txt +++ b/runtime/doc/help.txt @@ -1,6 +1,6 @@ *help.txt* Nvim - VIM - main help file + NVIM - help k Move around: Use the cursor keys, or "h" to go left, h l "j" to go down, "k" to go up, "l" to go right. j @@ -37,169 +37,149 @@ Get specific help: It is possible to go directly to whatever you want help Vim stands for Vi IMproved. Most of Vim was made by Bram Moolenaar, but only through the help of many others. See |credits|. + +============================================================================== +NVIM DOCUMENTATION + +------------------------------------------------------------------------------ +ABOUT NVIM *reference_toc* *doc-file-list* *Q_ct* + +|news| News since the previous release +|nvim| Transitioning from Vim +|vim-differences| Nvim compared to Vim +|user-manual| User manual: How to accomplish editing tasks. +|quickref| Overview of common commands +|tutor| 30-minute interactive course for beginners +|copying| About copyrights +|iccf| Helping poor children in Uganda +|sponsor| Sponsor Vim development, become a registered Vim user +|www| Vim on the World Wide Web +|bugs| Where to send bug reports +|support| Supported platforms + +------------------------------------------------------------------------------ +GENERAL + +|intro| Introduction to Vim; notation used in help files +|helphelp| Using the :help files +|index| Index of all commands +|tips| Various tips on using Vim +|message.txt| (Error) messages and explanations +|uganda.txt| Vim distribution and what to do with your money + +------------------------------------------------------------------------------ +BASIC EDITING + +|starting| Starting Vim, Vim command arguments, initialisation +|edit-files| Editing and writing files +|motion.txt| Commands for moving around +|scrolling| Scrolling the text in the window +|insert.txt| Insert and Replace mode +|change.txt| Deleting and replacing text +|undo-redo| Undo and Redo +|repeat.txt| Repeating commands, Vim scripts and debugging +|visual-mode| Using Visual mode (selecting text) +|various| Various other commands +|crash-recovery| Recovering from a crash + +------------------------------------------------------------------------------ +ADVANCED EDITING + +|cmdline| Command-line editing +|options| Description of all options +|pattern-searches| Vim regexp patterns and search commands +|key-mapping| Key mapping (shortcuts), abbreviations +|tags| Tags and special searches +|windows| Commands for using windows and buffers +|tabpage| Commands for using tabpages +|spell| Spell checking +|diff| Comparing files +|folding| Hide (fold) ranges of lines +|terminal| Embedded terminal emulator + +------------------------------------------------------------------------------ +API (EXTENSIBILITY/SCRIPTING/PLUGINS) + +|api| Nvim API via RPC, Lua and VimL +|ui| Nvim UI protocol +|lua-guide| Nvim Lua guide +|lua| Lua API +|luaref| Lua reference manual +|luvref| Luv (|vim.loop|) reference manual +|autocmd| Event handlers +|job-control| Spawn and control multiple processes +|channel| Nvim asynchronous IO +|vimscript| Vimscript reference +|vimscript-functions| Vimscript functions +|testing.txt| Vimscript testing functions +|remote-plugin| Nvim remote plugins + +------------------------------------------------------------------------------ +PROGRAMMING LANGUAGE SUPPORT + +|lsp| Language Server Protocol (LSP) +|diagnostic-api| Diagnostic framework +|treesitter| Incremental syntax parsing +|indent.txt| automatic indenting for C and other languages +|syntax| syntax highlighting +|filetype| Settings for specific types of files +|quickfix| Commands for a quick edit-compile-fix cycle +|ft_ada.txt| Ada filetype plugin +|ft_ps1.txt| PowerShell filetype plugin +|ft_raku.txt| Raku filetype plugin +|ft_rust.txt| Rust filetype plugin +|ft_sql.txt| SQL filetype plugin + +------------------------------------------------------------------------------ +UI + +|tui| Builtin UI +|gui| External (graphical) UIs +|signs| Signs displayed as window decorations (the "gutter") + +------------------------------------------------------------------------------ +LANGUAGE SUPPORT + +|digraph| List of available digraphs +|mbyte.txt| Multibyte text support +|mlang.txt| Non-English language support +|rileft.txt| Right-to-left editing mode +|arabic.txt| Arabic language support and editing +|hebrew.txt| Hebrew language support and editing +|russian.txt| Russian language support and editing + ------------------------------------------------------------------------------ - *doc-file-list* *Q_ct* -BASIC: -|quickref| Overview of the most common commands you will use -|tutor| 30-minute interactive course for beginners -|copying| About copyrights -|iccf| Helping poor children in Uganda -|sponsor| Sponsor Vim development, become a registered Vim user -|www| Vim on the World Wide Web -|bugs| Where to send bug reports - -USER MANUAL: These files explain how to accomplish an editing task. - -|usr_toc.txt| Table Of Contents - -Getting Started ~ -|usr_01.txt| About the manuals -|usr_02.txt| The first steps in Vim -|usr_03.txt| Moving around -|usr_04.txt| Making small changes -|usr_05.txt| Set your settings -|usr_06.txt| Using syntax highlighting -|usr_07.txt| Editing more than one file -|usr_08.txt| Splitting windows -|usr_09.txt| Using the GUI -|usr_10.txt| Making big changes -|usr_11.txt| Recovering from a crash -|usr_12.txt| Clever tricks - -Editing Effectively ~ -|usr_20.txt| Typing command-line commands quickly -|usr_21.txt| Go away and come back -|usr_22.txt| Finding the file to edit -|usr_23.txt| Editing other files -|usr_24.txt| Inserting quickly -|usr_25.txt| Editing formatted text -|usr_26.txt| Repeating -|usr_27.txt| Search commands and patterns -|usr_28.txt| Folding -|usr_29.txt| Moving through programs -|usr_30.txt| Editing programs -|usr_31.txt| Exploiting the GUI -|usr_32.txt| The undo tree - -Tuning Vim ~ -|usr_40.txt| Make new commands -|usr_41.txt| Write a Vim script -|usr_42.txt| Add new menus -|usr_43.txt| Using filetypes -|usr_44.txt| Your own syntax highlighted -|usr_45.txt| Select your language - - -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 -|tips.txt| various tips on using Vim -|message.txt| (error) messages and explanations -|develop.txt| development of Nvim -|debug.txt| debugging Vim itself -|uganda.txt| Vim distribution conditions and what to do with your money - -Basic editing ~ -|starting.txt| starting Vim, Vim command arguments, initialisation -|editing.txt| editing and writing files -|motion.txt| commands for moving around -|scroll.txt| scrolling the text in the window -|insert.txt| Insert and Replace mode -|change.txt| deleting and replacing text -|undo.txt| Undo and Redo -|repeat.txt| repeating commands, Vim scripts and debugging -|visual.txt| using the Visual mode (selecting a text area) -|various.txt| various remaining commands -|recover.txt| recovering from a crash - -Advanced editing ~ -|cmdline.txt| Command-line editing -|options.txt| description of all options -|pattern.txt| regexp patterns and search commands -|map.txt| key mapping and abbreviations -|tagsrch.txt| tags and special searches -|windows.txt| commands for using multiple windows and buffers -|tabpage.txt| commands for using multiple tab pages -|spell.txt| spell checking -|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 -|userfunc.txt| defining user 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_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 -|ft_rust.txt| Filetype plugin for Rust -|ft_sql.txt| about the SQL filetype plugin - -Language support ~ -|digraph.txt| list of available digraphs -|mbyte.txt| multibyte text support -|mlang.txt| non-English language support -|rileft.txt| right-to-left editing mode -|arabic.txt| Arabic language support and editing -|hebrew.txt| Hebrew language support and editing -|russian.txt| Russian language support and editing - -GUI ~ -|gui.txt| Graphical User Interface (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 - -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 -|luaref.txt| Lua reference manual -|luvref.txt| Luv (|vim.loop|) reference manual +INTEROP + +|provider| Builtin remote plugin hosts +|if_perl| Perl interface +|if_pyth| Python interface +|if_ruby| Ruby interface + +------------------------------------------------------------------------------ +VERSIONS + +|deprecated| Deprecated features that will be removed +|vi-differences| Differences between Vim and Vi + +------------------------------------------------------------------------------ +DEVELOPING NVIM + +|dev| Development of Nvim +|dev-style| Development style guidelines +|debug.txt| Debugging Vim itself *standard-plugin-list* Standard plugins ~ -|matchit.txt| Extended |%| matching -|pi_gzip.txt| Reading and writing compressed files -|pi_health.txt| Healthcheck framework -|pi_msgpack.txt| msgpack utilities -|pi_netrw.txt| Reading and writing files over a network -|pi_paren.txt| Highlight matching parens -|pi_spec.txt| Filetype plugin to work with rpm spec files -|pi_tar.txt| Tar file explorer -|pi_zip.txt| Zip archive explorer +|matchit.txt| Extended |%| matching +|pi_gzip.txt| Reading and writing compressed files +|pi_health.txt| Healthcheck framework +|pi_msgpack.txt| msgpack utilities +|pi_netrw.txt| Reading and writing files over a network +|pi_paren.txt| Highlight matching parens +|pi_spec.txt| Filetype plugin to work with rpm spec files +|pi_tar.txt| Tar file explorer +|pi_zip.txt| Zip archive explorer LOCAL ADDITIONS: *local-additions* @@ -212,8 +192,8 @@ CTRL-T, CTRL-O, g<RightMouse>, or <C-RightMouse> to go back to where you were. Note that tags are within | characters, but when highlighting is enabled these characters are hidden. That makes it easier to read a command. -Anyway, you can use CTRL-] on any word, also when it is not within |, and Vim -will try to find help for it. Especially for options in single quotes, e.g. +You can use CTRL-] on any word (even if it is not within "|") and Nvim will +try to find help for it. Especially for options in single quotes, e.g. 'hlsearch'. ------------------------------------------------------------------------------ diff --git a/runtime/doc/helphelp.txt b/runtime/doc/helphelp.txt index 4758cd37c6..da307dd241 100644 --- a/runtime/doc/helphelp.txt +++ b/runtime/doc/helphelp.txt @@ -251,7 +251,7 @@ At this moment translations are available for: Japanese - multiple authors Polish - translated by Mikolaj Machowski Russian - translated by Vassily Ragosin -See the Vim website to find them: http://www.vim.org/translations.php +See the Vim website to find them: https://www.vim.org/translations.php A set of translated help files consists of these files: @@ -342,7 +342,7 @@ should begin with the name of the Vim plugin. The tag name is usually right aligned on a line. When referring to an existing help tag and to create a hot-link, place the -name between two bars (|) eg. |help-writing|. +name between two bars ("|") eg. |help-writing|. When referring to a Vim command and to create a hot-link, place the name between two backticks, eg. inside `:filetype`. You will see this is diff --git a/runtime/doc/if_cscop.txt b/runtime/doc/if_cscop.txt deleted file mode 100644 index 8947aefc1b..0000000000 --- a/runtime/doc/if_cscop.txt +++ /dev/null @@ -1,374 +0,0 @@ -*if_cscop.txt* Nvim - - - VIM REFERENCE MANUAL by Andy Kahn - - *cscope* *Cscope* -Cscope is a "code intelligence" tool that helps you navigate C programs. It -can also perform some refactoring tasks, such as renaming a global variable in -all source files. Think of it as "ctags on steroids". - -See |cscope-usage| for a quickstart. - - Type |gO| to see the table of contents. - -============================================================================== -Cscope introduction *cscope-intro* - - -Cscope is designed to answer questions like: - Where is this symbol used? - Where is it defined? - Where did this variable get its value? - What is this global symbol's definition? - Where is this function in the source files? - What functions call this function? - What functions are called by this function? - Where does the message "out of space" come from? - Where is this source file in the directory structure? - What files include this header file? - -Cscope answers these questions from a symbol database that it builds the first -time it is used on the source files. On a subsequent call, cscope rebuilds -the database only if a source file has changed or the list of source files is -different. When the database is rebuilt the data for the unchanged files is -copied from the old database, which makes rebuilding much faster than the -initial build. - -See |cscope-usage| to get started. - -============================================================================== -Cscope commands *cscope-commands* - - *: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 - `:lcscope` or `:lcs` uses the location list, see |:lcscope| - -The available subcommands are: - - *E563* *E564* *E566* *E568* *E622* *E623* *E625* - *E626* *E609* - add : Add a new cscope database/connection. - - USAGE :cs add {file|dir} [pre-path] [flags] - - [pre-path] is the pathname used with the -P command to cscope. - - [flags] are any additional flags you want to pass to cscope. - - EXAMPLES > - :cscope add /usr/local/cdb/cscope.out - :cscope add /projects/vim/cscope.out /usr/local/vim - :cscope add cscope.out /usr/local/vim -C -< - *cscope-find* *cs-find* *E567* - find : Query cscope. All cscope query options are available - except option #5 ("Change this grep pattern"). - - USAGE :cs find {querytype} {name} - - {querytype} corresponds to the actual cscope line - interface numbers as well as default nvi commands: - - 0 or s: Find this C symbol - 1 or g: Find this definition - 2 or d: Find functions called by this function - 3 or c: Find functions calling this function - 4 or t: Find this text string - 6 or e: Find this egrep pattern - 7 or f: Find this file - 8 or i: Find files #including this file - 9 or a: Find places where this symbol is assigned a value - - For all types, except 4 and 6, leading white space for {name} is - removed. For 4 and 6 there is exactly one space between {querytype} - and {name}. Further white space is included in {name}. - - EXAMPLES > - :cscope find c vim_free - :cscope find 3 vim_free -< - These two examples perform the same query: functions calling - "vim_free". > - - :cscope find t initOnce - :cscope find t initOnce -< - The first one searches for the text "initOnce", the second one for - " initOnce". > - - :cscope find 0 DEFAULT_TERM -< - Executing this example on the source code for Vim 5.1 produces the - following output: - - Cscope tag: DEFAULT_TERM - # line filename / context / line - 1 1009 vim-5.1-gtk/src/term.c <<GLOBAL>> - #define DEFAULT_TERM (char_u *)"amiga" - 2 1013 vim-5.1-gtk/src/term.c <<GLOBAL>> - #define DEFAULT_TERM (char_u *)"win32" - 3 1017 vim-5.1-gtk/src/term.c <<GLOBAL>> - #define DEFAULT_TERM (char_u *)"pcterm" - 4 1021 vim-5.1-gtk/src/term.c <<GLOBAL>> - #define DEFAULT_TERM (char_u *)"ansi" - 5 1025 vim-5.1-gtk/src/term.c <<GLOBAL>> - #define DEFAULT_TERM (char_u *)"vt52" - 6 1029 vim-5.1-gtk/src/term.c <<GLOBAL>> - #define DEFAULT_TERM (char_u *)"os2ansi" - 7 1033 vim-5.1-gtk/src/term.c <<GLOBAL>> - #define DEFAULT_TERM (char_u *)"ansi" - 8 1037 vim-5.1-gtk/src/term.c <<GLOBAL>> - # undef DEFAULT_TERM - 9 1038 vim-5.1-gtk/src/term.c <<GLOBAL>> - #define DEFAULT_TERM (char_u *)"beos-ansi" - 10 1042 vim-5.1-gtk/src/term.c <<GLOBAL>> - #define DEFAULT_TERM (char_u *)"mac-ansi" - 11 1335 vim-5.1-gtk/src/term.c <<set_termname>> - term = DEFAULT_TERM; - 12 1459 vim-5.1-gtk/src/term.c <<set_termname>> - if (STRCMP(term, DEFAULT_TERM)) - 13 1826 vim-5.1-gtk/src/term.c <<termcapinit>> - term = DEFAULT_TERM; - 14 1833 vim-5.1-gtk/src/term.c <<termcapinit>> - term = DEFAULT_TERM; - 15 3635 vim-5.1-gtk/src/term.c <<update_tcap>> - p = find_builtin_term(DEFAULT_TERM); - Enter nr of choice (<CR> to abort): - - The output shows several pieces of information: - 1. The tag number (there are 15 in this example). - 2. The line number where the tag occurs. - 3. The filename where the tag occurs. - 4. The context of the tag (e.g., global, or the function name). - 5. The line from the file itself. - - help : Show a brief synopsis. - - USAGE :cs help - - *E261* - kill : Kill a cscope connection (or kill all cscope connections). - - USAGE :cs kill {num|partial_name} - - To kill a cscope connection, the connection number or a partial - name must be specified. The partial name is simply any part of - the pathname of the cscope database. Kill a cscope connection - using the partial name with caution! - - If the specified connection number is -1, then _ALL_ cscope - connections will be killed. - - reset : Reinit all cscope connections. - - USAGE :cs reset - - show : Show cscope connections. - - USAGE :cs show - - *:lcscope* *:lcs* -This command is same as the ":cscope" command, except when the -'cscopequickfix' option is set, the location list for the current window is -used instead of the quickfix list to show the cscope results. - - *:cstag* *E257* *E562* -If you use cscope as well as ctags, |:cstag| allows you to search one or -the other before making a jump. For example, you can choose to first -search your cscope database(s) for a match, and if one is not found, then -your tags file(s) will be searched. The order in which this happens -is determined by the value of |csto|. See |cscope-options| for more -details. - -|:cstag| performs the equivalent of ":cs find g" on the identifier when -searching through the cscope database(s). - -|:cstag| performs the equivalent of |:tjump| on the identifier when searching -through your tags file(s). - - -============================================================================== -Cscope options *cscope-options* - -Use the |:set| command to set all cscope options. Ideally, you would do -this in one of your startup files (e.g., vimrc). Some cscope related -variables are only valid within |init.vim|. Setting them after vim has -started will have no effect! - - *cscopeprg* *csprg* -'cscopeprg' specifies the command to execute cscope. The default is -"cscope". For example: > - :set csprg=/usr/local/bin/cscope -< - *cscopequickfix* *csqf* *E469* -'cscopequickfix' specifies whether to use quickfix window to show cscope -results. This is a list of comma-separated values. Each item consists of -|cscope-find| command (s, g, d, c, t, e, f, i or a) and flag (+, - or 0). -'+' indicates that results must be appended to quickfix window, -'-' implies previous results clearance, '0' or command absence - don't use -quickfix. Search is performed from start until first command occurrence. -The default value is "" (don't use quickfix anyway). The following value -seems to be useful: > - :set cscopequickfix=s-,c-,d-,i-,t-,e-,a- -< - *cscopetag* *cst* -If 'cscopetag' is set, the commands ":tag" and CTRL-] as well as "vim -t" -will always use |:cstag| instead of the default :tag behavior. Effectively, -by setting 'cst', you will always search your cscope databases as well as -your tag files. The default is off. - - *cscoperelative* *csre* -If 'cscoperelative' is set, then in absence of a prefix given to cscope -(prefix is the argument of -P option of cscope), basename of cscope.out -location (usually the project root directory) will be used as the prefix -to construct an absolute path. The default is off. Note: This option is -only effective when cscope (cscopeprg) is initialized without a prefix -path (-P). - - *cscopetagorder* *csto* -The value of 'csto' determines the order in which |:cstag| performs a search. -If 'csto' is set to zero, cscope database(s) are searched first, followed -by tag file(s) if cscope did not return any matches. If 'csto' is set to -one, tag file(s) are searched before cscope database(s). The default is zero. - - *cscopepathcomp* *cspc* -'cscopepathcomp' determines how many components of a file's path to display. -With the default value of zero the entire path will be displayed. -The value one will display only the filename with no path. Other values -display that many components. For example: > - :set cscopepathcomp=3 -will display the last 3 components of the file's path, including the file -name itself. - -============================================================================== -Using cscope in Nvim *cscope-usage* *cscope-howtouse* - -To get started, build the cscope database in your project root directory: > - cscope -bcqR - -See the cscope manpage for details: > - :Man cscope - -By default the cscope database file is named "cscope.out". After building the -database, connect to it from Nvim: > - :cscope add cscope.out - -That establishes a cscope connection for Nvim to use. You can check the -result with ":cs show". It will show something like: - - # pid database name prepend path - 0 28806 cscope.out <none> - -Once a cscope connection is established, you can make queries to cscope and -the results will be printed. Queries are made using the command ":cs find". -For example: > - :cs find g ALIGN_SIZE - -To make this easier you can configure mappings, see |cscope-suggestions|. - -If the results return only one match, you will automatically be taken to it. -If there is more than one match, you will be given a selection screen to pick -the match you want to go to. After you have jumped to the new location, -simply hit Ctrl-T to get back to the previous one. - - -============================================================================== -Limitations *cscope-limitations* - -Hard-coded limitation: doing a |:tjump| when |:cstag| searches the tag files -is not configurable (e.g., you can't do a tselect instead). - - -============================================================================== -Sample config *cscope-suggestions* - -Copy this into your init.vim (adjust paths for your system): > - - if has("cscope") - set csprg=/usr/local/bin/cscope - set csto=0 - set cst - " add any database in current directory - if filereadable("cscope.out") - silent cs add cscope.out - " else add database pointed to by environment - elseif $CSCOPE_DB != "" - silent cs add $CSCOPE_DB - endif - endif - -By setting 'cscopetag', we have effectively replaced all instances of the :tag -command with :cstag. This includes :tag, Ctrl-], and "vim -t". In doing -this, the regular tag command not only searches your ctags generated tag -files, but your cscope databases as well. - -Some users may want to keep the regular tag behavior and have a different -shortcut to access :cstag. For example, one could map Ctrl-_ (underscore) -to :cstag with the following command: > - - map <C-_> :cstag <C-R>=expand("<cword>")<CR><CR> - -A couple of very commonly used cscope queries (using ":cs find") is to -find all functions calling a certain function and to find all occurrences -of a particular C symbol. To do this, you can use these mappings as an -example: > - - map g<C-]> :cs find 3 <C-R>=expand("<cword>")<CR><CR> - map g<C-\> :cs find 0 <C-R>=expand("<cword>")<CR><CR> - -These mappings for Ctrl-] (right bracket) and Ctrl-\ (backslash) allow you to -place your cursor over the function name or C symbol and quickly query cscope -for any matches. - -Or you may use the following scheme, inspired by Vim/Cscope tutorial from -Cscope Home Page (http://cscope.sourceforge.net/): > - - nmap <C-_>s :cs find s <C-R>=expand("<cword>")<CR><CR> - nmap <C-_>g :cs find g <C-R>=expand("<cword>")<CR><CR> - nmap <C-_>c :cs find c <C-R>=expand("<cword>")<CR><CR> - nmap <C-_>t :cs find t <C-R>=expand("<cword>")<CR><CR> - nmap <C-_>e :cs find e <C-R>=expand("<cword>")<CR><CR> - nmap <C-_>f :cs find f <C-R>=expand("<cfile>")<CR><CR> - nmap <C-_>i :cs find i ^<C-R>=expand("<cfile>")<CR>$<CR> - nmap <C-_>d :cs find d <C-R>=expand("<cword>")<CR><CR> - nmap <C-_>a :cs find a <C-R>=expand("<cword>")<CR><CR> - - " Using 'CTRL-spacebar' then a search type makes the vim window - " split horizontally, with search result displayed in - " the new window. - - nmap <C-Space>s :scs find s <C-R>=expand("<cword>")<CR><CR> - nmap <C-Space>g :scs find g <C-R>=expand("<cword>")<CR><CR> - nmap <C-Space>c :scs find c <C-R>=expand("<cword>")<CR><CR> - nmap <C-Space>t :scs find t <C-R>=expand("<cword>")<CR><CR> - nmap <C-Space>e :scs find e <C-R>=expand("<cword>")<CR><CR> - nmap <C-Space>f :scs find f <C-R>=expand("<cfile>")<CR><CR> - nmap <C-Space>i :scs find i ^<C-R>=expand("<cfile>")<CR>$<CR> - nmap <C-Space>d :scs find d <C-R>=expand("<cword>")<CR><CR> - nmap <C-Space>a :scs find a <C-R>=expand("<cword>")<CR><CR> - - " Hitting CTRL-space *twice* before the search type does a vertical - " split instead of a horizontal one - - nmap <C-Space><C-Space>s - \:vert scs find s <C-R>=expand("<cword>")<CR><CR> - nmap <C-Space><C-Space>g - \:vert scs find g <C-R>=expand("<cword>")<CR><CR> - nmap <C-Space><C-Space>c - \:vert scs find c <C-R>=expand("<cword>")<CR><CR> - nmap <C-Space><C-Space>t - \:vert scs find t <C-R>=expand("<cword>")<CR><CR> - nmap <C-Space><C-Space>e - \:vert scs find e <C-R>=expand("<cword>")<CR><CR> - nmap <C-Space><C-Space>i - \:vert scs find i ^<C-R>=expand("<cfile>")<CR>$<CR> - nmap <C-Space><C-Space>d - \:vert scs find d <C-R>=expand("<cword>")<CR><CR> - nmap <C-Space><C-Space>a - \:vert scs find a <C-R>=expand("<cword>")<CR><CR> -< - - vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/if_pyth.txt b/runtime/doc/if_pyth.txt index 9b434e61d7..4c184ddf94 100644 --- a/runtime/doc/if_pyth.txt +++ b/runtime/doc/if_pyth.txt @@ -16,7 +16,7 @@ Commands *python-commands* *:python* *:py* *E263* *E264* *E887* :[range]py[thon] {stmt} Execute Python statement {stmt}. A simple check if - the `:python` command is working: > + the `:python` command is working: >vim :python print "Hello" :[range]py[thon] << [endmarker] @@ -31,7 +31,7 @@ The {endmarker} below the {script} must NOT be preceded by any white space. If [endmarker] is omitted from after the "<<", a dot '.' must be used after {script}, like for the |:append| and |:insert| commands. -Example: > +Example: >vim function! IcecreamInitialize() python << EOF class StrawberryIcecream: @@ -40,7 +40,7 @@ Example: > EOF endfunction -To see what version of Python you have: > +To see what version of Python you have: >vim :python print(sys.version) There is no need to "import sys", it's done by default. @@ -64,12 +64,12 @@ Note: Python is very sensitive to indenting. Make sure the "class" line and is the whole file: "1,$". Examples: -> +>vim :pydo return "%s\t%d" % (line[::-1], len(line)) :pydo if line: return "%4d: %s" % (linenr, line) < One can use `:pydo` in possible conjunction with `:py` to filter a range using -python. For example: > +python. For example: >vim :py3 << EOF needle = vim.eval('@a') @@ -94,12 +94,13 @@ In the case of :pyfile, the code to execute is the contents of the given file. Python commands cannot be used in the |sandbox|. -To pass arguments you need to set sys.argv[] explicitly. Example: > +To pass arguments you need to set sys.argv[] explicitly. Example: >vim :python sys.argv = ["foo", "bar"] :pyfile myscript.py -Here are some examples *python-examples* > +Here are some examples *python-examples* +>vim :python from vim import * :python from string import upper @@ -113,7 +114,7 @@ to the next, just like the Python REPL. *script-here* When using a script language in-line, you might want to skip this when the language isn't supported. Note that this mechanism doesn't work: -> +>vim if has('python') python << EOF this will NOT work! @@ -121,7 +122,7 @@ language isn't supported. Note that this mechanism doesn't work: endif Instead, put the Python command in a function and call that function: -> +>vim if has('python') function DefPython() python << EOF @@ -139,10 +140,10 @@ 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 methods, three constants, and one error object. You need to import the vim -module before using it: > +module before using it: >vim :python import vim -Overview > +Overview >vim :py print "Hello" # displays a message :py vim.command(cmd) # execute an Ex command :py w = vim.windows[n] # gets window "n" @@ -166,10 +167,10 @@ Methods of the "vim" module vim.command(str) *python-command* Executes the vim (ex-mode) command str. Returns None. - Examples: > + Examples: >vim :py vim.command("set tw=72") :py vim.command("%s/aaa/bbb/g") -< The following definition executes Normal mode commands: > +< The following definition executes Normal mode commands: >python def normal(str): vim.command("normal "+str) # Note the use of single quotes to delimit a string containing @@ -177,7 +178,7 @@ vim.command(str) *python-command* normal('"a2dd"aP') < *E659* The ":python" command cannot be used recursively with Python 2.2 and - older. This only works with Python 2.3 and later: > + older. This only works with Python 2.3 and later: >vim :py vim.command("python print 'Hello again Python'") vim.eval(str) *python-eval* @@ -187,7 +188,7 @@ vim.eval(str) *python-eval* - a list if the Vim expression evaluates to a Vim list - a dictionary if the Vim expression evaluates to a Vim dictionary Dictionaries and lists are recursively expanded. - Examples: > + Examples: >vim :py text_width = vim.eval("&tw") :py str = vim.eval("12+12") # NB result is a string! Use # string.atoi() to convert to @@ -215,7 +216,7 @@ Error object of the "vim" module vim.error *python-error* Upon encountering a Vim error, Python raises an exception of type vim.error. - Example: > + Example: >python try: vim.command("put a") except vim.error: @@ -229,7 +230,7 @@ Constants of the "vim" module vim.buffers *python-buffers* A mapping object providing access to the list of vim buffers. The - object supports the following operations: > + object supports the following operations: >vim :py b = vim.buffers[i] # Indexing (read-only) :py b in vim.buffers # Membership test :py n = len(vim.buffers) # Number of elements @@ -237,7 +238,7 @@ vim.buffers *python-buffers* < vim.windows *python-windows* A sequence object providing access to the list of vim windows. The - object supports the following operations: > + object supports the following operations: >vim :py w = vim.windows[i] # Indexing (read-only) :py w in vim.windows # Membership test :py n = len(vim.windows) # Number of elements @@ -251,7 +252,7 @@ vim.windows *python-windows* vim.tabpages *python-tabpages* A sequence object providing access to the list of vim tab pages. The - object supports the following operations: > + object supports the following operations: >vim :py t = vim.tabpages[i] # Indexing (read-only) :py t in vim.tabpages # Membership test :py n = len(vim.tabpages) # Number of elements @@ -277,7 +278,7 @@ vim.current *python-current* switching to given buffer, window or tab page. It is the only way to switch UI objects in python: you can't assign to |python-tabpage|.window attribute. To switch without triggering - autocommands use > + autocommands use >vim py << EOF saved_eventignore = vim.options['eventignore'] vim.options['eventignore'] = 'all' @@ -330,7 +331,7 @@ 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}/python3 and {rtp}/pythonx for each {rtp} found in 'runtimepath'. -Implementation is similar to the following, but written in C: > +Implementation is similar to the following, but written in C: >python from imp import find_module, load_module import vim @@ -461,12 +462,12 @@ The buffer object methods are: numbers s and e |inclusive|. Note that when adding a line it must not contain a line break character '\n'. -A trailing '\n' is allowed and ignored, so that you can do: > +A trailing '\n' is allowed and ignored, so that you can do: >vim :py b.append(f.readlines()) Buffer object type is available using "Buffer" attribute of vim module. -Examples (assume b is the current buffer) > +Examples (assume b is the current buffer) >vim :py print b.name # write the buffer file name :py b[0] = "hello!!!" # replace the top line :py b[:] = None # delete the whole buffer @@ -605,10 +606,10 @@ variants explicitly if Python 3 is required. {script} {endmarker} The `:py3` and `:python3` commands work similar to `:python`. A - simple check if the `:py3` command is working: > + simple check if the `:py3` command is working: >vim :py3 print("Hello") < - To see what version of Python you have: > + To see what version of Python you have: >vim :py3 import sys :py3 print(sys.version) < *:py3file* @@ -619,11 +620,12 @@ variants explicitly if Python 3 is required. The `:py3do` command works similar to `:pydo`. *E880* -Raising SystemExit exception in python isn't endorsed way to quit vim, use: > +Raising SystemExit exception in python isn't endorsed way to quit vim, use: +>vim :py vim.command("qall!") < *has-python* -You can test if Python is available with: > +You can test if Python is available with: >vim if has('pythonx') echo 'there is Python' endif @@ -642,10 +644,10 @@ 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 the same as `:python3`. To check if `:pyx` works: > +`:pyx` and `:pythonx` work the same as `:python3`. To check if `:pyx` works: >vim :pyx print("Hello") -To see what version of Python is being used: > +To see what version of Python is being used: >vim :pyx import sys :pyx print(sys.version) < @@ -656,7 +658,7 @@ To see what version of Python is being used: > `: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: >vim if has('pythonx') echo 'pyx* commands are available. (Python ' .. &pyx .. ')' endif diff --git a/runtime/doc/if_ruby.txt b/runtime/doc/if_ruby.txt index 47305c65fb..d88f59eb73 100644 --- a/runtime/doc/if_ruby.txt +++ b/runtime/doc/if_ruby.txt @@ -7,7 +7,7 @@ The Ruby Interface to Vim *if_ruby* *ruby* *Ruby* *E266* *E267* *E268* *E269* *E270* *E271* *E272* *E273* -The home page for ruby is http://www.ruby-lang.org/. You can find links for +The home page for ruby is https://www.ruby-lang.org/. You can find links for downloading Ruby there. Type |gO| to see the table of contents. diff --git a/runtime/doc/indent.txt b/runtime/doc/indent.txt index c5411b5f16..f3e196b426 100644 --- a/runtime/doc/indent.txt +++ b/runtime/doc/indent.txt @@ -35,7 +35,7 @@ The rest of this section describes the 'cindent' option. Note that 'cindent' indenting does not work for every code scenario. Vim 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 +that toplevel functions have a "{" in the first column. Otherwise they are easily confused with declarations. These five options control C program indenting: @@ -60,12 +60,12 @@ used instead. The format of 'cinkeys' and 'indentkeys' is equal. The default is "0{,0},0),0],:,0#,!^F,o,O,e" which specifies that indenting occurs as follows: - "0{" if you type '{' as the first character in a line - "0}" if you type '}' as the first character in a line - "0)" if you type ')' as the first character in a line - "0]" if you type ']' as the first character in a line - ":" if you type ':' after a label or case statement - "0#" if you type '#' as the first character in a line + "0{" if you type "{" as the first character in a line + "0}" if you type "}" as the first character in a line + "0)" if you type ")" as the first character in a line + "0]" if you type "]" as the first character in a line + ":" if you type ":" after a label or case statement + "0#" if you type "#" as the first character in a line "!^F" if you type CTRL-F (which is not inserted) "o" if you type a <CR> anywhere or use the "o" command (not in insert mode!) @@ -74,21 +74,21 @@ occurs as follows: line Characters that can precede each key: *i_CTRL-F* -! When a '!' precedes the key, Vim will not insert the key but will +! When a "!" precedes the key, Vim will not insert the key but will instead reindent the current line. This allows you to define a command key for reindenting the current line. CTRL-F is the default key for this. Be careful if you define CTRL-I for this because CTRL-I is the ASCII code for <Tab>. -* When a '*' precedes the key, Vim will reindent the line before +* When a "*" precedes the key, Vim will reindent the line before inserting the key. If 'cinkeys' contains "*<Return>", Vim reindents the current line before opening a new line. -0 When a zero precedes the key (but appears after '!' or '*') Vim will +0 When a zero precedes the key (but appears after "!" or "*") Vim will reindent the line only if the key is the first character you type in the line. When used before "=" Vim will only reindent the line if there is only white space before the word. -When neither '!' nor '*' precedes the key, Vim reindents the line after you -type the key. So ';' sets the indentation of a line which includes the ';'. +When neither "!" nor "*" precedes the key, Vim reindents the line after you +type the key. So ";" sets the indentation of a line which includes the ";". Special key names: <> Angle brackets mean spelled-out names of keys. For example: "<Up>", @@ -154,8 +154,8 @@ The examples below assume a 'shiftwidth' of 4. eN Add N to the prevailing indent inside a set of braces if the opening brace at the End of the line (more precise: is not the first character in a line). This is useful if you want a - different indent when the '{' is at the start of the line from - when '{' is at the end of the line. (default 0). + different indent when the "{" is at the start of the line from + when "{" is at the end of the line. (default 0). cino= cino=e2 cino=e-2 > if (cond) { if (cond) { if (cond) { @@ -169,8 +169,8 @@ The examples below assume a 'shiftwidth' of 4. *cino-n* nN Add N to the prevailing indent for a statement after an "if", "while", etc., if it is NOT inside a set of braces. This is - useful if you want a different indent when there is no '{' - before the statement from when there is a '{' before it. + useful if you want a different indent when there is no "{" + before the statement from when there is a "{" before it. (default 0). cino= cino=n2 cino=n-2 > @@ -193,7 +193,7 @@ The examples below assume a 'shiftwidth' of 4. int foo; int foo; int foo; < *cino-{* - {N Place opening braces N characters from the prevailing indent. + `{N` Place opening braces N characters from the prevailing indent. This applies only for opening braces that are inside other braces. (default 0). @@ -203,7 +203,7 @@ The examples below assume a 'shiftwidth' of 4. foo; foo; foo; < *cino-}* - }N Place closing braces N characters from the matching opening + `}N` Place closing braces N characters from the matching opening brace. (default 0). cino= cino={2,}-0.5s cino=}2 > @@ -724,7 +724,7 @@ Fortran with (possibly multiple) loops ending on a labelled executable statement of almost arbitrary type. Correct indentation requires compiler-quality parsing. Old code with do loops ending on labelled statements of arbitrary type can be indented with elaborate programs such as Tidy -(http://www.unb.ca/chem/ajit/f_tidy.htm). Structured do/continue loops are +(https://www.unb.ca/chem/ajit/f_tidy.htm). Structured do/continue loops are also left unindented because continue statements are also used for purposes other than ending a do loop. Programs such as Tidy can convert structured do/continue loops to the do/enddo form. Do loops of the do/enddo variety can @@ -846,7 +846,7 @@ own 'formatoptions'): > Else, 't' will be removed from the 'formatoptions' string and "qrowcb" will be added, see |fo-table| for more information. -------------- + *PHP_outdentSLComments* To add extra indentation to single-line comments: > @@ -858,7 +858,7 @@ Only single-line comments will be affected such as: > # Comment // Comment /* Comment */ -------------- +< *PHP_default_indenting* To add extra indentation to every PHP lines with N being the number of @@ -878,18 +878,17 @@ For example, with N = 1, this will give: $command_hist = TRUE; ?> (Notice the extra indentation between the PHP container markers and the code) -------------- *PHP_outdentphpescape* To indent PHP escape tags as the surrounding non-PHP code (only affects the PHP escape tags): > :let g:PHP_outdentphpescape = 0 -------------- +< *PHP_removeCRwhenUnix* To automatically remove '\r' characters when the 'fileformat' is set to Unix: > :let g:PHP_removeCRwhenUnix = 1 -------------- +< *PHP_BracesAtCodeLevel* To indent braces at the same level than the code they contain: > @@ -908,7 +907,6 @@ Instead of: > NOTE: Indenting will be a bit slower if this option is used because some optimizations won't be available. -------------- *PHP_vintage_case_default_indent* To indent 'case:' and 'default:' statements in switch() blocks: > @@ -918,7 +916,6 @@ In PHP braces are not required inside 'case/default' blocks therefore 'case:' and 'default:' are indented at the same level than the 'switch()' to avoid meaningless indentation. You can use the above option to return to the traditional way. -------------- *PHP_noArrowMatching* By default the indent script will indent multi-line chained calls by matching @@ -927,17 +924,16 @@ the position of the '->': > $user_name_very_long->name() ->age() ->info(); - +< You can revert to the classic way of indenting by setting this option to 1: > :let g:PHP_noArrowMatching = 1 - +< You will obtain the following result: > $user_name_very_long->name() ->age() ->info(); - -------------- +< *PHP_IndentFunctionCallParameters* Extra indentation levels to add to parameters in multi-line function calls. > @@ -954,14 +950,13 @@ Function call arguments will indent 1 extra level. For two-space indentation: > $and_that ); } - -------------- +< *PHP_IndentFunctionDeclarationParameters* Extra indentation levels to add to arguments in multi-line function definitions. > let g:PHP_IndentFunctionDeclarationParameters = 1 - +< Function arguments in declarations will indent 1 extra level. For two-space indentation: > @@ -974,13 +969,16 @@ indentation: > $and_that ); } - +< PYTHON *ft-python-indent* -The amount of indent can be set for the following situations. The examples -given are the defaults. Note that the dictionary values are set to an -expression, so that you can change the value of 'shiftwidth' later. +The amount of indent can be set with the `g:python_indent` |Dictionary|, which +needs to be created before adding the items: > + let g:python_indent = {} +The examples given are the defaults. Note that the dictionary values are set +to an expression, so that you can change the value of 'shiftwidth' later +without having to update these values. Indent after an open paren: > let g:python_indent.open_paren = 'shiftwidth() * 2' @@ -1142,7 +1140,6 @@ to the vimrc file, which causes the previous alignment example to change: > ); END ENTITY sync; ----------------------------------------- Alignment of right-hand side assignment "<=" statements are performed by default. This causes the following alignment example: > @@ -1161,7 +1158,6 @@ to the vimrc file, which causes the previous alignment example to change: > (sig_b OR sig_c)) OR (bus_a(0) AND sig_d); ----------------------------------------- Full-line comments (lines that begin with "--") are indented to be aligned with the very previous line's comment, PROVIDED that a whitespace follows after diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index 7318bc7f34..a6aa036b55 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -20,8 +20,7 @@ For a list of Vim variables see |vim-variable|. 1. Insert mode *insert-index* tag char action in Insert mode ~ ------------------------------------------------------------------------ - +------------------------------------------------------------------------------ ~ |i_CTRL-@| CTRL-@ insert previously inserted text and stop insert |i_CTRL-A| CTRL-A insert previously inserted text @@ -184,8 +183,7 @@ SECTION a section that possibly starts with '}' instead of '{' note: 1 = cursor movement command; 2 = can be undone/redone tag char note action in Normal mode ~ ------------------------------------------------------------------------------- - +------------------------------------------------------------------------------ ~ CTRL-@ not used |CTRL-A| CTRL-A 2 add N to number at/after cursor |CTRL-B| CTRL-B 1 scroll N screens Backwards @@ -470,8 +468,7 @@ tag char note action in Normal mode ~ These can be used after an operator or in Visual mode to select an object. tag command action in op-pending and Visual mode ~ ------------------------------------------------------------------------------- - +------------------------------------------------------------------------------ ~ |v_aquote| a" double quoted string |v_a'| a' single quoted string |v_a(| a( same as ab @@ -513,8 +510,7 @@ tag command action in op-pending and Visual mode ~ 2.2 Window commands *CTRL-W* tag command action in Normal mode ~ ------------------------------------------------------------------------------- - +------------------------------------------------------------------------------ ~ |CTRL-W_CTRL-B| CTRL-W CTRL-B same as "CTRL-W b" |CTRL-W_CTRL-C| CTRL-W CTRL-C same as "CTRL-W c" |CTRL-W_CTRL-D| CTRL-W CTRL-D same as "CTRL-W d" @@ -612,8 +608,7 @@ tag command action in Normal mode ~ 2.3 Square bracket commands *[* *]* tag char note action in Normal mode ~ ------------------------------------------------------------------------------- - +------------------------------------------------------------------------------ ~ |[_CTRL-D| [ CTRL-D jump to first #define found in current and included files matching the word under the cursor, start searching at beginning of @@ -703,8 +698,7 @@ tag char note action in Normal mode ~ 2.4 Commands starting with 'g' *g* tag char note action in Normal mode ~ ------------------------------------------------------------------------------- - +------------------------------------------------------------------------------ ~ g_CTRL-A g CTRL-A dump a memory profile |g_CTRL-G| g CTRL-G show information about current cursor position @@ -807,8 +801,7 @@ g_CTRL-A g CTRL-A dump a memory profile 2.5 Commands starting with 'z' *z* tag char note action in Normal mode ~ ------------------------------------------------------------------------------- - +------------------------------------------------------------------------------ ~ |z<CR>| z<CR> redraw, cursor line to top of window, cursor on first non-blank |zN<CR>| z{height}<CR> redraw, make window {height} lines high @@ -882,8 +875,7 @@ tag char note action in Normal mode ~ These can be used after an operator, but before a {motion} has been entered. tag char action in Operator-pending mode ~ ------------------------------------------------------------------------ - +------------------------------------------------------------------------------ ~ |o_v| v force operator to work charwise |o_V| V force operator to work linewise |o_CTRL-V| CTRL-V force operator to work blockwise @@ -895,8 +887,7 @@ Most commands in Visual mode are the same as in Normal mode. The ones listed here are those that are different. tag command note action in Visual mode ~ ------------------------------------------------------------------------------- - +------------------------------------------------------------------------------ ~ |v_CTRL-\_CTRL-N| CTRL-\ CTRL-N stop Visual mode |v_CTRL-\_CTRL-G| CTRL-\ CTRL-G go to Normal mode |v_CTRL-A| CTRL-A 2 add N to number in highlighted text @@ -1016,8 +1007,7 @@ Normal characters are inserted at the current cursor position. file names, tags, commands etc. as appropriate. tag command action in Command-line editing mode ~ ------------------------------------------------------------------------------- - +------------------------------------------------------------------------------ ~ CTRL-@ not used |c_CTRL-A| CTRL-A do completion on the pattern in front of the cursor and insert all matches @@ -1127,17 +1117,16 @@ mentioning any arguments. The optional part of the command name is inside []. The commands are sorted on the non-optional part of their name. tag command action ~ ------------------------------------------------------------------------------- - +------------------------------------------------------------------------------ ~ |:| : nothing |:range| :{range} go to last line in {range} |:!| :! filter lines or execute an external command |:!!| :!! repeat last ":!" command |:#| :# same as ":number" |:&| :& repeat last ":substitute" -|:star| :* execute contents of a register +|:star| :* use the last Visual area, like :'<,'> |:<| :< shift lines one 'shiftwidth' left -|:=| := print the cursor line number +|:=| := print the last line number |:>| :> shift lines one 'shiftwidth' right |:@| :@ execute contents of a register |:@@| :@@ repeat the previous ":@" @@ -1219,8 +1208,8 @@ tag command action ~ |:cgetfile| :cg[etfile] read file with error messages |:changes| :changes print the change list |:chdir| :chd[ir] change directory -|:checkhealth| :checkh[ealth] run healthchecks -|:checkpath| :che[ckpath] list included files +|:checkhealth| :che[ckhealth] run healthchecks +|:checkpath| :checkp[ath] list included files |:checktime| :checkt[ime] check timestamp of loaded buffers |:chistory| :chi[story] list the error lists |:clast| :cla[st] go to the specified error, default last one @@ -1250,8 +1239,6 @@ tag command action ~ |:cpfile| :cpf[ile] go to last error in previous file |:cquit| :cq[uit] quit Vim with an error code |:crewind| :cr[ewind] go to the specified error, default first one -|:cscope| :cs[cope] execute cscope command -|:cstag| :cst[ag] use cscope to jump to a tag |:cunmap| :cu[nmap] like ":unmap" but for Command-line mode |:cunabbrev| :cuna[bbrev] like ":unabbrev" but for Command-line mode |:cunmenu| :cunme[nu] remove menu for Command-line mode @@ -1323,7 +1310,6 @@ tag command action ~ |:grepadd| :grepa[dd] like :grep, but append to current list |:gui| :gu[i] start the GUI |:gvim| :gv[im] start the GUI -|:hardcopy| :ha[rdcopy] send text to the printer |:help| :h[elp] open a help window |:helpclose| :helpc[lose] close one help window |:helpgrep| :helpg[rep] like ":grep" but searches help files @@ -1331,6 +1317,7 @@ tag command action ~ |:highlight| :hi[ghlight] specify highlighting methods |:hide| :hid[e] hide current buffer for a command |:history| :his[tory] print a history list +|:horizontal| :hor[izontal] following window command work horizontally |:insert| :i[nsert] insert text |:iabbrev| :ia[bbrev] like ":abbrev" but for Insert mode |:iabclear| :iabc[lear] like ":abclear" but for Insert mode @@ -1375,7 +1362,6 @@ tag command action ~ |:lcd| :lc[d] change directory locally |:lchdir| :lch[dir] change directory locally |:lclose| :lcl[ose] close location window -|:lcscope| :lcs[cope] like ":cscope" but uses location list |:ldo| :ld[o] execute command in valid location list entries |:lfdo| :lfd[o] execute command in each file in location list |:left| :le[ft] left align lines @@ -1556,7 +1542,6 @@ tag command action ~ buffer list |:scriptnames| :scr[iptnames] list names of all sourced Vim scripts |:scriptencoding| :scripte[ncoding] encoding used in sourced Vim script -|:scscope| :scs[cope] split window and execute cscope command |:set| :se[t] show or set options |:setfiletype| :setf[iletype] set 'filetype', unless it was set already |:setglobal| :setg[lobal] show global values of options @@ -1647,6 +1632,7 @@ tag command action ~ |:topleft| :to[pleft] make split window appear at top or far left |:tprevious| :tp[revious] jump to previous matching tag |:trewind| :tr[ewind] jump to first matching tag +|:trust| :trust add or remove file from trust database |:try| :try execute commands, abort on error or exception |:tselect| :ts[elect] list matching tags and select one |:tunmap| :tunma[p] like ":unmap" but for |Terminal-mode| diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index 792c6ee6f4..e608b431f2 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -257,8 +257,8 @@ CTRL-] Trigger abbreviation, without inserting a character. *i_<Insert>* <Insert> Toggle between Insert and Replace mode. ------------------------------------------------------------------------ +----------------------------------------------------------------------- *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: @@ -378,6 +378,7 @@ CTRL-G u close undo sequence, start new change *i_CTRL-G_u* CTRL-G U don't start a new undo block with the next *i_CTRL-G_U* left/right cursor movement, if the cursor stays within the same line + ----------------------------------------------------------------------- The CTRL-O command sometimes has a side effect: If the cursor was beyond the @@ -857,29 +858,27 @@ invoked and what it should return. Here is an example that uses the "aiksaurus" command (provided by Magnus Groß): > - func Thesaur(findstart, base) - if a:findstart - let line = getline('.') - let start = col('.') - 1 - while start > 0 && line[start - 1] =~ '\a' - let start -= 1 - endwhile - return start - else - let res = [] - let h = '' - for l in split(system('aiksaurus ' .. shellescape(a:base)), '\n') - if l[:3] == '=== ' - let h = substitute(l[4:], ' =*$', '', '') - elseif l[0] =~ '\a' - call extend(res, map(split(l, ', '), {_, val -> {'word': val, 'menu': '('.h.')'}})) - endif - endfor - return res - endif - endfunc - - set thesaurusfunc=Thesaur + func Thesaur(findstart, base) + if a:findstart + return searchpos('\<', 'bnW', line('.'))[1] - 1 + endif + let res = [] + let h = '' + for l in systemlist('aiksaurus ' .. shellescape(a:base)) + if l[:3] == '=== ' + let h = '(' .. substitute(l[4:], ' =*$', ')', '') + elseif l ==# 'Alphabetically similar known words are: ' + let h = "\U0001f52e" + elseif l[0] =~ '\a' || (h ==# "\U0001f52e" && l[0] ==# "\t") + call extend(res, map(split(substitute(l, '^\t', '', ''), ', '), {_, val -> {'word': val, 'menu': h}})) + endif + endfor + return res + endfunc + + if exists('+thesaurusfunc') + set thesaurusfunc=Thesaur + endif Completing keywords in the current and included files *compl-keyword* @@ -1350,16 +1349,8 @@ 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 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/ - https://github.com/universal-ctags/ctags-win32 +Universal Ctags is preferred, Exuberant Ctags is no longer maintained. If you want to complete system functions you can do something like this. Use ctags to generate a tags file for all the system header files: > @@ -1459,14 +1450,14 @@ DOM compatibility At the moment (beginning of 2006) there are two main browsers - MS Internet Explorer and Mozilla Firefox. These two applications are covering over 90% of market. Theoretically standards are created by W3C organisation -(http://www.w3c.org) but they are not always followed/implemented. - +(https://www.w3.org/) but they are not always followed/implemented. +> IE FF W3C Omni completion ~ +/- +/- + + ~ + + - + ~ + - - - ~ - + - - ~ - +< Regardless from state of implementation in browsers but if element is defined in standards, completion plugin will place element in suggestion list. When both major engines implemented element, even if this is not in standards it @@ -1480,7 +1471,6 @@ 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: @@ -1779,12 +1769,12 @@ DTD -> Vim *dtd2vim* On |www| is the script |dtd2vim| which parses DTD and creates an XML data file for Vim XML omni completion. - dtd2vim: http://www.vim.org/scripts/script.php?script_id=1462 + dtd2vim: https://www.vim.org/scripts/script.php?script_id=1462 Check the beginning of that file for usage details. The script requires perl and: - perlSGML: http://savannah.nongnu.org/projects/perlsgml + perlSGML: https://savannah.nongnu.org/projects/perlsgml Commands diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt index 60c2b4c5dd..6bf6850ccf 100644 --- a/runtime/doc/intro.txt +++ b/runtime/doc/intro.txt @@ -373,7 +373,7 @@ notation meaning equivalent decimal value(s) ~ <M-…> alt-key or meta-key *META* *ALT* *<M-* <A-…> same as <M-…> *<A-* <D-…> command-key or "super" key *<D-* ------------------------------------------------------------------------ +----------------------------------------------------------------------- ~ Note: @@ -408,12 +408,12 @@ the ":map" command. The rules are: The <> notation uses <lt> to escape the special meaning of key names. Using a backslash also works, but only when 'cpoptions' does not include the 'B' flag. -Examples for mapping CTRL-H to the six characters "<Home>": > +Examples for mapping CTRL-H to the six characters "<Home>": >vim :imap <C-H> \<Home> :imap <C-H> <lt>Home> The first one only works when the 'B' flag is not in 'cpoptions'. The second one always works. -To get a literal "<lt>" in a mapping: > +To get a literal "<lt>" in a mapping: >vim :map <C-L> <lt>lt> The notation can be used in a double quoted strings, using "\<" at the start, @@ -520,12 +520,12 @@ CTRL-O in Insert mode you get a beep but you are still in Insert mode, type TO mode ~ Normal Visual Select Insert Replace Cmd-line Ex ~ FROM mode ~ -Normal v V ^V *4 *1 R gR : / ? ! Q -Visual *2 ^G c C -- : -- -Select *5 ^O ^G *6 -- -- -- +Normal v V ^V `*4` *1 R gR : / ? ! Q +Visual `*2` ^G c C -- : -- +Select `*5` ^O ^G `*6` -- -- -- Insert <Esc> -- -- <Insert> -- -- Replace <Esc> -- -- <Insert> -- -- -Command-line *3 -- -- :start -- -- +Command-line `*3` -- -- :start -- -- Ex :vi -- -- -- -- -- -- not possible @@ -589,26 +589,26 @@ Lines longer than the window width will wrap, unless the 'wrap' option is off If the window has room after the last line of the buffer, Vim will show '~' in the first column of the last lines in the window, like this: - +> +-----------------------+ |some line | |last line | |~ | |~ | +-----------------------+ - +< Thus the '~' lines indicate that the end of the buffer was reached. If the last line in a window doesn't fit, Vim will indicate this with a '@' in the first column of the last lines in the window, like this: - +> +-----------------------+ |first line | |second line | |@ | |@ | +-----------------------+ - +< Thus the '@' lines indicate that there is a line that doesn't fit in the window. @@ -616,14 +616,14 @@ When the "lastline" flag is present in the 'display' option, you will not see '@' characters at the left side of window. If the last line doesn't fit completely, only the part that fits is shown, and the last three characters of the last line are replaced with "@@@", like this: - +> +-----------------------+ |first line | |second line | |a very long line that d| |oesn't fit in the wi@@@| +-----------------------+ - +< If there is a single line that is too long to fit in the window, this is a special situation. Vim will show only part of the line, around where the cursor is. There are no special characters shown, so that you can edit all @@ -704,9 +704,9 @@ Definitions *definitions* *jargon* A screen contains one or more windows, separated by status lines and with the command line at the bottom. - +> +-------------------------------+ -screen | window 1 | window 2 | + screen | window 1 | window 2 | | | | | | | |= status line =|= status line =| @@ -716,7 +716,7 @@ screen | window 1 | window 2 | |==== status line ==============| |command line | +-------------------------------+ - +< The command line is also used for messages. It scrolls up the screen when there is not enough room in the command line. diff --git a/runtime/doc/job_control.txt b/runtime/doc/job_control.txt index 6a9d865c40..37a4e2ebb1 100644 --- a/runtime/doc/job_control.txt +++ b/runtime/doc/job_control.txt @@ -30,7 +30,7 @@ Usage *job-control-usage* To control jobs, use the "job…" family of functions: |jobstart()|, |jobstop()|, etc. -Example: > +Example: >vim function! s:OnEvent(job_id, data, event) dict if a:event == 'stdout' @@ -51,7 +51,7 @@ Example: > let job1 = jobstart(['bash'], extend({'shell': 'shell 1'}, s:callbacks)) let job2 = jobstart(['bash', '-c', 'for i in {1..10}; do echo hello $i!; sleep 1; done'], extend({'shell': 'shell 2'}, s:callbacks)) -To test the above script, copy it to a file ~/foo.vim and run it: > +To test the above script, copy it to a file ~/foo.vim and run it: >bash nvim -u ~/foo.vim < Description of what happens: @@ -75,7 +75,7 @@ Arguments passed to on_exit callback: will not trigger the on_stdout/on_stderr callback (but if the process ends, the on_exit callback will be invoked). For example, "ruby -e" buffers output, so small strings will be - buffered unless "auto-flushing" ($stdout.sync=true) is enabled. > + buffered unless "auto-flushing" ($stdout.sync=true) is enabled. >vim function! Receive(job_id, data, event) echom printf('%s: %s',a:event,string(a:data)) endfunction @@ -92,7 +92,7 @@ Arguments passed to on_exit callback: - `abc\nefg` may arrive as `['abc', '']`, `['efg']` or `['abc']`, `['','efg']`, or even `['ab']`, `['c','efg']`. Easy way to deal with this: initialize a list as `['']`, then append - to it as follows: > + to it as follows: >vim let s:chunks = [''] func! s:on_stdout(job_id, data, event) dict let s:chunks[-1] .= a:data[0] @@ -101,7 +101,7 @@ Arguments passed to on_exit callback: < The |jobstart-options| dictionary is passed as |self| to the callback. -The above example could be written in this "object-oriented" style: > +The above example could be written in this "object-oriented" style: >vim let Shell = {} @@ -129,16 +129,16 @@ The above example could be written in this "object-oriented" style: > let instance = Shell.new('bomb', \ 'for i in $(seq 9 -1 1); do echo $i 1>&$((i % 2 + 1)); sleep 1; done') < -To send data to the job's stdin, use |chansend()|: > +To send data to the job's stdin, use |chansend()|: >vim :call chansend(job1, "ls\n") :call chansend(job1, "invalid-command\n") :call chansend(job1, "exit\n") < -A job may be killed with |jobstop()|: > +A job may be killed with |jobstop()|: >vim :call jobstop(job1) < A job may be killed at any time with the |jobstop()| function: -> +>vim :call jobstop(job1) < Individual streams can be closed without killing the job, see |chanclose()|. diff --git a/runtime/doc/lsp-extension.txt b/runtime/doc/lsp-extension.txt index 6e9ad940c7..fe72e9eb18 100644 --- a/runtime/doc/lsp-extension.txt +++ b/runtime/doc/lsp-extension.txt @@ -6,7 +6,7 @@ The `vim.lsp` Lua module is a framework for building LSP plugins. 1. Start with |vim.lsp.start_client()| and |vim.lsp.buf_attach_client()|. - 2. Peek at the API: > + 2. Peek at the API: >vim :lua print(vim.inspect(vim.lsp)) < 3. See |lsp-extension-example| for a full example. @@ -30,7 +30,7 @@ The example will: 3. Create a new LSP for that root directory if one doesn't exist. 4. Attach the buffer to the client for that root directory. -> +>lua -- Some path manipulation utilities local function is_dir(filename) local stat = vim.loop.fs_stat(filename) diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 139f4c6bc5..215515a2d9 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -33,7 +33,7 @@ Follow these steps to get LSP features: 2. Configure the LSP client per language server. A minimal example: -> +>lua vim.lsp.start({ name = 'my-server-name', cmd = {'name-of-language-server-executable'}, @@ -44,7 +44,7 @@ Follow these steps to get LSP features: 3. Configure keymaps and autocmds to utilize LSP features. See |lsp-config|. -< + *lsp-config* Starting a LSP client will automatically report diagnostics via @@ -66,7 +66,7 @@ language server supports the functionality. To use other LSP features like hover, rename, etc. you can setup some additional keymaps. It's recommended to setup them in a |LspAttach| autocmd to ensure they're only active if there is a LSP client running. An example: -> +>lua vim.api.nvim_create_autocmd('LspAttach', { callback = function(args) vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = args.buf }) @@ -86,7 +86,7 @@ The most used functions are: Not all language servers provide the same capabilities. To ensure you only set keymaps if the language server supports a feature, you can guard the keymap calls behind capability checks: -> +>lua vim.api.nvim_create_autocmd('LspAttach', { callback = function(args) local client = vim.lsp.get_client_by_id(args.data.client_id) @@ -100,7 +100,7 @@ calls behind capability checks: To learn what capabilities are available you can run the following command in a buffer with a started LSP client: -> +>vim :lua =vim.lsp.get_active_clients()[1].server_capabilities < @@ -110,14 +110,14 @@ Full list of features provided by default can be found in |lsp-buf|. FAQ *lsp-faq* - Q: How to force-reload LSP? - A: Stop all clients, then reload the buffer. > + A: Stop all clients, then reload the buffer. >vim :lua vim.lsp.stop_client(vim.lsp.get_active_clients()) :edit - Q: Why isn't completion working? A: In the buffer where you want to use LSP, check that 'omnifunc' is set to - "v:lua.vim.lsp.omnifunc": > + "v:lua.vim.lsp.omnifunc": >vim :verbose set omnifunc? @@ -129,7 +129,7 @@ FAQ *lsp-faq* A: Check if the function has an `async` parameter and set the value to false. - E.g. code formatting: > + E.g. code formatting: >vim " Auto-format *.rs (rust) files prior to saving them " (async = false is the default for format) @@ -162,7 +162,7 @@ to the given buffer. |lsp-buf| LSP request/response handlers are implemented as Lua functions (see |lsp-handler|). The |vim.lsp.handlers| table defines default handlers used -when creating a new client. Keys are LSP method names: > +when creating a new client. Keys are LSP method names: >vim :lua print(vim.inspect(vim.tbl_keys(vim.lsp.handlers))) < @@ -291,7 +291,7 @@ To configure the behavior of a builtin |lsp-handler|, the convenient method To configure the behavior of |vim.lsp.diagnostic.on_publish_diagnostics()|, consider the following example, where a new |lsp-handler| is created using - |vim.lsp.with()| that no longer generates signs for the diagnostics: > + |vim.lsp.with()| that no longer generates signs for the diagnostics: >lua vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with( vim.lsp.diagnostic.on_publish_diagnostics, { @@ -301,7 +301,7 @@ To configure the behavior of a builtin |lsp-handler|, the convenient method ) < To enable signs, use |vim.lsp.with()| again to create and assign a new - |lsp-handler| to |vim.lsp.handlers| for the associated method: > + |lsp-handler| to |vim.lsp.handlers| for the associated method: >lua vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with( vim.lsp.diagnostic.on_publish_diagnostics, { @@ -311,7 +311,7 @@ To configure the behavior of a builtin |lsp-handler|, the convenient method ) < To configure a handler on a per-server basis, you can use the {handlers} key - for |vim.lsp.start_client()| > + for |vim.lsp.start_client()| >lua vim.lsp.start_client { ..., -- Other configuration omitted. @@ -325,7 +325,8 @@ To configure the behavior of a builtin |lsp-handler|, the convenient method }, } < - or if using 'nvim-lspconfig', you can use the {handlers} key of `setup()`: > + or if using "nvim-lspconfig", you can use the {handlers} key of `setup()`: + >lua require('lspconfig').rust_analyzer.setup { handlers = { @@ -340,7 +341,7 @@ To configure the behavior of a builtin |lsp-handler|, the convenient method < Some handlers do not have an explicitly named handler function (such as ||vim.lsp.diagnostic.on_publish_diagnostics()|). To override these, first - create a reference to the existing handler: > + create a reference to the existing handler: >lua local on_references = vim.lsp.handlers["textDocument/references"] vim.lsp.handlers["textDocument/references"] = vim.lsp.with( @@ -357,14 +358,14 @@ Handlers can be set by: 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: > + To override the handler for the `"textDocument/definition"` method: >lua vim.lsp.handlers["textDocument/definition"] = my_custom_default_definition < - The {handlers} parameter for |vim.lsp.start_client()|. This will set the |lsp-handler| as the default handler for this server. - For example: > + For example: >lua vim.lsp.start_client { ..., -- Other configuration omitted. @@ -376,7 +377,7 @@ Handlers can be set by: - The {handler} parameter for |vim.lsp.buf_request()|. This will set the |lsp-handler| ONLY for the current request. - For example: > + For example: >lua vim.lsp.buf_request( 0, @@ -403,7 +404,7 @@ and helper functions for creating protocol-related objects. https://github.com/microsoft/language-server-protocol/raw/gh-pages/_specifications/specification-3-14.md For example `vim.lsp.protocol.ErrorCodes` allows reverse lookup by number or -name: > +name: >lua vim.lsp.protocol.TextDocumentSyncKind.Full == 1 vim.lsp.protocol.TextDocumentSyncKind[1] == "Full" @@ -426,7 +427,7 @@ For the format of the notification message, see: - `context` table|nil. `ctx` from |lsp-handler| This table can be used with vim.fn.setqflist or vim.fn.setloclist. E.g.: -> +>lua local function on_list(options) vim.fn.setqflist({}, ' ', options) vim.api.nvim_command('cfirst') @@ -436,7 +437,7 @@ This table can be used with vim.fn.setqflist or vim.fn.setloclist. E.g.: vim.lsp.buf.references(nil, {on_list=on_list}) < If you prefer loclist do something like this: -> +>lua local function on_list(options) vim.fn.setloclist(0, {}, ' ', options) vim.api.nvim_command('lopen') @@ -487,7 +488,7 @@ EVENTS *lsp-events* *LspAttach* After an LSP client attaches to a buffer. The |autocmd-pattern| is the name of the buffer. When used from Lua, the client ID is passed to the -callback in the "data" table. Example: > +callback in the "data" table. Example: >lua vim.api.nvim_create_autocmd("LspAttach", { callback = function(args) @@ -505,7 +506,7 @@ callback in the "data" table. Example: > *LspDetach* Just before an LSP client detaches from a buffer. The |autocmd-pattern| is the name of the buffer. When used from Lua, the client ID is passed to the -callback in the "data" table. Example: > +callback in the "data" table. Example: >lua vim.api.nvim_create_autocmd("LspDetach", { callback = function(args) @@ -525,7 +526,7 @@ LspRequest *LspRequest* After a change to the active set of pending LSP requests. See {requests} in |vim.lsp.client|. -Example: > +Example: >vim autocmd User LspProgressUpdate redrawstatus autocmd User LspRequest redrawstatus < @@ -563,9 +564,9 @@ buf_notify({bufnr}, {method}, {params}) *vim.lsp.buf_notify()* Send a notification to a server Parameters: ~ - • {bufnr} [number] (optional): The number of the buffer - • {method} [string]: Name of the request method - • {params} [string]: Arguments to send to the server + • {bufnr} (number|nil) The number of the buffer + • {method} (string) Name of the request method + • {params} (any) Arguments to send to the server Return: ~ true if any client returns true; false otherwise @@ -579,7 +580,7 @@ buf_request_all({bufnr}, {method}, {params}, {callback}) Parameters: ~ • {bufnr} (number) Buffer handle, or 0 for current. • {method} (string) LSP method name - • {params} (optional, table) Parameters to send to the server + • {params} (table|nil) Parameters to send to the server • {callback} (function) The callback to call when all requests are finished. @@ -598,9 +599,9 @@ buf_request_sync({bufnr}, {method}, {params}, {timeout_ms}) Parameters: ~ • {bufnr} (number) Buffer handle, or 0 for current. • {method} (string) LSP method name - • {params} (optional, table) Parameters to send to the server - • {timeout_ms} (optional, number, default=1000) Maximum time in - milliseconds to wait for a result. + • {params} (table|nil) Parameters to send to the server + • {timeout_ms} (number|nil) Maximum time in milliseconds to wait for a + result. Defaults to 1000 Return: ~ Map of client_id:request_result. On timeout, cancel or error, returns @@ -667,7 +668,7 @@ client_is_stopped({client_id}) *vim.lsp.client_is_stopped()* Checks whether a client is stopped. Parameters: ~ - • {client_id} (Number) + • {client_id} (number) Return: ~ true if client is stopped, false otherwise. @@ -680,7 +681,7 @@ for_each_buffer_client({bufnr}, {fn}) • {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: > + number as arguments. Example: >lua vim.lsp.for_each_buffer_client(0, function(client, client_id, bufnr) print(vim.inspect(client)) @@ -746,8 +747,8 @@ omnifunc({findstart}, {base}) *vim.lsp.omnifunc()* Implements 'omnifunc' compatible LSP completion. Parameters: ~ - • {findstart} 0 or 1, decides behavior - • {base} If findstart=0, text to match against + • {findstart} (number) 0 or 1, decides behavior + • {base} (number) findstart=0, text to match against Return: ~ (number) Decided by {findstart}: @@ -769,7 +770,7 @@ set_log_level({level}) *vim.lsp.set_log_level()* Use `lsp.log_levels` for reverse lookup. Parameters: ~ - • {level} [number|string] the case insensitive level name or number + • {level} (number|string) the case insensitive level name or number See also: ~ |vim.lsp.log_levels| @@ -779,35 +780,31 @@ start({config}, {opts}) *vim.lsp.start()* running client if one is found matching `name` and `root_dir`. Attaches the current buffer to the client. - Example: -> + Example: >lua - vim.lsp.start({ - name = 'my-server-name', - cmd = {'name-of-language-server-executable'}, - root_dir = vim.fs.dirname(vim.fs.find({'pyproject.toml', 'setup.py'}, { upward = true })[1]), - }) + vim.lsp.start({ + name = 'my-server-name', + cmd = {'name-of-language-server-executable'}, + root_dir = vim.fs.dirname(vim.fs.find({'pyproject.toml', 'setup.py'}, { upward = true })[1]), + }) < See |vim.lsp.start_client()| for all available options. The most important are: - `name` is an arbitrary name for the LSP client. It should be unique per - language server. - - `cmd` the command as list - used to start the language server. The command must - be present in the `$PATH` environment variable or an absolute path to the executable. Shell - constructs like `~` are NOT expanded. - - `root_dir` path to the project root. By default this is used to decide if - an existing client should be re-used. The example above uses - |vim.fs.find()| and |vim.fs.dirname()| to detect the root by traversing - the file system upwards starting from the current directory until either a - `pyproject.toml` or `setup.py` file is found. - - `workspace_folders` a list of { uri:string, name: string } tables. The - project root folders used by the language server. If `nil` the property is - derived from the `root_dir` for convenience. + • `name` arbitrary name for the LSP client. Should be unique per language + server. + • `cmd` command (in list form) used to start the language server. Must be + absolute, or found on `$PATH`. Shell constructs like `~` are not + expanded. + • `root_dir` path to the project root. By default this is used to decide + if an existing client should be re-used. The example above uses + |vim.fs.find()| and |vim.fs.dirname()| to detect the root by traversing + the file system upwards starting from the current directory until either + a `pyproject.toml` or `setup.py` file is found. + • `workspace_folders` list of `{ uri:string, name: string }` tables + specifying the project root folders used by the language server. If + `nil` the property is derived from `root_dir` for convenience. Language servers use this information to discover metadata like the dependencies of your project and they tend to index the contents within @@ -835,128 +832,118 @@ start({config}, {opts}) *vim.lsp.start()* start_client({config}) *vim.lsp.start_client()* Starts and initializes a client with the given configuration. - Parameter `cmd` is required. - - The following parameters describe fields in the {config} table. - - Parameters: ~ - • {cmd} (table|string|fun(dispatchers: table):table) - command string or list treated like |jobstart()|. - The command must launch the language server - process. `cmd` can also be a function that - creates an RPC client. The function receives a - dispatchers table and must return a table with - the functions `request`, `notify`, `is_closing` - and `terminate` See |vim.lsp.rpc.request()| and - |vim.lsp.rpc.notify()| For TCP there is a - built-in rpc client factory: - |vim.lsp.rpc.connect()| - • {cmd_cwd} (string, default=|getcwd()|) Directory to launch - the `cmd` 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 with `k=v` pairs or both. Non-string values are coerced to - string. Example: > - - { "PRODUCTION=true"; "TEST=123"; PORT = 8080; HOST = "0.0.0.0"; } + Field `cmd` in {config} is required. + + Parameters: ~ + • {config} (table) Configuration for the server: + • cmd: (table|string|fun(dispatchers: table):table) command + string or list treated like |jobstart()|. The command must + launch the language server process. `cmd` can also be a + function that creates an RPC client. The function receives + a dispatchers table and must return a table with the + functions `request`, `notify`, `is_closing` and + `terminate` See |vim.lsp.rpc.request()| and + |vim.lsp.rpc.notify()| For TCP there is a built-in rpc + client factory: |vim.lsp.rpc.connect()| + • cmd_cwd: (string, default=|getcwd()|) Directory to launch + the `cmd` 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 + with `k=v` pairs or both. Non-string values are coerced to string. + Example: > + + { "PRODUCTION=true"; "TEST=123"; PORT = 8080; HOST = "0.0.0.0"; } < - • {detached} (boolean, default true) Daemonize the server - process so that it runs in a separate process - group from Nvim. Nvim will shutdown the process - on exit, but if Nvim fails to exit cleanly this - could leave behind orphaned server processes. - • {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()|, - passed to the language server on initialization. - Hint: use make_client_capabilities() and modify - its result. - • Note: To send an empty dictionary use - `{[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 - 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 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`. See `initialize` in the - LSP spec. - • {name} (string, default=client-id) Name in log messages. - • {get_language_id} function(bufnr, filetype) -> language ID as - string. Defaults to the filetype. - • {offset_encoding} (default="utf-16") One of "utf-8", "utf-16", or - "utf-32" which is the encoding that the LSP - server expects. Client does not verify this is - correct. - • {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.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, where `params` contains the parameters - being sent to the server and `config` is the - config that was passed to - |vim.lsp.start_client()|. You can use this to - modify parameters before they are sent. - • {on_init} Callback (client, initialize_result) invoked - after LSP "initialize", where `result` is a table - of `capabilities` and anything else the server - may send. For example, clangd sends - `initialize_result.offsetEncoding` if - `capabilities.offsetEncoding` was sent to it. You - can only modify the `client.offset_encoding` here - before any notifications are sent. Most language - servers expect to be sent client specified - settings after initialization. Neovim does not - make this assumption. A - `workspace/didChangeConfiguration` notification - should be sent to the server during on_init. - • {on_exit} Callback (code, signal, client_id) invoked on - client exit. - • code: exit code of the process - • signal: number describing the signal used to - terminate (if any) - • client_id: client handle - • {on_attach} Callback (client, bufnr) invoked when client - attaches to a buffer. - • {trace} "off" | "messages" | "verbose" | nil passed - directly to the language server in the initialize - request. Invalid/empty values will default to - "off" - • {flags} A table with flags for the client. The current - (experimental) flags are: - • allow_incremental_sync (bool, default true): - Allow using incremental sync for buffer edits - • debounce_text_changes (number, default 150): - Debounce didChange notifications to the server - by the given number in milliseconds. No - debounce occurs if nil - • exit_timeout (number|boolean, default false): - 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. + • detached: (boolean, default true) Daemonize the server + process so that it runs in a separate process group from + Nvim. Nvim will shutdown the process on exit, but if Nvim + fails to exit cleanly this could leave behind orphaned + server processes. + • 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()|, + passed to the language server on initialization. Hint: use + make_client_capabilities() and modify its result. + • Note: To send an empty dictionary use + `{[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 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 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`. See `initialize` in the LSP + spec. + • name: (string, default=client-id) Name in log messages. + • get_language_id: function(bufnr, filetype) -> language ID + as string. Defaults to the filetype. + • offset_encoding: (default="utf-16") One of "utf-8", + "utf-16", or "utf-32" which is the encoding that the LSP + server expects. Client does not verify this is correct. + • 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.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, where + `params` contains the parameters being sent to the server + and `config` is the config that was passed to + |vim.lsp.start_client()|. You can use this to modify + parameters before they are sent. + • on_init: Callback (client, initialize_result) invoked + after LSP "initialize", where `result` is a table of + `capabilities` and anything else the server may send. For + example, clangd sends `initialize_result.offsetEncoding` + if `capabilities.offsetEncoding` was sent to it. You can + only modify the `client.offset_encoding` here before any + notifications are sent. Most language servers expect to be + sent client specified settings after initialization. + Neovim does not make this assumption. A + `workspace/didChangeConfiguration` notification should be + sent to the server during on_init. + • on_exit Callback (code, signal, client_id) invoked on + client exit. + • code: exit code of the process + • signal: number describing the signal used to terminate + (if any) + • client_id: client handle + + • on_attach: Callback (client, bufnr) invoked when client + attaches to a buffer. + • trace: ("off" | "messages" | "verbose" | nil) passed + directly to the language server in the initialize request. + Invalid/empty values will default to "off" + • flags: A table with flags for the client. The current + (experimental) flags are: + • allow_incremental_sync (bool, default true): Allow using + incremental sync for buffer edits + • debounce_text_changes (number, default 150): Debounce + didChange notifications to the server by the given + number in milliseconds. No debounce occurs if nil + • exit_timeout (number|boolean, default false): + 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 not be fully @@ -966,19 +953,18 @@ start_client({config}) *vim.lsp.start_client()* stop_client({client_id}, {force}) *vim.lsp.stop_client()* Stops a client(s). - You can also use the `stop()` function on a |vim.lsp.client| object. To - stop all clients: -> + You can also use the `stop()` function on a |vim.lsp.client| object. To stop all clients: >lua - vim.lsp.stop_client(vim.lsp.get_active_clients()) + vim.lsp.stop_client(vim.lsp.get_active_clients()) < By default asks the server to shutdown, unless stop was requested already for this client, then force-shutdown is attempted. Parameters: ~ - • {client_id} client id or |vim.lsp.client| object, or list thereof - • {force} (boolean) (optional) shutdown forcefully + • {client_id} number|table id or |vim.lsp.client| object, or list + thereof + • {force} (boolean|nil) shutdown forcefully tagfunc({...}) *vim.lsp.tagfunc()* Provides an interface between the built-in client and 'tagfunc'. @@ -989,8 +975,8 @@ tagfunc({...}) *vim.lsp.tagfunc()* LSP servers, falls back to using built-in tags. Parameters: ~ - • {pattern} Pattern used to find a workspace symbol - • {flags} See |tag-function| + • {pattern} (string) Pattern used to find a workspace symbol + • {flags} (string) See |tag-function| Return: ~ A list of matching tags @@ -1026,6 +1012,8 @@ code_action({options}) *vim.lsp.buf.code_action()* • only (table|nil): List of LSP `CodeActionKind`s used to filter the code actions. Most language servers support values like `refactor` or `quickfix`. + • triggerKind (number|nil): The reason why code actions + were requested. • filter: (function|nil) Predicate taking an `CodeAction` and returning a boolean. @@ -1040,6 +1028,7 @@ code_action({options}) *vim.lsp.buf.code_action()* See also: ~ https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction + vim.lsp.protocol.constants.CodeActionTriggerKind completion({context}) *vim.lsp.buf.completion()* Retrieves the completion items at the current cursor position. Can only be @@ -1080,11 +1069,10 @@ definition({options}) *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`, e.g.: -> - 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() + by events such as `CursorHold` , e.g.: >vim + 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 following @@ -1128,12 +1116,12 @@ format({options}) *vim.lsp.buf.format()* buffer (0). • filter (function|nil): Predicate used to filter clients. Receives a client as argument and must return a boolean. - Clients matching the predicate are included. Example: • > + Clients matching the predicate are included. Example: • >lua - -- Never request typescript-language-server for formatting - vim.lsp.buf.format { - filter = function(client) return client.name ~= "tsserver" end - } + -- Never request typescript-language-server for formatting + vim.lsp.buf.format { + filter = function(client) return client.name ~= "tsserver" end + } < • async boolean|nil If true the method won't block. Defaults to false. Editing the buffer while formatting @@ -1179,7 +1167,7 @@ references({context}, {options}) *vim.lsp.buf.references()* window. Parameters: ~ - • {context} (table) Context for the request + • {context} (table|nil) Context for the request • {options} (table|nil) additional options • on_list: (function) handler for list results. See |lsp-on-list-handler| @@ -1255,7 +1243,7 @@ on_publish_diagnostics({_}, {result}, {ctx}, {config}) |lsp-handler| for the method "textDocument/publishDiagnostics" See |vim.diagnostic.config()| for configuration options. Handler-specific - configuration can be set using |vim.lsp.with()|: > + configuration can be set using |vim.lsp.with()|: >lua vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with( vim.lsp.diagnostic.on_publish_diagnostics, { @@ -1283,6 +1271,13 @@ on_publish_diagnostics({_}, {result}, {ctx}, {config}) ============================================================================== Lua module: vim.lsp.codelens *lsp-codelens* +clear({client_id}, {bufnr}) *vim.lsp.codelens.clear()* + Clear the lenses + + Parameters: ~ + • {client_id} (number|nil) filter by client_id. All clients if nil + • {bufnr} (number|nil) filter by buffer. All buffers if nil + display({lenses}, {bufnr}, {client_id}) *vim.lsp.codelens.display()* Display the lenses using virtual text @@ -1308,8 +1303,9 @@ refresh() *vim.lsp.codelens.refresh()* Refresh the codelens for the current buffer It is recommended to trigger this using an autocmd or via keymap. -> - autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh() + + Example: >vim + autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh() < run() *vim.lsp.codelens.run()* @@ -1325,17 +1321,79 @@ save({lenses}, {bufnr}, {client_id}) *vim.lsp.codelens.save()* ============================================================================== +Lua module: vim.lsp.semantic_tokens *lsp-semantic_tokens* + +force_refresh({bufnr}) *vim.lsp.semantic_tokens.force_refresh()* + Force a refresh of all semantic tokens + + Only has an effect if the buffer is currently active for semantic token + highlighting (|vim.lsp.semantic_tokens.start()| has been called for it) + + Parameters: ~ + • {bufnr} (nil|number) default: current buffer + + *vim.lsp.semantic_tokens.get_at_pos()* +get_at_pos({bufnr}, {row}, {col}) + Return the semantic token(s) at the given position. If called without + arguments, returns the token under the cursor. + + Parameters: ~ + • {bufnr} (number|nil) Buffer number (0 for current buffer, default) + • {row} (number|nil) Position row (default cursor position) + • {col} (number|nil) Position column (default cursor position) + + Return: ~ + (table|nil) List of tokens at position + +start({bufnr}, {client_id}, {opts}) *vim.lsp.semantic_tokens.start()* + Start the semantic token highlighting engine for the given buffer with the + given client. The client must already be attached to the buffer. + + NOTE: This is currently called automatically by + |vim.lsp.buf_attach_client()|. To opt-out of semantic highlighting with a + server that supports it, you can delete the semanticTokensProvider table + from the {server_capabilities} of your client in your |LspAttach| callback + or your configuration's `on_attach` callback: >lua + + client.server_capabilities.semanticTokensProvider = nil +< + + Parameters: ~ + • {bufnr} (number) + • {client_id} (number) + • {opts} (nil|table) Optional keyword arguments + • debounce (number, default: 200): Debounce token + requests to the server by the given number in + milliseconds + +stop({bufnr}, {client_id}) *vim.lsp.semantic_tokens.stop()* + Stop the semantic token highlighting engine for the given buffer with the + given client. + + NOTE: This is automatically called by a |LspDetach| autocmd that is set up + as part of `start()`, so you should only need this function to manually + disengage the semantic token engine without fully detaching the LSP client + from the buffer. + + Parameters: ~ + • {bufnr} (number) + • {client_id} (number) + + +============================================================================== Lua module: vim.lsp.handlers *lsp-handlers* hover({_}, {result}, {ctx}, {config}) *vim.lsp.handlers.hover()* - |lsp-handler| for the method "textDocument/hover" > - - vim.lsp.handlers["textDocument/hover"] = vim.lsp.with( - vim.lsp.handlers.hover, { - -- Use a sharp border with `FloatBorder` highlights - border = "single" - } - ) + |lsp-handler| for the method "textDocument/hover" >lua + + vim.lsp.handlers["textDocument/hover"] = vim.lsp.with( + vim.lsp.handlers.hover, { + -- Use a sharp border with `FloatBorder` highlights + border = "single", + -- add the title in hover float window + title = "hover" + } + ) < Parameters: ~ @@ -1347,14 +1405,14 @@ hover({_}, {result}, {ctx}, {config}) *vim.lsp.handlers.hover()* *vim.lsp.handlers.signature_help()* signature_help({_}, {result}, {ctx}, {config}) |lsp-handler| for the method "textDocument/signatureHelp". The active - parameter is highlighted with |hl-LspSignatureActiveParameter|. > - - vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with( - vim.lsp.handlers.signature_help, { - -- Use a sharp border with `FloatBorder` highlights - border = "single" - } - ) + parameter is highlighted with |hl-LspSignatureActiveParameter|. >lua + + vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with( + vim.lsp.handlers.signature_help, { + -- Use a sharp border with `FloatBorder` highlights + border = "single" + } + ) < Parameters: ~ @@ -1417,7 +1475,7 @@ buf_highlight_references({bufnr}, {references}, {offset_encoding}) • {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 + https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentContentChangeEvent *vim.lsp.util.character_offset()* character_offset({buf}, {row}, {col}, {offset_encoding}) @@ -1443,8 +1501,8 @@ convert_input_to_markdown_lines({input}, {contents}) Parameters: ~ • {input} (`MarkedString` | `MarkedString[]` | `MarkupContent`) - • {contents} (table, optional, default `{}`) List of strings to extend - with converted lines + • {contents} (table|nil) List of strings to extend with converted + lines. Defaults to {}. Return: ~ {contents}, extended with lines of converted markdown. @@ -1501,7 +1559,7 @@ jump_to_location({location}, {offset_encoding}, {reuse_win}) Parameters: ~ • {location} (table) (`Location`|`LocationLink`) • {offset_encoding} "utf-8" | "utf-16" | "utf-32" - • {reuse_win} (boolean) Jump to existing window if buffer is + • {reuse_win} (boolean|nil) Jump to existing window if buffer is already open. Return: ~ @@ -1547,6 +1605,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`, defaults to 50 + • relative ("mouse"|"cursor") defaults to "cursor" Return: ~ (table) Options @@ -1592,7 +1651,7 @@ make_position_params({window}, {offset_encoding}) Parameters: ~ • {window} (number|nil) window handle or 0 for current, defaults to current - • {offset_encoding} (string) utf-8|utf-16|utf-32|nil defaults to + • {offset_encoding} (string|nil) utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window` @@ -1723,7 +1782,7 @@ show_document({location}, {offset_encoding}, {opts}) Parameters: ~ • {location} (table) (`Location`|`LocationLink`) • {offset_encoding} "utf-8" | "utf-16" | "utf-32" - • {opts} (table) options + • {opts} (table|nil) options • reuse_win (boolean) Jump to existing window if buffer is already open. • focus (boolean) Whether to focus/jump to location @@ -1831,7 +1890,7 @@ set_level({level}) *vim.lsp.log.set_level()* Sets the current log level. Parameters: ~ - • {level} (string or number) One of `vim.lsp.log.levels` + • {level} (string|number) One of `vim.lsp.log.levels` should_log({level}) *vim.lsp.log.should_log()* Checks whether the level is sufficient for logging. @@ -1953,7 +2012,7 @@ compute_diff({___MissingCloseParenHere___}) • {offset_encoding} (string) encoding requested by language server Return: ~ - (table) TextDocumentContentChangeEvent see https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#textDocumentContentChangeEvent + (table) TextDocumentContentChangeEvent see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentContentChangeEvent ============================================================================== diff --git a/runtime/doc/lua-guide.txt b/runtime/doc/lua-guide.txt new file mode 100644 index 0000000000..b971a7d2ad --- /dev/null +++ b/runtime/doc/lua-guide.txt @@ -0,0 +1,757 @@ +*lua-guide.txt* Nvim + + NVIM REFERENCE MANUAL + + Guide to using Lua in Nvim + + + Type |gO| to see the table of contents. + +============================================================================== +Introduction *lua-guide* + +This guide will go through the basics of using Lua in Neovim. It is not meant +to be a comprehensive encyclopedia of all available features, nor will it +detail all intricacies. Think of it as a survival kit -- the bare minimum +needed to know to comfortably get started on using Lua in Neovim. + +An important thing to note is that this isn't a guide to the Lua language +itself. Rather, this is a guide on how to configure and modify Neovim through +the Lua language and the functions we provide to help with this. Take a look +at |luaref| and |lua-concepts| if you'd like to learn more about Lua itself. +Similarly, this guide assumes some familiarity with the basics of Neovim +(commands, options, mappings, autocommands), which are covered in the +|user-manual|. + +------------------------------------------------------------------------------ +Some words on the API *lua-guide-api* + +The purpose of this guide is to introduce the different ways of interacting +with Neovim through Lua (the "API"). This API consists of three different +layers: + +1. The "Vim API" inherited from Vim: |ex-commands| and |builtin-functions| as +well as |user-function|s in Vimscript. These are accessed through |vim.cmd()| +and |vim.fn| respectively, which are discussed under |lua-guide-vimscript| +below. + +2. The "Neovim API" written in C for use in remote plugins and GUIs; see |api|. +These functions are accessed through |vim.api|. + +3. The "Lua API" written in and specifically for Lua. These are any other +functions accessible through `vim.*` not mentioned already; see |lua-stdlib|. + +This distinction is important, as API functions inherit behavior from their +original layer: For example, Neovim API functions always need all arguments to +be specified even if Lua itself allows omitting arguments (which are then +passed as `nil`); and Vim API functions can use 0-based indexing even if Lua +arrays are 1-indexed by default. + +Through this, any possible interaction can be done through Lua without writing +a complete new API from scratch. For this reason, functions are usually not +duplicated between layers unless there is a significant benefit in +functionality or performance (e.g., you can map Lua functions directly through +|nvim_create_autocmd()| but not through |:autocmd|). In case there are multiple +ways of achieving the same thing, this guide will only cover what is most +convenient to use from Lua. + +============================================================================== +Using Lua *lua-guide-using-Lua* + +To run Lua code from the Neovim command line, use the |:lua| command: +>vim + :lua print("Hello!") +< +Note: each |:lua| command has its own scope and variables declared with the +local keyword are not accessible outside of the command. This won't work: +>vim + :lua local foo = 1 + :lua print(foo) + " prints "nil" instead of "1" +< +You can also use `:lua=`, which is the same as `:lua vim.pretty_print(...)`, +to conveniently check the value of a variable or a table: +>lua + :lua=package +< +To run a Lua script in an external file, you can use the |:source| command +exactly like for a Vimscript file: +>vim + :source ~/programs/baz/myluafile.lua +< +Finally, you can include Lua code in a Vimscript file by putting it inside a +|lua-heredoc| block: +>vim + lua << EOF + local tbl = {1, 2, 3} + for k, v in ipairs(tbl) do + print(v) + end + EOF +< +------------------------------------------------------------------------------ +Using Lua files on startup *lua-guide-config* + +Neovim supports using `init.vim` or `init.lua` as the configuration file, but +not both at the same time. This should be placed in your |config| directory, +which is typically `~/.config/nvim` for Linux, BSD, or macOS, and +`~/AppData/Local/nvim/` for Windows. Note that you can use Lua in `init.vim` +and Vimscript in `init.lua`, which will be covered below. + +If you'd like to run any other Lua script on |startup| automatically, then you +can simply put it in `plugin/` in your |'runtimepath'|. + +------------------------------------------------------------------------------ +Lua modules *lua-guide-modules* + +If you want to load Lua files on demand, you can place them in the `lua/` +directory in your |'runtimepath'| and load them with `require`. (This is the +Lua equivalent of Vimscript's |autoload| mechanism.) + +Let's assume you have the following directory structure: +> + ~/.config/nvim + |-- after/ + |-- ftplugin/ + |-- lua/ + | |-- myluamodule.lua + | |-- other_modules/ + | |-- anothermodule.lua + | |-- init.lua + |-- plugin/ + |-- syntax/ + |-- init.vim +< + +Then the following Lua code will load `myluamodule.lua`: +>lua + require("myluamodule") +< +Note the absence of a `.lua` extension. + +Similarly, loading `other_modules/anothermodule.lua` is done via +>lua + require('other_modules/anothermodule') + -- or + require('other_modules.anothermodule') +< + +Note how "submodules" are just subdirectories; the `.` is equivalent to the +path separator `/` (even on Windows). + +A folder containing an |init.lua| file can be required directly, without +having to specify the name of the file: +>lua + require('other_modules') -- loads other_modules/init.lua +< +Requiring a nonexistent module or a module which contains syntax errors aborts +the currently executing script. `pcall()` may be used to catch such errors. The +following example tries to load the `module_with_error` and only calls one of +its functions if this succeeds and prints an error message otherwise: +>lua + local ok, mymod = pcall(require, 'module_with_error') + if not ok then + print("Module had an error") + else + mymod.function() + end +< +In contrast to |:source|, |require()| not only searches through all `lua/` directories +under |'runtimepath'|, it also cache the module on first use. Calling +`require()` a second time will therefore _not_ execute the script again and +instead return the cached file. To rerun the file, you need to remove it from +the cache manually first: +>lua + package.loaded['myluamodule'] = nil + require('myluamodule') -- read and execute the module again from disk +< +------------------------------------------------------------------------------ +See also: +• |lua-require| +• |luaref-pcall()| + +============================================================================== +Using Vim commands and functions from Lua *lua-guide-vimscript* + +All Vim commands and functions are accessible from Lua. + +------------------------------------------------------------------------------ +Vim commands *lua-guide-vim-commands* + +To run an arbitrary Vim command from Lua, pass it as a string to |vim.cmd()|: +>lua + vim.cmd("colorscheme habamax") +< +Note that special characters will need to be escaped with backslashes: +>lua + vim.cmd("%s/\\Vfoo/bar/g") +< +An alternative is to use a literal string (see |luaref-literal|) delimited by +double brackets `[[ ]]` as in +>lua + vim.cmd([[%s/\Vfoo/bar/g]]) +< +Another benefit of using literal strings is that they can be multiple lines; +this allows you to pass multiple commands to a single call of |vim.cmd()|: +>lua + vim.cmd([[ + highlight Error guibg=red + highlight link Warning Error + ]]) +< +This is the converse of |lua-heredoc| and allows you to include Vimscript code in +your `init.lua`. + +If you want to build your Vim command programmatically, the following form can +be useful (all these are equivalent to the corresponding line above): +>lua + vim.cmd.colorscheme("habamax") + vim.cmd.highlight({ "Error", "guibg=red" }) + vim.cmd.highlight({ "link", "Warning", "Error" }) +< +------------------------------------------------------------------------------ +Vimscript functions *lua-guide-vim-functions* + +Use |vim.fn| to call Vimscript functions from Lua. Data types between Lua and +Vimscript are automatically converted: +>lua + print(vim.fn.printf('Hello from %s', 'Lua')) + + local reversed_list = vim.fn.reverse({ 'a', 'b', 'c' }) + print(vim.inspect(reversed_list)) -- { "c", "b", "a" } + + local function print_stdout(chan_id, data, name) + print(data[1]) + end + + vim.fn.jobstart('ls', { on_stdout = print_stdout }) + print(vim.fn.printf('Hello from %s', 'Lua')) +< +This works for both |builtin-functions| and |user-function|s. + +Note that hashes (`#`) are not valid characters for identifiers in Lua, so, +e.g., |autoload| functions have to be called with this syntax: +>lua + vim.fn['my#autoload#function']() +< +------------------------------------------------------------------------------ +See also: +• |builtin-functions|: alphabetic list of all Vimscript functions +• |function-list|: list of all Vimscript functions grouped by topic +• |:runtime|: run all Lua scripts matching a pattern in |'runtimepath'| +• |package.path|: list of all paths searched by `require()` + +============================================================================== +Variables *lua-guide-variables* + +Variables can be set and read using the following wrappers, which directly +correspond to their |variable-scope|: + +• |vim.g|: global variables (|g:|) +• |vim.b|: variables for the current buffer (|b:|) +• |vim.w|: variables for the current window (|w:|) +• |vim.t|: variables for the current tabpage (|t:|) +• |vim.v|: predefined Vim variables (|v:|) +• |vim.env|: environment variables defined in the editor session + +Data types are converted automatically. For example: +>lua + vim.g.some_global_variable = { + key1 = "value", + key2 = 300 + } + + print(vim.inspect(vim.g.some_global_variable)) + --> { key1 = "value", key2 = 300 } +< +You can target specific buffers (via number), windows (via |window-ID|), or +tabpages by indexing the wrappers: +>lua + vim.b[2].myvar = 1 -- set myvar for buffer number 2 + vim.w[1005].myothervar = true -- set myothervar for window ID 1005 +< +Some variable names may contain characters that cannot be used for identifiers +in Lua. You can still manipulate these variables by using the syntax +>lua + vim.g['my#variable'] = 1 +< +Note that you cannot directly change fields of array variables. This won't +work: +>lua + vim.g.some_global_variable.key2 = 400 + vim.pretty_print(vim.g.some_global_variable) + --> { key1 = "value", key2 = 300 } +< +Instead, you need to create an intermediate Lua table and change this: +>lua + local temp_table = vim.g.some_global_variable + temp_table.key2 = 400 + vim.g.some_global_variable = temp_table + vim.pretty_print(vim.g.some_global_variable) + --> { key1 = "value", key2 = 400 } +< +To delete a variable, simply set it to `nil`: +>lua + vim.g.myvar = nil +< +------------------------------------------------------------------------------ +See also: +• |lua-vim-variables| + +============================================================================== +Options *lua-guide-options* + +There are two complementary ways of setting |options| via Lua. + +------------------------------------------------------------------------------ +vim.opt + +The most convenient way for setting global and local options, e.g., in `init.lua`, +is through `vim.opt` and friends: + +• |vim.opt|: behaves like |:set| +• |vim.opt_global|: behaves like |:setglobal| +• |vim.opt_local|: behaves like |:setlocal| + +For example, the Vimscript commands +>vim + set smarttab + set nosmarttab +< +are equivalent to +>lua + vim.opt.smarttab = true + vim.opt.smarttab = false +< +In particular, they allow an easy way to working with list-like, map-like, and +set-like options through Lua tables: Instead of +>vim + set wildignore=*.o,*.a,__pycache__ + set listchars=space:_,tab:>~ + set formatoptions=njt +< +you can use +>lua + vim.opt.wildignore = { '*.o', '*.a', '__pycache__' } + vim.opt.listchars = { space = '_', tab = '>~' } + vim.opt.formatoptions = { n = true, j = true, t = true } +< +These wrappers also come with methods that work similarly to their |:set+=|, +|:set^=| and |:set-=| counterparts in Vimscript: +>lua + vim.opt.shortmess:append({ I = true }) + vim.opt.wildignore:prepend('*.o') + vim.opt.whichwrap:remove({ 'b', 's' }) +< +The price to pay is that you cannot access the option values directly but must +use |vim.opt:get()|: +>lua + print(vim.opt.smarttab) + --> {...} (big table) + print(vim.opt.smarttab:get()) + --> false + vim.pretty_print(vim.opt.listchars:get()) + --> { space = '_', tab = '>~' } +< +------------------------------------------------------------------------------ +vim.o + +For this reason, there exists a more direct variable-like access using `vim.o` +and friends, similarly to how you can get and set options via `:echo &number` +and `:let &listchars='space:_,tab:>~'`: + +• |vim.o|: behaves like |:set| +• |vim.go|: behaves like |:setglobal| +• |vim.bo|: for buffer-scoped options +• |vim.wo|: for window-scoped options + +For example: +>lua + vim.o.smarttab = false -- :set nosmarttab + print(vim.o.smarttab) + --> false + vim.o.listchars = 'space:_,tab:>~' -- :set listchars='space:_,tab:>~' + print(vim.o.listchars) + --> 'space:_,tab:>~' + vim.o.isfname = vim.o.isfname .. ',@-@' -- :set isfname+=@-@ + print(vim.o.isfname) + --> '@,48-57,/,.,-,_,+,,,#,$,%,~,=,@-@' + vim.bo.shiftwidth = 4 -- :setlocal shiftwidth=4 + print(vim.bo.shiftwidth) + --> 4 +< +Just like variables, you can specify a buffer number or |window-ID| for buffer +and window options, respectively. If no number is given, the current buffer or +window is used: +>lua + vim.bo[4].expandtab = true -- sets expandtab to true in buffer 4 + vim.wo.number = true -- sets number to true in current window + print(vim.wo[0].number) --> true +< +------------------------------------------------------------------------------ +See also: +• |lua-options| + +============================================================================== +Mappings *lua-guide-mappings* + +You can map either Vim commands or Lua functions to key sequences. + +------------------------------------------------------------------------------ +Creating mappings *lua-guide-mappings-set* + +Mappings can be created using |vim.keymap.set()|. This function takes three +mandatory arguments: +• {mode} is a string or a table of strings containing the mode + prefix for which the mapping will take effect. The prefixes are the ones + listed in |:map-modes|, or "!" for |:map!|, or empty string for |:map|. +• {lhs} is a string with the key sequences that should trigger the mapping. +• {rhs} is either a string with a Vim command or a Lua function that should + be executed when the {lhs} is entered. + An empty string is equivalent to |<Nop>|, which disables a key. + +Examples: +>lua + -- Normal mode mapping for Vim command + vim.keymap.set('n', '<Leader>ex1', '<cmd>echo "Example 1"<cr>') + -- Normal and Command-line mode mapping for Vim command + vim.keymap.set({'n', 'c'}, '<Leader>ex2', '<cmd>echo "Example 2"<cr>') + -- Normal mode mapping for Lua function + vim.keymap.set('n', '<Leader>ex3', vim.treesitter.start) + -- Normal mode mapping for Lua function with arguments + vim.keymap.set('n', '<Leader>ex4', function() print('Example 4') end) +< +You can map functions from Lua modules via +>lua + vim.keymap.set('n', '<Leader>pl1', require('plugin').action) +< +Note that this loads the plugin at the time the mapping is defined. If you +want to defer the loading to the time when the mapping is executed (as for +|autoload| functions), wrap it in `function() end`: +>lua + vim.keymap.set('n', '<Leader>pl2', function() require('plugin').action() end) +< +The fourth, optional, argument is a table with keys that modify the behavior +of the mapping such as those from |:map-arguments|. The following are the most +useful options: +• `buffer`: If given, only set the mapping for the buffer with the specified + number; `0` or `true` means the current buffer. >lua + -- set mapping for the current buffer + vim.keymap.set('n', '<Leader>pl1', require('plugin').action, { buffer = true }) + -- set mapping for the buffer number 4 + vim.keymap.set('n', '<Leader>pl1', require('plugin').action, { buffer = 4 }) +< +• `silent`: If set to `true`, suppress output such as error messages. >lua + vim.keymap.set('n', '<Leader>pl1', require('plugin').action, { silent = true }) +< +• `expr`: If set to `true`, do not execute the {rhs} but use the return value + as input. Special |keycodes| are converted automatically. For example, the following + mapping replaces <down> with <c-n> in the popupmenu only: >lua + vim.keymap.set('c', '<down>', function() + if vim.fn.pumvisible() == 1 then return '<c-n>' end + return '<down>' + end, { expr = true }) +< +• `desc`: A string that is shown when listing mappings with, e.g., |:map|. + This is useful since Lua functions as {rhs} are otherwise only listed as + `Lua: <number> <source file>:<line>`. Plugins should therefore always use this + for mappings they create. >lua + vim.keymap.set('n', '<Leader>pl1', require('plugin').action, + { desc = 'Execute action from plugin' }) +< +• `remap`: By default, all mappings are nonrecursive by default (i.e., + |vim.keymap.set()| behaves like |:noremap|). If the {rhs} is itself a mapping + that should be executed, set `remap = true`: >lua + vim.keymap.set('n', '<Leader>ex1', '<cmd>echo "Example 1"<cr>') + -- add a shorter mapping + vim.keymap.set('n', 'e', '<Leader>ex1', { remap = true }) +< + Note: |<Plug>| mappings are always expanded even with the default `remap = false`: >lua + vim.keymap.set('n', '[%', '<Plug>(MatchitNormalMultiBackward)') +< +------------------------------------------------------------------------------ +Removing mappings *lua-guide-mappings-del* + +A specific mapping can be removed with |vim.keymap.del()|: +>lua + vim.keymap.del('n', '<Leader>ex1') + vim.keymap.del({'n', 'c'}, '<Leader>ex2', {buffer = true}) +< +------------------------------------------------------------------------------ +See also: +• `vim.api.`|nvim_get_keymap()|: return all global mapping +• `vim.api.`|nvim_buf_get_keymap()|: return all mappings for buffer + +============================================================================== +Autocommands *lua-guide-autocommands* + +An |autocommand| is a Vim command or a Lua function that is automatically +executed whenever one or more |events| are triggered, e.g., when a file is +read or written, or when a window is created. These are accessible from Lua +through the Neovim API. + +------------------------------------------------------------------------------ +Creating autocommands *lua-guide-autocommand-create* + +Autocommands are created using `vim.api.`|nvim_create_autocmd()|, which takes +two mandatory arguments: +• {event}: a string or table of strings containing the event(s) which should + trigger the command or function. +• {opts}: a table with keys that control what should happen when the event(s) + are triggered. + +The most important options are: + +• `pattern`: A string or table of strings containing the |autocmd-pattern|. + Note: Environment variable like `$HOME` and `~` are not automatically + expanded; you need to explicitly use `vim.fn.`|expand()| for this. +• `command`: A string containing a Vim command. +• `callback`: A Lua function. + +You must specify one and only one of `command` and `callback`. If `pattern` is +omitted, it defaults to `pattern = '*'`. +Examples: +>lua + vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { + pattern = {"*.c", "*.h"}, + command = "echo 'Entering a C or C++ file'", + }) + + -- Same autocommand written with a Lua function instead + vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { + pattern = {"*.c", "*.h"}, + callback = function() print("Entering a C or C++ file") end, + }) + + -- User event triggered by MyPlugin + vim.api.nvim_create_autocmd("User", { + pattern = "MyPlugin", + callback = function() print("My Plugin Works!") end, + }) +< + +Neovim will always call a Lua function with a single table containing information +about the triggered autocommand. The most useful keys are +• `match`: a string that matched the `pattern` (see |<amatch>|) +• `buf`: the number of the buffer the event was triggered in (see |<abuf>|) +• `file`: the file name of the buffer the event was triggered in (see |<afile>|) +• `data`: a table with other relevant data that is passed for some events + +For example, this allows you to set buffer-local mappings for some filetypes: +>lua + vim.api.nvim.create_autocmd("FileType", { + pattern = "lua", + callback = function(args) + vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = args.buf }) + end + }) +< +This means that if your callback itself takes an (even optional) argument, you +must wrap it in `function() end` to avoid an error: +>lua + vim.api.nvim_create_autocmd('TextYankPost', { + callback = function() vim.highlight.on_yank() end + }) +< +(Since unused arguments can be omitted in Lua function definitions, this is +equivalent to `function(args) ... end`.) + +Instead of using a pattern, you can create a buffer-local autocommand (see +|autocmd-buflocal|) with `buffer`; in this case, `pattern` cannot be used: +>lua + -- set autocommand for current buffer + vim.api.nvim_create_autocmd("CursorHold", { + buffer = 0, + callback = function() print("hold") end, + }) + + -- set autocommand for buffer number 33 + vim.api.nvim_create_autocmd("CursorHold", { + buffer = 33, + callback = function() print("hold") end, + }) +< +Similarly to mappings, you can (and should) add a description using `desc`: +>lua + vim.api.nvim_create_autocmd('TextYankPost', { + callback = function() vim.highlight.on_yank() end, + desc = "Briefly highlight yanked text" + }) +< +Finally, you can group autocommands using the `group` key; this will be +covered in detail in the next section. + +------------------------------------------------------------------------------ +Grouping autocommands *lua-guide-autocommands-group* + +Autocommand groups can be used to group related autocommands together; see +|autocmd-groups|. This is useful for organizing autocommands and especially +for preventing autocommands to be set multiple times. + +Groups can be created with `vim.api.`|nvim_create_augroup()|. This function +takes two mandatory arguments: a string with the name of a group and a table +determining whether the group should be cleared (i.e., all grouped +autocommands removed) if it already exists. The function returns a number that +is the internal identifier of the group. Groups can be specified either by +this identifier or by the name (but only if the group has been created first). + +For example, a common Vimscript pattern for autocommands defined in files that +may be reloaded is +>vim + augroup vimrc + " Remove all vimrc autocommands + autocmd! + au BufNewFile,BufRead *.html set shiftwidth=4 + au BufNewFile,BufRead *.html set expandtab + augroup END +< +This is equivalent to the following Lua code: +>lua + local mygroup = vim.api.nvim_create_augroup('vimrc', { clear = true }) + vim.api.nvim_create_autocmd({ 'BufNewFile', 'BufRead' }, { + pattern = '*.html', + group = mygroup, + command = 'set shiftwidth=4', + }) + vim.api.nvim_create_autocmd({ 'BufNewFile', 'BufRead' }, { + pattern = '*.html', + group = 'vimrc', -- equivalent to group=mygroup + command = 'set expandtab', + }) +< +Autocommand groups are unique for a given name, so you can reuse them, e.g., +in a different file: +>lua + local mygroup = vim.api.nvim_create_augroup('vimrc', { clear = false }) + vim.api.nvim_create_autocmd({ 'BufNewFile', 'BufRead' }, { + pattern = '*.html', + group = mygroup, + command = 'set shiftwidth=4', + }) +< +------------------------------------------------------------------------------ +Deleting autocommands *lua-guide-autocommands-delete* + +You can use `vim.api.`|nvim_clear_autocmds()| to remove autocommands. This +function takes a single mandatory argument that is a table of keys describing +the autocommands that are to be removed: +>lua + -- Delete all BufEnter and InsertLeave autocommands + vim.api.nvim_clear_autocmds({event = {"BufEnter", "InsertLeave"}}) + + -- Delete all autocommands that uses "*.py" pattern + vim.api.nvim_clear_autocmds({pattern = "*.py"}) + + -- Delete all autocommands in group "scala" + vim.api.nvim_clear_autocmds({group = "scala"}) + + -- Delete all ColorScheme autocommands in current buffer + vim.api.nvim_clear_autocmds({event = "ColorScheme", buffer = 0 }) +< +Note: Autocommands in groups will only be removed if the `group` key is +specified, even if another option matches it. + +------------------------------------------------------------------------------ +See also +• |nvim_get_autocmds()|: return all matching autocommands +• |nvim_exec_autocmds()|: execute all matching autocommands + +============================================================================== +User commands *lua-guide-usercommands* + +|user-commands| are custom Vim commands that call a Vimscript or Lua function. +Just like built-in commands, they can have arguments, act on ranges, or have +custom completion of arguments. As these are most useful for plugins, we will +cover only the basics of this advanced topic. + +------------------------------------------------------------------------------ +Creating user commands *lua-guide-usercommands-create* + +User commands can be created through the Neovim API with +`vim.api.`|nvim_create_user_command()|. This function takes three mandatory +arguments: +• a string that is the name of the command (which must start with an uppercase + letter to distinguish it from builtin commands); +• a string containing Vim commands or a Lua function that is executed when the + command is invoked; +• a table with |command-attributes|; in addition, it can contain the keys + `desc` (a string describing the command); `force` (set to `false` to avoid + replacing an already existing command with the same name), and `preview` (a + Lua function that is used for |:command-preview|). + +Example: +>lua + vim.api.nvim_create_user_command('Test', 'echo "It works!"', {}) + vim.cmd.Test() + --> It works! +< +(Note that the third argument is mandatory even if no attributes are given.) + +Lua functions are called with a single table argument containing arguments and +modifiers. The most important are: +• `name`: a string with the command name +• `fargs`: a table containing the command arguments split by whitespace (see |<f-args>|) +• `bang`: `true` if the command was executed with a `!` modifier (see |<bang>|) +• `line1`: the starting line number of the command range (see |<line1>|) +• `line2`: the final line number of the command range (see |<line2>|) +• `range`: the number of items in the command range: 0, 1, or 2 (see |<range>|) +• `count`: any count supplied (see |<count>|) +• `smods`: a table containing the command modifiers (see |<mods>|) + +For example: +>lua + vim.api.nvim_create_user_command('Upper', + function(opts) + print(string.upper(opts.fargs[1])) + end, + { nargs = 1 }) + + vim.cmd.Upper('foo') + --> FOO +< +The `complete` attribute can take a Lua function in addition to the +attributes listed in |:command-complete|. >lua + + vim.api.nvim_create_user_command('Upper', + function(opts) + print(string.upper(opts.fargs[1])) + end, + { nargs = 1, + complete = function(ArgLead, CmdLine, CursorPos) + -- return completion candidates as a list-like table + return { "foo", "bar", "baz" } + end, + }) +< +Buffer-local user commands are created with `vim.api.`|nvim_buf_create_user_command()|. +Here the first argument is the buffer number (`0` being the current buffer); +the remaining arguments are the same as for |nvim_create_user_command()|: +>lua + vim.api.nvim_buf_create_user_command(0, 'Upper', + function(opts) + print(string.upper(opts.fargs[1])) + end, + { nargs = 1 }) +< +------------------------------------------------------------------------------ +Deleting user commands *lua-guide-usercommands-delete* + +User commands can be deleted with `vim.api.`|nvim_del_user_command()|. The only +argument is the name of the command: +>lua + vim.api.nvim_del_user_command('Upper') +< +To delete buffer-local user commands use `vim.api.`|nvim_buf_del_user_command()|. +Here the first argument is the buffer number (`0` being the current buffer), +and second is command name: +>lua + vim.api.nvim_buf_del_user_command(4, 'Upper') +< +============================================================================== +Credits *lua-guide-credits* +This guide is in large part taken from nanotee's Lua guide: +https://github.com/nanotee/nvim-lua-guide + +Thank you @nanotee! + +vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl: diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 7330453778..47249a484b 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -12,7 +12,7 @@ Lua engine *lua* *Lua* INTRODUCTION *lua-intro* The Lua 5.1 script engine is builtin and always available. Try this command to -get an idea of what lurks beneath: > +get an idea of what lurks beneath: >vim :lua print(vim.inspect(package.loaded)) @@ -21,20 +21,19 @@ Nvim includes a "standard library" |lua-stdlib| for Lua. It complements the which can be used from Lua code (|lua-vimscript| |vim.api|). Together these "namespaces" form the Nvim programming interface. -The |:source| and |:runtime| commands can run Lua scripts. Lua modules can be -loaded with `require('name')`, which by convention usually returns a table. -See |lua-require| for how Nvim finds and loads Lua modules. - -See this page for more insight into Nvim Lua: - https://github.com/nanotee/nvim-lua-guide +Lua plugins and user config are automatically discovered and loaded, just like +Vimscript. See |lua-guide| for practical guidance. +You can also run Lua scripts from your shell using the |-l| argument: > + nvim -l foo.lua [args...] +< *lua-compat* Lua 5.1 is the permanent interface for Nvim Lua. Plugins need only consider Lua 5.1, not worry about forward-compatibility with future Lua versions. If Nvim ever ships with Lua 5.4+, a Lua 5.1 compatibility shim will be provided so that old plugins continue to work transparently. ------------------------------------------------------------------------------- +============================================================================== LUA CONCEPTS AND IDIOMS *lua-concepts* Lua is very simple: this means that, while there are some quirks, once you @@ -56,20 +55,20 @@ https://www.lua.org/doc/cacm2018.pdf versatile control for both Lua and its host (Nvim). *lua-call-function* -Lua functions can be called in multiple ways. Consider the function: > +Lua functions can be called in multiple ways. Consider the function: >lua local foo = function(a, b) print("A: ", a) print("B: ", b) end -The first way to call this function is: > +The first way to call this function is: >lua foo(1, 2) -- ==== Result ==== -- A: 1 -- B: 2 This way of calling a function is familiar from most scripting languages. -In Lua, any missing arguments are passed as `nil`. Example: > +In Lua, any missing arguments are passed as `nil`. Example: >lua foo(1) -- ==== Result ==== -- A: 1 @@ -78,25 +77,24 @@ In Lua, any missing arguments are passed as `nil`. Example: > Furthermore it is not an error if extra parameters are passed, they are just discarded. -It is also allowed to omit the parentheses (only) if the function takes -exactly one string (`"foo"`) or table literal (`{1,2,3}`). The latter is often -used to approximate the "named parameters" feature of languages like Python -("kwargs" or "keyword args"). Example: > + *kwargs* +When calling a function, you can omit the parentheses if the function takes +exactly one string literal (`"foo"`) or table literal (`{1,2,3}`). The latter +is often used to approximate "named parameters" ("kwargs" or "keyword args") +as in languages like Python and C#. Example: >lua local func_with_opts = function(opts) local will_do_foo = opts.foo local filename = opts.filename - ... end func_with_opts { foo = true, filename = "hello.world" } < -There is nothing special going on here except that parentheses are treated as +There's nothing special going on here except that parentheses are treated as whitespace. But visually, this small bit of sugar gets reasonably close to a "keyword args" interface. -It is of course also valid to call the function with parentheses: > - +It is of course also valid to call the function with parentheses: >lua func_with_opts({ foo = true, filename = "hello.world" }) < Nvim tends to prefer the keyword args style. @@ -105,27 +103,22 @@ Nvim tends to prefer the keyword args style. LUA PATTERNS *lua-patterns* Lua intentionally does not support regular expressions, instead it has limited -"patterns" which avoid the performance pitfalls of extended regex. -|luaref-patterns| +"patterns" |luaref-patterns| which avoid the performance pitfalls of extended +regex. Lua scripts can also use Vim regex via |vim.regex()|. -Examples using |string.match()|: > +These examples use |string.match()| to demonstrate Lua patterns: >lua print(string.match("foo123bar123", "%d+")) -- 123 - print(string.match("foo123bar123", "[^%d]+")) -- foo - print(string.match("foo123bar123", "[abc]+")) -- ba - print(string.match("foo.bar", "%.bar")) -- .bar -For more complex matching you can use Vim regex from Lua via |vim.regex()|. - ============================================================================== -IMPORTING LUA MODULES *lua-require* +IMPORTING LUA MODULES *require()* *lua-require* Modules are searched for under the directories specified in 'runtimepath', in the order they appear. Any "." in the module name is treated as a directory @@ -139,8 +132,7 @@ back to Lua's default search mechanism. The first script found is run and The return value is cached after the first call to `require()` for each module, with subsequent calls returning the cached value without searching for, or -executing any script. For further details on `require()`, see the Lua -documentation at https://www.lua.org/manual/5.1/manual.html#pdf-require. +executing any script. For further details on `require()`, see |luaref-require()|. For example, if 'runtimepath' is `foo,bar` and |package.cpath| was `./?.so;./?.dll` at startup, `require('mod')` searches these paths in order @@ -205,7 +197,7 @@ Note: - Although adjustments happen automatically, Nvim does not track current values of |package.path| or |package.cpath|. If you happen to delete some - paths from there you can set 'runtimepath' to trigger an update: > + paths from there you can set 'runtimepath' to trigger an update: >vim let &runtimepath = &runtimepath - Skipping paths from 'runtimepath' which contain semicolons applies both to @@ -231,11 +223,11 @@ arguments separated by " " (space) instead of "\t" (tab). chunk is evaluated as an expression and printed. `:lua =expr` is equivalent to `:lua print(vim.inspect(expr))` - Examples: > + Examples: >vim :lua vim.api.nvim_command('echo "Hello, Nvim!"') -< To see the Lua version: > +< To see the Lua version: >vim :lua print(_VERSION) -< To see the LuaJIT version: > +< To see the LuaJIT version: >vim :lua =jit.version < *:lua-heredoc* @@ -246,7 +238,7 @@ arguments separated by " " (space) instead of "\t" (tab). be preceded by whitespace. You can omit [endmarker] after the "<<" and use a dot "." after {script} (similar to |:append|, |:insert|). - Example: > + Example: >vim function! CurrentLineInfo() lua << EOF local linenr = vim.api.nvim_win_get_cursor(0)[1] @@ -268,7 +260,7 @@ arguments separated by " " (space) instead of "\t" (tab). that becomes the text of the corresponding buffer line. Default [range] is the whole file: "1,$". - Examples: > + Examples: >vim :luado return string.format("%s\t%d", line:reverse(), #line) :lua require"lpeg" @@ -282,7 +274,7 @@ arguments separated by " " (space) instead of "\t" (tab). The whole argument is used as the filename (like |:edit|), spaces do not need to be escaped. Alternatively you can |:source| Lua files. - Examples: > + Examples: >vim :luafile script.lua :luafile % < @@ -293,7 +285,7 @@ luaeval() *lua-eval* *luaeval()* The (dual) equivalent of "vim.eval" for passing Lua values to Nvim is "luaeval". "luaeval" takes an expression string and an optional argument used for _A inside expression and returns the result of the expression. It is -semantically equivalent in Lua to: > +semantically equivalent in Lua to: >lua local chunkheader = "local _A = select(1, ...) return " function luaeval (expstr, arg) @@ -307,11 +299,11 @@ converted to a |Blob|. Conversion of other Lua types is an error. The magic global "_A" contains the second argument to luaeval(). -Example: > +Example: >vim :echo luaeval('_A[1] + _A[2]', [40, 2]) - 42 + " 42 :echo luaeval('string.match(_A, "[a-z]+")', 'XYXfoo123') - foo + " foo < Lua tables are used as both dictionaries and lists, so it is impossible to determine whether empty table is meant to be empty list or empty dictionary. @@ -343,7 +335,7 @@ cases there is the following agreement: form a 1-step sequence from 1 to N are ignored, as well as all non-integral keys. -Examples: > +Examples: >vim :echo luaeval('math.pi') :function Rand(x,y) " random uniform between x and y @@ -360,29 +352,29 @@ treated specially. Vimscript v:lua interface *v:lua-call* From Vimscript the special `v:lua` prefix can be used to call Lua functions -which are global or accessible from global tables. The expression > - v:lua.func(arg1, arg2) -is equivalent to the Lua chunk > +which are global or accessible from global tables. The expression >vim + call v:lua.func(arg1, arg2) +is equivalent to the Lua chunk >lua return func(...) -where the args are converted to Lua values. The expression > - v:lua.somemod.func(args) -is equivalent to the Lua chunk > +where the args are converted to Lua values. The expression >vim + call v:lua.somemod.func(args) +is equivalent to the Lua chunk >lua return somemod.func(...) -In addition, functions of packages can be accessed like > - v:lua.require'mypack'.func(arg1, arg2) - v:lua.require'mypack.submod'.func(arg1, arg2) +In addition, functions of packages can be accessed like >vim + call v:lua.require'mypack'.func(arg1, arg2) + call v:lua.require'mypack.submod'.func(arg1, arg2) Note: Only single quote form without parens is allowed. Using `require"mypack"` or `require('mypack')` as prefixes do NOT work (the latter is still valid as a function call of itself, in case require returns a useful value). The `v:lua` prefix may be used to call Lua functions as |method|s. For -example: > - arg1->v:lua.somemod.func(arg2) +example: >vim + :eval arg1->v:lua.somemod.func(arg2) < You can use `v:lua` in "func" options like 'tagfunc', 'omnifunc', etc. -For example consider the following Lua omnifunc handler: > +For example consider the following Lua omnifunc handler: >lua function mymod.omnifunc(findstart, base) if findstart == 1 then @@ -394,10 +386,10 @@ For example consider the following Lua omnifunc handler: > vim.api.nvim_buf_set_option(0, 'omnifunc', 'v:lua.mymod.omnifunc') Note: The module ("mymod" in the above example) must either be a Lua global, -or use the require syntax as specified above to access it from a package. +or use require() as shown above to access it from a package. Note: `v:lua` without a call is not allowed in a Vimscript expression: -|Funcref|s cannot represent Lua functions. The following are errors: > +|Funcref|s cannot represent Lua functions. The following are errors: >vim let g:Myvar = v:lua.myfunc " Error call SomeFunc(v:lua.mycallback) " Error @@ -411,7 +403,7 @@ The Nvim Lua "standard library" (stdlib) is the `vim` module, which exposes various functions and sub-modules. It is always loaded, thus `require("vim")` is unnecessary. -You can peek at the module properties: > +You can peek at the module properties: >vim :lua print(vim.inspect(vim)) @@ -431,7 +423,7 @@ Result is something like this: > ... } -To find documentation on e.g. the "deepcopy" function: > +To find documentation on e.g. the "deepcopy" function: >vim :help vim.deepcopy() @@ -443,7 +435,7 @@ VIM.LOOP *lua-loop* *vim.loop* `vim.loop` exposes all features of the Nvim event-loop. This is a low-level API that provides functionality for networking, filesystem, and process -management. Try this command to see available functions: > +management. Try this command to see available functions: >vim :lua print(vim.inspect(vim.loop)) < @@ -452,14 +444,14 @@ see |luv-intro| for a full reference manual. *E5560* *lua-loop-callbacks* It is an error to directly invoke `vim.api` functions (except |api-fast|) in -`vim.loop` callbacks. For example, this is an error: > +`vim.loop` callbacks. For example, this is an error: >lua local timer = vim.loop.new_timer() timer:start(1000, 0, function() vim.api.nvim_command('echomsg "test"') end) < -To avoid the error use |vim.schedule_wrap()| to defer the callback: > +To avoid the error use |vim.schedule_wrap()| to defer the callback: >lua local timer = vim.loop.new_timer() timer:start(1000, 0, vim.schedule_wrap(function() @@ -471,7 +463,7 @@ wrapping.) Example: repeating timer 1. Save this code to a file. - 2. Execute it with ":luafile %". > + 2. Execute it with ":luafile %". >lua -- Create a timer handle (implementation detail: uv_timer_t). local timer = vim.loop.new_timer() @@ -492,7 +484,7 @@ Example: File-change detection *watch-file* 3. Use ":Watch %" to watch any file. 4. Try editing the file from another text editor. 5. Observe that the file reloads in Nvim (because on_change() calls - |:checktime|). > + |:checktime|). >lua local w = vim.loop.new_fs_event() local function on_change(err, fname, status) @@ -515,7 +507,7 @@ Example: TCP echo-server *tcp-server* 1. Save this code to a file. 2. Execute it with ":luafile %". 3. Note the port number. - 4. Connect from any TCP client (e.g. "nc 0.0.0.0 36795"): > + 4. Connect from any TCP client (e.g. "nc 0.0.0.0 36795"): >lua local function create_server(host, port, on_connect) local server = vim.loop.new_tcp() @@ -564,16 +556,16 @@ VIM.HIGHLIGHT *lua-highlight* Nvim includes a function for highlighting a selection on yank (see for example https://github.com/machakann/vim-highlightedyank). To enable it, add -> +>vim au TextYankPost * silent! lua vim.highlight.on_yank() < to your `init.vim`. You can customize the highlight group and the duration of the highlight via -> +>vim au TextYankPost * silent! lua vim.highlight.on_yank {higroup="IncSearch", timeout=150} < If you want to exclude visual selections from highlighting on yank, use -> +>vim au TextYankPost * silent! lua vim.highlight.on_yank {on_visual=false} < vim.highlight.on_yank({opts}) *vim.highlight.on_yank()* @@ -609,6 +601,7 @@ 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 + • `semantic_tokens`: `125`, used for LSP semantic token 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 @@ -646,20 +639,19 @@ vim.diff({a}, {b}, {opts}) *vim.diff()* Run diff on strings {a} and {b}. Any indices returned by this function, either directly or via callback arguments, are 1-based. - Examples: > - + Examples: >lua vim.diff('a\n', 'b\nc\n') - => - @@ -1 +1,2 @@ - -a - +b - +c + -- => + -- @@ -1 +1,2 @@ + -- -a + -- +b + -- +c vim.diff('a\n', 'b\nc\n', {result_type = 'indices'}) - => - { - {1, 1, 1, 2} - } + -- => + -- { + -- {1, 1, 1, 2} + -- } < Parameters: ~ • {a} First string to compare @@ -677,6 +669,9 @@ vim.diff({a}, {b}, {opts}) *vim.diff()* • "unified": (default) String in unified format. • "indices": Array of hunk locations. Note: This option is ignored if `on_hunk` is used. + • `linematch` (boolean): Run linematch on the resulting hunks + from xdiff. Requires `result_type = indices`, ignored + otherwise. • `algorithm` (string): Diff algorithm to use. Values: • "myers" the default algorithm @@ -717,6 +712,22 @@ vim.mpack.decode({str}) *vim.mpack.decode* Decodes (or "unpacks") the msgpack-encoded {str} to a Lua object. ------------------------------------------------------------------------------ +VIM.JSON *lua-json* + +The *vim.json* module provides encoding and decoding of Lua objects to and +from JSON-encoded strings. Supports |vim.NIL| and |vim.empty_dict()|. + +vim.json.encode({obj}) *vim.json.encode* + Encodes (or "packs") Lua object {obj} as JSON in a Lua string. + +vim.json.decode({str}[, {opts}]) *vim.json.decode* + Decodes (or "unpacks") the JSON-encoded {str} to a Lua object. + + {opts} is a table with the key `luanil = { object: bool, array: bool }` + that controls whether `null` in JSON objects or arrays should be converted + to Lua `nil` instead of `vim.NIL`. + +------------------------------------------------------------------------------ VIM.SPELL *lua-spell* vim.spell.check({str}) *vim.spell.check()* @@ -727,13 +738,12 @@ vim.spell.check({str}) *vim.spell.check()* 'spellfile', 'spellcapcheck' and 'spelloptions' which can all be local to the buffer. Consider calling this with |nvim_buf_call()|. - Example: > - + Example: >lua vim.spell.check("the quik brown fox") - => - { - {'quik', 'bad', 4} - } + -- => + -- { + -- {'quik', 'bad', 5} + -- } < Parameters: ~ • {str} String to spell check. @@ -753,7 +763,7 @@ VIM *lua-builtin* vim.api.{func}({...}) *vim.api* Invokes Nvim |API| function {func} with arguments {...}. - Example: call the "nvim_get_current_line()" API function: > + Example: call the "nvim_get_current_line()" API function: >lua print(tostring(vim.api.nvim_get_current_line())) vim.version() *vim.version* @@ -878,7 +888,7 @@ vim.wait({time} [, {callback}, {interval}, {fast_only}]) *vim.wait()* If {callback} errors, the error is raised. - Examples: > + Examples: >lua --- -- Wait for 100 ms, allowing other events to process @@ -919,7 +929,7 @@ vim.ui_attach({ns}, {options}, {callback}) *vim.ui_attach()* used to handle messages when setting 'cmdheight' to zero (which is likewise experimental). - Example (stub for a |ui-popupmenu| implementation): > + Example (stub for a |ui-popupmenu| implementation): >lua ns = vim.api.nvim_create_namespace('my_fancy_pum') @@ -947,7 +957,7 @@ vim.type_idx *vim.type_idx* vim.val_idx *vim.val_idx* Value index for tables representing |Float|s. A table representing - floating-point value 1.0 looks like this: > + floating-point value 1.0 looks like this: >lua { [vim.type_idx] = vim.types.float, [vim.val_idx] = 1.0, @@ -994,7 +1004,7 @@ See also https://github.com/nanotee/nvim-lua-guide. vim.call({func}, {...}) *vim.call()* Invokes |vim-function| or |user-function| {func} with arguments {...}. See also |vim.fn|. - Equivalent to: > + Equivalent to: >lua vim.fn[func]({...}) vim.cmd({command}) @@ -1002,7 +1012,7 @@ vim.cmd({command}) vim.fn.{func}({...}) *vim.fn* Invokes |vim-function| or |user-function| {func} with arguments {...}. - To call autoload functions, use the syntax: > + To call autoload functions, use the syntax: >lua vim.fn['some#function']({...}) < Unlike vim.api.|nvim_call_function()| this converts directly between Vim @@ -1025,7 +1035,7 @@ from Lua conveniently and idiomatically by referencing the `vim.*` Lua tables described below. In this way you can easily read and modify global Vimscript variables from Lua. -Example: > +Example: >lua vim.g.foo = 5 -- Set the g:foo Vimscript variable. print(vim.g.foo) -- Get and print the g:foo Vimscript variable. @@ -1038,7 +1048,7 @@ Nvim. This is because the index into the namespace simply returns a copy. Instead the whole dictionary must be written as one. This can be achieved by creating a short-lived temporary. -Example: > +Example: >lua vim.g.my_dict.field1 = 'value' -- Does not work @@ -1073,7 +1083,7 @@ vim.env *vim.env* Environment variables defined in the editor session. See |expand-env| and |:let-environment| for the Vimscript behavior. Invalid or unset key returns `nil`. - Example: > + Example: >lua vim.env.FOO = 'bar' print(vim.env.TERM) < @@ -1107,7 +1117,7 @@ vim.o *vim.o* Note: this works on both buffer-scoped and window-scoped options using the current buffer and window. - Example: > + Example: >lua vim.o.cmdheight = 4 print(vim.o.columns) print(vim.o.foo) -- error: invalid key @@ -1120,7 +1130,7 @@ vim.go *vim.go* option value and thus is mostly useful for use with |global-local| options. - Example: > + Example: >lua vim.go.cmdheight = 4 print(vim.go.columns) print(vim.go.bar) -- error: invalid key @@ -1132,7 +1142,7 @@ vim.bo[{bufnr}] * Note: this is equivalent to both `:set` and `:setlocal`. - Example: > + Example: >lua local bufnr = vim.api.nvim_get_current_buf() vim.bo[bufnr].buflisted = true -- same as vim.bo.buflisted = true print(vim.bo.comments) @@ -1143,11 +1153,11 @@ vim.wo[{winid}] * Like `:set`. If [{winid}] is omitted then the current window is used. Invalid {winid} or key is an error. - Note: this does not access |local-options| (`:setlocal`) instead use: > + Note: this does not access |local-options| (`:setlocal`) instead use: >lua nvim_get_option_value(OPTION, { scope = 'local', win = winid }) nvim_set_option_value(OPTION, VALUE, { scope = 'local', win = winid } < - Example: > + Example: >lua local winid = vim.api.nvim_get_current_win() vim.wo[winid].number = true -- same as vim.wo.number = true print(vim.wo.foldmarker) @@ -1156,9 +1166,8 @@ vim.wo[{winid}] * - *lua-vim-opt* - *lua-vim-optlocal* - *lua-vim-optglobal* + *vim.opt_local* + *vim.opt_global* *vim.opt* @@ -1169,37 +1178,37 @@ offers object-oriented method for adding and removing entries. Examples: ~ The following methods of setting a list-style option are equivalent: - In Vimscript: - `set wildignore=*.o,*.a,__pycache__` - - In Lua using `vim.o`: - `vim.o.wildignore = '*.o,*.a,__pycache__'` - - In Lua using `vim.opt`: - `vim.opt.wildignore = { '*.o', '*.a', '__pycache__' }` - - To replicate the behavior of |:set+=|, use: > + In Vimscript: >vim + set wildignore=*.o,*.a,__pycache__ +< + In Lua using `vim.o`: >lua + vim.o.wildignore = '*.o,*.a,__pycache__' +< + In Lua using `vim.opt`: >lua + vim.opt.wildignore = { '*.o', '*.a', '__pycache__' } +< + To replicate the behavior of |:set+=|, use: >lua vim.opt.wildignore:append { "*.pyc", "node_modules" } < - To replicate the behavior of |:set^=|, use: > + To replicate the behavior of |:set^=|, use: >lua vim.opt.wildignore:prepend { "new_first_value" } < - To replicate the behavior of |:set-=|, use: > + To replicate the behavior of |:set-=|, use: >lua vim.opt.wildignore:remove { "node_modules" } < The following methods of setting a map-style option are equivalent: - In Vimscript: - `set listchars=space:_,tab:>~` - - In Lua using `vim.o`: - `vim.o.listchars = 'space:_,tab:>~'` - - In Lua using `vim.opt`: - `vim.opt.listchars = { space = '_', tab = '>~' }` - + In Vimscript: >vim + set listchars=space:_,tab:>~ +< + In Lua using `vim.o`: >lua + vim.o.listchars = 'space:_,tab:>~' +< + In Lua using `vim.opt`: >lua + vim.opt.listchars = { space = '_', tab = '>~' } +< Note that |vim.opt| returns an `Option` object, not the value of the option, which is accessed through |vim.opt:get()|: @@ -1207,15 +1216,15 @@ which is accessed through |vim.opt:get()|: Examples: ~ The following methods of getting a list-style option are equivalent: - In Vimscript: - `echo wildignore` - - In Lua using `vim.o`: - `print(vim.o.wildignore)` - - In Lua using `vim.opt`: - `vim.pretty_print(vim.opt.wildignore:get())` - + In Vimscript: >vim + echo wildignore +< + In Lua using `vim.o`: >lua + print(vim.o.wildignore) +< + In Lua using `vim.opt`: >lua + vim.pretty_print(vim.opt.wildignore:get()) +< In any of the above examples, to replicate the behavior |:setlocal|, use `vim.opt_local`. Additionally, to replicate the behavior of |:setglobal|, use @@ -1230,7 +1239,7 @@ Option:get() values will be returned in exactly the same fashion. For values that are comma-separated lists, an array will be returned with - the values as entries in the array: > + the values as entries in the array: >lua vim.cmd [[set wildignore=*.pyc,*.o]] vim.pretty_print(vim.opt.wildignore:get()) @@ -1243,7 +1252,7 @@ Option:get() -- Will ignore: *.o < For values that are comma-separated maps, a table will be returned with - the names as keys and the values as entries: > + the names as keys and the values as entries: >lua vim.cmd [[set listchars=space:_,tab:>~]] vim.pretty_print(vim.opt.listchars:get()) @@ -1254,7 +1263,7 @@ Option:get() end < For values that are lists of flags, a set will be returned with the flags - as keys and `true` as entries. > + as keys and `true` as entries. >lua vim.cmd [[set formatoptions=njtcroql]] vim.pretty_print(vim.opt.formatoptions:get()) @@ -1270,28 +1279,28 @@ Option:append(value) Append a value to string-style options. See |:set+=| - These are equivalent: - `vim.opt.formatoptions:append('j')` - `vim.opt.formatoptions = vim.opt.formatoptions + 'j'` - + These are equivalent: >lua + vim.opt.formatoptions:append('j') + vim.opt.formatoptions = vim.opt.formatoptions + 'j' +< *vim.opt:prepend()* Option:prepend(value) Prepend a value to string-style options. See |:set^=| - These are equivalent: - `vim.opt.wildignore:prepend('*.o')` - `vim.opt.wildignore = vim.opt.wildignore ^ '*.o'` - + These are equivalent: >lua + vim.opt.wildignore:prepend('*.o') + vim.opt.wildignore = vim.opt.wildignore ^ '*.o' +< *vim.opt:remove()* Option:remove(value) Remove a value from string-style options. See |:set-=| - These are equivalent: - `vim.opt.wildignore:remove('*.pyc')` - `vim.opt.wildignore = vim.opt.wildignore - '*.pyc'` - + These are equivalent: >lua + vim.opt.wildignore:remove('*.pyc') + vim.opt.wildignore = vim.opt.wildignore - '*.pyc' +< ============================================================================== Lua module: vim *lua-vim* @@ -1302,7 +1311,7 @@ cmd({command}) *vim.cmd()* Note that `vim.cmd` can be indexed with a command name to return a callable function to the command. - Example: > + Example: >lua vim.cmd('echo 42') vim.cmd([[ @@ -1436,7 +1445,7 @@ paste({lines}, {phase}) *vim.paste()* Paste handler, invoked by |nvim_paste()| when a conforming UI (such as the |TUI|) pastes text into the editor. - Example: To remove ANSI color codes when pasting: > + Example: To remove ANSI color codes when pasting: >lua vim.paste = (function(overridden) return function(lines, phase) @@ -1465,7 +1474,7 @@ paste({lines}, {phase}) *vim.paste()* |paste| @alias paste_phase -1 | 1 | 2 | 3 pretty_print({...}) *vim.pretty_print()* - Prints given arguments in human-readable format. Example: > + Prints given arguments in human-readable format. Example: >lua -- 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)) < @@ -1490,8 +1499,7 @@ region({bufnr}, {pos1}, {pos2}, {regtype}, {inclusive}) *vim.region()* end-inclusive Return: ~ - table<integer, {}> region lua table of the form {linenr = - {startcol,endcol}} + (table) region Table of the form `{linenr = {startcol,endcol}}` schedule_wrap({cb}) *vim.schedule_wrap()* Defers callback `cb` until the Nvim API is safe to call. @@ -1508,6 +1516,56 @@ schedule_wrap({cb}) *vim.schedule_wrap()* |vim.in_fast_event()| +============================================================================== +Lua module: inspector *lua-inspector* + +inspect_pos({bufnr}, {row}, {col}, {filter}) *vim.inspect_pos()* + Get all the items at a given buffer position. + + Can also be pretty-printed with `:Inspect!`. *:Inspect!* + + Parameters: ~ + • {bufnr} (number|nil) defaults to the current buffer + • {row} (number|nil) row to inspect, 0-based. Defaults to the row of + the current cursor + • {col} (number|nil) col to inspect, 0-based. Defaults to the col of + the current cursor + • {filter} (table|nil) a table with key-value pairs to filter the items + • syntax (boolean): include syntax based highlight groups + (defaults to true) + • treesitter (boolean): include treesitter based highlight + groups (defaults to true) + • extmarks (boolean|"all"): include extmarks. When `all`, + then extmarks without a `hl_group` will also be included + (defaults to true) + • semantic_tokens (boolean): include semantic tokens + (defaults to true) + + Return: ~ + (table) a table with the following key-value pairs. Items are in + "traversal order": + • treesitter: a list of treesitter captures + • syntax: a list of syntax groups + • semantic_tokens: a list of semantic tokens + • extmarks: a list of extmarks + • buffer: the buffer used to get the items + • row: the row used to get the items + • col: the col used to get the items + +show_pos({bufnr}, {row}, {col}, {filter}) *vim.show_pos()* + Show all the items at a given buffer position. + + Can also be shown with `:Inspect`. *:Inspect* + + Parameters: ~ + • {bufnr} (number|nil) defaults to the current buffer + • {row} (number|nil) row to inspect, 0-based. Defaults to the row of + the current cursor + • {col} (number|nil) col to inspect, 0-based. Defaults to the col of + the current cursor + • {filter} (table|nil) see |vim.inspect_pos()| + + deep_equal({a}, {b}) *vim.deep_equal()* @@ -1544,10 +1602,11 @@ defaulttable({create}) *vim.defaulttable()* If {create} is `nil`, this will create a defaulttable whose constructor function is this function, effectively allowing to create nested tables on the fly: -> - local a = vim.defaulttable() - a.b.c = 1 + >lua + + local a = vim.defaulttable() + a.b.c = 1 < Parameters: ~ @@ -1573,7 +1632,7 @@ gsplit({s}, {sep}, {plain}) *vim.gsplit()* Parameters: ~ • {s} (string) String to split • {sep} (string) Separator or pattern - • {plain} (boolean) If `true` use `sep` literally (passed to + • {plain} (boolean|nil) If `true` use `sep` literally (passed to string.find) Return: ~ @@ -1581,6 +1640,7 @@ gsplit({s}, {sep}, {plain}) *vim.gsplit()* See also: ~ |vim.split()| + |luaref-patterns| https://www.lua.org/pil/20.2.html http://lua-users.org/wiki/StringLibraryTutorial @@ -1616,8 +1676,8 @@ list_slice({list}, {start}, {finish}) *vim.list_slice()* Parameters: ~ • {list} (list) Table - • {start} (number) Start range of slice - • {finish} (number) End range of slice + • {start} (number|nil) Start range of slice + • {finish} (number|nil) End range of slice Return: ~ (list) Copy of table sliced from start to finish (inclusive) @@ -1634,22 +1694,29 @@ pesc({s}) *vim.pesc()* See also: ~ https://github.com/rxi/lume +spairs({t}) *vim.spairs()* + Enumerate a table sorted by its keys. + + Parameters: ~ + • {t} (table) List-like table + + Return: ~ + iterator over sorted keys and their values + + See also: ~ + Based on https://github.com/premake/premake-core/blob/master/src/base/table.lua + split({s}, {sep}, {kwargs}) *vim.split()* Splits a string at each instance of a separator. - Examples: > + Examples: >lua - split(":aa::b:", ":") => {'','aa','','b',''} - split("axaby", "ab?") => {'','x','y'} - split("x*yz*o", "*", {plain=true}) => {'x','yz','o'} - split("|x|y|z|", "|", {trimempty=true}) => {'x', 'y', 'z'} + split(":aa::b:", ":") --> {'','aa','','b',''} + split("axaby", "ab?") --> {'','x','y'} + split("x*yz*o", "*", {plain=true}) --> {'x','yz','o'} + split("|x|y|z|", "|", {trimempty=true}) --> {'x', 'y', 'z'} < - @alias split_kwargs {plain: boolean, trimempty: boolean} | boolean | nil - - See also: ~ - |vim.gsplit()| - Parameters: ~ • {s} (string) String to split • {sep} (string) Separator or pattern @@ -1662,6 +1729,9 @@ split({s}, {sep}, {kwargs}) *vim.split()* Return: ~ string[] List of split components + See also: ~ + |vim.gsplit()| + startswith({s}, {prefix}) *vim.startswith()* Tests if `s` starts with `prefix`. @@ -1696,10 +1766,11 @@ tbl_contains({t}, {value}) *vim.tbl_contains()* tbl_count({t}) *vim.tbl_count()* Counts the number of non-nil values in table `t`. -> - vim.tbl_count({ a=1, b=2 }) => 2 - vim.tbl_count({ 1, 2 }) => 2 + >lua + + vim.tbl_count({ a=1, b=2 }) --> 2 + vim.tbl_count({ 1, 2 }) --> 2 < Parameters: ~ @@ -1772,7 +1843,7 @@ 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: > + Examples: >lua vim.tbl_get({ key = { nested_key = true }}, 'key', 'nested_key') == true vim.tbl_get({ key = {}}, 'key', 'nested_key') == nil @@ -1854,12 +1925,13 @@ trim({s}) *vim.trim()* (string) String with whitespace removed from its beginning and end See also: ~ + |luaref-patterns| https://www.lua.org/pil/20.2.html validate({opt}) *vim.validate()* Validates a parameter specification (types and values). - Usage example: > + Usage example: >lua function user.new(name, age, hobbies) vim.validate{ @@ -1871,25 +1943,25 @@ validate({opt}) *vim.validate()* end < - Examples with explicit argument values (can be run directly): > + Examples with explicit argument values (can be run directly): >lua vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}} - => NOP (success) + --> NOP (success) vim.validate{arg1={1, 'table'}} - => error('arg1: expected table, got number') + --> 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') + --> error('arg1: expected even number, got 3') < - If multiple types are valid they can be given as a list. > + If multiple types are valid they can be given as a list. >lua vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}} - => NOP (success) + --> NOP (success) vim.validate{arg1={1, {'string', table'}}} - => error('arg1: expected string|table, got number') + --> error('arg1: expected string|table, got number') < Parameters: ~ @@ -1958,7 +2030,7 @@ Lua module: ui *lua-ui* input({opts}, {on_confirm}) *vim.ui.input()* Prompts the user for input - Example: > + Example: >lua vim.ui.input({ prompt = 'Enter value for shiftwidth: ' }, function(input) vim.o.shiftwidth = tonumber(input) @@ -1977,12 +2049,13 @@ input({opts}, {on_confirm}) *vim.ui.input()* highlighting user inputs. • {on_confirm} (function) ((input|nil) -> ()) Called once the user confirms or abort the input. `input` is what the user - typed. `nil` if the user aborted the dialog. + typed (it might be an empty string if nothing was + entered), or `nil` if the user aborted the dialog. select({items}, {opts}, {on_choice}) *vim.ui.select()* Prompts the user to pick a single item from a collection of entries - Example: > + Example: >lua vim.ui.select({ 'tabs', 'spaces' }, { prompt = 'Select tabs or spaces:', @@ -2045,10 +2118,7 @@ add({filetypes}) *vim.filetype.add()* See $VIMRUNTIME/lua/vim/filetype.lua for more examples. - Note that Lua filetype detection is disabled when |g:do_legacy_filetype| - is set. - - Example: > + Example: >lua vim.filetype.add({ extension = { @@ -2084,7 +2154,7 @@ add({filetypes}) *vim.filetype.add()* }) < - To add a fallback match on contents (see |new-filetype-scripts|), use > + To add a fallback match on contents, use >lua vim.filetype.add { pattern = { @@ -2123,19 +2193,20 @@ match({args}) *vim.filetype.match()* Each of the three options is specified using a key to the single argument of this function. Example: -> - -- Using a buffer number - vim.filetype.match({ buf = 42 }) + >lua - -- Override the filename of the given buffer - vim.filetype.match({ buf = 42, filename = 'foo.c' }) + -- Using a buffer number + vim.filetype.match({ buf = 42 }) - -- Using a filename without a buffer - vim.filetype.match({ filename = 'main.lua' }) + -- Override the filename of the given buffer + vim.filetype.match({ buf = 42, filename = 'foo.c' }) - -- Using file contents - vim.filetype.match({ contents = {'#!/usr/bin/env bash'} }) + -- Using a filename without a buffer + vim.filetype.match({ filename = 'main.lua' }) + + -- Using file contents + vim.filetype.match({ contents = {'#!/usr/bin/env bash'} }) < Parameters: ~ @@ -2165,7 +2236,7 @@ match({args}) *vim.filetype.match()* Lua module: keymap *lua-keymap* del({modes}, {lhs}, {opts}) *vim.keymap.del()* - Remove an existing mapping. Examples: > + Remove an existing mapping. Examples: >lua vim.keymap.del('n', 'lhs') @@ -2181,7 +2252,7 @@ del({modes}, {lhs}, {opts}) *vim.keymap.del()* |vim.keymap.set()| set({mode}, {lhs}, {rhs}, {opts}) *vim.keymap.set()* - Add a new |mapping|. Examples: > + Add a new |mapping|. Examples: >lua -- Can add mapping to Lua functions vim.keymap.set('n', 'lhs', function() print("real lua function") end) @@ -2200,14 +2271,14 @@ set({mode}, {lhs}, {rhs}, {opts}) *vim.keymap.set()* vim.keymap.set('n', '[%', '<Plug>(MatchitNormalMultiBackward)') < - Note that in a mapping like: > + Note that in a mapping like: >lua 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: > + example: >lua vim.keymap.set('n', 'asdf', function() return require('jkl').my_fun() end) < @@ -2249,13 +2320,18 @@ basename({file}) *vim.fs.basename()* Return: ~ (string) Basename of {file} -dir({path}) *vim.fs.dir()* +dir({path}, {opts}) *vim.fs.dir()* Return an iterator over the files and directories located in {path} Parameters: ~ • {path} (string) An absolute or relative path to the directory to iterate over. The path is first normalized |vim.fs.normalize()|. + • {opts} table|nil Optional keyword arguments: + • depth: integer|nil How deep the traverse (default 1) + • skip: (fun(dir_name: string): boolean)|nil Predicate to + control traversal. Return false to stop searching the + current directory. Only useful when depth > 1 Return: ~ Iterator over files and directories in {path}. Each iteration yields @@ -2280,17 +2356,18 @@ find({names}, {opts}) *vim.fs.find()* searches are recursive and may search through many directories! If {stop} is non-nil, then the search stops when the directory given in {stop} is reached. The search terminates when {limit} (default 1) matches are found. - The search can be narrowed to find only files or or only directories by + The search can be narrowed to find only files or only directories by specifying {type} to be "file" or "directory", respectively. Parameters: ~ • {names} (string|table|fun(name: string): boolean) Names of the files and directories to find. Must be base names, paths and globs - are not supported. If a function it is called per file and - dir within the traversed directories to test if they match. + are not supported. The function is called per file and + directory within the traversed directories to test if they + match {names}. • {opts} (table) Optional keyword arguments: • path (string): Path to begin searching from. If omitted, - the current working directory is used. + the |current-directory| is used. • upward (boolean, default false): If true, search upward through parent directories. Otherwise, search through child directories (recursively). @@ -2298,13 +2375,14 @@ find({names}, {opts}) *vim.fs.find()* reached. The directory itself is not searched. • type (string): Find only files ("file") or directories ("directory"). If omitted, both files and directories that - match {name} are included. + match {names} are included. • limit (number, default 1): Stop the search after finding this many matches. Use `math.huge` to place no limit on the number of matches. Return: ~ - (table) The paths of all matching files or directories + (table) Normalized paths |vim.fs.normalize()| of all matching files or + directories normalize({path}) *vim.fs.normalize()* Normalize a path to a standard format. A tilde (~) character at the @@ -2312,16 +2390,16 @@ normalize({path}) *vim.fs.normalize()* backslash (\) characters are converted to forward slashes (/). Environment variables are also expanded. - Example: > + Examples: >lua - vim.fs.normalize('C:\Users\jdoe') - => 'C:/Users/jdoe' + vim.fs.normalize('C:\\Users\\jdoe') + --> 'C:/Users/jdoe' - vim.fs.normalize('~/src/neovim') - => '/home/jdoe/src/neovim' + vim.fs.normalize('~/src/neovim') + --> '/home/jdoe/src/neovim' - vim.fs.normalize('$XDG_CONFIG_HOME/nvim/init.vim') - => '/Users/jdoe/.config/nvim/init.vim' + vim.fs.normalize('$XDG_CONFIG_HOME/nvim/init.vim') + --> '/Users/jdoe/.config/nvim/init.vim' < Parameters: ~ @@ -2333,7 +2411,7 @@ normalize({path}) *vim.fs.normalize()* parents({start}) *vim.fs.parents()* Iterate over all the parents of the given file or directory. - Example: > + Example: >lua local root_dir for dir in vim.fs.parents(vim.api.nvim_buf_get_name(0)) do @@ -2354,4 +2432,44 @@ parents({start}) *vim.fs.parents()* Return: ~ (function) Iterator + +============================================================================== +Lua module: secure *lua-secure* + +read({path}) *vim.secure.read()* + Attempt to read the file at {path} prompting the user if the file should + be trusted. The user's choice is persisted in a trust database at + $XDG_STATE_HOME/nvim/trust. + + Parameters: ~ + • {path} (string) Path to a file to read. + + Return: ~ + (string|nil) The contents of the given file if it exists and is + trusted, or nil otherwise. + + See also: ~ + |:trust| + +trust({opts}) *vim.secure.trust()* + Manage the trust database. + + The trust database is located at |$XDG_STATE_HOME|/nvim/trust. + + Parameters: ~ + • {opts} (table) + • action (string): "allow" to add a file to the trust database + and trust it, "deny" to add a file to the trust database and + deny it, "remove" to remove file from the trust database + • path (string|nil): Path to a file to update. Mutually + exclusive with {bufnr}. Cannot be used when {action} is + "allow". + • bufnr (number|nil): Buffer number to update. Mutually + exclusive with {path}. + + Return: ~ + (boolean, string) success, msg: + • true and full path of target file if operation was successful + • false and error message on failure + vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl: diff --git a/runtime/doc/luaref.txt b/runtime/doc/luaref.txt index ffbb405804..aafdd5c43e 100644 --- a/runtime/doc/luaref.txt +++ b/runtime/doc/luaref.txt @@ -136,7 +136,7 @@ For convenience, when the opening long bracket is immediately followed by a newline, the newline is not included in the string. As an example, in a system using ASCII (in which `a` is coded as 97, newline is coded as 10, and `1` is coded as 49), the five literals below denote the same string: -> +>lua a = 'alo\n123"' a = "alo\n123\"" a = '\97lo\10\04923"' @@ -283,7 +283,7 @@ library; see |luaref-libDebug|.) An access to a global variable `x` is equivalent to `_env.x`, which in turn is equivalent to -> +>lua gettable_event(_env, "x") < where `_env` is the environment of the running function. (The `_env` variable is @@ -366,13 +366,13 @@ before the adjustment (except when the call is enclosed in parentheses; see The assignment statement first evaluates all its expressions and only then are the assignments performed. Thus the code -> +>lua i = 3 i, a[i] = i+1, 20 < sets `a[3]` to 20, without affecting `a[4]` because the `i` in `a[i]` is evaluated (to 3) before it is assigned 4. Similarly, the line -> +>lua x, y = y, x < exchanges the values of `x` and `y`. @@ -385,7 +385,7 @@ defined or callable in Lua. We use it here only for explanatory purposes.) An assignment to a global variable `x = val` is equivalent to the assignment `_env.x = val`, which in turn is equivalent to -> +>lua settable_event(_env, "x", val) < where `_env` is the environment of the running function. (The `_env` variable is @@ -448,11 +448,11 @@ through an arithmetic progression. It has the following syntax: < The `block` is repeated for `name` starting at the value of the first `exp`, until it passes the second `exp` by steps of the third `exp`. More precisely, -a `for` statement like > +a `for` statement like - for var = e1, e2, e3 do block end + `for var = e1, e2, e3 do block end` -< is equivalent to the code: > +is equivalent to the code: >lua do local var, limit, step = tonumber(e1), tonumber(e2), tonumber(e3) @@ -489,7 +489,7 @@ A `for` statement like `for` `var1, ..., varn` `in` `explist` `do` `block` `end` -is equivalent to the code: > +is equivalent to the code: >lua do local f, s, var = explist @@ -582,7 +582,7 @@ adjusts the result list to one element, discarding all values except the first one. Here are some examples: -> +>lua f() -- adjusted to 0 results g(f(), x) -- f() is adjusted to 1 result g(x, f()) -- g gets x plus all results from f() @@ -615,7 +615,7 @@ or strings that can be converted to numbers (see |luaref-langCoercion|), then al operations have the usual meaning. Exponentiation works for any exponent. For instance, `x^(-0.5)` computes the inverse of the square root of `x`. Modulo is defined as -> +>lua a % b == a - math.floor(a/b)*b < That is, it is the remainder of a division that rounds the quotient towards @@ -742,11 +742,11 @@ key `exp1` and value `exp2`. A field of the form `name = exp` is equivalent to `["name"] = exp`. Finally, fields of the form `exp` are equivalent to `[i] = exp`, where `i` are consecutive numerical integers, starting with 1. Fields in the other formats do not affect this counting. For example, -> +>lua a = { [f(1)] = g; "x", "y"; x = 1, f(x), [30] = 23; 45 } < is equivalent to -> +>lua do local t = {} t[f(1)] = g @@ -802,7 +802,7 @@ argument list is a single new table. A call of the form `f'` `string` `'` As an exception to the free-format syntax of Lua, you cannot put a line break before the `(` in a function call. This restriction avoids some ambiguities in the language. If you write -> +>lua a = f (g).x(a) < @@ -820,7 +820,7 @@ function. Note that a tail call only happens with a particular syntax, where the `return` has one single function call as argument; this syntax makes the calling function return exactly the returns of the called function. So, none of the following examples are tail calls: -> +>lua return (f(x)) -- results adjusted to 1 return 2 * f(x) return x, f(x) -- additional results @@ -901,7 +901,7 @@ expression is used as the last element of a list of expressions, then no adjustment is made (unless the call is enclosed in parentheses). As an example, consider the following definitions: -> +>lua function f(a, b) end function g(a, b, ...) end function r() return 1,2,3 end @@ -942,7 +942,7 @@ is syntactic sugar for Lua is a lexically scoped language. The scope of variables begins at the first statement after their declaration and lasts until the end of the innermost block that includes the declaration. Consider the following example: -> +>lua x = 10 -- global variable do -- new block local x = x -- new `x`, with value 10 @@ -967,7 +967,7 @@ function. Notice that each execution of a local statement defines new local variables. Consider the following example: -> +>lua a = {} local x = 20 for i=1,10 do @@ -1043,7 +1043,7 @@ given object, we use the expression metatable(obj)[event] < This should be read as -> +>lua rawget(metatable(obj) or {}, event) < That is, the access to a metamethod does not invoke other metamethods, and the @@ -1057,13 +1057,13 @@ the `+` operation. The function `getbinhandler` below defines how Lua chooses a handler for a binary operation. First, Lua tries the first operand. If its type does not define a handler for the operation, then Lua tries the second operand. -> +>lua function getbinhandler (op1, op2, event) return metatable(op1)[event] or metatable(op2)[event] end < By using this function, the behavior of the `op1 + op2` is -> +>lua function add_event (op1, op2) local o1, o2 = tonumber(op1), tonumber(op2) if o1 and o2 then -- both operands are numeric? @@ -1104,7 +1104,7 @@ with the function `pow` (from the C math library) as the primitive operation. "unm": *__unm()* ------ the unary `-` operation. -> +>lua function unm_event (op) local o = tonumber(op) if o then -- operand is numeric? @@ -1124,7 +1124,7 @@ the unary `-` operation. "concat": *__concat()* --------- the `..` (concatenation) operation. -> +>lua function concat_event (op1, op2) if (type(op1) == "string" or type(op1) == "number") and (type(op2) == "string" or type(op2) == "number") then @@ -1142,7 +1142,7 @@ the `..` (concatenation) operation. "len": *__len()* ------ the `#` operation. -> +>lua function len_event (op) if type(op) == "string" then return strlen(op) -- primitive string length @@ -1167,7 +1167,7 @@ The function `getcomphandler` defines how Lua chooses a metamethod for comparison operators. A metamethod only is selected when both objects being compared have the same type and the same metamethod for the selected operation. -> +>lua function getcomphandler (op1, op2, event) if type(op1) ~= type(op2) then return nil end local mm1 = metatable(op1)[event] @@ -1176,7 +1176,7 @@ operation. end < The "eq" event is defined as follows: -> +>lua function eq_event (op1, op2) if type(op1) ~= type(op2) then -- different types? return false -- different objects @@ -1198,7 +1198,7 @@ The "eq" event is defined as follows: "lt": *__lt()* ----- the `<` operation. -> +>lua function lt_event (op1, op2) if type(op1) == "number" and type(op2) == "number" then return op1 < op2 -- numeric comparison @@ -1219,7 +1219,7 @@ the `<` operation. "le": *__le()* ----- the `<=` operation. -> +>lua function le_event (op1, op2) if type(op1) == "number" and type(op2) == "number" then return op1 <= op2 -- numeric comparison @@ -1247,7 +1247,7 @@ to `not (b < a)`. "index": *__index()* -------- The indexing access `table[key]`. -> +>lua function gettable_event (table, key) local h if type(table) == "table" then @@ -1269,7 +1269,7 @@ The indexing access `table[key]`. "newindex": *__newindex()* ----------- The indexing assignment `table[key] = value`. -> +>lua function settable_event (table, key, value) local h if type(table) == "table" then @@ -1291,7 +1291,7 @@ The indexing assignment `table[key] = value`. "call": *__call()* ------- called when Lua calls a value. -> +>lua function function_event (func, ...) if type(func) == "function" then return func(...) -- primitive call @@ -1386,7 +1386,7 @@ Garbage userdata with a field `__gc` in their metatables are not collected immediately by the garbage collector. Instead, Lua puts them in a list. After the collection, Lua does the equivalent of the following function for each userdata in that list: -> +>lua function gc_event (udata) local h = metatable(udata).__gc if h then @@ -1469,7 +1469,7 @@ coroutine. Any arguments passed to this function go as extra arguments to propagated to the caller. As an example, consider the next code: -> +>lua function foo1 (a) print("foo", a) return coroutine.yield(2*a) @@ -1559,7 +1559,7 @@ Most query functions accept as indices any value inside the available stack space, that is, indices up to the maximum stack size you have set through `lua_checkstack`. Such indices are called acceptable indices. More formally, we define an acceptable index as follows: -> +>lua (index < 0 && abs(index) <= top) || (index > 0 && index <= stackspace) < Note that 0 is never an acceptable index. @@ -1580,7 +1580,7 @@ pseudo-index `LUA_ENVIRONINDEX`. To access and change the value of global variables, you can use regular table operations over an environment table. For instance, to access the value of a global variable, do -> +>c lua_getfield(L, LUA_GLOBALSINDEX, varname); < @@ -1639,7 +1639,7 @@ Inside a C function you can raise an error by calling `lua_error` (see Here we list all functions and types from the C API in alphabetical order. lua_Alloc *lua_Alloc()* -> +>c typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, @@ -1663,7 +1663,7 @@ lua_Alloc *lua_Alloc()* Here is a simple implementation for the allocator function. It is used in the auxiliary library by `luaL_newstate` (see |luaL_newstate()|). -> +>c static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) { (void)ud; (void)osize; /* not used */ @@ -1680,7 +1680,7 @@ lua_Alloc *lua_Alloc()* behaviors. lua_atpanic *lua_atpanic()* -> +>c lua_CFunction lua_atpanic (lua_State *L, lua_CFunction panicf); < Sets a new panic function and returns the old one. @@ -1694,7 +1694,7 @@ lua_atpanic *lua_atpanic()* stack. lua_call *lua_call()* -> +>c void lua_call (lua_State *L, int nargs, int nresults); < Calls a function. @@ -1718,11 +1718,11 @@ lua_call *lua_call()* The following example shows how the host program may do the equivalent to this Lua code: -> +>lua a = f("how", t.x, 14) < Here it is in C: -> +>c lua_getfield(L, LUA_GLOBALSINDEX, "f"); // function to be called lua_pushstring(L, "how"); // 1st argument lua_getfield(L, LUA_GLOBALSINDEX, "t"); // table to be indexed @@ -1737,7 +1737,7 @@ lua_call *lua_call()* practice. lua_CFunction *luaref-cfunction* *lua_CFunction()* -> +>c typedef int (*lua_CFunction) (lua_State *L); < Type for C functions. @@ -1758,7 +1758,7 @@ lua_CFunction *luaref-cfunction* *lua_CFunction()* *luaref-cfunctionexample* As an example, the following function receives a variable number of numerical arguments and returns their average and sum: -> +>c static int foo (lua_State *L) { int n = lua_gettop(L); /* number of arguments */ lua_Number sum = 0; @@ -1777,7 +1777,7 @@ lua_CFunction *luaref-cfunction* *lua_CFunction()* < lua_checkstack *lua_checkstack()* -> +>c int lua_checkstack (lua_State *L, int extra); < Ensures that there are at least `extra` free stack slots in the stack. @@ -1786,7 +1786,7 @@ lua_checkstack *lua_checkstack()* the new size, it is left unchanged. lua_close *lua_close()* -> +>c void lua_close (lua_State *L); < Destroys all objects in the given Lua state (calling the corresponding @@ -1798,7 +1798,7 @@ lua_close *lua_close()* are not needed, to avoid growing too large. lua_concat *lua_concat()* -> +>c void lua_concat (lua_State *L, int n); < Concatenates the `n` values at the top of the stack, pops them, and @@ -1808,7 +1808,7 @@ lua_concat *lua_concat()* usual semantics of Lua (see |luaref-langConcat|). lua_cpcall *lua_cpcall()* -> +>c int lua_cpcall (lua_State *L, lua_CFunction func, void *ud); < Calls the C function `func` in protected mode. `func` starts with only @@ -1819,7 +1819,7 @@ lua_cpcall *lua_cpcall()* returned by `func` are discarded. lua_createtable *lua_createtable()* -> +>c void lua_createtable (lua_State *L, int narr, int nrec); < Creates a new empty table and pushes it onto the stack. The new table @@ -1829,7 +1829,7 @@ lua_createtable *lua_createtable()* `lua_newtable` (see |lua_newtable()|). lua_dump *lua_dump()* -> +>c int lua_dump (lua_State *L, lua_Writer writer, void *data); < Dumps a function as a binary chunk. Receives a Lua function on the top @@ -1844,7 +1844,7 @@ lua_dump *lua_dump()* This function does not pop the Lua function from the stack. lua_equal *lua_equal()* -> +>c int lua_equal (lua_State *L, int index1, int index2); < Returns 1 if the two values in acceptable indices `index1` and @@ -1853,7 +1853,7 @@ lua_equal *lua_equal()* if any of the indices is non valid. lua_error *lua_error()* -> +>c int lua_error (lua_State *L); < Generates a Lua error. The error message (which can actually be a Lua @@ -1861,7 +1861,7 @@ lua_error *lua_error()* jump, and therefore never returns (see |luaL_error()|). lua_gc *lua_gc()* -> +>c int lua_gc (lua_State *L, int what, int data); < Controls the garbage collector. @@ -1893,7 +1893,7 @@ lua_gc *lua_gc()* previous value of the step multiplier. lua_getallocf *lua_getallocf()* -> +>c lua_Alloc lua_getallocf (lua_State *L, void **ud); < Returns the memory-allocation function of a given state. If `ud` is @@ -1901,14 +1901,14 @@ lua_getallocf *lua_getallocf()* `lua_newstate` (see |lua_newstate()|). lua_getfenv *lua_getfenv()* -> +>c void lua_getfenv (lua_State *L, int index); < Pushes onto the stack the environment table of the value at the given index. lua_getfield *lua_getfield()* -> +>c void lua_getfield (lua_State *L, int index, const char *k); < Pushes onto the stack the value `t[k]`, where `t` is the value at the @@ -1916,17 +1916,17 @@ lua_getfield *lua_getfield()* metamethod for the "index" event (see |luaref-langMetatables|). lua_getglobal *lua_getglobal()* -> +>c void lua_getglobal (lua_State *L, const char *name); < Pushes onto the stack the value of the global `name`. It is defined as a macro: -> +>c #define lua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, s) < lua_getmetatable *lua_getmetatable()* -> +>c int lua_getmetatable (lua_State *L, int index); < Pushes onto the stack the metatable of the value at the given @@ -1935,7 +1935,7 @@ lua_getmetatable *lua_getmetatable()* stack. lua_gettable *lua_gettable()* -> +>c void lua_gettable (lua_State *L, int index); < Pushes onto the stack the value `t[k]`, where `t` is the value at the @@ -1947,7 +1947,7 @@ lua_gettable *lua_gettable()* the "index" event (see |luaref-langMetatables|). lua_gettop *lua_gettop()* -> +>c int lua_gettop (lua_State *L); < Returns the index of the top element in the stack. Because indices @@ -1956,7 +1956,7 @@ lua_gettop *lua_gettop()* 0 means an empty stack). lua_insert *lua_insert()* -> +>c void lua_insert (lua_State *L, int index); < Moves the top element into the given valid index, shifting up the @@ -1964,7 +1964,7 @@ lua_insert *lua_insert()* pseudo-index, because a pseudo-index is not an actual stack position. lua_Integer *lua_Integer()* -> +>c typedef ptrdiff_t lua_Integer; < The type used by the Lua API to represent integral values. @@ -1973,77 +1973,77 @@ lua_Integer *lua_Integer()* type the machine handles "comfortably". lua_isboolean *lua_isboolean()* -> +>c int lua_isboolean (lua_State *L, int index); < Returns 1 if the value at the given acceptable index has type boolean, and 0 otherwise. lua_iscfunction *lua_iscfunction()* -> +>c int lua_iscfunction (lua_State *L, int index); < Returns 1 if the value at the given acceptable index is a C function, and 0 otherwise. lua_isfunction *lua_isfunction()* -> +>c int lua_isfunction (lua_State *L, int index); < Returns 1 if the value at the given acceptable index is a function (either C or Lua), and 0 otherwise. lua_islightuserdata *lua_islightuserdata()* -> +>c int lua_islightuserdata (lua_State *L, int index); < Returns 1 if the value at the given acceptable index is a light userdata, and 0 otherwise. lua_isnil *lua_isnil()* -> +>c int lua_isnil (lua_State *L, int index); < Returns 1 if the value at the given acceptable index is `nil`, and 0 otherwise. lua_isnumber *lua_isnumber()* -> +>c int lua_isnumber (lua_State *L, int index); < Returns 1 if the value at the given acceptable index is a number or a string convertible to a number, and 0 otherwise. lua_isstring *lua_isstring()* -> +>c int lua_isstring (lua_State *L, int index); < Returns 1 if the value at the given acceptable index is a string or a number (which is always convertible to a string), and 0 otherwise. lua_istable *lua_istable()* -> +>c int lua_istable (lua_State *L, int index); < Returns 1 if the value at the given acceptable index is a table, and 0 otherwise. lua_isthread *lua_isthread()* -> +>c int lua_isthread (lua_State *L, int index); < Returns 1 if the value at the given acceptable index is a thread, and 0 otherwise. lua_isuserdata *lua_isuserdata()* -> +>c int lua_isuserdata (lua_State *L, int index); < Returns 1 if the value at the given acceptable index is a userdata (either full or light), and 0 otherwise. lua_lessthan *lua_lessthan()* -> +>c int lua_lessthan (lua_State *L, int index1, int index2); < Returns 1 if the value at acceptable index `index1` is smaller than @@ -2052,7 +2052,7 @@ lua_lessthan *lua_lessthan()* Also returns 0 if any of the indices is non valid. lua_load *lua_load()* -> +>c int lua_load (lua_State *L, lua_Reader reader, void *data, @@ -2079,7 +2079,7 @@ lua_load *lua_load()* error messages and in debug information (see |luaref-apiDebug|). lua_newstate *lua_newstate()* -> +>c lua_State *lua_newstate (lua_Alloc f, void *ud); < Creates a new, independent state. Returns `NULL` if cannot create the @@ -2089,7 +2089,7 @@ lua_newstate *lua_newstate()* simply passes to the allocator in every call. lua_newtable *lua_newtable()* -> +>c void lua_newtable (lua_State *L); < Creates a new empty table and pushes it onto the stack. It is @@ -2097,7 +2097,7 @@ lua_newtable *lua_newtable()* |lua_createtable()|). lua_newthread *lua_newthread()* -> +>c lua_State *lua_newthread (lua_State *L); < Creates a new thread, pushes it on the stack, and returns a pointer to @@ -2110,7 +2110,7 @@ lua_newthread *lua_newthread()* are subject to garbage collection, like any Lua object. lua_newuserdata *lua_newuserdata()* -> +>c void *lua_newuserdata (lua_State *L, size_t size); < This function allocates a new block of memory with the given size, @@ -2128,7 +2128,7 @@ lua_newuserdata *lua_newuserdata()* is collected again then Lua frees its corresponding memory. lua_next *lua_next()* -> +>c int lua_next (lua_State *L, int index); < Pops a key from the stack, and pushes a key-value pair from the table @@ -2138,7 +2138,7 @@ lua_next *lua_next()* *luaref-tabletraversal* A typical traversal looks like this: -> +>c /* table is in the stack at index 't' */ lua_pushnil(L); /* first key */ while (lua_next(L, t) != 0) { @@ -2156,7 +2156,7 @@ lua_next *lua_next()* value at the given index; this confuses the next call to `lua_next`. lua_Number *lua_Number()* -> +>c typedef double lua_Number; < The type of numbers in Lua. By default, it is double, but that can be @@ -2166,7 +2166,7 @@ lua_Number *lua_Number()* another type for numbers (e.g., float or long). lua_objlen *lua_objlen()* -> +>c size_t lua_objlen (lua_State *L, int index); < Returns the "length" of the value at the given acceptable index: for @@ -2175,7 +2175,7 @@ lua_objlen *lua_objlen()* block of memory allocated for the userdata; for other values, it is 0. lua_pcall *lua_pcall()* -> +>c lua_pcall (lua_State *L, int nargs, int nresults, int errfunc); < Calls a function in protected mode. @@ -2210,19 +2210,19 @@ lua_pcall *lua_pcall()* - `LUA_ERRERR` error while running the error handler function. lua_pop *lua_pop()* -> +>c void lua_pop (lua_State *L, int n); < Pops `n` elements from the stack. lua_pushboolean *lua_pushboolean()* -> +>c void lua_pushboolean (lua_State *L, int b); < Pushes a boolean value with value `b` onto the stack. lua_pushcclosure *lua_pushcclosure()* -> +>c void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n); < Pushes a new C closure onto the stack. @@ -2238,7 +2238,7 @@ lua_pushcclosure *lua_pushcclosure()* pops these values from the stack. lua_pushcfunction *lua_pushcfunction()* -> +>c void lua_pushcfunction (lua_State *L, lua_CFunction f); < Pushes a C function onto the stack. This function receives a pointer @@ -2250,12 +2250,12 @@ lua_pushcfunction *lua_pushcfunction()* |lua_CFunction()|). `lua_pushcfunction` is defined as a macro: -> +>c #define lua_pushcfunction(L,f) lua_pushcclosure(L,f,0) < lua_pushfstring *lua_pushfstring()* -> +>c const char *lua_pushfstring (lua_State *L, const char *fmt, ...); < Pushes onto the stack a formatted string and returns a pointer to this @@ -2274,13 +2274,13 @@ lua_pushfstring *lua_pushfstring()* character). lua_pushinteger *lua_pushinteger()* -> +>c void lua_pushinteger (lua_State *L, lua_Integer n); < Pushes a number with value `n` onto the stack. lua_pushlightuserdata *lua_pushlightuserdata()* -> +>c void lua_pushlightuserdata (lua_State *L, void *p); < Pushes a light userdata onto the stack. @@ -2292,7 +2292,7 @@ lua_pushlightuserdata *lua_pushlightuserdata()* same C address. lua_pushlstring *lua_pushlstring()* -> +>c void lua_pushlstring (lua_State *L, const char *s, size_t len); < Pushes the string pointed to by `s` with size `len` onto the stack. @@ -2301,19 +2301,19 @@ lua_pushlstring *lua_pushlstring()* returns. The string can contain embedded zeros. lua_pushnil *lua_pushnil()* -> +>c void lua_pushnil (lua_State *L); < Pushes a nil value onto the stack. lua_pushnumber *lua_pushnumber()* -> +>c void lua_pushnumber (lua_State *L, lua_Number n); < Pushes a number with value `n` onto the stack. lua_pushstring *lua_pushstring()* -> +>c void lua_pushstring (lua_State *L, const char *s); < Pushes the zero-terminated string pointed to by `s` onto the stack. @@ -2323,20 +2323,20 @@ lua_pushstring *lua_pushstring()* end at the first zero. lua_pushthread *lua_pushthread()* -> +>c int lua_pushthread (lua_State *L); < Pushes the thread represented by `L` onto the stack. Returns 1 if this thread is the main thread of its state. lua_pushvalue *lua_pushvalue()* -> +>c void lua_pushvalue (lua_State *L, int index); < Pushes a copy of the element at the given valid index onto the stack. lua_pushvfstring *lua_pushvfstring()* -> +>c const char *lua_pushvfstring (lua_State *L, const char *fmt, va_list argp); @@ -2346,7 +2346,7 @@ lua_pushvfstring *lua_pushvfstring()* arguments. lua_rawequal *lua_rawequal()* -> +>c int lua_rawequal (lua_State *L, int index1, int index2); < Returns 1 if the two values in acceptable indices `index1` and @@ -2355,14 +2355,14 @@ lua_rawequal *lua_rawequal()* valid. lua_rawget *lua_rawget()* -> +>c void lua_rawget (lua_State *L, int index); < Similar to `lua_gettable` (see |lua_gettable()|), but does a raw access (i.e., without metamethods). lua_rawgeti *lua_rawgeti()* -> +>c void lua_rawgeti (lua_State *L, int index, int n); < Pushes onto the stack the value `t[n]`, where `t` is the value at the @@ -2370,14 +2370,14 @@ lua_rawgeti *lua_rawgeti()* invoke metamethods. lua_rawset *lua_rawset()* -> +>c void lua_rawset (lua_State *L, int index); < Similar to `lua_settable` (see |lua_settable()|), but does a raw assignment (i.e., without metamethods). lua_rawseti *lua_rawseti()* -> +>c void lua_rawseti (lua_State *L, int index, int n); < Does the equivalent of `t[n] = v`, where `t` is the value at the given @@ -2387,7 +2387,7 @@ lua_rawseti *lua_rawseti()* that is, it does not invoke metamethods. lua_Reader *lua_Reader()* -> +>c typedef const char * (*lua_Reader) (lua_State *L, void *data, size_t *size); @@ -2402,20 +2402,20 @@ lua_Reader *lua_Reader()* zero. lua_register *lua_register()* -> +>c void lua_register (lua_State *L, const char *name, lua_CFunction f); < Sets the C function `f` as the new value of global `name`. It is defined as a macro: -> +>c #define lua_register(L,n,f) \ (lua_pushcfunction(L, f), lua_setglobal(L, n)) < lua_remove *lua_remove()* -> +>c void lua_remove (lua_State *L, int index); < Removes the element at the given valid index, shifting down the @@ -2423,7 +2423,7 @@ lua_remove *lua_remove()* pseudo-index, because a pseudo-index is not an actual stack position. lua_replace *lua_replace()* -> +>c void lua_replace (lua_State *L, int index); < Moves the top element into the given position (and pops it), without @@ -2431,7 +2431,7 @@ lua_replace *lua_replace()* position). lua_resume *lua_resume()* -> +>c int lua_resume (lua_State *L, int narg); < Starts and resumes a coroutine in a given thread. @@ -2452,14 +2452,14 @@ lua_resume *lua_resume()* and then call `lua_resume`. lua_setallocf *lua_setallocf()* -> +>c void lua_setallocf (lua_State *L, lua_Alloc f, void *ud); < Changes the allocator function of a given state to `f` with user data `ud`. lua_setfenv *lua_setfenv()* -> +>c int lua_setfenv (lua_State *L, int index); < Pops a table from the stack and sets it as the new environment for the @@ -2468,7 +2468,7 @@ lua_setfenv *lua_setfenv()* Otherwise it returns 1. lua_setfield *lua_setfield()* -> +>c void lua_setfield (lua_State *L, int index, const char *k); < Does the equivalent to `t[k] = v`, where `t` is the value at the given @@ -2479,24 +2479,24 @@ lua_setfield *lua_setfield()* |luaref-langMetatables|). lua_setglobal *lua_setglobal()* -> +>c void lua_setglobal (lua_State *L, const char *name); < Pops a value from the stack and sets it as the new value of global `name`. It is defined as a macro: -> +>c #define lua_setglobal(L,s) lua_setfield(L, LUA_GLOBALSINDEX, s) < lua_setmetatable *lua_setmetatable()* -> +>c int lua_setmetatable (lua_State *L, int index); < Pops a table from the stack and sets it as the new metatable for the value at the given acceptable index. lua_settable *lua_settable()* -> +>c void lua_settable (lua_State *L, int index); < Does the equivalent to `t[k] = v`, where `t` is the value at the given @@ -2508,7 +2508,7 @@ lua_settable *lua_settable()* (see |luaref-langMetatables|). lua_settop *lua_settop()* -> +>c void lua_settop (lua_State *L, int index); < Accepts any acceptable index, or 0, and sets the stack top to this @@ -2517,7 +2517,7 @@ lua_settop *lua_settop()* elements are removed. lua_State *lua_State()* -> +>c typedef struct lua_State lua_State; < Opaque structure that keeps the whole state of a Lua interpreter. The @@ -2529,7 +2529,7 @@ lua_State *lua_State()* |lua_newstate()|), which creates a Lua state from scratch. lua_status *lua_status()* -> +>c int lua_status (lua_State *L); < Returns the status of the thread `L`. @@ -2539,7 +2539,7 @@ lua_status *lua_status()* suspended. lua_toboolean *lua_toboolean()* -> +>c int lua_toboolean (lua_State *L, int index); < Converts the Lua value at the given acceptable index to a C boolean @@ -2550,14 +2550,14 @@ lua_toboolean *lua_toboolean()* |lua_isboolean()| to test the value's type.) lua_tocfunction *lua_tocfunction()* -> +>c lua_CFunction lua_tocfunction (lua_State *L, int index); < Converts a value at the given acceptable index to a C function. That value must be a C function; otherwise it returns `NULL`. lua_tointeger *lua_tointeger()* -> +>c lua_Integer lua_tointeger (lua_State *L, int idx); < Converts the Lua value at the given acceptable index to the signed @@ -2569,7 +2569,7 @@ lua_tointeger *lua_tointeger()* way. lua_tolstring *lua_tolstring()* -> +>c const char *lua_tolstring (lua_State *L, int index, size_t *len); < Converts the Lua value at the given acceptable index to a C string. If @@ -2588,7 +2588,7 @@ lua_tolstring *lua_tolstring()* value is removed from the stack. lua_tonumber *lua_tonumber()* -> +>c lua_Number lua_tonumber (lua_State *L, int index); < Converts the Lua value at the given acceptable index to the C type @@ -2597,7 +2597,7 @@ lua_tonumber *lua_tonumber()* otherwise, `lua_tonumber` returns 0. lua_topointer *lua_topointer()* -> +>c const void *lua_topointer (lua_State *L, int index); < Converts the value at the given acceptable index to a generic C @@ -2609,14 +2609,14 @@ lua_topointer *lua_topointer()* Typically this function is used only for debug information. lua_tostring *lua_tostring()* -> +>c const char *lua_tostring (lua_State *L, int index); < Equivalent to `lua_tolstring` (see |lua_tolstring()|) with `len` equal to `NULL`. lua_tothread *lua_tothread()* -> +>c lua_State *lua_tothread (lua_State *L, int index); < Converts the value at the given acceptable index to a Lua thread @@ -2624,7 +2624,7 @@ lua_tothread *lua_tothread()* thread; otherwise, the function returns `NULL`. lua_touserdata *lua_touserdata()* -> +>c void *lua_touserdata (lua_State *L, int index); < If the value at the given acceptable index is a full userdata, returns @@ -2632,7 +2632,7 @@ lua_touserdata *lua_touserdata()* pointer. Otherwise, it returns `NULL`. lua_type *lua_type()* -> +>c int lua_type (lua_State *L, int index); < Returns the type of the value in the given acceptable index, or @@ -2643,14 +2643,14 @@ lua_type *lua_type()* `LUA_TUSERDATA`, `LUA_TTHREAD`, and `LUA_TLIGHTUSERDATA`. lua_typename *lua_typename()* -> +>c const char *lua_typename (lua_State *L, int tp); < Returns the name of the type encoded by the value `tp`, which must be one the values returned by `lua_type`. lua_Writer *lua_Writer()* -> +>c typedef int (*lua_Writer) (lua_State *L, const void* p, size_t sz, @@ -2665,7 +2665,7 @@ lua_Writer *lua_Writer()* means an error and stops `lua_dump` from calling the writer again. lua_xmove *lua_xmove()* -> +>c void lua_xmove (lua_State *from, lua_State *to, int n); < Exchange values between different threads of the `same` global state. @@ -2674,14 +2674,14 @@ lua_xmove *lua_xmove()* onto the stack `to`. lua_yield *lua_yield()* -> +>c int lua_yield (lua_State *L, int nresults); < Yields a coroutine. This function should only be called as the return expression of a C function, as follows: -> +>c return lua_yield (L, nresults); < When a C function calls `lua_yield` in that way, the running coroutine @@ -2715,7 +2715,7 @@ need "inside information" from the interpreter. lua_Debug *lua_Debug()* -> +>c typedef struct lua_Debug { int event; const char *name; /* (n) */ @@ -2768,25 +2768,25 @@ The fields of `lua_Debug` have the following meaning: upvalues of the function. lua_gethook *lua_gethook()* -> +>c lua_Hook lua_gethook (lua_State *L); < Returns the current hook function. lua_gethookcount *lua_gethookcount()* -> +>c int lua_gethookcount (lua_State *L); < Returns the current hook count. lua_gethookmask *lua_gethookmask()* -> +>c int lua_gethookmask (lua_State *L); < Returns the current hook mask. lua_getinfo *lua_getinfo()* -> +>c int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar); < Returns information about a specific function or function invocation. @@ -2801,7 +2801,7 @@ lua_getinfo *lua_getinfo()* `lua_getinfo` pops the function in the top of the stack.) For instance, to know in which line a function `f` was defined, you can write the following code: -> +>c lua_Debug ar; lua_getfield(L, LUA_GLOBALSINDEX, "f"); /* get global 'f' */ lua_getinfo(L, ">S", &ar); @@ -2826,7 +2826,7 @@ lua_getinfo *lua_getinfo()* `what`). lua_getlocal *lua_getlocal()* -> +>c const char *lua_getlocal (lua_State *L, lua_Debug *ar, int n); < Gets information about a local variable of a given activation record. @@ -2846,7 +2846,7 @@ lua_getlocal *lua_getlocal()* number of active local variables. lua_getstack *lua_getstack()* -> +>c int lua_getstack (lua_State *L, int level, lua_Debug *ar); < Gets information about the interpreter runtime stack. @@ -2859,7 +2859,7 @@ lua_getstack *lua_getstack()* with a level greater than the stack depth, it returns 0. lua_getupvalue *lua_getupvalue()* -> +>c const char *lua_getupvalue (lua_State *L, int funcindex, int n); < Gets information about a closure's upvalue. (For Lua functions, @@ -2875,7 +2875,7 @@ lua_getupvalue *lua_getupvalue()* string `""` as a name for all upvalues. lua_Hook *lua_Hook()* -> +>c typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar); < Type for debugging hook functions. @@ -2897,7 +2897,7 @@ lua_Hook *lua_Hook()* lua_sethook *lua_sethook()* -> +>c int lua_sethook (lua_State *L, lua_Hook f, int mask, int count); < Sets the debugging hook function. @@ -2926,7 +2926,7 @@ lua_sethook *lua_sethook()* A hook is disabled by setting `mask` to zero. lua_setlocal *lua_setlocal()* -> +>c const char *lua_setlocal (lua_State *L, lua_Debug *ar, int n); < Sets the value of a local variable of a given activation record. @@ -2939,7 +2939,7 @@ lua_setlocal *lua_setlocal()* number of active local variables. lua_setupvalue *lua_setupvalue()* -> +>c const char *lua_setupvalue (lua_State *L, int funcindex, int n); < Sets the value of a closure's upvalue. It assigns the value at the top @@ -2953,7 +2953,7 @@ lua_setupvalue *lua_setupvalue()* *luaref-debugexample* As an example, the following function lists the names of all local variables and upvalues for a function at a given level of the stack: -> +>c int listvars (lua_State *L, int level) { lua_Debug ar; int i; @@ -3002,20 +3002,20 @@ Here we list all functions and types from the auxiliary library in alphabetical order. luaL_addchar *luaL_addchar()* -> +>c void luaL_addchar (luaL_Buffer *B, char c); < Adds the character `c` to the buffer `B` (see |luaL_Buffer()|). luaL_addlstring *luaL_addlstring()* -> +>c void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l); < Adds the string pointed to by `s` with length `l` to the buffer `B` (see |luaL_Buffer()|). The string may contain embedded zeros. luaL_addsize *luaL_addsize()* -> +>c void luaL_addsize (luaL_Buffer *B, size_t n); < Adds to the buffer `B` (see |luaL_Buffer()|) a string of length @@ -3023,14 +3023,14 @@ luaL_addsize *luaL_addsize()* |luaL_prepbuffer()|). luaL_addstring *luaL_addstring()* -> +>c void luaL_addstring (luaL_Buffer *B, const char *s); < Adds the zero-terminated string pointed to by `s` to the buffer `B` (see |luaL_Buffer()|). The string may not contain embedded zeros. luaL_addvalue *luaL_addvalue()* -> +>c void luaL_addvalue (luaL_Buffer *B); < Adds the value at the top of the stack to the buffer `B` (see @@ -3041,7 +3041,7 @@ luaL_addvalue *luaL_addvalue()* added to the buffer. luaL_argcheck *luaL_argcheck()* -> +>c void luaL_argcheck (lua_State *L, int cond, int narg, @@ -3054,7 +3054,7 @@ luaL_argcheck *luaL_argcheck()* < luaL_argerror *luaL_argerror()* -> +>c int luaL_argerror (lua_State *L, int narg, const char *extramsg); < Raises an error with the following message, where `func` is retrieved @@ -3066,7 +3066,7 @@ luaL_argerror *luaL_argerror()* functions as `return luaL_argerror(` `args` `)`. luaL_Buffer *luaL_Buffer()* -> +>c typedef struct luaL_Buffer luaL_Buffer; < Type for a `string buffer`. @@ -3095,14 +3095,14 @@ luaL_Buffer *luaL_Buffer()* string on its top. luaL_buffinit *luaL_buffinit()* -> +>c void luaL_buffinit (lua_State *L, luaL_Buffer *B); < Initializes a buffer `B`. This function does not allocate any space; the buffer must be declared as a variable (see |luaL_Buffer()|). luaL_callmeta *luaL_callmeta()* -> +>c int luaL_callmeta (lua_State *L, int obj, const char *e); < Calls a metamethod. @@ -3115,49 +3115,49 @@ luaL_callmeta *luaL_callmeta()* 0 (without pushing any value on the stack). luaL_checkany *luaL_checkany()* -> +>c void luaL_checkany (lua_State *L, int narg); < Checks whether the function has an argument of any type (including `nil`) at position `narg`. luaL_checkint *luaL_checkint()* -> +>c int luaL_checkint (lua_State *L, int narg); < Checks whether the function argument `narg` is a number and returns this number cast to an `int`. luaL_checkinteger *luaL_checkinteger()* -> +>c lua_Integer luaL_checkinteger (lua_State *L, int narg); < Checks whether the function argument `narg` is a number and returns this number cast to a `lua_Integer` (see |lua_Integer()|). luaL_checklong *luaL_checklong()* -> +>c long luaL_checklong (lua_State *L, int narg); < Checks whether the function argument `narg` is a number and returns this number cast to a `long`. luaL_checklstring *luaL_checklstring()* -> +>c const char *luaL_checklstring (lua_State *L, int narg, size_t *l); < Checks whether the function argument `narg` is a string and returns this string; if `l` is not `NULL` fills `*l` with the string's length. luaL_checknumber *luaL_checknumber()* -> +>c lua_Number luaL_checknumber (lua_State *L, int narg); < Checks whether the function argument `narg` is a number and returns this number (see |lua_Number()|). luaL_checkoption *luaL_checkoption()* -> +>c int luaL_checkoption (lua_State *L, int narg, const char *def, @@ -3177,7 +3177,7 @@ luaL_checkoption *luaL_checkoption()* select options.) luaL_checkstack *luaL_checkstack()* -> +>c void luaL_checkstack (lua_State *L, int sz, const char *msg); < Grows the stack size to `top + sz` elements, raising an error if the @@ -3185,48 +3185,48 @@ luaL_checkstack *luaL_checkstack()* the error message. luaL_checkstring *luaL_checkstring()* -> +>c const char *luaL_checkstring (lua_State *L, int narg); < Checks whether the function argument `narg` is a string and returns this string. luaL_checktype *luaL_checktype()* -> +>c void luaL_checktype (lua_State *L, int narg, int t); < Checks whether the function argument `narg` has type `t` (see |lua_type()|). luaL_checkudata *luaL_checkudata()* -> +>c void *luaL_checkudata (lua_State *L, int narg, const char *tname); < Checks whether the function argument `narg` is a userdata of the type `tname` (see |luaL_newmetatable()|). luaL_dofile *luaL_dofile()* -> +>c int luaL_dofile (lua_State *L, const char *filename); < Loads and runs the given file. It is defined as the following macro: -> +>c (luaL_loadfile(L, filename) || lua_pcall(L, 0, LUA_MULTRET, 0)) < It returns 0 if there are no errors or 1 in case of errors. luaL_dostring *luaL_dostring()* -> +>c int luaL_dostring (lua_State *L, const char *str); < Loads and runs the given string. It is defined as the following macro: -> +>c (luaL_loadstring(L, str) || lua_pcall(L, 0, LUA_MULTRET, 0)) < It returns 0 if there are no errors or 1 in case of errors. luaL_error *luaL_error()* -> +>c int luaL_error (lua_State *L, const char *fmt, ...); < Raises an error. The error message format is given by `fmt` plus any @@ -3239,7 +3239,7 @@ luaL_error *luaL_error()* functions as `return luaL_error(` `args` `)`. luaL_getmetafield *luaL_getmetafield()* -> +>c int luaL_getmetafield (lua_State *L, int obj, const char *e); < Pushes onto the stack the field `e` from the metatable of the object @@ -3247,14 +3247,14 @@ luaL_getmetafield *luaL_getmetafield()* metatable does not have this field, returns 0 and pushes nothing. luaL_getmetatable *luaL_getmetatable()* -> +>c void luaL_getmetatable (lua_State *L, const char *tname); < Pushes onto the stack the metatable associated with name `tname` in the registry (see |luaL_newmetatable()|). luaL_gsub *luaL_gsub()* -> +>c const char *luaL_gsub (lua_State *L, const char *s, const char *p, @@ -3265,7 +3265,7 @@ luaL_gsub *luaL_gsub()* returns it. luaL_loadbuffer *luaL_loadbuffer()* -> +>c int luaL_loadbuffer (lua_State *L, const char *buff, size_t sz, @@ -3279,7 +3279,7 @@ luaL_loadbuffer *luaL_loadbuffer()* chunk name, used for debug information and error messages. luaL_loadfile *luaL_loadfile()* -> +>c int luaL_loadfile (lua_State *L, const char *filename); < Loads a file as a Lua chunk. This function uses `lua_load` (see @@ -3293,7 +3293,7 @@ luaL_loadfile *luaL_loadfile()* As `lua_load`, this function only loads the chunk; it does not run it. luaL_loadstring *luaL_loadstring()* -> +>c int luaL_loadstring (lua_State *L, const char *s); < Loads a string as a Lua chunk. This function uses `lua_load` (see @@ -3306,7 +3306,7 @@ luaL_loadstring *luaL_loadstring()* run it. luaL_newmetatable *luaL_newmetatable()* -> +>c int luaL_newmetatable (lua_State *L, const char *tname); < If the registry already has the key `tname`, returns 0. Otherwise, @@ -3317,7 +3317,7 @@ luaL_newmetatable *luaL_newmetatable()* `tname` in the registry. luaL_newstate *luaL_newstate()* -> +>c lua_State *luaL_newstate (void); < Creates a new Lua state. It calls `lua_newstate` (see @@ -3330,14 +3330,14 @@ luaL_newstate *luaL_newstate()* error. luaL_openlibs *luaL_openlibs()* -> +>c void luaL_openlibs (lua_State *L); < Opens all standard Lua libraries into the given state. See also |luaref-openlibs| for details on how to open individual libraries. luaL_optint *luaL_optint()* -> +>c int luaL_optint (lua_State *L, int narg, int d); < If the function argument `narg` is a number, returns this number cast @@ -3345,7 +3345,7 @@ luaL_optint *luaL_optint()* Otherwise, raises an error. luaL_optinteger *luaL_optinteger()* -> +>c lua_Integer luaL_optinteger (lua_State *L, int narg, lua_Integer d); @@ -3355,7 +3355,7 @@ luaL_optinteger *luaL_optinteger()* absent or is `nil`, returns `d`. Otherwise, raises an error. luaL_optlong *luaL_optlong()* -> +>c long luaL_optlong (lua_State *L, int narg, long d); < If the function argument `narg` is a number, returns this number cast @@ -3363,7 +3363,7 @@ luaL_optlong *luaL_optlong()* Otherwise, raises an error. luaL_optlstring *luaL_optlstring()* -> +>c const char *luaL_optlstring (lua_State *L, int narg, const char *d, @@ -3376,7 +3376,7 @@ luaL_optlstring *luaL_optlstring()* If `l` is not `NULL`, fills the position `*l` with the results' length. luaL_optnumber *luaL_optnumber()* -> +>c lua_Number luaL_optnumber (lua_State *L, int narg, lua_Number d); < If the function argument `narg` is a number, returns this number. If @@ -3384,7 +3384,7 @@ luaL_optnumber *luaL_optnumber()* error. luaL_optstring *luaL_optstring()* -> +>c const char *luaL_optstring (lua_State *L, int narg, const char *d); @@ -3394,7 +3394,7 @@ luaL_optstring *luaL_optstring()* error. luaL_prepbuffer *luaL_prepbuffer()* -> +>c char *luaL_prepbuffer (luaL_Buffer *B); < Returns an address to a space of size `LUAL_BUFFERSIZE` where you can @@ -3404,14 +3404,14 @@ luaL_prepbuffer *luaL_prepbuffer()* add it to the buffer. luaL_pushresult *luaL_pushresult()* -> +>c void luaL_pushresult (luaL_Buffer *B); < Finishes the use of buffer `B` leaving the final string on the top of the stack. luaL_ref *luaL_ref()* -> +>c int luaL_ref (lua_State *L, int t); < Creates and returns a `reference`, in the table at index `t`, for the @@ -3429,7 +3429,7 @@ luaL_ref *luaL_ref()* different from any reference returned by `luaL_ref`. luaL_Reg *luaL_Reg()* -> +>c typedef struct luaL_Reg { const char *name; lua_CFunction func; @@ -3441,7 +3441,7 @@ luaL_Reg *luaL_Reg()* sentinel entry in which both `name` and `func` are `NULL`. luaL_register *luaL_register()* -> +>c void luaL_register (lua_State *L, const char *libname, const luaL_Reg *l); @@ -3462,13 +3462,13 @@ luaL_register *luaL_register()* In any case the function leaves the table on the top of the stack. luaL_typename *luaL_typename()* -> +>c const char *luaL_typename (lua_State *L, int idx); < Returns the name of the type of the value at index `idx`. luaL_typerror *luaL_typerror()* -> +>c int luaL_typerror (lua_State *L, int narg, const char *tname); < Generates an error with a message like the following: @@ -3481,7 +3481,7 @@ luaL_typerror *luaL_typerror()* `rt` is the type name of the actual argument. luaL_unref *luaL_unref()* -> +>c void luaL_unref (lua_State *L, int t, int ref); < Releases reference `ref` from the table at index `t` (see @@ -3492,7 +3492,7 @@ luaL_unref *luaL_unref()* If `ref` is `LUA_NOREF` or `LUA_REFNIL`, `luaL_unref` does nothing. luaL_where *luaL_where()* -> +>c void luaL_where (lua_State *L, int lvl); < Pushes onto the stack a string identifying the current position of the @@ -3641,7 +3641,7 @@ loadstring({string} [, {chunkname}]) *luaref-loadstring()* given {string}. To load and run a given string, use the idiom -> +>lua assert(loadstring(s))() < @@ -3756,7 +3756,7 @@ type({v}) *luaref-type()* unpack({list} [, {i} [, {j}]]) *luaref-unpack()* Returns the elements from the given table. This function is equivalent to -> +>lua return list[i], list[i+1], ..., list[j] < except that the above code can be written only for a fixed number of @@ -4021,11 +4021,11 @@ string.format({formatstring}, {...}) *string.format()* interpreter: the string is written between double quotes, and all double quotes, newlines, embedded zeros, and backslashes in the string are correctly escaped when written. For instance, the call -> +>lua string.format('%q', 'a string with "quotes" and \n new line') < will produce the string: -> +>lua "a string with \"quotes\" and \ new line" < @@ -4043,7 +4043,7 @@ string.gmatch({s}, {pattern}) *string.gmatch()* in each call. As an example, the following loop -> +>lua s = "hello world from Lua" for w in string.gmatch(s, "%a+") do print(w) @@ -4052,7 +4052,7 @@ string.gmatch({s}, {pattern}) *string.gmatch()* will iterate over all the words from string {s}, printing one per line. The next example collects all pairs `key=value` from the given string into a table: -> +>lua t = {} s = "from=world, to=Lua" for k, v in string.gmatch(s, "(%w+)=(%w+)") do @@ -4091,7 +4091,7 @@ string.gsub({s}, {pattern}, {repl} [, {n}]) *string.gsub()* occurrence of `pattern` is replaced. Here are some examples: -> +>lua x = string.gsub("hello world", "(%w+)", "%1 %1") --> x="hello hello world world" @@ -4831,7 +4831,7 @@ A BIBLIOGRAPHY *luaref-bibliography* This help file is a minor adaptation from this main reference: - R. Ierusalimschy, L. H. de Figueiredo, and W. Celes., - "Lua: 5.1 reference manual", http://www.lua.org/manual/5.1/manual.html + "Lua: 5.1 reference manual", https://www.lua.org/manual/5.1/manual.html Lua is discussed in these references: @@ -4887,10 +4887,10 @@ a few exceptions and adaptations -- a copy of the Lua 5.1 Reference Manual The main ideas and concepts on how to implement this reference were taken from Christian Habermann's CRefVim project -(http://www.vim.org/scripts/script.php?script_id=614). +(https://www.vim.org/scripts/script.php?script_id=614). Adapted for bundled Nvim documentation; the original plugin can be found at -http://www.vim.org/scripts/script.php?script_id=1291 +https://www.vim.org/scripts/script.php?script_id=1291 ------------------------------------------------------------------------------ vi:tw=78:ts=4:ft=help:norl:et diff --git a/runtime/doc/luvref.txt b/runtime/doc/luvref.txt index 6b77ee89a8..859e75e4af 100644 --- a/runtime/doc/luvref.txt +++ b/runtime/doc/luvref.txt @@ -3,7 +3,7 @@ LUV REFERENCE MANUAL - + *luvref* This file documents the Lua bindings for the LibUV library which is used for Nvim's event-loop and is accessible from Lua via |vim.loop| (e.g., |uv.version()| is exposed as `vim.loop.version()`). @@ -28,7 +28,7 @@ TCP Echo Server Example~ Here is a small example showing a TCP echo server: - > + >lua local uv = vim.loop local server = uv.new_tcp() @@ -97,7 +97,7 @@ used here to facilitate documenting consistent behavior: CONTENTS *luv-contents* This documentation is mostly a retelling of the libuv API documentation -(http://docs.libuv.org/en/v1.x/api.html) within the context of luv's Lua API. +(https://docs.libuv.org/en/v1.x/api.html) within the context of luv's Lua API. Low-level implementation details and unexposed C functions and types are not documented here except for when they are relevant to behavior seen in the Lua module. @@ -250,7 +250,7 @@ uv.loop_configure({option}, {...}) *uv.loop_configure()* An example of a valid call to this function is: - > + >lua uv.loop_configure("block_signal", "sigprof") < @@ -343,7 +343,7 @@ uv.walk({callback}) *uv.walk()* Returns: Nothing. - > + >lua -- Example usage of uv.walk to close all handles that -- aren't already closing. uv.walk(function (handle) @@ -613,7 +613,7 @@ uv.new_timer() *uv.new_timer()* Returns: `uv_timer_t userdata` or `fail` - > + >lua -- Creating a simple setTimeout wrapper local function setTimeout(timeout, callback) local timer = uv.new_timer() @@ -737,7 +737,7 @@ uv.timer_get_due_in({timer}) *uv.timer_get_due_in()* Prepare handles will run the given callback once per loop iteration, right before polling for I/O. - > + >lua local prepare = uv.new_prepare() prepare:start(function() print("Before I/O polling") @@ -782,7 +782,7 @@ uv.prepare_stop({prepare}) *uv.prepare_stop()* Check handles will run the given callback once per loop iteration, right after polling for I/O. - > + >lua local check = uv.new_check() check:start(function() print("After I/O polling") @@ -834,7 +834,7 @@ blocking for I/O. WARNING: Despite the name, idle handles will get their callbacks called on every loop iteration, not when the loop is actually "idle". - > + >lua local idle = uv.new_idle() idle:start(function() print("Before I/O polling, no blocking") @@ -879,7 +879,7 @@ uv.idle_stop({check}) *uv.idle_stop()* Async handles allow the user to "wakeup" the event loop and get a callback called from another thread. - > + >lua local async async = uv.new_async(function() print("async operation ran") @@ -933,7 +933,7 @@ uv.async_send({async}, {...}) *uv.async_send()* Poll handles are used to watch file descriptors for readability and writability, similar to the purpose of poll(2) -(http://linux.die.net/man/2/poll). +(https://linux.die.net/man/2/poll). The purpose of poll handles is to enable integrating external libraries that rely on the event loop to signal it about the socket status changes, like @@ -1062,7 +1062,7 @@ Unix Notes: will lead to unpredictable behavior and is strongly discouraged. Future versions of libuv may simply reject them. - > + >lua -- Create a new signal handler local signal = uv.new_signal() -- Define a handler function @@ -1164,7 +1164,7 @@ uv.spawn({path}, {options}, {on_exit}) *uv.spawn()* permissions to use the setuid or setgid specified, or not having enough memory to allocate for the new process. - > + >lua local stdin = uv.new_pipe() local stdout = uv.new_pipe() local stderr = uv.new_pipe() @@ -1358,7 +1358,7 @@ uv.accept({stream}, {client_stream}) *uv.accept()* Returns: `0` or `fail` - > + >lua server:listen(128, function (err) local client = uv.new_tcp() server:accept(client) @@ -1382,7 +1382,7 @@ uv.read_start({stream}, {callback}) *uv.read_start()* Returns: `0` or `fail` - > + >lua stream:read_start(function (err, chunk) if err then -- handle read error @@ -1690,7 +1690,7 @@ uv.tcp_connect({tcp}, {host}, {port}, {callback}) *uv.tcp_connect()* Returns: `uv_connect_t userdata` or `fail` - > + >lua local client = uv.new_tcp() client:connect("127.0.0.1", 8080, function (err) -- check error and carry on. @@ -1755,7 +1755,7 @@ uv.socketpair([{socktype}, [{protocol}, [{flags1}, [{flags2}]]]]) Returns: `table` or `fail` - `[1, 2]` : `integer` (file descriptor) - > + >lua -- Simple read/write with tcp local fds = uv.socketpair(nil, nil, {nonblock=true}, {nonblock=true}) @@ -1780,7 +1780,7 @@ uv.socketpair([{socktype}, [{protocol}, [{flags1}, [{flags2}]]]]) Pipe handles provide an abstraction over local domain sockets on Unix and named pipes on Windows. - > + >lua local pipe = uv.new_pipe(false) pipe:bind('/tmp/sock.test') @@ -1959,7 +1959,7 @@ uv.pipe({read_flags}, {write_flags}) *uv.pipe()* - `read` : `integer` (file descriptor) - `write` : `integer` (file descriptor) - > + >lua -- Simple read/write with pipe_open local fds = uv.pipe({nonblock=true}, {nonblock=true}) @@ -1983,7 +1983,7 @@ uv.pipe({read_flags}, {write_flags}) *uv.pipe()* TTY handles represent a stream for the console. - > + >lua -- Simple echo program local stdin = uv.new_tty(0, true) local stdout = uv.new_tty(1, false) @@ -2537,7 +2537,7 @@ FS call. Synchronous and asynchronous versions of `readFile` (with naive error handling) are implemented below as an example: - > + >lua local function readFileSync(path) local fd = assert(uv.fs_open(path, "r", 438)) local stat = assert(uv.fs_fstat(fd)) @@ -2550,7 +2550,7 @@ handling) are implemented below as an example: print("synchronous read", data) < - > + >lua local function readFile(path, callback) uv.fs_open(path, "r", 438, function(err, fd) assert(not err, err) @@ -2626,7 +2626,7 @@ uv.fs_read({fd}, {size} [, {offset} [, {callback}]]) *uv.fs_read()* indicates EOF. If `offset` is nil or omitted, it will default to `-1`, which - indicates 'use and update the current file offset.' + indicates "use and update the current file offset." Note: When `offset` is >= 0, the current file offset will not be updated by the read. @@ -2665,7 +2665,7 @@ uv.fs_write({fd}, {data} [, {offset} [, {callback}]]) *uv.fs_write()* written. If `offset` is nil or omitted, it will default to `-1`, which - indicates 'use and update the current file offset.' + indicates "use and update the current file offset." Note: When `offset` is >= 0, the current file offset will not be updated by the write. @@ -3253,7 +3253,7 @@ Libuv provides a threadpool which can be used to run user code and get notified in the loop thread. This threadpool is internally used to run all file system operations, as well as `getaddrinfo` and `getnameinfo` requests. - > + >lua local function work_callback(a, b) return a + b end diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index cbc92a8cb5..cb8b162eb6 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -215,6 +215,9 @@ The search string will not be echoed when using this mapping. Messages from the executed command are still given though. To shut them up too, add a ":silent" in the executed command: > :map <silent> ,h :exe ":silent normal /Header\r"<CR> +Note that the effect of a command might also be silenced, e.g., when the +mapping selects another entry for command line completion it won't be +displayed. Prompts will still be given, e.g., for inputdialog(). Using "<silent>" for an abbreviation is possible, but will cause redrawing of the command line to fail. @@ -648,6 +651,10 @@ The special key name "<Plug>" can be used for an internal mapping, which is not to be matched with any key sequence. This is useful in plugins |using-<Plug>|. + *<MouseMove>* +The special key name "<MouseMove>" can be used to handle mouse movement. It +needs to be enabled with 'mousemoveevent'. + *<Char>* *<Char->* To map a character by its decimal, octal or hexadecimal number the <Char> construct can be used: @@ -659,9 +666,9 @@ This is useful to specify a (multibyte) character in a 'keymap' file. Upper and lowercase differences are ignored. *map-comments* -It is not possible to put a comment after these commands, because the '"' +It is not possible to put a comment after these commands, because the `"` character is considered to be part of the {lhs} or {rhs}. However, one can -use |", since this starts a new, empty command with a comment. +use `|"`, since this starts a new, empty command with a comment. *map_bar* *map-bar* Since the '|' character is used to separate a map command from the next @@ -691,8 +698,8 @@ To avoid mapping of the characters you type in insert or Command-line mode, type a CTRL-V first. The mapping in Insert mode is disabled if the 'paste' option is on. *map-error* -Note that when an error is encountered (that causes an error message or beep) -the rest of the mapping is not executed. This is Vi-compatible. +Note that when an error is encountered (that causes an error message or might +cause a beep) the rest of the mapping is not executed. This is Vi-compatible. Note that the second character (argument) of the commands @zZtTfF[]rm'`"v and CTRL-X is not mapped. This was done to be able to use all the named @@ -865,28 +872,56 @@ Here is an example that counts the number of spaces with <F4>: > " doubling <F4> works on a line nnoremap <expr> <F4><F4> CountSpaces() .. '_' - function CountSpaces(type = '') abort + function CountSpaces(context = {}, type = '') abort if a:type == '' - set opfunc=CountSpaces + let context = #{ + \ dot_command: v:false, + \ extend_block: '', + \ virtualedit: [&l:virtualedit, &g:virtualedit], + \ } + let &operatorfunc = function('CountSpaces', [context]) + set virtualedit=block return 'g@' endif - let sel_save = &selection - let reg_save = getreginfo('"') - let cb_save = &clipboard - let visual_marks_save = [getpos("'<"), getpos("'>")] + let save = #{ + \ clipboard: &clipboard, + \ selection: &selection, + \ virtualedit: [&l:virtualedit, &g:virtualedit], + \ register: getreginfo('"'), + \ visual_marks: [getpos("'<"), getpos("'>")], + \ } try - set clipboard= selection=inclusive - let commands = #{line: "'[V']y", char: "`[v`]y", block: "`[\<c-v>`]y"} - silent exe 'noautocmd keepjumps normal! ' .. get(commands, a:type, '') - echom count(getreg('"'), ' ') + set clipboard= selection=inclusive virtualedit= + let commands = #{ + \ line: "'[V']", + \ char: "`[v`]", + \ block: "`[\<C-V>`]", + \ }[a:type] + let [_, _, col, off] = getpos("']") + if off != 0 + let vcol = getline("'[")->strpart(0, col + off)->strdisplaywidth() + if vcol >= [line("'["), '$']->virtcol() - 1 + let a:context.extend_block = '$' + else + let a:context.extend_block = vcol .. '|' + endif + endif + if a:context.extend_block != '' + let commands ..= 'oO' .. a:context.extend_block + endif + let commands ..= 'y' + execute 'silent noautocmd keepjumps normal! ' .. commands + echomsg getreg('"')->count(' ') finally - call setreg('"', reg_save) - call setpos("'<", visual_marks_save[0]) - call setpos("'>", visual_marks_save[1]) - let &clipboard = cb_save - let &selection = sel_save + call setreg('"', save.register) + call setpos("'<", save.visual_marks[0]) + call setpos("'>", save.visual_marks[1]) + let &clipboard = save.clipboard + let &selection = save.selection + let [&l:virtualedit, &g:virtualedit] = get(a:context.dot_command ? save : a:context, 'virtualedit') + let a:context.dot_command = v:true endtry endfunction @@ -1336,7 +1371,6 @@ completion can be enabled: -complete=color color schemes -complete=command Ex command (and arguments) -complete=compiler compilers - -complete=cscope |:cscope| suboptions -complete=dir directory names -complete=environment environment variable names -complete=event autocommand events @@ -1398,9 +1432,11 @@ The function arguments are: The function may use these for determining context. For the "custom" argument, it is not necessary to filter candidates against the (implicit pattern in) ArgLead. Vim will filter the candidates with its regexp engine -after function return, and this is probably more efficient in most cases. For -the "customlist" argument, Vim will not filter the returned completion -candidates and the user supplied function should filter the candidates. +after function return, and this is probably more efficient in most cases. If +'wildoptions' contains "fuzzy", then the candidates will be filtered using +|fuzzy-matching|. For the "customlist" argument, Vim will not +filter the returned completion candidates and the user supplied function +should filter the candidates. The following example lists user names to a Finger command > :com -complete=custom,ListUsers -nargs=1 Finger !finger <args> @@ -1437,7 +1473,7 @@ Possible attributes are: number. -count=N A count (default N) which is specified either in the line number position, or as an initial argument (like |:Next|). - Specifying -count (without a default) acts like -count=0 + -count Acts like -count=0 Note that -range=N and -count=N are mutually exclusive - only one should be specified. @@ -1448,14 +1484,16 @@ which by default correspond to the current line, last line and the whole buffer, relate to arguments, (loaded) buffers, windows or tab pages. Possible values are (second column is the short name used in listing): - -addr=lines line Range of lines (this is the default) + -addr=lines Range of lines (this is the default for -range) -addr=arguments arg Range for arguments -addr=buffers buf Range for buffers (also not loaded buffers) -addr=loaded_buffers load Range for loaded buffers -addr=windows win Range for windows -addr=tabs tab Range for tab pages -addr=quickfix qf Range for quickfix entries - -addr=other ? other kind of range + -addr=other ? Other kind of range; can use ".", "$" and "%" + as with "lines" (this is the default for + -count) Incremental preview ~ @@ -1711,7 +1749,7 @@ When executed as: > This will invoke: > :call Myfunc("arg1","arg2") -< *q-args-example* +< *q-args-example* A more substantial example: > :function Allargs(command) : let i = 0 diff --git a/runtime/doc/mbyte.txt b/runtime/doc/mbyte.txt index dd2c99669c..99dfa54218 100644 --- a/runtime/doc/mbyte.txt +++ b/runtime/doc/mbyte.txt @@ -341,34 +341,7 @@ Useful utilities for converting the charset: All: iconv GNU iconv can convert most encodings. Unicode is used as the intermediate encoding, which allows conversion from and to all other - encodings. See http://www.gnu.org/directory/libiconv.html. - - Japanese: nkf - Nkf is "Network Kanji code conversion Filter". One of the most unique - facility of nkf is the guess of the input Kanji code. So, you don't - need to know what the inputting file's |charset| is. When convert to - EUC-JP from ISO-2022-JP or Shift_JIS, simply do the following command - in Vim: - :%!nkf -e - Nkf can be found at: - http://www.sfc.wide.ad.jp/~max/FreeBSD/ports/distfiles/nkf-1.62.tar.gz - - Chinese: hc - Hc is "Hanzi Converter". Hc convert a GB file to a Big5 file, or Big5 - file to GB file. Hc can be found at: - ftp://ftp.cuhk.hk/pub/chinese/ifcss/software/unix/convert/hc-30.tar.gz - - Korean: hmconv - Hmconv is Korean code conversion utility especially for E-mail. It can - convert between EUC-KR and ISO-2022-KR. Hmconv can be found at: - ftp://ftp.kaist.ac.kr/pub/hangul/code/hmconv/ - - Multilingual: lv - Lv is a Powerful Multilingual File Viewer. And it can be worked as - |charset| converter. Supported |charset|: ISO-2022-CN, ISO-2022-JP, - ISO-2022-KR, EUC-CN, EUC-JP, EUC-KR, EUC-TW, UTF-7, UTF-8, ISO-8859 - series, Shift_JIS, Big5 and HZ. Lv can be found at: - http://www.ff.iij4u.or.jp/~nrt/lv/index.html + encodings. See https://directory.fsf.org/wiki/Libiconv. *mbyte-conversion* @@ -404,17 +377,6 @@ is suitable for complex input, such as CJK. large overhead in communication, but it provides safe synchronization with no restrictions on applications. - For example, there are xwnmo and kinput2 Japanese |IM-server|, both are - FrontEnd system. Xwnmo is distributed with Wnn (see below), kinput2 can be - found at: ftp://ftp.sra.co.jp/pub/x11/kinput2/ - - For Chinese, there's a great XIM server named "xcin", you can input both - Traditional and Simplified Chinese characters. And it can accept other - locale if you make a correct input table. Xcin can be found at: - http://cle.linux.org.tw/xcin/ - Others are scim: http://scim.freedesktop.org/ and fcitx: - http://www.fcitx.org/ - - Conversion Server *conversion-server* Some system needs additional server: conversion server. Most of Japanese @@ -663,7 +625,7 @@ and what the keymaps are to get those characters: glyph encoding keymap ~ Char UTF-8 cp1255 hebrew hebrewp name ~ -א 0x5d0 0xe0 t a 'alef +א 0x5d0 0xe0 t a ´alef ב 0x5d1 0xe1 c b bet ג 0x5d2 0xe2 d g gimel ד 0x5d3 0xe3 s d dalet @@ -744,7 +706,7 @@ Char UTF-8 hebrew name Combining forms: ﬠ 0xfb20 X` Alternative `ayin -ﬡ 0xfb21 X' Alternative 'alef +ﬡ 0xfb21 X' Alternative ´alef ﬢ 0xfb22 X-d Alternative dalet ﬣ 0xfb23 X-h Alternative he ﬤ 0xfb24 X-k Alternative kaf diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt index dac4df5ee9..dffdb5950f 100644 --- a/runtime/doc/message.txt +++ b/runtime/doc/message.txt @@ -573,9 +573,7 @@ when using ":w"), therefore Vim requires using a ! after the command, e.g.: VirtualBinding Messages like this appear when starting up. This is not a Vim problem, your -X11 configuration is wrong. You can find a hint on how to solve this here: -http://groups.yahoo.com/group/solarisonintel/message/12179. -[this URL is no longer valid] +X11 configuration is wrong. *W10* > Warning: Changing a readonly file @@ -603,6 +601,7 @@ probably means that some other program changed the file. You will have to find out what happened, and decide which version of the file you want to keep. Set the 'autoread' option if you want to do this automatically. This message is not given when 'buftype' is not empty. +Also see the |FileChangedShell| autocommand. There is one situation where you get this message even though there is nothing wrong: If you save a file in Windows on the day the daylight saving time @@ -821,7 +820,7 @@ Type effect ~ the clipboard ("* and "+ registers) {menu-entry} what the menu is defined to in Cmdline-mode. - <LeftMouse> (*) next page + <LeftMouse> next page (*) Any other key causes the meaning of the keys to be displayed. diff --git a/runtime/doc/mlang.txt b/runtime/doc/mlang.txt index 9d3a51302d..84b8498d39 100644 --- a/runtime/doc/mlang.txt +++ b/runtime/doc/mlang.txt @@ -92,27 +92,10 @@ use of "-" and "_". :lang mes en < -MS-WINDOWS MESSAGE TRANSLATIONS *win32-gettext* - -If you used the self-installing .exe file, message translations should work -already. Otherwise get the libintl.dll file if you don't have it yet: - - http://sourceforge.net/projects/gettext -Or: - https://mlocati.github.io/gettext-iconv-windows/ - -This also contains tools xgettext, msgformat and others. - -libintl.dll should be placed in same directory as (g)vim.exe, or one of the -directories listed in the PATH environment value. Vim also looks for the -alternate names "libintl-8.dll" and "intl.dll". - Message files (vim.mo) have to be placed in "$VIMRUNTIME/lang/xx/LC_MESSAGES", -where "xx" is the abbreviation of the language (mostly two letters). - -If you write your own translations you need to generate the .po file and -convert it to a .mo file. You need to get the source distribution and read -the file "src/po/README.txt". +where "xx" is the abbreviation of the language (mostly two letters). If you +write your own translations you need to generate the .po file and convert it +to a .mo file. To overrule the automatic choice of the language, set the $LANG variable to the language of your choice. use "en" to disable translations. > diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt index 8ff4ed4f96..929efee19f 100644 --- a/runtime/doc/motion.txt +++ b/runtime/doc/motion.txt @@ -309,10 +309,10 @@ g<Down> [count] display lines downward. |exclusive| motion. an operator, because it's not linewise. *-* -- <minus> [count] lines upward, on the first non-blank +`-` <minus> [count] lines upward, on the first non-blank character |linewise|. -+ or *+* +`+` or *+* CTRL-M or *CTRL-M* *<CR>* <CR> [count] lines downward, on the first non-blank character |linewise|. @@ -438,9 +438,9 @@ between Vi and Vim. } [count] |paragraph|s forward. |exclusive| motion. *]]* -]] [count] |section|s forward or to the next '{' in the +]] [count] |section|s forward or to the next "{" in the first column. When used after an operator, then also - stops below a '}' in the first column. |exclusive| + stops below a "}" in the first column. |exclusive| Note that |exclusive-linewise| often applies. *][* @@ -449,12 +449,12 @@ between Vi and Vim. Note that |exclusive-linewise| often applies. *[[* -[[ [count] |section|s backward or to the previous '{' in +[[ [count] |section|s backward or to the previous "{" in the first column. |exclusive| Note that |exclusive-linewise| often applies. *[]* -[] [count] |section|s backward or to the previous '}' in +[] [count] |section|s backward or to the previous "}" in the first column. |exclusive| Note that |exclusive-linewise| often applies. @@ -1004,8 +1004,8 @@ These commands are not marks themselves, but jump to a mark: Note that ":keepjumps" must be used for every command. When invoking a function the commands in that function can still change the jumplist. Also, for - ":keepjumps exe 'command '" the "command" won't keep - jumps. Instead use: ":exe 'keepjumps command'" + `:keepjumps exe 'command '` the "command" won't keep + jumps. Instead use: `:exe 'keepjumps command'` ============================================================================== 8. Jumps *jump-motions* diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 0fdd7aaaf9..5c234677ef 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -13,21 +13,176 @@ BREAKING CHANGES *news-breaking* The following changes may require adaptations in user config or plugins. +• Cscope support is now removed (see |cscope| and |nvim-features-removed|): + - Commands removed: + - `:cscope` + - `:lcscope` + - `:scscope` + - `:cstag` + - Options removed: + - `cscopepathcomp` + - `cscopeprg` + - `cscopequickfix` + - `cscoperelative` + - `cscopetag` + - `cscopetagorder` + - `cscopeverbose` + - Eval functions removed: + - `cscope_connection()` + + Note: support for |ctags| remains with no plans to remove. + + See https://github.com/neovim/neovim/pull/20545 for more information. + +• `:hardcopy` is now removed (see |hardcopy| and |nvim-features-removed|): + - Commands removed: + - `:hardcopy` + - Options removed: + - `printdevice` + - `printencoding` + - `printexpr` + - `printfont` + - `printheader` + - `printmbcharset` + +• libiconv is now a required build dependency. + ============================================================================== NEW FEATURES *news-features* The following new APIs or features were added. +• |nvim_open_win()| now accepts a relative `mouse` option to open a floating win + relative to the mouse. Note that the mouse doesn't update frequently without + setting `vim.o.mousemoveevent = true` + +• EditorConfig support is now builtin. This is enabled by default and happens + automatically. To disable it, users should add >lua + + vim.g.editorconfig = false +< + (or the Vimscript equivalent) to their |config| file. + +• Run Lua scripts from your shell using |-l|. > + nvim -l foo.lua --arg1 --arg2 +< Also works with stdin: > + echo "print(42)" | nvim -l - + +• Added a |vim.lsp.codelens.clear()| function to clear codelenses. + +• |vim.inspect_pos()|, |vim.show_pos()| and |:Inspect| allow a user to get or show items + at a given buffer position. Currently this includes treesitter captures, + semantic tokens, syntax groups and extmarks. + +• Added support for semantic token highlighting to the LSP client. This + functionality is enabled by default when a client that supports this feature + is attached to a buffer. Opt-out can be performed by deleting the + `semanticTokensProvider` from the LSP client's {server_capabilities} in the + `LspAttach` callback. + + See |lsp-semantic_tokens| for more information. + +• |vim.treesitter.show_tree()| opens a split window showing a text + representation of the nodes in a language tree for the current buffer. + +• Added support for the `willSave` and `willSaveWaitUntil` capabilities to the + LSP client. `willSaveWaitUntil` allows a server to modify a document before it + gets saved. Example use-cases by language servers include removing unused + imports, or formatting the file. + +• Treesitter syntax highlighting for `help` files now supports highlighted + code examples. To enable, create a `.config/nvim/ftplugin/help.lua` with + the contents >lua + vim.treesitter.start() +< + Note: Highlighted code examples are only available in the Nvim manual, not + in help files taken from Vim. The treesitter `help` parser is also work in + progress and not guaranteed to correctly highlight every help file in the + wild. + +• |vim.secure.trust()|, |:trust| allows the user to manage files in trust + database. + +• |vim.diagnostic.open_float()| (and therefore |vim.diagnostic.config()|) now + accepts a `suffix` option which, by default, renders LSP error codes. + Similarly, the `virtual_text` configuration in |vim.diagnostic.config()| now + has a `suffix` option which does nothing by default. + +• |vim.fs.dir()| now has a `opts` argument with a depth field to allow + recursively searching a directory tree. + +• |vim.secure.read()| reads a file and prompts the user if it should be + trusted and, if so, returns the file's contents. + +• When using Nvim inside tmux 3.2 or later, the default clipboard provider + will now copy to the system clipboard. |provider-clipboard| + +• |'showcmdloc'| option to display the 'showcmd' information in the + status line or tab line. A new %S statusline item is available to place + the 'showcmd' text in a custom 'statusline'. Useful for when |'cmdheight'| + is set to 0. + +• |'splitkeep'| option to control the scroll behavior of horizontal splits. + +• |'statuscolumn'| option to customize the area to the side of a window, + normally containing the fold, sign and number columns. This new option follows + the 'statusline' syntax and can be used to transform the line numbers, create + mouse click callbacks for |signs|, introduce a custom margin or separator etc. + +• |nvim_select_popupmenu_item()| now supports |cmdline-completion| popup menu. + +• |'diffopt'| now includes a `linematch` option to enable a second-stage diff + on individual hunks to provide much more accurate diffs. This option is also + available to |vim.diff()| + + See https://github.com/neovim/neovim/pull/14537. + +• |vim.diagnostic.is_disabled()| checks if diagnostics are disabled in a given + buffer or namespace. + +• |--remote-ui| option was added to connect to a remote instance and display + in it in a |TUI| in the local terminal. This can be used run a headless nvim + instance in the background and display its UI on demand, which previously + only was possible using an external UI implementation. + +• Several improvements were made to make the code generation scripts more + deterministic, and a `LUA_GEN_PRG` build parameter has been introduced to + allow for a workaround for some remaining reproducibility problems. + +• |:highlight| now supports an additional attribute "altfont". + ============================================================================== CHANGED FEATURES *news-changes* The following changes to existing APIs or features add new behavior. +• 'exrc' now supports `.nvim.lua` file. +• 'exrc' is no longer marked deprecated. + +• The |TUI| is changed to run in a separate process (previously, a separate + thread was used). This is not supposed to be a visible change to the user, + but might be the cause of subtle changes of behavior and bugs. + + Previously, the TUI could be disabled as a build time feature (+tui/-tui), + resulting in a nvim binary which only could be run headless or embedded + in an external process. As of this version, TUI is always available. + +• API calls now show more information about where an exception happened. + ============================================================================== REMOVED FEATURES *news-removed* The following deprecated functions or APIs were removed. +• It is no longer possible to scroll the whole screen when showing messages + longer than 'cmdheight'. |msgsep| is now always enabled even if 'display' + doesn't contain the "msgsep" flag. + +• `filetype.vim` is removed in favor of |lua-filetype| + (Note that filetype logic and tests still align with Vim, so additions or + changes need to be contributed there first.) + See https://github.com/neovim/neovim/pull/20674. + ============================================================================== DEPRECATIONS *news-deprecations* diff --git a/runtime/doc/nvim.txt b/runtime/doc/nvim.txt index 203f57024c..ef407922da 100644 --- a/runtime/doc/nvim.txt +++ b/runtime/doc/nvim.txt @@ -9,7 +9,7 @@ Nvim *nvim* *nvim-intro* Nvim is based on Vim by Bram Moolenaar. If you already use Vim see |nvim-from-vim| for a quickstart. -If you are new to Vim, try the 30-minute tutorial: > +If you are new to Vim, try the 30-minute tutorial: >vim :Tutor<Enter> @@ -22,12 +22,12 @@ Nvim is emphatically a fork of Vim, not a clone: compatibility with Vim ============================================================================== Transitioning from Vim *nvim-from-vim* -1. To start the transition, create your |init.vim| (user config) file: > +1. To start the transition, create your |init.vim| (user config) file: >vim - :call mkdir(stdpath('config'), 'p') :exe 'edit '.stdpath('config').'/init.vim' + :write ++p -2. Add these contents to the file: > +2. Add these contents to the file: >vim set runtimepath^=~/.vim runtimepath+=~/.vim/after let &packpath = &runtimepath @@ -43,19 +43,19 @@ Your Vim configuration might not be entirely Nvim-compatible (see because mouse support is always enabled if possible. If you use the same |vimrc| for Vim and Nvim you could guard |'ttymouse'| in your configuration like so: -> +>vim if !has('nvim') set ttymouse=xterm2 endif And for Nvim-specific configuration, you can do this: -> +>vim if has('nvim') tnoremap <Esc> <C-\><C-n> endif For a more granular approach use |exists()|: -> +>vim if exists(':tnoremap') tnoremap <Esc> <C-\><C-n> endif @@ -67,7 +67,7 @@ for more information. Because Nvim follows the XDG |base-directories| standard, configuration on Windows is stored in ~/AppData instead of ~/.config. But you can still share the same Nvim configuration on all of your machines, by creating -~/AppData/Local/nvim/init.vim containing just this line: > +~/AppData/Local/nvim/init.vim containing just this line: >vim source ~/.config/nvim/init.vim ============================================================================== diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt index 546f92e92f..96f99528ed 100644 --- a/runtime/doc/nvim_terminal_emulator.txt +++ b/runtime/doc/nvim_terminal_emulator.txt @@ -27,12 +27,12 @@ There are several ways to create a terminal buffer: - Run the |:terminal| command. - Call the |nvim_open_term()| or |termopen()| function. -- Edit a "term://" buffer. Examples: > +- Edit a "term://" buffer. Examples: >vim :edit term://bash :vsplit term://top < Note: To open a "term://" buffer from an autocmd, the |autocmd-nested| - modifier is required. > + modifier is required. >vim autocmd VimEnter * ++nested split term://sh < (This is only mentioned for reference; use |:terminal| instead.) @@ -62,13 +62,13 @@ Terminal-mode forces these local options: Terminal-mode has its own |:tnoremap| namespace for mappings, this can be used to automate any terminal interaction. -To map <Esc> to exit terminal-mode: > +To map <Esc> to exit terminal-mode: >vim :tnoremap <Esc> <C-\><C-n> -To simulate |i_CTRL-R| in terminal-mode: > +To simulate |i_CTRL-R| in terminal-mode: >vim :tnoremap <expr> <C-R> '<C-\><C-N>"'.nr2char(getchar()).'pi' -To use `ALT+{h,j,k,l}` to navigate windows from any mode: > +To use `ALT+{h,j,k,l}` to navigate windows from any mode: >vim :tnoremap <A-h> <C-\><C-N><C-w>h :tnoremap <A-j> <C-\><C-N><C-w>j :tnoremap <A-k> <C-\><C-N><C-w>k @@ -109,7 +109,7 @@ global configuration. - 'list' is disabled - 'wrap' is disabled -You can change the defaults with a TermOpen autocommand: > +You can change the defaults with a TermOpen autocommand: >vim au TermOpen * setlocal list TERMINAL COLORS ~ @@ -117,7 +117,7 @@ TERMINAL COLORS ~ The `{g,b}:terminal_color_x` variables control the terminal color palette, where `x` is the color index between 0 and 255 inclusive. The variables are read during |TermOpen|. The value must be a color name or hexadecimal string. -Example: > +Example: >vim let g:terminal_color_4 = '#ff0000' let g:terminal_color_5 = 'green' Only works for RGB UIs (see 'termguicolors'); for 256-color terminals the @@ -131,7 +131,7 @@ Status Variables *terminal-status* Terminal buffers maintain some buffer-local variables and options. The values are initialized before TermOpen, so you can use them in a local 'statusline'. -Example: > +Example: >vim :autocmd TermOpen * setlocal statusline=%{b:term_title} - *b:term_title* Terminal title (user-writable), typically displayed in the @@ -141,10 +141,10 @@ Example: > input to the terminal. - The |TermClose| event gives the terminal job exit code in the |v:event| "status" field. For example, this autocmd closes terminal buffers if the job - exited without error: > + exited without error: >vim autocmd TermClose * if !v:event.status | exe 'bdelete! '..expand('<abuf>') | endif -Use |jobwait()| to check if the terminal job has finished: > +Use |jobwait()| to check if the terminal job has finished: >vim let running = jobwait([&channel], 0)[0] == -1 ============================================================================== @@ -156,11 +156,11 @@ Vim this also works remotely over an ssh connection. Starting ~ *termdebug-starting* -Load the plugin with this command: > +Load the plugin with this command: >vim packadd termdebug < *:Termdebug* To start debugging use `:Termdebug` or `:TermdebugCommand` followed by the -command name, for example: > +command name, for example: >vim :Termdebug vim This opens two windows: @@ -189,16 +189,16 @@ Only one debugger can be active at a time. *:TermdebugCommand* If you want to give specific commands to the command being debugged, you can use the `:TermdebugCommand` command followed by the command name and -additional parameters. > +additional parameters. >vim :TermdebugCommand vim --clean -c ':set nu' Both the `:Termdebug` and `:TermdebugCommand` support an optional "!" bang argument to start the command right away, without pausing at the gdb window -(and cursor will be in the debugged window). For example: > +(and cursor will be in the debugged window). For example: >vim :TermdebugCommand! vim --clean To attach gdb to an already running executable or use a core file, pass extra -arguments. E.g.: > +arguments. E.g.: >vim :Termdebug vim core :Termdebug vim 98343 @@ -212,7 +212,7 @@ Start in the Vim "src" directory and build Vim: > % make Start Vim: > % ./vim -Load the termdebug plugin and start debugging Vim: > +Load the termdebug plugin and start debugging Vim: >vim :packadd termdebug :Termdebug vim You should now have three windows: @@ -223,7 +223,7 @@ You should now have three windows: Put focus on the gdb window and type: > break ex_help run -Vim will start running in the program window. Put focus there and type: > +Vim will start running in the program window. Put focus there and type: >vim :help gui Gdb will run into the ex_help breakpoint. The source window now shows the ex_cmds.c file. A red "1 " marker will appear in the signcolumn where the @@ -329,7 +329,7 @@ Other commands ~ Events ~ *termdebug-events* -Four autocommands can be used: > +Four autocommands can be used: >vim au User TermdebugStartPre echomsg 'debugging starting' au User TermdebugStartPost echomsg 'debugging started' au User TermdebugStopPre echomsg 'debugging stopping' @@ -355,6 +355,20 @@ TermdebugStopPost After debugging has ended, gdb-related windows the state before the debugging was restored. +Customizing ~ + *termdebug-customizing* *g:termdebug_config* +In the past several global variables were used for configuration. These are +deprecated and using the g:termdebug_config dictionary is preferred. When +g:termdebug_config exists the other global variables will NOT be used. +The recommended way is to start with an empty dictionary: >vim + let g:termdebug_config = {} + +Then you can add entries to the dictionary as mentioned below. The +deprecated global variable names are mentioned for completeness. If you are +switching over to using g:termdebug_config you can find the old variable name +and take over the value, then delete the deprecated variable. + + Prompt mode ~ *termdebug-prompt* When on MS-Windows, gdb will run in a buffer with 'buftype' set to "prompt". @@ -366,23 +380,23 @@ This works slightly differently: - A separate :terminal window will be opened to run the debugged program in. *termdebug_use_prompt* -Prompt mode can be used with: > +Prompt mode can be used with: >vim let g:termdebug_config['use_prompt'] = 1 -Or if there is no g:termdebug_config: > +If there is no g:termdebug_config you can use: >vim let g:termdebug_use_prompt = 1 < *termdebug_map_K* -The K key is normally mapped to :Evaluate. If you do not want this use: > +The K key is normally mapped to :Evaluate. If you do not want this use: >vim let g:termdebug_config['map_K'] = 0 -Or if there is no g:termdebug_config: > +If there is no g:termdebug_config you can use: >vim let g:termdebug_map_K = 0 < *termdebug_disasm_window* If you want the Asm window shown by default, set the flag to 1. -the "disasm_window_height" entry can be used to set the window height: > +the "disasm_window_height" entry can be used to set the window height: >vim let g:termdebug_config['disasm_window'] = 1 let g:termdebug_config['disasm_window_height'] = 15 -or, if there is no g:termdebug_config: > +If there is no g:termdebug_config you can use: >vim let g:termdebug_disasm_window = 15 Any value greater than 1 will set the Asm window height to that value. @@ -400,45 +414,38 @@ interrupt the running program. But after using the MI command communication channel. -Customizing ~ - *termdebug-customizing* *g:termdebug_config* -In the past several global variables were used for configuration. These are -deprecated, using the g:termdebug_config dictionary is preferred. When -g:termdebug_config exists the other global variables will not be used. - - GDB command ~ *g:termdebugger* To change the name of the gdb command, set "debugger" entry in g:termdebug_config or the "g:termdebugger" variable before invoking -`:Termdebug`: > +`:Termdebug`: >vim let g:termdebug_config['command'] = "mygdb" -Or if there is no g:termdebug_config: > +If there is no g:termdebug_config you can use: >vim let g:termdebugger = "mygdb" -If the command needs an argument use a List: > +If the command needs an argument use a List: >vim let g:termdebug_config['command'] = ['rr', 'replay', '--'] -Or if there is no g:termdebug_config: > +If there is no g:termdebug_config you can use: >vim let g:termdebugger = ['rr', 'replay', '--'] To not use neovim floating windows for previewing variable evaluation, set the -`g:termdebug_useFloatingHover` variable like this: > +`g:termdebug_useFloatingHover` variable like this: >vim let g:termdebug_useFloatingHover = 0 If you are a mouse person, you can also define a mapping using your right click to one of the terminal command like evaluate the variable under the -cursor: > +cursor: >vim nnoremap <RightMouse> :Evaluate<CR> -or set/unset a breakpoint: > +or set/unset a breakpoint: >vim nnoremap <RightMouse> :Break<CR> Several arguments will be added to make gdb work well for the debugger. -If you want to modify them, add a function to filter the argument list: > +If you want to modify them, add a function to filter the argument list: >vim let g:termdebug_config['command_filter'] = MyDebugFilter If you do not want the arguments to be added, but you do need to set the -"pty", use a function to add the necessary arguments: > +"pty", use a function to add the necessary arguments: >vim let g:termdebug_config['command_add_args'] = MyAddArguments The function will be called with the list of arguments so far, and a second argument that is the name of the pty. @@ -451,7 +458,7 @@ Then your gdb is too old. Colors ~ - *hl-debugPC* *hl-debugBreakpoint* + *hl-debugPC* *hl-debugBreakpoint* The color of the signs can be adjusted with these highlight groups: - debugPC the current position - debugBreakpoint a breakpoint @@ -467,34 +474,31 @@ When 'background' is "dark": Shortcuts ~ *termdebug_shortcuts* - You can define your own shortcuts (mappings) to control gdb, that can work in -any window, using the TermDebugSendCommand() function. Example: > +any window, using the TermDebugSendCommand() function. Example: >vim map ,w :call TermDebugSendCommand('where')<CR> The argument is the gdb command. Popup menu ~ *termdebug_popup* - By default the Termdebug plugin sets 'mousemodel' to "popup_setpos" and adds these entries to the popup menu: Set breakpoint `:Break` Clear breakpoint `:Clear` Evaluate `:Evaluate` -If you don't want this then disable it with: > +If you don't want this then disable it with: >vim let g:termdebug_config['popup'] = 0 -or if there is no g:termdebug_config: > +If there is no g:termdebug_config you can use: >vim let g:termdebug_popup = 0 Vim window width ~ *termdebug_wide* - To change the width of the Vim window when debugging starts and use a vertical -split: > +split: >vim let g:termdebug_config['wide'] = 163 -Or if there is no g:termdebug_config: > +If there is no g:termdebug_config you can use: >vim let g:termdebug_wide = 163 This will set 'columns' to 163 when `:Termdebug` is used. The value is diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index fd1415c589..2bdff2fbb2 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -145,7 +145,7 @@ This sets the 'titlestring' option to "hi" and 'iconstring' to "there": > Similarly, the double quote character starts a comment. To include the '"' in the option value, use '\"' instead. This example sets the 'titlestring' -option to 'hi "there"': > +option to "hi "there"": > :set titlestring=hi\ \"there\" For Win32 backslashes in file names are mostly not removed. More precise: For @@ -163,7 +163,7 @@ halved and when you expect the backslashes to be kept. The third gives a result which is probably not what you want. Avoid it. *add-option-flags* *remove-option-flags* - *E539* *E550* *E551* *E552* + *E539* Some options are a list of flags. When you want to add a flag to such an option, without changing the existing ones, you can do it like this: > :set guioptions+=a @@ -313,14 +313,23 @@ Note: In the future more global options can be made |global-local|. Using *option-value-function* -Some options ('completefunc', 'imactivatefunc', 'imstatusfunc', 'omnifunc', -'operatorfunc', 'quickfixtextfunc' and 'tagfunc') are set to a function name -or a function reference or a lambda function. Examples: +Some options ('completefunc', 'omnifunc', 'operatorfunc', 'quickfixtextfunc', +'tagfunc' and 'thesaurusfunc') are set to a function name or a function +reference or a lambda function. When using a lambda it will be converted to +the name, e.g. "<lambda>123". Examples: > set opfunc=MyOpFunc - set opfunc=function("MyOpFunc") - set opfunc=funcref("MyOpFunc") - set opfunc={t\ ->\ MyOpFunc(t)} + set opfunc=function('MyOpFunc') + set opfunc=funcref('MyOpFunc') + set opfunc={a\ ->\ MyOpFunc(a)} + " set using a funcref variable + let Fn = function('MyTagFunc') + let &tagfunc = Fn + " set using a lambda expression + let &tagfunc = {t -> MyTagFunc(t)} + " set using a variable with lambda expression + let L = {a, b, c -> MyTagFunc(a, b , c)} + let &tagfunc = L < Setting the filetype @@ -440,9 +449,9 @@ se[t] the string "set " or "se " (note the space); When : a colon [text] any text or empty -Examples: - /* vim: set ai tw=75: */ ~ - /* Vim: set ai tw=75: */ ~ +Examples: > + /* vim: set ai tw=75: */ + /* Vim: set ai tw=75: */ The white space before {vi:|vim:|Vim:|ex:} is required. This minimizes the chance that a normal word like "lex:" is caught. There is one exception: @@ -479,10 +488,10 @@ number can be specified where "vim:" or "Vim:" is used: vim={vers}: version {vers} vim>{vers}: version after {vers} {vers} is 700 for Vim 7.0 (hundred times the major version plus minor). -For example, to use a modeline only for Vim 7.0: - /* vim700: set foldmethod=marker */ ~ -To use a modeline for Vim after version 7.2: - /* vim>702: set cole=2: */ ~ +For example, to use a modeline only for Vim 7.0: > + /* vim700: set foldmethod=marker */ +To use a modeline for Vim after version 7.2: > + /* vim>702: set cole=2: */ There can be no blanks between "vim" and the ":". The modeline is ignored if {vers} does not fit in an integer. @@ -491,16 +500,16 @@ The number of lines that are checked can be set with the 'modelines' option. If 'modeline' is off or 'modelines' is 0 no lines are checked. Note that for the first form all of the rest of the line is used, thus a line -like: - /* vi:ts=4: */ ~ -will give an error message for the trailing "*/". This line is OK: - /* vi:set ts=4: */ ~ +like: > + /* vi:ts=4: */ +will give an error message for the trailing "*/". This line is OK: > + /* vi:set ts=4: */ If an error is detected the rest of the line is skipped. If you want to include a ':' in a set command precede it with a '\'. The -backslash in front of the ':' will be removed. Example: - /* vi:set fillchars=stl\:^,vert\:\|: */ ~ +backslash in front of the ':' will be removed. Example: > + /* vi:set fillchars=stl\:^,vert\:\|: */ This sets the 'fillchars' option to "stl:^,vert:\|". Only a single backslash before the ':' is removed. Thus to include "\:" you have to specify "\\:". *E992* @@ -621,7 +630,7 @@ A jump table for the options with a short description can be found at |Q_op|. by Vim with the width of glyphs in the font. Perhaps it also has to be set to "double" under CJK MS-Windows when the system locale is set to one of CJK locales. See Unicode Standard Annex #11 - (http://www.unicode.org/reports/tr11). + (https://www.unicode.org/reports/tr11). *'autochdir'* *'acd'* *'noautochdir'* *'noacd'* 'autochdir' 'acd' boolean (default off) @@ -1053,19 +1062,26 @@ A jump table for the options with a short description can be found at |Q_op|. text should normally be narrower. This prevents text indented almost to the right window border occupying lot of vertical space when broken. + (default: 20) shift:{n} After applying 'breakindent', the wrapped line's beginning will be shifted by the given number of characters. It permits dynamic French paragraph indentation (negative) or emphasizing the line continuation (positive). + (default: 0) sbr Display the 'showbreak' value before applying the additional indent. + (default: off) list:{n} Adds an additional indent for lines that match a numbered or bulleted list (using the 'formatlistpat' setting). list:-1 Uses the length of a match with 'formatlistpat' for indentation. - The default value for min is 20, shift and list is 0. + (default: 0) + column:{n} Indent at column {n}. Will overrule the other + sub-options. Note: an additional indent may be + added for the 'showbreak' setting. + (default: off) *'browsedir'* *'bsdir'* 'browsedir' 'bsdir' string (default: "last") @@ -1309,7 +1325,7 @@ A jump table for the options with a short description can be found at |Q_op|. These names are recognized: *clipboard-unnamed* - unnamed When included, Vim will use the clipboard register '*' + unnamed When included, Vim will use the clipboard register "*" for all yank, delete, change and put operations which would normally go to the unnamed register. When a register is explicitly specified, it will always be @@ -1320,8 +1336,8 @@ A jump table for the options with a short description can be found at |Q_op|. *clipboard-unnamedplus* unnamedplus A variant of the "unnamed" flag which uses the - clipboard register '+' (|quoteplus|) instead of - register '*' for all yank, delete, change and put + clipboard register "+" (|quoteplus|) instead of + register "*" for all yank, delete, change and put operations which would normally go to the unnamed register. When "unnamed" is also included to the option, yank and delete operations (but not put) @@ -1440,7 +1456,9 @@ A jump table for the options with a short description can be found at |Q_op|. This option specifies a function to be used for Insert mode completion with CTRL-X CTRL-U. |i_CTRL-X_CTRL-U| See |complete-functions| for an explanation of how the function is - invoked and what it should return. + invoked and what it should return. The value can be the name of a + function, a |lambda| or a |Funcref|. See |option-value-function| for + more information. This option cannot be set from a |modeline| or in the |sandbox|, for security reasons. @@ -1793,43 +1811,6 @@ A jump table for the options with a short description can be found at |Q_op|. _ When using |cw| on a word, do not include the whitespace following the word in the motion. - *'cscopepathcomp'* *'cspc'* -'cscopepathcomp' 'cspc' number (default 0) - global - Determines how many components of the path to show in a list of tags. - See |cscopepathcomp|. - - *'cscopeprg'* *'csprg'* -'cscopeprg' 'csprg' string (default "cscope") - global - Specifies the command to execute cscope. See |cscopeprg|. - This option cannot be set from a |modeline| or in the |sandbox|, for - security reasons. - - *'cscopequickfix'* *'csqf'* -'cscopequickfix' 'csqf' string (default "") - global - Specifies whether to use quickfix window to show cscope results. - See |cscopequickfix|. - - *'cscoperelative'* *'csre'* *'nocscoperelative'* *'nocsre'* -'cscoperelative' 'csre' boolean (default off) - global - In the absence of a prefix (-P) for cscope. setting this option enables - to use the basename of cscope.out path as the prefix. - See |cscoperelative|. - - *'cscopetag'* *'cst'* *'nocscopetag'* *'nocst'* -'cscopetag' 'cst' boolean (default off) - global - Use cscope for tag commands. See |cscope-options|. - - *'cscopetagorder'* *'csto'* -'cscopetagorder' 'csto' number (default 0) - global - Determines the order in which ":cstag" performs a search. See - |cscopetagorder|. - *'cursorbind'* *'crb'* *'nocursorbind'* *'nocrb'* 'cursorbind' 'crb' boolean (default off) local to window @@ -1910,9 +1891,9 @@ A jump table for the options with a short description can be found at |Q_op|. ^\(#\s*define\|[a-z]*\s*const\s*[a-z]*\) < You can also use "\ze" just before the name and continue the pattern to check what is following. E.g. for Javascript, if a function is - defined with "func_name = function(args)": > + defined with `func_name = function(args)`: > ^\s*\ze\i\+\s*=\s*function( -< If the function is defined with "func_name : function() {...": > +< If the function is defined with `func_name : function() {...`: > ^\s*\ze\i\+\s*[:]\s*(*function\s*( < When using the ":set" command, you need to double the backslashes! To avoid that use `:let` with a single quote string: > @@ -2055,6 +2036,16 @@ A jump table for the options with a short description can be found at |Q_op|. Use the indent heuristic for the internal diff library. + linematch:{n} Enable a second stage diff on each generated + hunk in order to align lines. When the total + number of lines in a hunk exceeds {n}, the + second stage diff will not be performed as + very large hunks can cause noticeable lag. A + recommended setting is "linematch:60", as this + will enable alignment for a 2 buffer diff with + hunks of up to 30 lines each, or a 3 buffer + diff with hunks of up to 20 lines each. + algorithm:{text} Use the specified diff algorithm with the internal diff engine. Currently supported algorithms are: @@ -2173,6 +2164,16 @@ A jump table for the options with a short description can be found at |Q_op|. See 'fileencoding' to control file-content encoding. + *'endoffile'* *'eof'* *'noendoffile'* *'noeof'* +'endoffile' 'eof' boolean (default off) + local to buffer + Indicates that a CTRL-Z character was found at the end of the file + when reading it. Normally only happens when 'fileformat' is "dos". + When writing a file and this option is off and the 'binary' option + is on, or 'fixeol' option is off, no CTRL-Z will be written at the + end of the file. + See |eol-and-eof| for example settings. + *'endofline'* *'eol'* *'noendofline'* *'noeol'* 'endofline' 'eol' boolean (default on) local to buffer @@ -2187,6 +2188,7 @@ A jump table for the options with a short description can be found at |Q_op|. to remember the presence of a <EOL> for the last line in the file, so that when you write the file the situation from the original file can be kept. But you can change it if you want to. + See |eol-and-eof| for example settings. *'equalalways'* *'ea'* *'noequalalways'* *'noea'* 'equalalways' 'ea' boolean (default on) @@ -2263,6 +2265,22 @@ A jump table for the options with a short description can be found at |Q_op|. This option is reset when the 'paste' option is set and restored when the 'paste' option is reset. + *'exrc'* *'ex'* *'noexrc'* *'noex'* +'exrc' 'ex' boolean (default off) + global + Enables the reading of .nvim.lua, .nvimrc, and .exrc files in the current + directory. + + The file is only sourced if the user indicates the file is trusted. If + it is, the SHA256 hash of the file contents and the full path of the + file are persisted to a trust database. The user is only prompted + again if the file contents change. See |vim.secure.read()|. + + Use |:trust| to manage the trusted file database. + + This option cannot be set from a |modeline| or in the |sandbox|, for + security reasons. + *'fileencoding'* *'fenc'* *E213* 'fileencoding' 'fenc' string (default: "") local to buffer @@ -2444,13 +2462,13 @@ A jump table for the options with a short description can be found at |Q_op|. this use the ":filetype on" command. |:filetype| Setting this option to a different value is most useful in a modeline, for a file for which the file type is not automatically recognized. - Example, for in an IDL file: - /* vim: set filetype=idl : */ ~ - |FileType| |filetypes| + Example, for in an IDL file: > + /* vim: set filetype=idl : */ +< |FileType| |filetypes| When a dot appears in the value then this separates two filetype - names. Example: - /* vim: set filetype=c.doxygen : */ ~ - This will use the "c" filetype first, then the "doxygen" filetype. + names. Example: > + /* vim: set filetype=c.doxygen : */ +< This will use the "c" filetype first, then the "doxygen" filetype. This works both for filetype plugins and for syntax files. More than one dot may appear. This option is not copied to another buffer, independent of the 's' or @@ -2530,11 +2548,12 @@ A jump table for the options with a short description can be found at |Q_op|. 'fixendofline' 'fixeol' boolean (default on) local to buffer When writing a file and this option is on, <EOL> at the end of file - will be restored if missing. Turn this option off if you want to + will be restored if missing. Turn this option off if you want to preserve the situation from the original file. When the 'binary' option is set the value of this option doesn't matter. See the 'endofline' option. + See |eol-and-eof| for example settings. *'foldclose'* *'fcl'* 'foldclose' 'fcl' string (default "") @@ -2722,6 +2741,11 @@ A jump table for the options with a short description can be found at |Q_op|. When the expression evaluates to non-zero Vim will fall back to using the internal format mechanism. + If the expression starts with s: or |<SID>|, then it is replaced with + the script ID (|local-function|). Example: > + set formatexpr=s:MyFormatExpr() + set formatexpr=<SID>SomeFormatExpr() +< The expression will be evaluated in the |sandbox| when set from a modeline, see |sandbox-option|. That stops the option from working, since changing the buffer text is not allowed. @@ -3335,13 +3359,22 @@ A jump table for the options with a short description can be found at |Q_op|. local to buffer Expression to be used to transform the string found with the 'include' option to a file name. Mostly useful to change "." to "/" for Java: > - :set includeexpr=substitute(v:fname,'\\.','/','g') + :setlocal includeexpr=substitute(v:fname,'\\.','/','g') < The "v:fname" variable will be set to the file name that was detected. - + Note the double backslash: the `:set` command first halves them, then + one remains in the value, where "\." matches a dot literally. For + simple character replacements `tr()` avoids the need for escaping: > + :setlocal includeexpr=tr(v:fname,'.','/') +< Also used for the |gf| command if an unmodified file name can't be found. Allows doing "gf" on the name after an 'include' statement. Also used for |<cfile>|. + If the expression starts with s: or |<SID>|, then it is replaced with + the script ID (|local-function|). Example: > + set includeexpr=s:MyIncludeExpr(v:fname) + set includeexpr=<SID>SomeIncludeExpr(v:fname) +< The expression will be evaluated in the |sandbox| when set from a modeline, see |sandbox-option|. This option cannot be set in a modeline when 'modelineexpr' is off. @@ -3392,11 +3425,16 @@ A jump table for the options with a short description can be found at |Q_op|. in Insert mode as specified with the 'indentkeys' option. When this option is not empty, it overrules the 'cindent' and 'smartindent' indenting. When 'lisp' is set, this option is - overridden by the Lisp indentation algorithm. + is only used when 'lispoptions' contains "expr:1". When 'paste' is set this option is not used for indenting. The expression is evaluated with |v:lnum| set to the line number for which the indent is to be computed. The cursor is also in this line when the expression is evaluated (but it may be moved around). + If the expression starts with s: or |<SID>|, then it is replaced with + the script ID (|local-function|). Example: > + set indentexpr=s:MyIndentExpr() + set indentexpr=<SID>SomeIndentExpr() +< The expression must return the number of spaces worth of indent. It can return "-1" to keep the current indent (this means 'autoindent' is used for the indent). @@ -3759,6 +3797,17 @@ A jump table for the options with a short description can be found at |Q_op|. calling an external program if 'equalprg' is empty. This option is not used when 'paste' is set. + *'lispoptions'* *'lop'* +'lispoptions' 'lop' string (default "") + local to buffer + Comma-separated list of items that influence the Lisp indenting when + enabled with the |'lisp'| option. Currently only one item is + supported: + expr:1 use 'indentexpr' for Lisp indenting when it is set + expr:0 do not use 'indentexpr' for Lisp indenting (default) + Note that when using 'indentexpr' the `=` operator indents all the + lines, otherwise the first line is not indented (Vi-compatible). + *'lispwords'* *'lw'* 'lispwords' 'lw' string (default is very long) global or local to buffer |global-local| @@ -3797,21 +3846,21 @@ A jump table for the options with a short description can be found at |Q_op|. The third character is optional. tab:xy The 'x' is always used, then 'y' as many times as will - fit. Thus "tab:>-" displays: + fit. Thus "tab:>-" displays: > > >- >-- etc. - +< tab:xyz The 'z' is always used, then 'x' is prepended, and then 'y' is used as many times as will fit. Thus - "tab:<->" displays: + "tab:<->" displays: > > <> <-> <--> etc. - +< When "tab:" is omitted, a tab is shown as ^I. *lcs-space* space:c Character to show for a space. When omitted, spaces @@ -3823,22 +3872,25 @@ A jump table for the options with a short description can be found at |Q_op|. setting, except for single spaces. When omitted, the "space" setting is used. For example, `:set listchars=multispace:---+` shows ten consecutive - spaces as: - ---+---+-- ~ + spaces as: > + ---+---+-- +< *lcs-lead* lead:c Character to show for leading spaces. When omitted, leading spaces are blank. Overrides the "space" and "multispace" settings for leading spaces. You can combine it with "tab:", for example: > :set listchars+=tab:>-,lead:. -< *lcs-leadmultispace* +< + *lcs-leadmultispace* leadmultispace:c... Like the |lcs-multispace| value, but for leading spaces only. Also overrides |lcs-lead| for leading multiple spaces. `:set listchars=leadmultispace:---+` shows ten - consecutive leading spaces as: - ---+---+--XXX ~ + consecutive leading spaces as: > + ---+---+--XXX +< Where "XXX" denotes the first non-blank characters in the line. *lcs-trail* @@ -4077,7 +4129,8 @@ A jump table for the options with a short description can be found at |Q_op|. checked for set commands. If 'modeline' is off or 'modelines' is zero no lines are checked. See |modeline|. - *'modifiable'* *'ma'* *'nomodifiable'* *'noma'* *E21* + *'modifiable'* *'ma'* *'nomodifiable'* *'noma'* + *E21* 'modifiable' 'ma' boolean (default on) local to buffer When off the buffer contents cannot be changed. The 'fileformat' and @@ -4310,7 +4363,7 @@ A jump table for the options with a short description can be found at |Q_op|. w x updown up-down sizing arrows w x leftright left-right sizing arrows w x busy The system's usual busy pointer - w x no The system's usual 'no input' pointer + w x no The system's usual "no input" pointer x udsizing indicates up-down resizing x lrsizing indicates left-right resizing x crosshair like a big thin + @@ -4387,12 +4440,12 @@ A jump table for the options with a short description can be found at |Q_op|. 'nonu' 'nu' 'nonu' 'nu' 'nornu' 'nornu' 'rnu' 'rnu' - +> |apple | 1 apple | 2 apple | 2 apple |pear | 2 pear | 1 pear | 1 pear |nobody | 3 nobody | 0 nobody |3 nobody |there | 4 there | 1 there | 1 there - +< *'numberwidth'* *'nuw'* 'numberwidth' 'nuw' number (default: 4) local to window @@ -4413,7 +4466,9 @@ A jump table for the options with a short description can be found at |Q_op|. This option specifies a function to be used for Insert mode omni completion with CTRL-X CTRL-O. |i_CTRL-X_CTRL-O| See |complete-functions| for an explanation of how the function is - invoked and what it should return. + invoked and what it should return. The value can be the name of a + function, a |lambda| or a |Funcref|. See |option-value-function| for + more information. This option is usually set by a filetype plugin: |:filetype-plugin-on| This option cannot be set from a |modeline| or in the |sandbox|, for @@ -4576,7 +4631,7 @@ A jump table for the options with a short description can be found at |Q_op|. < - A directory name may end in a ':' or '/'. - Environment variables are expanded |:set_env|. - When using |netrw.vim| URLs can be used. For example, adding - "http://www.vim.org" will make ":find index.html" work. + "https://www.vim.org" will make ":find index.html" work. - Search upwards and downwards in a directory tree using "*", "**" and ";". See |file-searching| for info and syntax. - Careful with '\' characters, type two to get one in the option: > @@ -4632,58 +4687,6 @@ A jump table for the options with a short description can be found at |Q_op|. set. It's normally not set directly, but by using one of the commands |:ptag|, |:pedit|, etc. - *'printdevice'* *'pdev'* -'printdevice' 'pdev' string (default empty) - global - The name of the printer to be used for |:hardcopy|. - See |pdev-option|. - This option cannot be set from a |modeline| or in the |sandbox|, for - security reasons. - - *'printencoding'* *'penc'* -'printencoding' 'penc' string (default empty, except for some systems) - global - Sets the character encoding used when printing. - See |penc-option|. - - *'printexpr'* *'pexpr'* -'printexpr' 'pexpr' string (default: see below) - global - Expression used to print the PostScript produced with |:hardcopy|. - See |pexpr-option|. - This option cannot be set from a |modeline| or in the |sandbox|, for - security reasons. - - *'printfont'* *'pfn'* -'printfont' 'pfn' string (default "courier") - global - The name of the font that will be used for |:hardcopy|. - See |pfn-option|. - - *'printheader'* *'pheader'* -'printheader' 'pheader' string (default "%<%f%h%m%=Page %N") - global - The format of the header produced in |:hardcopy| output. - See |pheader-option|. - - *'printmbcharset'* *'pmbcs'* -'printmbcharset' 'pmbcs' string (default "") - global - The CJK character set to be used for CJK output from |:hardcopy|. - See |pmbcs-option|. - - *'printmbfont'* *'pmbfn'* -'printmbfont' 'pmbfn' string (default "") - global - List of font names to be used for CJK output from |:hardcopy|. - See |pmbfn-option|. - - *'printoptions'* *'popt'* -'printoptions' 'popt' string (default "") - global - List of items that control the format of the output of |:hardcopy|. - See |popt-option|. - *'pumblend'* *'pb'* 'pumblend' 'pb' number (default 0) global @@ -4941,8 +4944,7 @@ A jump table for the options with a short description can be found at |Q_op|. $XDG_CONFIG_HOME/nvim/after") global List of directories to be searched for these runtime files: - filetype.vim filetypes by file name |new-filetype| - scripts.vim filetypes by file contents |new-filetype-scripts| + filetype.lua filetypes |new-filetype| autoload/ automatically loaded scripts |autoload-functions| colors/ color scheme files |:colorscheme| compiler/ compiler files |:compiler| @@ -4956,7 +4958,6 @@ A jump table for the options with a short description can be found at |Q_op|. pack/ packages |:packadd| parser/ |treesitter| syntax parsers plugin/ plugin scripts |write-plugin| - print/ files for printing |postscript-print-encoding| query/ |treesitter| queries rplugin/ |remote-plugin| scripts spell/ spell checking files |spell| @@ -5103,19 +5104,6 @@ A jump table for the options with a short description can be found at |Q_op|. two letters (See |object-motions|). The default makes a section start at the nroff macros ".SH", ".NH", ".H", ".HU", ".nh" and ".sh". - *'secure'* *'nosecure'* *E523* -'secure' boolean (default off) - global - When on, ":autocmd", shell and write commands are not allowed in - ".nvimrc" and ".exrc" in the current directory and map commands are - displayed. Switch it off only if you know that you will not run into - problems, or when the 'exrc' option is off. On Unix this option is - only used if the ".nvimrc" or ".exrc" is not owned by you. This can be - dangerous if the systems allows users to do a "chown". You better set - 'secure' at the end of your |init.vim| then. - This option cannot be set from a |modeline| or in the |sandbox|, for - security reasons. - *'selection'* *'sel'* 'selection' 'sel' string (default "inclusive") global @@ -5528,42 +5516,48 @@ A jump table for the options with a short description can be found at |Q_op|. messages, for example with CTRL-G, and to avoid some other messages. It is a list of flags: 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, 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]" - w use "[w]" instead of "written" for file write message + f use "(3 of 5)" instead of "(file 3 of 5)" *shm-f* + i use "[noeol]" instead of "[Incomplete last line]" *shm-i* + l use "999L, 888B" instead of "999 lines, 888 bytes" *shm-l* + m use "[+]" instead of "[Modified]" *shm-m* + n use "[New]" instead of "[New File]" *shm-n* + r use "[RO]" instead of "[readonly]" *shm-r* + w use "[w]" instead of "written" for file write message *shm-w* and "[a]" instead of "appended" for ':w >> file' command - x use "[dos]" instead of "[dos format]", "[unix]" instead of - "[unix format]" and "[mac]" instead of "[mac format]". - a all of the above abbreviations - - o overwrite message for writing a file with subsequent message - for reading a file (useful for ":wn" or when 'autowrite' on) - O message for reading a file overwrites any previous message. - Also for quickfix message (e.g., ":cn"). - s don't give "search hit BOTTOM, continuing at TOP" or "search - hit TOP, continuing at BOTTOM" messages; when using the search - count do not show "W" after the count message (see S below) - t truncate file message at the start if it is too long to fit - on the command-line, "<" will appear in the left most column. - Ignored in Ex mode. - T truncate other messages in the middle if they are too long to - fit on the command line. "..." will appear in the middle. - Ignored in Ex mode. - W don't give "written" or "[w]" when writing a file - A don't give the "ATTENTION" message when an existing swap file - is found. - I don't give the intro message when starting Vim |:intro|. - c don't give |ins-completion-menu| messages. For example, - "-- XXX completion (YYY)", "match 1 of 2", "The only match", - "Pattern not found", "Back at original", etc. - q use "recording" instead of "recording @a" - F don't give the file info when editing a file, like `:silent` - was used for the command - S do not show search count message when searching, e.g. + x use "[dos]" instead of "[dos format]", "[unix]" *shm-x* + instead of "[unix format]" and "[mac]" instead of "[mac + format]" + a all of the above abbreviations *shm-a* + + o overwrite message for writing a file with subsequent *shm-o* + message for reading a file (useful for ":wn" or when + 'autowrite' on) + O message for reading a file overwrites any previous *shm-O* + message; also for quickfix message (e.g., ":cn") + s don't give "search hit BOTTOM, continuing at TOP" or *shm-s* + "search hit TOP, continuing at BOTTOM" messages; when using + the search count do not show "W" after the count message (see + S below) + t truncate file message at the start if it is too long *shm-t* + to fit on the command-line, "<" will appear in the left most + column; ignored in Ex mode + T truncate other messages in the middle if they are too *shm-T* + long to fit on the command line; "..." will appear in the + middle; ignored in Ex mode + W don't give "written" or "[w]" when writing a file *shm-W* + A don't give the "ATTENTION" message when an existing *shm-A* + swap file is found + I don't give the intro message when starting Vim, *shm-I* + see |:intro| + c don't give |ins-completion-menu| messages; for *shm-c* + example, "-- XXX completion (YYY)", "match 1 of 2", "The only + match", "Pattern not found", "Back at original", etc. + C don't give messages while scanning for ins-completion *shm-C* + items, for instance "scanning tags" + q use "recording" instead of "recording @a" *shm-q* + F don't give the file info when editing a file, like *shm-F* + `:silent` was used for the command + S do not show search count message when searching, e.g. *shm-S* "[1/5]" This gives you the opportunity to avoid that a change between buffers @@ -5600,7 +5594,6 @@ A jump table for the options with a short description can be found at |Q_op|. global Show (partial) command in the last line of the screen. Set this option off if your terminal is slow. - The option has no effect when 'cmdheight' is zero. In Visual mode the size of the selected area is shown: - When selecting characters within a line, the number of characters. If the number of bytes is different it is also displayed: "2-6" @@ -5608,6 +5601,23 @@ A jump table for the options with a short description can be found at |Q_op|. - When selecting more than one line, the number of lines. - When selecting a block, the size in screen characters: {lines}x{columns}. + This information can be displayed in an alternative location using the + 'showcmdloc' option, useful when 'cmdheight' is 0. + + *'showcmdloc'* *'sloc'* +'showcmdloc' 'sloc' string (default "last") + global + This option can be used to display the (partially) entered command in + another location. Possible values are: + last Last line of the screen (default). + statusline Status line of the current window. + tabline First line of the screen if 'showtabline' is enabled. + Setting this option to "statusline" or "tabline" means that these will + be redrawn whenever the command changes, which can be on every key + pressed. + The %S 'statusline' item can be used in 'statusline' or 'tabline' to + place the text. Without a custom 'statusline' or 'tabline' it will be + displayed in a convenient location. *'showfulltag'* *'sft'* *'noshowfulltag'* *'nosft'* 'showfulltag' 'sft' boolean (default off) @@ -5713,7 +5723,7 @@ A jump table for the options with a short description can be found at |Q_op|. "number" display signs in the 'number' column. If the number column is not present, then behaves like "auto". - Note regarding 'orphaned signs': with signcolumn numbers higher than + 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 appearance not appear weird @@ -5741,11 +5751,11 @@ A jump table for the options with a short description can be found at |Q_op|. alternative. Normally 'autoindent' should also be on when using 'smartindent'. An indent is automatically inserted: - - After a line ending in '{'. + - After a line ending in "{". - After a line starting with a keyword from 'cinwords'. - - Before a line starting with '}' (only with the "O" command). + - Before a line starting with "}" (only with the "O" command). When typing '}' as the first character in a new line, that line is - given the same indent as the matching '{'. + given the same indent as the matching "{". When typing '#' as the first character in a new line, the indent for that line is removed, the '#' is put in the first column. The indent is restored for the next line. If you don't want this, use this @@ -5976,7 +5986,7 @@ A jump table for the options with a short description can be found at |Q_op|. For the "screen" and "topline" values, the cursor position will be changed when necessary. In this case, the jumplist will be populated with the previous cursor position. For "screen", the text cannot always - be kept on the same screen line when 'wrap' is enabled. + be kept on the same screen line when 'wrap' is enabled. *'splitright'* *'spr'* *'nosplitright'* *'nospr'* 'splitright' 'spr' boolean (default off) @@ -5997,6 +6007,60 @@ A jump table for the options with a short description can be found at |Q_op|. In case of buffer changing commands the cursor is placed at the column where it was the last time the buffer was edited. + *'statuscolumn'* *'stc'* +'statuscolumn' 'stc' string (default: empty) + local to window + EXPERIMENTAL + When non-empty, this option determines the content of the area to the + side of a window, normally containing the fold, sign and number columns. + The format of this option is like that of 'statusline'. + + Some of the items from the 'statusline' format are different for + 'statuscolumn': + + %l line number of currently drawn line + %r relative line number of currently drawn line + %s sign column for currently drawn line + %C fold column for currently drawn line + + NOTE: To draw the sign and fold columns, their items must be included in + 'statuscolumn'. Even when they are not included, the status column width + will adapt to the 'signcolumn' and 'foldcolumn' width. + + The |v:lnum| variable holds the line number to be drawn. + The |v:relnum| variable holds the relative line number to be drawn. + The |v:virtnum| variable is negative when drawing virtual lines, zero + when drawing the actual buffer line, and positive when + drawing the wrapped part of a buffer line. + + NOTE: The %@ click execute function item is supported as well but the + specified function will be the same for each row in the same column. + It cannot be switched out through a dynamic 'statuscolumn' format, the + handler should be written with this in mind. + + Examples: >vim + " Relative number with bar separator and click handlers: + :set statuscolumn=%@SignCb@%s%=%T%@NumCb@%r│%T + + " Right aligned relative cursor line number: + :let &stc='%=%{v:relnum?v:relnum:v:lnum} ' + + " Line numbers in hexadecimal for non wrapped part of lines: + :let &stc='%=%{v:virtnum>0?"":printf("%x",v:lnum)} ' + + " Human readable line numbers with thousands separator: + :let &stc='%{substitute(v:lnum,"\\d\\zs\\ze\\' + . '%(\\d\\d\\d\\)\\+$",",","g")}' + + " Both relative and absolute line numbers with different + " highlighting for odd and even relative numbers: + :let &stc='%#NonText#%{&nu?v:lnum:""}' . + '%=%{&rnu&&(v:lnum%2)?"\ ".v:relnum:""}' . + '%#LineNr#%{&rnu&&!(v:lnum%2)?"\ ".v:relnum:""}' + +< WARNING: this expression is evaluated for each screen line so defining + an expensive expression can negatively affect render performance. + *'statusline'* *'stl'* *E540* *E542* 'statusline' 'stl' string (default empty) global or local to window |global-local| @@ -6021,6 +6085,8 @@ A jump table for the options with a short description can be found at |Q_op|. When there is error while evaluating the option then it will be made empty to avoid further errors. Otherwise screen updating would loop. + When the result contains unprintable characters the result is + unpredictable. Note that the only effect of 'ruler' when this option is set (and 'laststatus' is 2 or 3) is controlling the output of |CTRL-G|. @@ -6028,12 +6094,12 @@ A jump table for the options with a short description can be found at |Q_op|. field meaning ~ - Left justify the item. The default is right justified when minwid is larger than the length of the item. - 0 Leading zeroes in numeric items. Overridden by '-'. - minwid Minimum width of the item, padding as set by '-' & '0'. + 0 Leading zeroes in numeric items. Overridden by "-". + minwid Minimum width of the item, padding as set by "-" & "0". Value must be 50 or less. - maxwid Maximum width of the item. Truncation occurs with a '<' + maxwid Maximum width of the item. Truncation occurs with a "<" on the left for text items. Numeric items will be - shifted down to maxwid-2 digits followed by '>'number + shifted down to maxwid-2 digits followed by ">"number where number is the amount of missing digits, much like an exponential notation. item A one letter code as described below. @@ -6069,7 +6135,6 @@ A jump table for the options with a short description can be found at |Q_op|. o N Byte number in file of byte under cursor, first byte is 1. Mnemonic: Offset from start of file (with one added) O N As above, in hexadecimal. - N N Printer page number. (Only works in the 'printheader' option.) l N Line number. L N Number of lines in buffer. c N Column number (byte index). @@ -6079,24 +6144,25 @@ A jump table for the options with a short description can be found at |Q_op|. P S Percentage through file of displayed window. This is like the percentage described for 'ruler'. Always 3 in length, unless translated. + S S 'showcmd' content, see 'showcmdloc'. a S Argument list status as in default title. ({current} of {max}) Empty if the argument file count is zero or one. - { NF Evaluate expression between '%{' and '}' and substitute result. - Note that there is no '%' before the closing '}'. The - expression cannot contain a '}' character, call a function to + { NF Evaluate expression between "%{" and "}" and substitute result. + Note that there is no "%" before the closing "}". The + expression cannot contain a "}" character, call a function to work around that. See |stl-%{| below. - {% - This is almost same as { except the result of the expression is + `{%` - This is almost same as "{" except the result of the expression is re-evaluated as a statusline format string. Thus if the - return value of expr contains % items they will get expanded. - The expression can contain the } character, the end of - expression is denoted by %}. + return value of expr contains "%" items they will get expanded. + The expression can contain the "}" character, the end of + expression is denoted by "%}". For example: > func! Stl_filename() abort return "%t" endfunc < `stl=%{Stl_filename()}` results in `"%t"` `stl=%{%Stl_filename()%}` results in `"Name of current file"` - %} - End of `{%` expression + %} - End of "{%" expression ( - Start of item group. Can be used for setting the width and alignment of a section. Must be followed by %) somewhere. ) - End of item group. No width fields allowed. @@ -6291,12 +6357,12 @@ A jump table for the options with a short description can be found at |Q_op|. Otherwise this option does not always reflect the current syntax (the b:current_syntax variable does). This option is most useful in a modeline, for a file which syntax is - not automatically recognized. Example, in an IDL file: - /* vim: set syntax=idl : */ ~ - When a dot appears in the value then this separates two filetype - names. Example: - /* vim: set syntax=c.doxygen : */ ~ - This will use the "c" syntax first, then the "doxygen" syntax. + not automatically recognized. Example, in an IDL file: > + /* vim: set syntax=idl : */ +< When a dot appears in the value then this separates two filetype + names. Example: > + /* vim: set syntax=c.doxygen : */ +< This will use the "c" syntax first, then the "doxygen" syntax. Note that the second one must be prepared to be loaded as an addition, otherwise it will be skipped. More than one dot may appear. To switch off syntax highlighting for the current file, use: > @@ -6349,7 +6415,7 @@ A jump table for the options with a short description can be found at |Q_op|. 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. The value must be more than 0 and less than 10000. There are four main ways to use tabs in Vim: @@ -6442,7 +6508,9 @@ A jump table for the options with a short description can be found at |Q_op|. This option specifies a function to be used to perform tag searches. The function gets the tag pattern and should return a List of matching tags. See |tag-function| for an explanation of how to write the - function and an example. + function and an example. The value can be the name of a function, a + |lambda| or a |Funcref|. See |option-value-function| for more + information. *'taglength'* *'tl'* 'taglength' 'tl' number (default 0) @@ -6565,6 +6633,8 @@ A jump table for the options with a short description can be found at |Q_op|. global or local to buffer |global-local| This option specifies a function to be used for thesaurus completion with CTRL-X CTRL-T. |i_CTRL-X_CTRL-T| See |compl-thesaurusfunc|. + The value can be the name of a function, a |lambda| or a |Funcref|. + See |option-value-function| for more information. This option cannot be set from a |modeline| or in the |sandbox|, for security reasons. @@ -7067,6 +7137,14 @@ A jump table for the options with a short description can be found at |Q_op|. global A list of words that change how |cmdline-completion| is done. The following values are supported: + fuzzy Use |fuzzy-matching| to find completion matches. When + this value is specified, wildcard expansion will not + be used for completion. The matches will be sorted by + the "best match" rather than alphabetically sorted. + This will find more matches than the wildcard + expansion. Currently fuzzy matching based completion + is not supported for file and directory names and + instead wildcard expansion is used. pum Display the completion matches using the popup menu in the same style as the |ins-completion-menu|. tagfile When using CTRL-D to list matching tags, the kind of diff --git a/runtime/doc/pattern.txt b/runtime/doc/pattern.txt index 371a210847..5357aaa3f1 100644 --- a/runtime/doc/pattern.txt +++ b/runtime/doc/pattern.txt @@ -372,7 +372,7 @@ Vim includes two regexp engines: 1. An old, backtracking engine that supports everything. 2. A new, NFA engine that works much faster on some patterns, possibly slower on some patterns. - + *E1281* Vim will automatically select the right engine for you. However, if you run into a problem or want to specifically select one engine or the other, you can prepend one of the following to the pattern: diff --git a/runtime/doc/pi_health.txt b/runtime/doc/pi_health.txt index 8a6437fdc8..5ba5d1beef 100644 --- a/runtime/doc/pi_health.txt +++ b/runtime/doc/pi_health.txt @@ -21,8 +21,8 @@ Plugin authors are encouraged to write new healthchecks. |health-dev| ============================================================================== Commands *health-commands* - *:checkhealth* *:CheckHealth* -:checkhealth Run all healthchecks. + *:che* *:checkhealth* *:CheckHealth* +:che[ckhealth] Run all healthchecks. *E5009* Nvim depends on |$VIMRUNTIME|, 'runtimepath' and 'packpath' to find the standard "runtime files" for syntax highlighting, @@ -30,7 +30,7 @@ Commands *health-commands* :checkhealth). If the runtime files cannot be found then those features will not work. -:checkhealth {plugins} +:che[ckhealth] {plugins} Run healthcheck(s) for one or more plugins. E.g. to run only the standard Nvim healthcheck: > :checkhealth nvim diff --git a/runtime/doc/pi_netrw.txt b/runtime/doc/pi_netrw.txt index 972c42107c..5167b4baf7 100644 --- a/runtime/doc/pi_netrw.txt +++ b/runtime/doc/pi_netrw.txt @@ -2419,15 +2419,6 @@ from the current window (where one does the mf) to the target. Associated setting variable: |g:netrw_localmovecmd| |g:netrw_ssh_cmd| -MARKED FILES: PRINTING *netrw-mp* {{{2 - (See |netrw-mf| and |netrw-mr| for how to mark files) - (uses the local marked file list) - -When "mp" is used, netrw will apply the |:hardcopy| command to marked files. -What netrw does is open each file in a one-line window, execute hardcopy, then -close the one-line window. - - MARKED FILES: SOURCING *netrw-ms* {{{2 (See |netrw-mf| and |netrw-mr| for how to mark files) (uses the local marked file list) @@ -2821,12 +2812,12 @@ your browsing preferences. (see also: |netrw-settings|) function 'netrw_gitignore#Hide() automatically hiding all gitignored files. For more details see |netrw-gitignore|. + default: "" - Examples: - let g:netrw_list_hide= '.*\.swp$' - let g:netrw_list_hide= netrw_gitignore#Hide() .. '.*\.swp$' - default: "" - + Examples: > + let g:netrw_list_hide= '.*\.swp$' + let g:netrw_list_hide= netrw_gitignore#Hide() .. '.*\.swp$' +< *g:netrw_localcopycmd* ="cp" Linux/Unix/MacOS/Cygwin =expand("$COMSPEC") Windows Copies marked files (|netrw-mf|) to target @@ -3268,7 +3259,7 @@ If there are marked files: (see |netrw-mf|) mr [query: reply with *.c] R [query: reply with s/^\(.*\)\.c$/\1.cpp/] < - This example will mark all *.c files and then rename them to *.cpp + This example will mark all "*.c" files and then rename them to "*.cpp" files. Netrw will protect you from overwriting local files without confirmation, but not remote ones. @@ -3280,7 +3271,7 @@ If there are marked files: (see |netrw-mf|) <c-x><c-x> : a pair of contiguous ctrl-x's tells netrw to ignore any portion of the string preceding the double ctrl-x's. < - WARNING:~ + WARNING: ~ Note that moving files is a dangerous operation; copies are safer. That's because a "move" for remote files is actually a copy + delete -- and if @@ -3776,9 +3767,9 @@ Example: Clear netrw's marked file list via a mapping on gu > < *netrw-P22* P22. I get an error message when I try to copy or move a file: {{{2 - +> **error** (netrw) tried using g:netrw_localcopycmd<cp>; it doesn't work! - +< What's wrong? Netrw uses several system level commands to do things (see @@ -4001,9 +3992,9 @@ netrw: Nov 22, 2016 * (glacambre) reported that files containing spaces weren't being obtained properly via scp. Fix: apparently using single quotes - such as with 'file name' wasn't enough; the + such as with "file name" wasn't enough; the spaces inside the quotes also had to be - escaped (ie. 'file\ name'). + escaped (ie. "file\ name"). * Also fixed obtain (|netrw-O|) to be able to obtain files with spaces in their names Dec 20, 2016 * (xc1427) Reported that using "I" (|netrw-I|) diff --git a/runtime/doc/pi_spec.txt b/runtime/doc/pi_spec.txt index 6d45a0f064..d485d6ad49 100644 --- a/runtime/doc/pi_spec.txt +++ b/runtime/doc/pi_spec.txt @@ -34,8 +34,8 @@ also check if the name, version and release matches. The plugin is smart enough to ask you if it should update the package release, if you have not done so. +------------------------------------------------------------------------------ Setting a map *spec-setting-a-map* -------------- As you should know, you can easily set a map to access any Vim command (or anything, for that matter). If you don't like the default map of @@ -54,8 +54,8 @@ This command will add a map only in the spec file buffers. ============================================================================== 2. Customizing *spec-customizing* +------------------------------------------------------------------------------ The format string *spec_chglog_format* ------------------ You can easily customize how your spec file entry will look like. To do this just set the variable "spec_chglog_format" in your vimrc file like @@ -72,8 +72,8 @@ address once. To discover which format options you can use, take a look at the strftime() function man page. +------------------------------------------------------------------------------ Where to insert new items *spec_chglog_prepend* -------------------------- The plugin will usually insert new %changelog entry items (note that it's not the entry itself) after the existing ones. If you set the @@ -83,8 +83,8 @@ spec_chglog_prepend variable > it will insert new items before the existing ones. +------------------------------------------------------------------------------ Inserting release info *spec_chglog_release_info* ----------------------- If you want, the plugin may automatically insert release information on each changelog entry. One advantage of turning this feature on is diff --git a/runtime/doc/pi_tar.txt b/runtime/doc/pi_tar.txt index c6c0596ea0..2230b82dec 100644 --- a/runtime/doc/pi_tar.txt +++ b/runtime/doc/pi_tar.txt @@ -80,25 +80,25 @@ Copyright 2005-2017: *tar-copyright* These options are variables that one may change, typically in one's <.vimrc> file. - Default - Variable Value Explanation - *g:tar_browseoptions* "Ptf" used to get a list of contents - *g:tar_readoptions* "OPxf" used to extract a file from a tarball - *g:tar_cmd* "tar" the name of the tar program - *g:tar_nomax* 0 if true, file window will not be maximized - *g:tar_secure* undef if exists: + Default + Variable Value Explanation + *g:tar_browseoptions* "Ptf" used to get a list of contents + *g:tar_readoptions* "OPxf" used to extract a file from a tarball + *g:tar_cmd* "tar" the name of the tar program + *g:tar_nomax* 0 if true, file window will not be maximized + *g:tar_secure* undef if exists: "--"s will be used to prevent unwanted option expansion in tar commands. Please be sure that your tar command accepts "--"; Posix compliant tar utilities do accept them. if not exists: - The tar plugin will reject any tar + The tar plugin will reject any tar files or member files that begin with "-" Not all tar's support the "--" which is why it isn't default. - *g:tar_writeoptions* "uf" used to update/replace a file + *g:tar_writeoptions* "uf" used to update/replace a file ============================================================================== diff --git a/runtime/doc/pi_zip.txt b/runtime/doc/pi_zip.txt index 2bbd6eea06..9b531d78b4 100644 --- a/runtime/doc/pi_zip.txt +++ b/runtime/doc/pi_zip.txt @@ -39,7 +39,7 @@ Copyright: Copyright (C) 2005-2015 Charles E Campbell *zip-copyright* OPTIONS~ - *g:zip_nomax* + *g:zip_nomax* If this variable exists and is true, the file window will not be automatically maximized when opened. @@ -54,21 +54,21 @@ Copyright: Copyright (C) 2005-2015 Charles E Campbell *zip-copyright* under Windows ("). If you'd rather have no quotes, simply set g:zip_shq to the empty string (let g:zip_shq= "") in your <.vimrc>. - *g:zip_unzipcmd* + *g:zip_unzipcmd* Use this option to specify the program which does the duty of "unzip". It's used during browsing. By default: > - let g:zip_unzipcmd= "unzip" + let g:zip_unzipcmd= "unzip" < *g:zip_zipcmd* Use this option to specify the program which does the duty of "zip". It's used during the writing (updating) of a file already in a zip file; by default: > - let g:zip_zipcmd= "zip" + let g:zip_zipcmd= "zip" < *g:zip_extractcmd* This option specifies the program (and any options needed) used to extract a file from a zip archive. By default, > - let g:zip_extractcmd= g:zip_unzipcmd + let g:zip_extractcmd= g:zip_unzipcmd < PREVENTING LOADING~ @@ -103,14 +103,14 @@ Copyright: Copyright (C) 2005-2015 Charles E Campbell *zip-copyright* ============================================================================== 4. History *zip-history* {{{1 v32 Oct 22, 2021 * to avoid an issue with a vim 8.2 patch, zipfile: has - been changed to zipfile:// . This often shows up + been changed to zipfile:// . This often shows up as zipfile:/// with zipped files that are root-based. v29 Apr 02, 2017 * (Klartext) reported that an encrypted zip file could - opened but the swapfile held unencrypted contents. + opened but the swapfile held unencrypted contents. The solution is to edit the contents of a zip file using the |:noswapfile| modifier. v28 Oct 08, 2014 * changed the sanity checks for executables to reflect - the command actually to be attempted in zip#Read() + the command actually to be attempted in zip#Read() and zip#Write() * added the extraction of a file capability Nov 30, 2015 * added *.epub to the |g:zipPlugin_ext| list diff --git a/runtime/doc/print.txt b/runtime/doc/print.txt deleted file mode 100644 index 0e02c7d42d..0000000000 --- a/runtime/doc/print.txt +++ /dev/null @@ -1,721 +0,0 @@ -*print.txt* Nvim - - - VIM REFERENCE MANUAL by Bram Moolenaar - - -Printing *printing* - - Type |gO| to see the table of contents. - -============================================================================== -1. Introduction *print-intro* - -On MS-Windows Vim can print your text on any installed printer. On other -systems a PostScript file is produced. This can be directly sent to a -PostScript printer. For other printers a program like ghostscript needs to be -used. - -Note: If you have problems printing with |:hardcopy|, an alternative is to use -|:TOhtml| and print the resulting html file from a browser. - - *:ha* *:hardcopy* *E237* *E238* *E324* -:[range]ha[rdcopy][!] [arguments] - Send [range] lines (default whole file) to the - printer. - - On MS-Windows a dialog is displayed to allow selection - of printer, paper size etc. To skip the dialog, use - the [!]. In this case the printer defined by - 'printdevice' is used, or, if 'printdevice' is empty, - the system default printer. - - For systems other than MS-Windows, PostScript is - written in a temp file and 'printexpr' is used to - actually print it. Then [arguments] can be used by - 'printexpr' through |v:cmdarg|. Otherwise [arguments] - is ignored. 'printoptions' can be used to specify - paper size, duplex, etc. - Note: If you want PDF, there are tools such as - "ps2pdf" that can convert the PostScript to PDF. - -:[range]ha[rdcopy][!] >{filename} - As above, but write the resulting PostScript in file - {filename}. - Things like "%" are expanded |cmdline-special| - Careful: An existing file is silently overwritten. - On MS-Windows use the "print to file" feature of the - printer driver. - -Progress is displayed during printing as a page number and a percentage. To -abort printing use the interrupt key (CTRL-C or, on MS-systems, CTRL-Break). - -Printer output is controlled by the 'printfont' and 'printoptions' options. -'printheader' specifies the format of a page header. - -The printed file is always limited to the selected margins, irrespective of -the current window's 'wrap' or 'linebreak' settings. The "wrap" item in -'printoptions' can be used to switch wrapping off. -The current highlighting colors are used in the printout, with the following -considerations: -1) The normal background is always rendered as white (i.e. blank paper). -2) White text or the default foreground is rendered as black, so that it shows - up! -3) If 'background' is "dark", then the colours are darkened to compensate for - the fact that otherwise they would be too bright to show up clearly on - white paper. - -============================================================================== -2. Print options *print-options* - -Here are the details for the options that change the way printing is done. -For generic info about setting options see |options.txt|. - - *pdev-option* -'printdevice' 'pdev' string (default empty) - global -This defines the name of the printer to be used when the |:hardcopy| command -is issued with a bang (!) to skip the printer selection dialog. On Win32, it -should be the printer name exactly as it appears in the standard printer -dialog. -If the option is empty, then vim will use the system default printer for -":hardcopy!" - - *penc-option* *E620* -'printencoding' 'penc' String (default empty, except for: - Windows: cp1252, - Macintosh: mac-roman, - HPUX: hp-roman8) - global -Sets the character encoding used when printing. This option tells Vim which -print character encoding file from the "print" directory in 'runtimepath' to -use. - -This option will accept any value from |encoding-names|. Any recognized names -are converted to Vim standard names - see 'encoding' for more details. Names -not recognized by Vim will just be converted to lower case and underscores -replaced with '-' signs. - -If 'printencoding' is empty or Vim cannot find the file then it will use -'encoding' (if it is set an 8-bit encoding) to find the print character -encoding file. If Vim is unable to find a character encoding file then it -will use the "latin1" print character encoding file. - -When 'encoding' is set to a multibyte encoding, Vim will try to convert -characters to the printing encoding for printing (if 'printencoding' is empty -then the conversion will be to latin1). If no conversion is possible then -printing will fail. Any characters that cannot be converted will be replaced -with upside down question marks. - -Two print character encoding files are provided to support default Mac and -HPUX character encodings and are used by default on these platforms. Code page -1252 print character encoding is used by default on the Windows platform. - - *pexpr-option* -'printexpr' 'pexpr' String (default: see below) - global -Expression that is evaluated to print the PostScript produced with -|:hardcopy|. -The file name to be printed is in |v:fname_in|. -The arguments to the ":hardcopy" command are in |v:cmdarg|. -The expression must take care of deleting the file after printing it. -When there is an error, the expression must return a non-zero number. -If there is no error, return zero or an empty string. -The default for non MS-Windows systems is to simply use "lpr" to print the -file: > - - system(['lpr'] - + (empty(&printdevice)?[]:['-P', &printdevice]) - + [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) - -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 delete(a:fname) - : return v:shell_error - :endfunc - -Be aware that some print programs return control before they have read the -file. If you delete the file too soon it will not be printed. These programs -usually offer an option to have them remove the file when printing is done. - *E365* -If evaluating the expression fails or it results in a non-zero number, you get -an error message. In that case Vim will delete the file. In the default -value for non-MS-Windows a trick is used: Adding "v:shell_error" will result -in a non-zero number when the system() call fails. - -This option cannot be set from a |modeline| or in the |sandbox|, for security -reasons. - - *pfn-option* *E613* -'printfont' 'pfn' string (default "courier") - global -This is the name of the font that will be used for the |:hardcopy| command's -output. It has the same format as the 'guifont' option, except that only one -font may be named, and the special "guifont=*" syntax is not available. - -In the Win32 GUI version this specifies a font name with its extra attributes, -as with the 'guifont' option. - -For other systems, only ":h11" is recognized, where "11" is the point size of -the font. When omitted, the point size is 10. - - *pheader-option* -'printheader' 'pheader' string (default "%<%f%h%m%=Page %N") - global -This defines the format of the header produced in |:hardcopy| output. The -option is defined in the same way as the 'statusline' option. The same simple -header is used when this option is empty. - - *pmbcs-option* -'printmbcharset' 'pmbcs' string (default "") - global -Sets the CJK character set to be used when generating CJK output from -|:hardcopy|. The following predefined values are currently recognised by Vim: - - Value Description ~ - Chinese GB_2312-80 - (Simplified) GBT_12345-90 - MAC Apple Mac Simplified Chinese - GBT-90_MAC GB/T 12345-90 Apple Mac Simplified - Chinese - GBK GBK (GB 13000.1-93) - ISO10646 ISO 10646-1:1993 - - Chinese CNS_1993 CNS 11643-1993, Planes 1 & 2 - (Traditional) BIG5 - ETEN Big5 with ETen extensions - ISO10646 ISO 10646-1:1993 - - Japanese JIS_C_1978 - JIS_X_1983 - JIS_X_1990 - MSWINDOWS Win3.1/95J (JIS X 1997 + NEC + - IBM extensions) - KANJITALK6 Apple Mac KanjiTalk V6.x - KANJITALK7 Apple Mac KanjiTalk V7.x - - Korean KS_X_1992 - MAC Apple Macintosh Korean - MSWINDOWS KS X 1992 with MS extensions - ISO10646 ISO 10646-1:1993 - -Only certain combinations of the above values and 'printencoding' are -possible. The following tables show the valid combinations: - - euc-cn gbk ucs-2 utf-8 ~ - Chinese GB_2312-80 x - (Simplified) GBT_12345-90 x - MAC x - GBT-90_MAC x - GBK x - ISO10646 x x - - euc-tw big5 ucs-2 utf-8 ~ - Chinese CNS_1993 x - (Traditional) BIG5 x - ETEN x - ISO10646 x x - - euc-jp sjis ucs-2 utf-8 ~ - Japanese JIS_C_1978 x x - JIS_X_1983 x x - JIS_X_1990 x x x - MSWINDOWS x - KANJITALK6 x - KANJITALK7 x - - euc-kr cp949 ucs-2 utf-8 ~ - Korean KS_X_1992 x - MAC x - MSWINDOWS x - ISO10646 x x - -To set up the correct encoding and character set for printing some -Japanese text you would do the following; > - :set printencoding=euc-jp - :set printmbcharset=JIS_X_1983 - -If 'printmbcharset' is not one of the above values then it is assumed to -specify a custom multibyte character set and no check will be made that it is -compatible with the value for 'printencoding'. Vim will look for a file -defining the character set in the "print" directory in 'runtimepath'. - - *pmbfn-option* -'printmbfont' 'pmbfn' string (default "") - global -This is a comma-separated list of fields for font names to be used when -generating CJK output from |:hardcopy|. Each font name has to be preceded -with a letter indicating the style the font is to be used for as follows: - - r:{font-name} font to use for normal characters - b:{font-name} font to use for bold characters - i:{font-name} font to use for italic characters - o:{font-name} font to use for bold-italic characters - -A field with the r: prefix must be specified when doing CJK printing. The -other fontname specifiers are optional. If a specifier is missing then -another font will be used as follows: - - if b: is missing, then use r: - if i: is missing, then use r: - if o: is missing, then use b: - -Some CJK fonts do not contain characters for codes in the ASCII code range. -Also, some characters in the CJK ASCII code ranges differ in a few code points -from traditional ASCII characters. There are two additional fields to control -printing of characters in the ASCII code range. - - c:yes Use Courier font for characters in the ASCII - c:no (default) code range. - - a:yes Use ASCII character set for codes in the ASCII - a:no (default) code range. - -The following is an example of specifying two multibyte fonts, one for normal -and italic printing and one for bold and bold-italic printing, and using -Courier to print codes in the ASCII code range but using the national -character set: > - :set printmbfont=r:WadaMin-Regular,b:WadaMin-Bold,c:yes -< - *popt-option* -'printoptions' 'popt' string (default "") - global -This is a comma-separated list of items that control the format of the output -of |:hardcopy|: - - left:{spec} left margin (default: 10pc) - right:{spec} right margin (default: 5pc) - top:{spec} top margin (default: 5pc) - bottom:{spec} bottom margin (default: 5pc) - {spec} is a number followed by "in" for inches, "pt" - for points (1 point is 1/72 of an inch), "mm" for - millimeters or "pc" for a percentage of the media - size. - Weird example: - left:2in,top:30pt,right:16mm,bottom:3pc - If the unit is not recognized there is no error and - the default value is used. - - header:{nr} Number of lines to reserve for the header. - Only the first line is actually filled, thus when {nr} - is 2 there is one empty line. The header is formatted - according to 'printheader'. - header:0 Do not print a header. - header:2 (default) Use two lines for the header - - syntax:n Do not use syntax highlighting. This is faster and - thus useful when printing large files. - syntax:y Do syntax highlighting. - syntax:a (default) Use syntax highlighting if the printer appears to be - able to print color or grey. - - number:y Include line numbers in the printed output. - number:n (default) No line numbers. - - wrap:y (default) Wrap long lines. - wrap:n Truncate long lines. - - duplex:off Print on one side. - duplex:long (default) Print on both sides (when possible), bind on long - side. - duplex:short Print on both sides (when possible), bind on short - side. - - collate:y (default) Collating: 1 2 3, 1 2 3, 1 2 3 - collate:n No collating: 1 1 1, 2 2 2, 3 3 3 - - jobsplit:n (default) Do all copies in one print job - jobsplit:y Do each copy as a separate print job. Useful when - doing N-up postprocessing. - - portrait:y (default) Orientation is portrait. - portrait:n Orientation is landscape. - *a4* *letter* - paper:A4 (default) Paper size: A4 - paper:{name} Paper size from this table: - {name} size in cm size in inch ~ - 10x14 25.4 x 35.57 10 x 14 - A3 29.7 x 42 11.69 x 16.54 - A4 21 x 29.7 8.27 x 11.69 - A5 14.8 x 21 5.83 x 8.27 - B4 25 x 35.3 10.12 x 14.33 - B5 17.6 x 25 7.17 x 10.12 - executive 18.42 x 26.67 7.25 x 10.5 - folio 21 x 33 8.27 x 13 - ledger 43.13 x 27.96 17 x 11 - legal 21.59 x 35.57 8.5 x 14 - letter 21.59 x 27.96 8.5 x 11 - quarto 21.59 x 27.5 8.5 x 10.83 - statement 13.97 x 21.59 5.5 x 8.5 - tabloid 27.96 x 43.13 11 x 17 - - formfeed:n (default) Treat form feed characters (0x0c) as a normal print - character. - formfeed:y When a form feed character is encountered, continue - printing of the current line at the beginning of the - first line on a new page. - -The item indicated with (default) is used when the item is not present. The -values are not always used, especially when using a dialog to select the -printer and options. -Example: > - :set printoptions=paper:letter,duplex:off - -============================================================================== -3. PostScript Printing *postscript-printing* - *E455* *E456* *E457* *E624* -Provided you have enough disk space there should be no problems generating a -PostScript file. You need to have the runtime files correctly installed (if -you can find the help files, they probably are). - -There are currently a number of limitations with PostScript printing: - -- 'printfont' - The font name is ignored (the Courier family is always used - - it should be available on all PostScript printers) but the font size is - used. - -- 'printoptions' - The duplex setting is used when generating PostScript - output, but it is up to the printer to take notice of the setting. If the - printer does not support duplex printing then it should be silently ignored. - Some printers, however, don't print at all. - -- 8-bit support - While a number of 8-bit print character encodings are - supported it is possible that some characters will not print. Whether a - character will print depends on the font in the printer knowing the - character. Missing characters will be replaced with an upside down question - mark, or a space if that character is also not known by the font. It may be - possible to get all the characters in an encoding to print by installing a - new version of the Courier font family. - -- Multi-byte support - Currently Vim will try to convert multibyte characters - to the 8-bit encoding specified by 'printencoding' (or latin1 if it is - empty). Any characters that are not successfully converted are shown as - unknown characters. Printing will fail if Vim cannot convert the multibyte - to the 8-bit encoding. - -============================================================================== -4. Custom 8-bit Print Character Encodings *postscript-print-encoding* - *E618* *E619* -To use your own print character encoding when printing 8-bit character data -you need to define your own PostScript font encoding vector. Details on how -to define a font encoding vector is beyond the scope of this help file, but -you can find details in the PostScript Language Reference Manual, 3rd Edition, -published by Addison-Wesley and available in PDF form at -http://www.adobe.com/. The following describes what you need to do for Vim to -locate and use your print character encoding. - -i. Decide on a unique name for your encoding vector, one that does not clash - with any of the recognized or standard encoding names that Vim uses (see - |encoding-names| for a list), and that no one else is likely to use. -ii. Copy $VIMRUNTIME/print/latin1.ps to the print subdirectory in your - 'runtimepath' and rename it with your unique name. -iii. Edit your renamed copy of latin1.ps, replacing all occurrences of latin1 - with your unique name (don't forget the line starting %%Title:), and - modify the array of glyph names to define your new encoding vector. The - array must have exactly 256 entries or you will not be able to print! -iv. Within Vim, set 'printencoding' to your unique encoding name and then - print your file. Vim will now use your custom print character encoding. - -Vim will report an error with the resource file if you change the order or -content of the first 3 lines, other than the name of the encoding on the line -starting %%Title: or the version number on the line starting %%Version:. - -[Technical explanation for those that know PostScript - Vim looks for a file -with the same name as the encoding it will use when printing. The file -defines a new PostScript Encoding resource called /VIM-name, where name is the -print character encoding Vim will use.] - -============================================================================== -5. PostScript CJK Printing *postscript-cjk-printing* - *E673* *E674* *E675* - -Vim supports printing of Chinese, Japanese, and Korean files. Setting up Vim -to correctly print CJK files requires setting up a few more options. - -Each of these countries has many standard character sets and encodings which -require that both be specified when printing. In addition, CJK fonts normally -do not have the concept of italic glyphs and use different weight or stroke -style to achieve emphasis when printing. This in turn requires a different -approach to specifying fonts to use when printing. - -The encoding and character set are specified with the 'printencoding' and -'printmbcharset' options. If 'printencoding' is not specified then 'encoding' -is used as normal. If 'printencoding' is specified then characters will be -translated to this encoding for printing. You should ensure that the encoding -is compatible with the character set needed for the file contents or some -characters may not appear when printed. - -The fonts to use for CJK printing are specified with 'printmbfont'. This -option allows you to specify different fonts to use when printing characters -which are syntax highlighted with the font styles normal, italic, bold and -bold-italic. - -No CJK fonts are supplied with Vim. There are some free Korean, Japanese, and -Traditional Chinese fonts available at: - - http://examples.oreilly.com/cjkvinfo/adobe/samples/ - -You can find descriptions of the various fonts in the read me file at - - http://examples.oreilly.de/english_examples/cjkvinfo/adobe/00README - -Please read your printer documentation on how to install new fonts. - -CJK fonts can be large containing several thousand glyphs, and it is not -uncommon to find that they only contain a subset of a national standard. It -is not unusual to find the fonts to not include characters for codes in the -ASCII code range. If you find half-width Roman characters are not appearing -in your printout then you should configure Vim to use the Courier font the -half-width ASCII characters with 'printmbfont'. If your font does not include -other characters then you will need to find another font that does. - -Another issue with ASCII characters, is that the various national character -sets specify a couple of different glyphs in the ASCII code range. If you -print ASCII text using the national character set you may see some unexpected -characters. If you want true ASCII code printing then you need to configure -Vim to output ASCII characters for the ASCII code range with 'printmbfont'. - -It is possible to define your own multibyte character set although this -should not be attempted lightly. A discussion on the process if beyond the -scope of these help files. You can find details on CMap (character map) files -in the document 'Adobe CMap and CIDFont Files Specification, Version 1.0', -available from http://www.adobe.com as a PDF file. - -============================================================================== -6. PostScript Printing Troubleshooting *postscript-print-trouble* - *E621* -Usually the only sign of a problem when printing with PostScript is that your -printout does not appear. If you are lucky you may get a printed page that -tells you the PostScript operator that generated the error that prevented the -print job completing. - -There are a number of possible causes as to why the printing may have failed: - -- Wrong version of the prolog resource file. The prolog resource file - contains some PostScript that Vim needs to be able to print. Each version - of Vim needs one particular version. Make sure you have correctly installed - the runtime files, and don't have any old versions of a file called prolog - in the print directory in your 'runtimepath' directory. - -- Paper size. Some PostScript printers will abort printing a file if they do - not support the requested paper size. By default Vim uses A4 paper. Find - out what size paper your printer normally uses and set the appropriate paper - size with 'printoptions'. If you cannot find the name of the paper used, - measure a sheet and compare it with the table of supported paper sizes listed - for 'printoptions', using the paper that is closest in both width AND height. - Note: The dimensions of actual paper may vary slightly from the ones listed. - If there is no paper listed close enough, then you may want to try psresize - from PSUtils, discussed below. - -- Two-sided printing (duplex). Normally a PostScript printer that does not - support two-sided printing will ignore any request to do it. However, some - printers may abort the job altogether. Try printing with duplex turned off. - Note: Duplex prints can be achieved manually using PS utils - see below. - -- Collated printing. As with Duplex printing, most PostScript printers that - do not support collating printouts will ignore a request to do so. Some may - not. Try printing with collation turned off. - -- Syntax highlighting. Some print management code may prevent the generated - PostScript file from being printed on a black and white printer when syntax - highlighting is turned on, even if solid black is the only color used. Try - printing with syntax highlighting turned off. - -A safe printoptions setting to try is: > - - :set printoptions=paper:A4,duplex:off,collate:n,syntax:n - -Replace "A4" with the paper size that best matches your printer paper. - -============================================================================== -7. PostScript Utilities *postscript-print-util* - -7.1 Ghostscript - -Ghostscript is a PostScript and PDF interpreter that can be used to display -and print on non-PostScript printers PostScript and PDF files. It can also -generate PDF files from PostScript. - -Ghostscript will run on a wide variety of platforms. - -There are three available versions: - -- AFPL Ghostscript (formerly Aladdin Ghostscript) which is free for - non-commercial use. It can be obtained from: - - http://www.cs.wisc.edu/~ghost/ - -- GNU Ghostscript which is available under the GNU General Public License. It - can be obtained from: - - ftp://mirror.cs.wisc.edu/pub/mirrors/ghost/gnu/ - -- A commercial version for inclusion in commercial products. - -Additional information on Ghostscript can also be found at: - - http://www.ghostscript.com/ - -Support for a number of non PostScript printers is provided in the -distribution as standard, but if you cannot find support for your printer -check the Ghostscript site for other printers not included by default. - - -7.2 Ghostscript Previewers. - -The interface to Ghostscript is very primitive so a number of graphical front -ends have been created. These allow easier PostScript file selection, -previewing at different zoom levels, and printing. Check supplied -documentation for full details. - -X11 - -- Ghostview. Obtainable from: - - http://www.cs.wisc.edu/~ghost/gv/ - -- gv. Derived from Ghostview. Obtainable from: - - http://wwwthep.physik.uni-mainz.de/~plass/gv/ - - Copies (possibly not the most recent) can be found at: - - http://www.cs.wisc.edu/~ghost/gv/ - -MS-Windows - -- GSview. Obtainable from: - - http://www.cs.wisc.edu/~ghost/gsview/ - -Linux - -- GSview. Linux version of the popular MS-Windows previewer. - Obtainable from: - - http://www.cs.wisc.edu/~ghost/gsview/ - -- BMV. Different from Ghostview and gv in that it doesn't use X but svgalib. - Obtainable from: - - ftp://sunsite.unc.edu/pub/Linux/apps/graphics/viewers/svga/bmv-1.2.tgz - - -7.3 PSUtils - -PSUtils is a collection of utility programs for manipulating PostScript -documents. Binary distributions are available for many platforms, as well as -the full source. PSUtils can be found at: - - http://knackered.org/angus/psutils - -The utilities of interest include: - -- psnup. Convert PS files for N-up printing. -- psselect. Select page range and order of printing. -- psresize. Change the page size. -- psbook. Reorder and lay out pages ready for making a book. - -The output of one program can be used as the input to the next, allowing for -complex print document creation. - - -N-UP PRINTING - -The psnup utility takes an existing PostScript file generated from Vim and -convert it to an n-up version. The simplest way to create a 2-up printout is -to first create a PostScript file with: > - - :hardcopy > test.ps - -Then on your command line execute: > - - psnup -n 2 test.ps final.ps - -Note: You may get warnings from some Ghostscript previewers for files produced -by psnup - these may safely be ignored. - -Finally print the file final.ps to your PostScript printer with your -platform's print command. (You will need to delete the two PostScript files -afterwards yourself.) 'printexpr' could be modified to perform this extra -step before printing. - - -ALTERNATE DUPLEX PRINTING - -It is possible to achieve a poor man's version of duplex printing using the PS -utility psselect. This utility has options -e and -o for printing just the -even or odd pages of a PS file respectively. - -First generate a PS file with the ":hardcopy" command, then generate new -files with all the odd and even numbered pages with: > - - psselect -o test.ps odd.ps - psselect -e test.ps even.ps - -Next print odd.ps with your platform's normal print command. Then take the -print output, turn it over and place it back in the paper feeder. Now print -even.ps with your platform's print command. All the even pages should now -appear on the back of the odd pages. - -There are a couple of points to bear in mind: - -1. Position of the first page. If the first page is on top of the printout - when printing the odd pages then you need to reverse the order that the odd - pages are printed. This can be done with the -r option to psselect. This - will ensure page 2 is printed on the back of page 1. - Note: it is better to reverse the odd numbered pages rather than the even - numbered in case there are an odd number of pages in the original PS file. - -2. Paper flipping. When turning over the paper with the odd pages printed on - them you may have to either flip them horizontally (along the long edge) or - vertically (along the short edge), as well as possibly rotating them 180 - degrees. All this depends on the printer - it will be more obvious for - desktop ink jets than for small office laser printers where the paper path - is hidden from view. - - -============================================================================== -8. Formfeed Characters *printing-formfeed* - -By default Vim does not do any special processing of formfeed control -characters. Setting the 'printoptions' formfeed item will make Vim recognize -formfeed characters and continue printing the current line at the beginning -of the first line on a new page. The use of formfeed characters provides -rudimentary print control but there are certain things to be aware of. - -Vim will always start printing a line (including a line number if enabled) -containing a formfeed character, even if it is the first character on the -line. This means if a line starting with a formfeed character is the first -line of a page then Vim will print a blank page. - -Since the line number is printed at the start of printing the line containing -the formfeed character, the remainder of the line printed on the new page -will not have a line number printed for it (in the same way as the wrapped -lines of a long line when wrap in 'printoptions' is enabled). - -If the formfeed character is the last character on a line, then printing will -continue on the second line of the new page, not the first. This is due to -Vim processing the end of the line after the formfeed character and moving -down a line to continue printing. - -Due to the points made above it is recommended that when formfeed character -processing is enabled, printing of line numbers is disabled, and that form -feed characters are not the last character on a line. Even then you may need -to adjust the number of lines before a formfeed character to prevent -accidental blank pages. - -============================================================================== - vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt index 99ec84c625..5375d971f0 100644 --- a/runtime/doc/provider.txt +++ b/runtime/doc/provider.txt @@ -36,7 +36,7 @@ itself). For Python 3 plugins: 1. Make sure Python 3.4+ is available in your $PATH. -2. Install the module (try "python" if "python3" is missing): > +2. Install the module (try "python" if "python3" is missing): >bash python3 -m pip install --user --upgrade pynvim The pip `--upgrade` flag ensures that you get the latest version even if @@ -46,7 +46,7 @@ See also |python-virtualenv|. Note: The old "neovim" module was renamed to "pynvim". https://github.com/neovim/neovim/wiki/Following-HEAD#20181118 -If you run into problems, uninstall _both_ then install "pynvim" again: > +If you run into problems, uninstall _both_ then install "pynvim" again: >bash python -m pip uninstall neovim pynvim python -m pip install --user --upgrade pynvim @@ -55,11 +55,11 @@ PYTHON PROVIDER CONFIGURATION ~ *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"). > +check for has("python3"). >vim let g:python3_host_prog = '/path/to/python3' < *g:loaded_python3_provider* -To disable Python 3 support: > +To disable Python 3 support: >vim let g:loaded_python3_provider = 0 @@ -70,13 +70,13 @@ virtualenv for Neovim and hard-code the interpreter path via |g:python3_host_prog| so that the "pynvim" package is not required for each virtualenv. -Example using pyenv: > +Example using pyenv: >bash pyenv install 3.4.4 pyenv virtualenv 3.4.4 py3nvim pyenv activate py3nvim python3 -m pip install pynvim pyenv which python # Note the path -The last command reports the interpreter path, add it to your init.vim: > +The last command reports the interpreter path, add it to your init.vim: >vim let g:python3_host_prog = '/path/to/py3nvim/bin/python' See also: https://github.com/zchee/deoplete-jedi/wiki/Setting-up-Python-for-Neovim @@ -90,7 +90,7 @@ Nvim supports Ruby |remote-plugin|s and the Vim legacy |ruby-vim| interface RUBY QUICKSTART ~ -To use Ruby plugins with Nvim, install the latest "neovim" RubyGem: > +To use Ruby plugins with Nvim, install the latest "neovim" RubyGem: >bash gem install neovim Run |:checkhealth| to see if your system is up-to-date. @@ -98,7 +98,7 @@ Run |:checkhealth| to see if your system is up-to-date. RUBY PROVIDER CONFIGURATION ~ *g:loaded_ruby_provider* -To disable Ruby support: > +To disable Ruby support: >vim let g:loaded_ruby_provider = 0 < *g:ruby_host_prog* @@ -106,10 +106,10 @@ Command to start the Ruby host. By default this is "neovim-ruby-host". With project-local Ruby versions (via tools like RVM or rbenv) setting this can avoid the need to install the "neovim" gem in every project. -To use an absolute path (e.g. to an rbenv installation): > +To use an absolute path (e.g. to an rbenv installation): >vim let g:ruby_host_prog = '~/.rbenv/versions/2.4.1/bin/neovim-ruby-host' -To use the RVM "system" Ruby installation: > +To use the RVM "system" Ruby installation: >vim let g:ruby_host_prog = 'rvm system do neovim-ruby-host' ============================================================================== @@ -125,7 +125,7 @@ Note: Only perl versions from 5.22 onward are supported. PERL QUICKSTART~ -To use perl remote-plugins with Nvim, install the "Neovim::Ext" cpan package: > +To use perl remote-plugins with Nvim, install the "Neovim::Ext" cpan package: >bash cpanm -n Neovim::Ext Run |:checkhealth| to see if your system is up-to-date. @@ -133,12 +133,12 @@ Run |:checkhealth| to see if your system is up-to-date. PERL PROVIDER CONFIGURATION~ *g:loaded_perl_provider* -To disable Perl support: > +To disable Perl support: >vim :let g:loaded_perl_provider = 0 < *g:perl_host_prog* Command to start the Perl executable. Must be set before any -check for has("perl"). > +check for has("perl"). >vim let g:perl_host_prog = '/path/to/perl' < ============================================================================== @@ -150,7 +150,7 @@ https://github.com/neovim/node-client/ NODEJS QUICKSTART~ -To use javascript remote-plugins with Nvim, install the "neovim" npm package: > +To use javascript remote-plugins with Nvim, install the "neovim" npm package: >bash npm install -g neovim Run |:checkhealth| to see if your system is up-to-date. @@ -158,14 +158,14 @@ Run |:checkhealth| to see if your system is up-to-date. NODEJS PROVIDER CONFIGURATION~ *g:loaded_node_provider* -To disable Node.js support: > +To disable Node.js support: >vim :let g:loaded_node_provider = 0 < *g:node_host_prog* Command to start the Node.js host. Setting this makes startup faster. By default, Nvim searches for "neovim-node-host" using "npm root -g", which -can be slow. To avoid this, set g:node_host_prog to the host path: > +can be slow. To avoid this, set g:node_host_prog to the host path: >vim let g:node_host_prog = '/usr/local/bin/neovim-node-host' < ============================================================================== @@ -176,7 +176,7 @@ a |provider| which transparently uses shell commands to communicate with the system clipboard or any other clipboard "backend". To ALWAYS use the clipboard for ALL operations (instead of interacting with -the '+' and/or '*' registers explicitly): > +the "+" and/or "*" registers explicitly): >vim set clipboard+=unnamedplus See 'clipboard' for details and options. @@ -188,17 +188,18 @@ registers. Nvim looks for these clipboard tools, in order of priority: - |g:clipboard| - pbcopy, pbpaste (macOS) - wl-copy, wl-paste (if $WAYLAND_DISPLAY is set) + - waycopy, waypaste (if $WAYLAND_DISPLAY is set) - xclip (if $DISPLAY is set) - xsel (if $DISPLAY is set) - lemonade (for SSH) https://github.com/pocke/lemonade - - doitclient (for SSH) http://www.chiark.greenend.org.uk/~sgtatham/doit/ + - doitclient (for SSH) https://www.chiark.greenend.org.uk/~sgtatham/doit/ - win32yank (Windows) - termux (via termux-clipboard-set, termux-clipboard-set) - tmux (if $TMUX is set) *g:clipboard* To configure a custom clipboard tool, set g:clipboard to a dictionary. -For example this configuration integrates the tmux clipboard: > +For example this configuration integrates the tmux clipboard: >vim let g:clipboard = { \ 'name': 'myClipboard', @@ -218,7 +219,8 @@ the selection until the copy command process dies. When pasting, if the copy process has not died the cached selection is applied. g:clipboard can also use functions (see |lambda|) instead of strings. -For example this configuration uses the g:foo variable as a fake clipboard: > +For example this configuration uses the g:foo variable as a fake clipboard: +>vim let g:clipboard = { \ 'name': 'myClipboard', @@ -238,7 +240,7 @@ a list of lines and `regtype` is a register type conforming to |setreg()|. *clipboard-wsl* For Windows WSL, try this g:clipboard definition: -> +>vim let g:clipboard = { \ 'name': 'WslClipboard', \ 'copy': { @@ -282,7 +284,7 @@ many commands. Use the |cmdline-window| if you really want to paste multiple lines to the cmdline. You can implement a custom paste handler by redefining |vim.paste()|. -Example: > +Example: >lua vim.paste = (function(lines, phase) vim.api.nvim_put(lines, 'c', true, true) diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt index 924a6d4743..b1f7c927cc 100644 --- a/runtime/doc/quickfix.txt +++ b/runtime/doc/quickfix.txt @@ -1259,6 +1259,21 @@ not "b:current_compiler". What the command actually does is the following: For writing a compiler plugin, see |write-compiler-plugin|. +DOTNET *compiler-dotnet* + +The .NET CLI compiler outputs both errors and warnings by default. The output +may be limited to include only errors, by setting the g:dotnet_errors_only +variable to |v:true|. + +The associated project name is included in each error and warning. To suppress +the project name, set the g:dotnet_show_project_file variable to |v:false|. + +Example: limit output to only display errors, and suppress the project name: > + let dotnet_errors_only = v:true + let dotnet_show_project_file = v:false + compiler dotnet +< + GCC *quickfix-gcc* *compiler-gcc* There's one variable you can set for the GCC compiler: @@ -1287,7 +1302,7 @@ PYUNIT COMPILER *compiler-pyunit* This is not actually a compiler, but a unit testing framework for the Python language. It is included into standard Python distribution starting from version 2.0. For older versions, you can get it from -http://pyunit.sourceforge.net. +https://pyunit.sourceforge.net. When you run your tests with the help of the framework, possible errors are parsed by Vim and presented for you in quick-fix mode. @@ -1298,8 +1313,6 @@ Useful values for the 'makeprg' options therefore are: setlocal makeprg=./alltests.py " Run a testsuite setlocal makeprg=python\ %:S " Run a single testcase -Also see http://vim.sourceforge.net/tip_view.php?tip_id=280. - TEX COMPILER *compiler-tex* @@ -1572,8 +1585,9 @@ A call of |:clist| writes them accordingly with their correct filenames: Unlike the other prefixes that all match against whole lines, %P, %Q and %O can be used to match several patterns in the same line. Thus it is possible -to parse even nested files like in the following line: +to parse even nested files like in the following line: > {"file1" {"file2" error1} error2 {"file3" error3 {"file4" error4 error5}}} +< The %O then parses over strings that do not contain any push/pop file name information. See |errorformat-LaTeX| for an extended example. @@ -1823,7 +1837,7 @@ In English, that sed script: it as a "continuation of a multi-line message." *errorformat-ant* -For ant (http://jakarta.apache.org/) the above errorformat has to be modified +For ant (https://jakarta.apache.org/) the above errorformat has to be modified to honour the leading [javac] in front of each javac output line: > :set efm=%A\ %#[javac]\ %f:%l:\ %m,%-Z\ %#[javac]\ %p^,%-C%.%# @@ -1933,9 +1947,9 @@ by Vim. The default format for the lines displayed in the quickfix window and location list window is: - +> <filename>|<lnum> col <col>|<text> - +< The values displayed in each line correspond to the "bufnr", "lnum", "col" and "text" fields returned by the |getqflist()| function. diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index 5b100c73a9..d17df3cd61 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -673,12 +673,6 @@ Short explanation of each option: *option-list* 'confirm' 'cf' ask what to do about unsaved/read-only files 'copyindent' 'ci' make 'autoindent' use existing indent structure 'cpoptions' 'cpo' flags for Vi-compatible behavior -'cscopepathcomp' 'cspc' how many components of the path to show -'cscopeprg' 'csprg' command to execute cscope -'cscopequickfix' 'csqf' use quickfix window for cscope results -'cscoperelative' 'csre' Use cscope.out path basename as prefix -'cscopetag' 'cst' use cscope for tag commands -'cscopetagorder' 'csto' determines ":cstag" search order 'cursorbind' 'crb' move cursor in window as it moves in other windows 'cursorcolumn' 'cuc' highlight the screen column of the cursor 'cursorline' 'cul' highlight the screen line of the cursor @@ -695,6 +689,7 @@ Short explanation of each option: *option-list* 'display' 'dy' list of flags for how to display text 'eadirection' 'ead' in which direction 'equalalways' works 'encoding' 'enc' encoding used internally +'endoffile' 'eof' write CTRL-Z at end of the file 'endofline' 'eol' write <EOL> for last line in file 'equalalways' 'ea' windows are automatically made the same size 'equalprg' 'ep' external program to use for "=" command @@ -703,7 +698,7 @@ Short explanation of each option: *option-list* 'errorformat' 'efm' description of the lines in the error file 'eventignore' 'ei' autocommand events that are ignored 'expandtab' 'et' use spaces when <Tab> is inserted -'exrc' 'ex' read .nvimrc and .exrc in the current directory +'exrc' 'ex' read init files in the current directory 'fileencoding' 'fenc' file encoding for multibyte text 'fileencodings' 'fencs' automatically detected character encodings 'fileformat' 'ff' file format used for file I/O @@ -778,6 +773,7 @@ Short explanation of each option: *option-list* 'lines' number of lines in the display 'linespace' 'lsp' number of pixel lines to use between characters 'lisp' automatic indenting for Lisp +'lispoptions' 'lop' changes how Lisp indenting is done 'lispwords' 'lw' words that change how lisp indenting works 'list' show <Tab> and <EOL> 'listchars' 'lcs' characters for displaying in list mode @@ -804,6 +800,7 @@ Short explanation of each option: *option-list* 'mousefocus' 'mousef' keyboard focus follows the mouse 'mousehide' 'mh' hide mouse pointer while typing 'mousemodel' 'mousem' changes meaning of mouse buttons +'mousemoveevent' 'mousemev' report mouse moves with <MouseMove> 'mousescroll' amount to scroll by when scrolling with a mouse 'mouseshape' 'mouses' shape of the mouse pointer in different modes 'mousetime' 'mouset' max time between mouse double-click @@ -824,15 +821,7 @@ Short explanation of each option: *option-list* 'previewheight' 'pvh' height of the preview window 'previewpopup' 'pvp' use popup window for preview 'previewwindow' 'pvw' identifies the preview window -'printdevice' 'pdev' name of the printer to be used for :hardcopy -'printencoding' 'penc' encoding to be used for printing -'printexpr' 'pexpr' expression used to print PostScript for :hardcopy -'printfont' 'pfn' name of the font to be used for :hardcopy -'printheader' 'pheader' format of the header used for :hardcopy -'printmbcharset' 'pmbcs' CJK character set to be used for :hardcopy -'printmbfont' 'pmbfn' font names to be used for CJK output of :hardcopy -'printoptions' 'popt' controls the format of :hardcopy output -'pumheight' 'ph' maximum height of the popup menu +'pumheight' 'ph' maximum number of items to show in the popup menu 'pumwidth' 'pw' minimum width of the popup menu 'pyxversion' 'pyx' Python version used for pyx* commands 'quoteescape' 'qe' escape characters used in a string @@ -871,7 +860,8 @@ Short explanation of each option: *option-list* 'shiftwidth' 'sw' number of spaces to use for (auto)indent step 'shortmess' 'shm' list of flags, reduce length of messages 'showbreak' 'sbr' string to use at the start of wrapped lines -'showcmd' 'sc' show (partial) command in status line +'showcmd' 'sc' show (partial) command somewhere +'showcmdloc' 'sloc' where to show (partial) command 'showfulltag' 'sft' show full tag pattern when completing tag 'showmatch' 'sm' briefly jump to matching bracket if insert one 'showmode' 'smd' message on status line to show current mode @@ -893,6 +883,7 @@ Short explanation of each option: *option-list* 'splitkeep' 'spk' determines scroll behavior for split windows 'splitright' 'spr' new window is put right of the current one 'startofline' 'sol' commands move cursor to first non-blank in line +'statuscolumn' 'stc' custom format for the status column 'statusline' 'stl' custom format for the status line 'suffixes' 'su' suffixes that are ignored with multiple match 'suffixesadd' 'sua' suffixes added when searching for a file @@ -905,6 +896,7 @@ Short explanation of each option: *option-list* 'tabstop' 'ts' number of spaces that <Tab> in file uses 'tagbsearch' 'tbs' use binary searching in tags files 'tagcase' 'tc' how to handle case when searching in tags files +'tagfunc' 'tfu' function to get list of tag matches 'taglength' 'tl' number of significant characters for a tag 'tagrelative' 'tr' file names in tag file are relative 'tags' 'tag' list of file names used by the tag command diff --git a/runtime/doc/remote.txt b/runtime/doc/remote.txt index 0c1e3438de..4610088ab0 100644 --- a/runtime/doc/remote.txt +++ b/runtime/doc/remote.txt @@ -52,6 +52,10 @@ The following command line arguments are available: *--remote-expr* --remote-expr {expr} Evaluate {expr} in server and print the result on stdout. + *--remote-ui* + --remote-ui Display the UI of the server in the terminal. + Fully interactive: keyboard and mouse input + are forwarded to the server. *--server* --server {addr} Connect to the named pipe or socket at the given address for executing remote commands. diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt index 21945dc499..1bbd20702b 100644 --- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -289,7 +289,7 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|. how this can be useful. This is normally done automatically during startup, - after loading your .vimrc file. With this command it + after loading your |vimrc| file. With this command it can be done earlier. Packages will be loaded only once. Using @@ -540,6 +540,11 @@ You would now have these files under ~/.local/share/nvim/site: On startup after processing your |config|, Nvim scans all directories in 'packpath' for plugins in "pack/*/start/*", then loads the plugins. +To allow for calling into package functionality while parsing your |vimrc|, +|:colorscheme| and |autoload| will both automatically search under 'packpath' +as well in addition to 'runtimepath'. See the documentation for each for +details. + In the example Nvim will find "pack/foo/start/foobar/plugin/foo.vim" and load it. @@ -684,7 +689,7 @@ found automatically. Your package would have these files: < pack/foo/start/lib/autoload/foolib.vim > func foolib#getit() -This works, because start packages will be searchd for autoload files, when +This works, because start packages will be searched for autoload files, when sourcing the plugins. ============================================================================== diff --git a/runtime/doc/rileft.txt b/runtime/doc/rileft.txt index aa11462595..4f68ab562c 100644 --- a/runtime/doc/rileft.txt +++ b/runtime/doc/rileft.txt @@ -12,8 +12,9 @@ These functions were originally created by Avner Lottem: E-mail: alottem@iil.intel.com Phone: +972-4-8307322 +------------------------------------------------------------------------------ Introduction ------------- + Some languages such as Arabic, Farsi, Hebrew (among others) require the ability to display their text from right-to-left. Files in those languages are stored conventionally and the right-to-left requirement is only a @@ -32,8 +33,9 @@ as this kind of support is out of the scope of a simple addition to an existing editor (and it's not sanctioned by Unicode either). +------------------------------------------------------------------------------ Highlights ----------- + o Editing left-to-right files as in the original Vim, no change. o Viewing and editing files in right-to-left windows. File orientation @@ -56,11 +58,11 @@ o Many languages use and require right-to-left support. These languages current supported languages include - |arabic.txt| and |hebrew.txt|. +------------------------------------------------------------------------------ Of Interest... --------------- o Invocations - ----------- + + 'rightleft' ('rl') sets window orientation to right-to-left. + 'delcombine' ('deco'), boolean, if editing UTF-8 encoded languages, allows one to remove a composing character which gets superimposed @@ -69,7 +71,7 @@ o Invocations (such as search) to be utilized in right-to-left orientation as well. o Typing backwards *ins-reverse* - ---------------- + In lieu of using the full-fledged 'rightleft' option, one can opt for reverse insertion. When the 'revins' (reverse insert) option is set, inserting happens backwards. This can be used to type right-to-left @@ -85,15 +87,16 @@ o Typing backwards *ins-reverse* in the status line when reverse Insert mode is active. o Pasting when in a rightleft window - ---------------------------------- + When cutting text with the mouse and pasting it in a rightleft window the text will be reversed, because the characters come from the cut buffer from the left to the right, while inserted in the file from the right to the left. In order to avoid it, toggle 'revins' before pasting. +------------------------------------------------------------------------------ Bugs ----- + o Does not handle CTRL-A and CTRL-X commands (add and subtract) correctly when in rightleft window. diff --git a/runtime/doc/russian.txt b/runtime/doc/russian.txt index a2bc9f3b5e..8d3ed360c8 100644 --- a/runtime/doc/russian.txt +++ b/runtime/doc/russian.txt @@ -45,7 +45,7 @@ If you wish to use messages, help files, menus and other items translated to Russian, you will need to install the RuVim Language Pack, available in different codepages from - http://www.sourceforge.net/projects/ruvim/ + https://www.sourceforge.net/projects/ruvim/ After downloading an archive from RuVim project, unpack it into your $VIMRUNTIME directory. We recommend using UTF-8 archive. diff --git a/runtime/doc/sign.txt b/runtime/doc/sign.txt index a2a5645baa..d09d0f226f 100644 --- a/runtime/doc/sign.txt +++ b/runtime/doc/sign.txt @@ -334,8 +334,10 @@ See |sign_getplaced()| for the equivalent Vim script function. :sign place group=* buffer={nr} List signs in all the groups placed in buffer {nr}. +:sign place List placed signs in the global group in all files. + :sign place group={group} - List placed signs in all sign groups in all the files. + List placed signs with sign group {group} in all files. :sign place group=* List placed signs in all sign groups in all files. @@ -381,15 +383,14 @@ sign_define({list}) icon full path to the bitmap file for the sign. linehl highlight group used for the whole line the sign is placed in. + numhl highlight group used for the line number where + the sign is placed. 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|. If the sign named {name} already exists, then the attributes of the sign are updated. @@ -431,6 +432,8 @@ sign_getdefined([{name}]) *sign_getdefined()* linehl highlight group used for the whole line the sign is placed in; not present if not set. name name of the sign + numhl highlight group used for the line number where + the sign is placed; not present if not set. 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; not @@ -439,9 +442,6 @@ sign_getdefined([{name}]) *sign_getdefined()* 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|; not present if not set. Returns an empty List if there are no signs and when {name} is not found. @@ -606,9 +606,9 @@ sign_placelist({list}) then a new unique identifier is allocated. Otherwise the specified number is used. See |sign-identifier| for more information. - lnum line number in the buffer {buf} where the - sign is to be placed. For the accepted values, - see |line()|. + lnum line number in the buffer where the sign is to + be placed. For the accepted values, see + |line()|. name name of the sign to place. See |sign_define()| for more information. priority priority of the sign. When multiple signs are diff --git a/runtime/doc/spell.txt b/runtime/doc/spell.txt index 15aa0117ec..98a6af1b8b 100644 --- a/runtime/doc/spell.txt +++ b/runtime/doc/spell.txt @@ -482,7 +482,7 @@ You can create a Vim spell file from the .aff and .dic files that Myspell uses. Myspell is used by OpenOffice.org and Mozilla. The OpenOffice .oxt files are zip files which contain the .aff and .dic files. You should be able to find them here: - http://extensions.services.openoffice.org/dictionary + https://extensions.services.openoffice.org/dictionary The older, OpenOffice 2 files may be used if this doesn't work: http://wiki.services.openoffice.org/wiki/Dictionaries You can also use a plain word list. The results are the same, the choice @@ -764,13 +764,13 @@ them before the Vim word list is made. The tools for this can be found in the The format for the affix and word list files is based on what Myspell uses (the spell checker of Mozilla and OpenOffice.org). A description can be found here: - http://lingucomponent.openoffice.org/affix.readme ~ + https://lingucomponent.openoffice.org/affix.readme ~ Note that affixes are case sensitive, this isn't obvious from the description. Vim supports quite a few extras. They are described below |spell-affix-vim|. Attempts have been made to keep this compatible with other spell checkers, so that the same files can often be used. One other project that offers more -than Myspell is Hunspell ( http://hunspell.sf.net ). +than Myspell is Hunspell ( https://hunspell.github.io ). WORD LIST FORMAT *spell-dic-format* @@ -886,7 +886,7 @@ right encoding. *spell-AUTHOR* *spell-EMAIL* *spell-COPYRIGHT* NAME Name of the language VERSION 1.0.1 with fixes - HOME http://www.myhome.eu + HOME https://www.example.com AUTHOR John Doe EMAIL john AT Doe DOT net COPYRIGHT LGPL @@ -992,8 +992,8 @@ Note: even when using "num" or "long" the number of flags available to compounding and prefixes is limited to about 250. -AFFIXES - *spell-PFX* *spell-SFX* +AFFIXES *spell-PFX* *spell-SFX* + The usual PFX (prefix) and SFX (suffix) lines are supported (see the Myspell documentation or the Aspell manual: http://aspell.net/man-html/Affix-Compression.html). diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index baa60f431f..179bacdb24 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -9,7 +9,7 @@ Starting Vim *starting* Type |gO| to see the table of contents. ============================================================================== -Nvim arguments *vim-arguments* +Nvim arguments *cli-arguments* Most often, Nvim is started to edit a single file with the command: > @@ -31,8 +31,8 @@ filename One or more file names. The first one will be the current To avoid a file name starting with a '-' being interpreted as an option, precede the arglist with "--", e.g.: > nvim -- -filename -< All arguments after the "--" will be interpreted as file names, - no other options or "+command" argument can follow. +< All arguments after "--" are interpreted as file names, no + other options or "+command" arguments can follow. *--* `-` Alias for stdin (standard input). @@ -143,15 +143,13 @@ argument. these commands, independently from "-c" commands. *-S* --S {file} Vimscript or Lua (".lua") {file} will be |:source|d after the - first file has been read. Equivalent to: > +-S [file] Executes Vimscript or Lua (".lua") [file] after the first file + has been read. See also |:source|. If [file] is not given, + defaults to "Session.vim". Equivalent to: > -c "source {file}" < Can be repeated like "-c", subject to the same limit of 10 "-c" arguments. {file} cannot start with a "-". --S Works like "-S Session.vim". Only when used as the last - argument or when another "-" option follows. - -L *-L* *-r* -r Recovery mode. Without a file name argument, a list of existing swap files is given. With a file name, a swap file @@ -192,8 +190,9 @@ argument. -E reads stdin as text (into buffer 1). -es *-es* *-Es* *-s-ex* *silent-mode* --Es Silent mode (no UI), for scripting. Unrelated to |-s|. - Disables most prompts, messages, warnings and errors. +-Es Script mode, aka "silent mode", aka "batch mode". No UI, + disables most prompts and messages. Unrelated to |-s|. + See also |-S| to run script files. -es reads/executes stdin as Ex commands. > printf "put ='foo'\n%%print\n" | nvim -es @@ -211,10 +210,35 @@ argument. nvim -es +":verbose echo 'foo'" nvim -V1 -es +foo -< User |config| is skipped (unless given with |-u|). +< User |config| is skipped unless |-u| was given. Swap file is skipped (like |-n|). User |shada| is loaded (unless "-i NONE" is given). + *-l* +-l {script} [args] + Executes Lua {script} non-interactively (no UI) with optional + [args] after processing any preceding Nvim |cli-arguments|, + then exits. Exits 1 on Lua error. See |-S| to run multiple Lua + scripts without args, with a UI. + *lua-args* + All [args] are treated as {script} arguments and stored in the + Lua `_G.arg` global table, thus "-l" ends processing of Nvim + arguments. The {script} name is stored at `_G.arg[0]`. + + Sets 'verbose' to 1 (like "-V1"), so Lua `print()` writes to + output. + + Arguments before "-l" are processed before executing {script}. + This example quits before executing "foo.lua": > + nvim +q -l foo.lua +< This loads Lua module "bar" before executing "foo.lua": > + nvim +"lua require('bar')" -l foo.lua +< + Skips user |config| unless |-u| was given. + Disables plugins unless 'loadplugins' was set. + Disables |shada| unless |-i| was given. + Disables swapfile (like |-n|). + *-b* -b Binary mode. File I/O will only recognize <NL> to separate lines. The 'expandtab' option will be reset. The 'textwidth' @@ -222,9 +246,6 @@ argument. is set. This is done after reading the |vimrc| but before reading any file in the arglist. See also |edit-binary|. - *-l* --l Lisp mode. Sets the 'lisp' and 'showmatch' options on. - *-A* -A Arabic mode. Sets the 'arabic' option on. @@ -239,10 +260,10 @@ argument. Example: > nvim -V8 --V[N]{filename} - Like -V and set 'verbosefile' to {filename}. Messages are not - displayed; instead they are written to the file {filename}. - {filename} must not start with a digit. +-V[N]{file} + Like -V and sets 'verbosefile' to {file} (must not start with + a digit). Messages are not displayed, instead they are + written to {file}. Example: > nvim -V20vimlog < @@ -401,18 +422,20 @@ accordingly, proceeding as follows: The |-V| argument can be used to display or log what happens next, useful for debugging the initializations. -3. Wait for UI to connect. +3. Start a server (unless |--listen| was given) and set |v:servername|. + +4. Wait for UI to connect. Nvim started with |--embed| waits for the UI to connect before proceeding to load user configuration. -4. Setup |default-mappings| and |default-autocmds|. Create |popup-menu|. +5. Setup |default-mappings| and |default-autocmds|. Create |popup-menu|. -5. Enable filetype and indent plugins. +6. 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, …). +7. 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* @@ -451,24 +474,25 @@ accordingly, proceeding as follows: set or when using $VIMINIT. c. If the 'exrc' option is on (which is NOT the default), the current - directory is searched for two files. The first that exists is used, - the others are ignored. - - The file ".nvimrc" - - The file ".exrc" + directory is searched for the following files, in order of precedence: + - ".nvim.lua" + - ".nvimrc" + - ".exrc" + The first that exists is used, the others are ignored. -7. Enable filetype detection. +8. Enable filetype detection. This does the same as the command: > - :runtime! filetype.lua filetype.vim + :runtime! filetype.lua < Skipped if ":filetype off" was called or if the "-u NONE" command line argument was given. -8. Enable syntax highlighting. +9. 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. -9. Load the plugin scripts. *load-plugins* +10. Load the plugin scripts. *load-plugins* This does the same as the command: > :runtime! plugin/**/*.vim :runtime! plugin/**/*.lua @@ -480,13 +504,13 @@ accordingly, proceeding as follows: However, directories in 'runtimepath' ending in "after" are skipped here and only loaded after packages, see below. Loading plugins won't be done when: - - The 'loadplugins' option was reset in a vimrc file. + - The |'loadplugins'| option was reset in a vimrc file. - The |--noplugin| command line argument is used. - The |--clean| command line argument is used. - The "-u NONE" command line argument is used |-u|. - Note that using "-c 'set noloadplugins'" doesn't work, because the + Note that using `-c 'set noloadplugins'` doesn't work, because the commands from the command line have not been executed yet. You can - use "--cmd 'set noloadplugins'" or "--cmd 'set loadplugins'" |--cmd|. + use `--cmd 'set noloadplugins'` or `--cmd 'set loadplugins'` |--cmd|. Packages are loaded. These are plugins, as above, but found in the "start" directory of each entry in 'packpath'. Every plugin directory @@ -498,21 +522,21 @@ accordingly, proceeding as follows: if packages have been found, but that should not add a directory ending in "after". -10. Set 'shellpipe' and 'shellredir' +11. 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. -11. Set 'updatecount' to zero, if "-n" command argument used +12. Set 'updatecount' to zero, if "-n" command argument used -12. Set binary options if the |-b| flag was given. +13. Set binary options if the |-b| flag was given. -13. Read the |shada-file|. +14. Read the |shada-file|. -14. Read the quickfix file if the |-q| flag was given, or exit on failure. +15. Read the quickfix file if the |-q| flag was given, or exit on failure. -15. Open all windows +16. 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 @@ -522,7 +546,7 @@ accordingly, proceeding as follows: Buffers for all windows will be loaded, without triggering |BufAdd| autocommands. -16. Execute startup commands +17. Execute startup commands If a |-t| flag was given, the tag is jumped to. Commands given with |-c| and |+cmd| are executed. The starting flag is reset, has("vim_starting") will now return zero. diff --git a/runtime/doc/support.txt b/runtime/doc/support.txt new file mode 100644 index 0000000000..481959d8f1 --- /dev/null +++ b/runtime/doc/support.txt @@ -0,0 +1,52 @@ +*support.txt* Nvim + + + NVIM REFERENCE MANUAL + + +Support *support* + + Type |gO| to see the table of contents. + +============================================================================== +Supported platforms *supported-platforms* + +`System` `Tier` `Versions` `Tested versions` +Linux 1 >= 2.6.32, glibc >= 2.12 Ubuntu 22.04 +macOS (Intel) 1 >= 10.15 macOS 12 +Windows 64-bit 1 >= 8 Windows Server 2019 +FreeBSD 1 >= 10 FreeBSD 13 +macOS (M1) 2 >= 10.15 +OpenBSD 2 >= 7 +MinGW 2 MinGW-w64 + +Support types ~ + +* Tier 1: Officially supported and tested with CI. Any contributed patch + MUST NOT break such systems. + +* Tier 2: Officially supported, but not necessarily tested with CI. These + systems are maintained to the best of our ability, without being a top + priority. + +* Tier 3: Not tested and no guarantees, but may work. + +Adding support for a new platform ~ + +IMPORTANT: Before attempting to add support for a new platform please open +an issue about it for discussion. + + +============================================================================== +Common + +Some common notes when adding support for new platforms: + +Cmake is the only supported build system. The platform must be buildable with cmake. + +All functionality related to the new platform must be implemented in its own +file inside `src/nvim/os` unless it's already done in a common file, in which +case adding an `#ifdef` is fine. + + + vim:tw=78:ts=8:et:ft=help:norl: diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 74778addc7..bd5a4f1926 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -464,7 +464,7 @@ Force to omit the line numbers: > Go back to the default to use 'number' by deleting the variable: > :unlet g:html_number_lines < - *g:html_line_ids* + *g:html_line_ids* Default: 1 if |g:html_number_lines| is set, 0 otherwise. When 1, adds an HTML id attribute to each line number, or to an empty <span> inserted for that purpose if no line numbers are shown. This ID attribute @@ -656,6 +656,22 @@ the rendered page generated by 2html.vim. > :let g:html_no_pre = 1 < + *g:html_no_doc* +Default: 0. +When 1 it doesn't generate a full HTML document with a DOCTYPE, <head>, +<body>, etc. If |g:html_use_css| is enabled (the default) you'll have to +define the CSS manually. The |g:html_dynamic_folds| and |g:html_line_ids| +settings (off by default) also insert some JavaScript. + + + *g:html_no_links* +Default: 0. +Don't generate <a> tags for text that looks like an URL. + + *g:html_no_modeline* +Default: 0. +Don't generate a modeline disabling folding. + *g:html_expand_tabs* Default: 0 if 'tabstop' is 8, 'expandtab' is 0, 'vartabstop' is not in use, and no fold column or line numbers occur in the generated HTML; @@ -687,13 +703,13 @@ Automatic detection works for the encodings mentioned specifically by name in |encoding-names|, but TOhtml will only automatically use those encodings with wide browser support. However, you can override this to support specific encodings that may not be automatically detected by default (see options -below). See http://www.iana.org/assignments/character-sets for the IANA names. +below). See https://www.iana.org/assignments/character-sets for the IANA names. Note: By default all Unicode encodings are converted to UTF-8 with no BOM in the generated HTML, as recommended by W3C: - http://www.w3.org/International/questions/qa-choosing-encodings - http://www.w3.org/International/questions/qa-byte-order-mark + https://www.w3.org/International/questions/qa-choosing-encodings + https://www.w3.org/International/questions/qa-byte-order-mark *g:html_use_encoding* Default: none, uses IANA name for current 'fileencoding' as above. @@ -832,7 +848,7 @@ files are included: asm68k Motorola 680x0 assembly asmh8300 Hitachi H-8300 version of GNU assembly ia64 Intel Itanium 64 - fasm Flat assembly (http://flatassembler.net) + fasm Flat assembly (https://flatassembler.net) masm Microsoft assembly (probably works for any 80x86) nasm Netwide assembly tasm Turbo Assembly (with opcodes 80x86 up to Pentium, and @@ -1393,9 +1409,9 @@ Two syntax highlighting files exist for Euphoria. One for Euphoria version 3.1.1, which is the default syntax highlighting file, and one for Euphoria version 4.0.5 or later. -Euphoria version 3.1.1 (http://www.rapideuphoria.com/) is still necessary +Euphoria version 3.1.1 (https://www.rapideuphoria.com/) is still necessary for developing applications for the DOS platform, which Euphoria version 4 -(http://www.openeuphoria.org/) does not support. +(https://www.openeuphoria.org/) does not support. The following file extensions are auto-detected as Euphoria file type: @@ -1452,7 +1468,7 @@ Elixir. FLEXWIKI *flexwiki.vim* *ft-flexwiki-syntax* -FlexWiki is an ASP.NET-based wiki package available at http://www.flexwiki.com +FlexWiki is an ASP.NET-based wiki package available at https://www.flexwiki.com NOTE: This site currently doesn't work, on Wikipedia is mentioned that development stopped in 2009. @@ -1808,7 +1824,7 @@ are read during initialization) > :let html_my_rendering=1 If you'd like to see an example download mysyntax.vim at -http://www.fleiner.com/vim/download.html +https://www.fleiner.com/vim/download.html You can also disable this rendering by adding the following line to your vimrc file: > @@ -1838,6 +1854,16 @@ following two lines to the syntax coloring file for that language Now you just need to make sure that you add all regions that contain the preprocessor language to the cluster htmlPreproc. + *html-folding* +The HTML syntax file provides syntax |folding| (see |:syn-fold|) between start +and end tags. This can be turned on by > + + :let g:html_syntax_folding = 1 + :set foldmethod=syntax + +Note: Syntax folding might slow down syntax highlighting significantly, +especially for large files. + HTML/OS (by Aestiva) *htmlos.vim* *ft-htmlos-syntax* @@ -1936,7 +1962,7 @@ highlight them use: > :let java_highlight_java_lang_ids=1 You can also highlight identifiers of most standard Java packages if you -download the javaid.vim script at http://www.fleiner.com/vim/download.html. +download the javaid.vim script at https://www.fleiner.com/vim/download.html. If you prefer to only highlight identifiers of a certain package, say java.io use the following: > :let java_highlight_java_io=1 @@ -2449,7 +2475,7 @@ from the rest of the name (like 'PkgName::' in '$PkgName::VarName'): > (In Vim 6.x it was the other way around: "perl_want_scope_in_variables" enabled it.) -If you do not want complex things like '@{${"foo"}}' to be parsed: > +If you do not want complex things like `@{${"foo"}}` to be parsed: > :let perl_no_extended_vars = 1 @@ -2897,7 +2923,7 @@ Default folding is rather detailed, i.e., small syntax units like "if", "do", You can set "ruby_foldable_groups" to restrict which groups are foldable: > - :let ruby_foldable_groups = 'if case %' + :let ruby_foldable_groups = 'if case %' < The value is a space-separated list of keywords: @@ -2905,22 +2931,22 @@ The value is a space-separated list of keywords: -------- ------------------------------------- ~ ALL Most block syntax (default) NONE Nothing - if "if" or "unless" block + if "if" or "unless" block def "def" block class "class" block module "module" block - do "do" block + do "do" block begin "begin" block case "case" block for "for", "while", "until" loops - { Curly bracket block or hash literal - [ Array literal - % Literal with "%" notation, e.g.: %w(STRING), %!STRING! - / Regexp + { Curly bracket block or hash literal + [ Array literal + % Literal with "%" notation, e.g.: %w(STRING), %!STRING! + / Regexp string String and shell command output (surrounded by ', ", `) - : Symbol - # Multiline comment - << Here documents + : Symbol + # Multiline comment + << Here documents __END__ Source code after "__END__" directive *ruby_no_expensive* @@ -2986,16 +3012,25 @@ satisfied with it for my own projects. SED *sed.vim* *ft-sed-syntax* To make tabs stand out from regular blanks (accomplished by using Todo -highlighting on the tabs), define "highlight_sedtabs" by putting > - - :let highlight_sedtabs = 1 +highlighting on the tabs), define "g:sed_highlight_tabs" by putting > + :let g:sed_highlight_tabs = 1 +< in the vimrc file. (This special highlighting only applies for tabs inside search patterns, replacement texts, addresses or text included by an Append/Change/Insert command.) If you enable this option, it is also a good idea to set the tab width to one character; by doing that, you can easily count the number of tabs in a string. +GNU sed allows comments after text on the same line. BSD sed only allows +comments where "#" is the first character of the line. To enforce BSD-style +comments, i.e. mark end-of-line comments as errors, use: > + + :let g:sed_dialect = "bsd" +< +Note that there are other differences between GNU sed and BSD sed which are +not (yet) affected by this setting. + Bugs: The transform command (y) is treated exactly like the substitute @@ -3353,13 +3388,11 @@ of specialized LaTeX commands, syntax, and fonts. If you're using such a package you'll often wish that the distributed syntax/tex.vim would support it. However, clearly this is impractical. So please consider using the techniques in |mysyntaxfile-add| to extend or modify the highlighting provided -by syntax/tex.vim. Please consider uploading any extensions that you write, -which typically would go in $HOME/after/syntax/tex/[pkgname].vim, to -http://vim.sf.net/. +by syntax/tex.vim. I've included some support for various popular packages on my website: > - http://www.drchip.org/astronaut/vim/index.html#LATEXPKGS + https://www.drchip.org/astronaut/vim/index.html#LATEXPKGS < The syntax files there go into your .../after/syntax/tex/ directory. @@ -3538,6 +3571,14 @@ highlighting is to put the following line in your |vimrc|: > < +WDL *wdl.vim* *wdl-syntax* + +The Workflow Description Language is a way to specify data processing workflows +with a human-readable and writeable syntax. This is used a lot in +bioinformatics. More info on the spec can be found here: +https://github.com/openwdl/wdl + + XF86CONFIG *xf86conf.vim* *ft-xf86conf-syntax* The syntax of XF86Config file differs in XFree86 v3.x and v4.x. Both @@ -3690,12 +3731,13 @@ DEFINING CASE *:syn-case* *E390* items until the next ":syntax case" command are affected. :sy[ntax] case - Show either "syntax case match" or "syntax case ignore" (translated). + Show either "syntax case match" or "syntax case ignore". DEFINING FOLDLEVEL *:syn-foldlevel* -:sy[ntax] foldlevel [start | minimum] +:sy[ntax] foldlevel start +:sy[ntax] foldlevel minimum This defines how the foldlevel of a line is computed when using foldmethod=syntax (see |fold-syntax| and |:syn-fold|): @@ -3708,11 +3750,14 @@ DEFINING FOLDLEVEL *:syn-foldlevel* may close and open horizontally within a line. :sy[ntax] foldlevel - Show either "syntax foldlevel start" or "syntax foldlevel minimum". + Show the current foldlevel method, either "syntax foldlevel start" or + "syntax foldlevel minimum". SPELL CHECKING *:syn-spell* -:sy[ntax] spell [toplevel | notoplevel | default] +:sy[ntax] spell toplevel +:sy[ntax] spell notoplevel +:sy[ntax] spell default This defines where spell checking is to be done for text that is not in a syntax item: @@ -3727,8 +3772,8 @@ SPELL CHECKING *:syn-spell* To activate spell checking the 'spell' option must be set. :sy[ntax] spell - Show either "syntax spell toplevel", "syntax spell notoplevel" or - "syntax spell default" (translated). + Show the current syntax spell checking method, either "syntax spell + toplevel", "syntax spell notoplevel" or "syntax spell default". SYNTAX ISKEYWORD SETTING *:syn-iskeyword* @@ -3739,7 +3784,7 @@ SYNTAX ISKEYWORD SETTING *:syn-iskeyword* clear: Syntax specific iskeyword setting is disabled and the buffer-local 'iskeyword' setting is used. - {option} Set the syntax 'iskeyword' option to a new value. + {option} Set the syntax 'iskeyword' option to a new value. Example: > :syntax iskeyword @,48-57,192-255,$,_ @@ -4326,7 +4371,7 @@ IMPLICIT CONCEAL *:syn-conceal-implicit* given explicitly. :sy[ntax] conceal - Show either "syntax conceal on" or "syntax conceal off" (translated). + Show either "syntax conceal on" or "syntax conceal off". ============================================================================== 8. Syntax patterns *:syn-pattern* *E401* *E402* @@ -4806,7 +4851,7 @@ Note that the ":syntax" command can be abbreviated to ":sy", although ":syn" is mostly used, because it looks better. ============================================================================== -12. Highlight command *:highlight* *:hi* *E28* *E411* *E415* +13. Highlight command *:highlight* *:hi* *E28* *E411* *E415* There are two types of highlight groups: - The built-in |highlight-groups|. @@ -4857,7 +4902,7 @@ in their own color. *highlight-clear* *:hi-clear* :hi[ghlight] clear Reset all highlighting to the defaults. Removes all - highlighting for groups added by the user! + highlighting for groups added by the user. Uses the current value of 'background' to decide which default colors to use. If there was a default link, restore it. |:hi-link| @@ -4913,7 +4958,8 @@ the same syntax file on all UIs. *bold* *underline* *undercurl* *underdouble* *underdotted* *underdashed* *inverse* *italic* - *standout* *nocombine* *strikethrough* + *standout* *strikethrough* *altfont* + *nocombine* cterm={attr-list} *attr-list* *highlight-cterm* *E418* attr-list is a comma-separated list (without spaces) of the following items (in any order): @@ -4928,6 +4974,7 @@ cterm={attr-list} *attr-list* *highlight-cterm* *E418* inverse same as reverse italic standout + altfont nocombine override attributes instead of combining them NONE no attributes used (used to reset it) @@ -4997,7 +5044,7 @@ ctermbg={color-nr} *ctermbg* a number instead of a color name. Note that for 16 color ansi style terminals (including xterms), the - numbers in the NR-8 column is used. Here '*' means 'add 8' so that + numbers in the NR-8 column is used. Here "*" means "add 8" so that Blue is 12, DarkGray is 8 etc. Note that for some color terminals these names may result in the wrong @@ -5099,7 +5146,7 @@ guisp={color-name} *guisp* "gg" is the Green value "bb" is the Blue value All values are hexadecimal, range from "00" to "ff". Examples: > - :highlight Comment guifg=#11f0c3 guibg=#ff00ff + :highlight Comment guifg=#11f0c3 guibg=#ff00ff < blend={integer} *highlight-blend* Override the blend level for a highlight group within the popupmenu @@ -5174,10 +5221,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-CursorLineSign* +CursorLineSign Like SignColumn when 'cursorline' is set for the cursor line. *hl-MatchParen* MatchParen Character under the cursor or just before it, if it is a paired bracket, and its match. |pi_paren.txt| @@ -5288,7 +5335,7 @@ Tooltip Current font, background and foreground of the tooltips. Applicable highlight arguments: font, guibg, guifg. ============================================================================== -13. Linking groups *:hi-link* *:highlight-link* *E412* *E413* +14. Linking groups *:hi-link* *:highlight-link* *E412* *E413* When you want to use the same highlighting for several syntax groups, you can do this more easily by linking the groups into one common highlight @@ -5397,8 +5444,7 @@ 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 Universal Ctags (found at https://ctags.io) or Exuberant ctags (found -at http://ctags.sf.net). +must use Universal Ctags (https://ctags.io) or Exuberant ctags. Put these lines in your Makefile: @@ -5448,7 +5494,7 @@ is loaded into that window or the file is reloaded. When splitting the window, the new window will use the original syntax. ============================================================================== -17. Color xterms *xterm-color* *color-xterm* +18. Color xterms *xterm-color* *color-xterm* *colortest.vim* To test your color setup, a file has been included in the Vim distribution. @@ -5458,7 +5504,7 @@ To use it, execute this command: > Nvim uses 256-color and |true-color| terminal capabilities wherever possible. ============================================================================== -18. When syntax is slow *:syntime* +19. When syntax is slow *:syntime* This is aimed at authors of a syntax file. diff --git a/runtime/doc/tagsrch.txt b/runtime/doc/tagsrch.txt index 82deb0fa0c..0f785dd1eb 100644 --- a/runtime/doc/tagsrch.txt +++ b/runtime/doc/tagsrch.txt @@ -59,9 +59,9 @@ CTRL-] Jump to the definition of the keyword under the CTRL-] is the default telnet escape key. When you type CTRL-] to jump to a tag, you will get the telnet prompt instead. Most versions of telnet allow changing or disabling the default escape key. See the telnet man page. You -can 'telnet -E {Hostname}' to disable the escape character, or 'telnet -e -{EscapeCharacter} {Hostname}' to specify another escape character. If -possible, try to use "ssh" instead of "telnet" to avoid this problem. +can `telnet -E {Hostname}` to disable the escape character, or +`telnet -e {EscapeCharacter} {Hostname}` to specify another escape character. +If possible, try to use "ssh" instead of "telnet" to avoid this problem. *tag-priority* When there are multiple matches for a tag, this priority is used: @@ -404,7 +404,7 @@ is added to the command and on the 'autowrite' option: tag in file autowrite ~ current file changed ! option action ~ ------------------------------------------------------------------------------ + --------------------------------------------------------------------------- yes x x x goto tag no no x x read other file, goto tag no yes yes x abandon current file, read other file, goto @@ -412,7 +412,7 @@ current file changed ! option action ~ no yes no on write current file, read other file, goto tag no yes no off fail ------------------------------------------------------------------------------ + --------------------------------------------------------------------------- - If the tag is in the current file, the command will always work. - If the tag is in another file and the current file was not changed, the @@ -514,18 +514,13 @@ ctags As found on most Unix systems. Only supports C. Only universal ctags A maintained version of ctags based on exuberant ctags. See https://ctags.io. *Exuberant_ctags* -exuberant ctags This is a very good one. It works for C, C++, Java, - Fortran, Eiffel and others. It can generate tags for - many items. See http://ctags.sourceforge.net. - No new version since 2009. +exuberant ctags Works for C, C++, Java, Fortran, Eiffel and others. + See https://ctags.sourceforge.net. No new version + since 2009. JTags For Java, in Java. It can be found at - http://www.fleiner.com/jtags/. + https://www.fleiner.com/jtags/. ptags.py For Python, in Python. Found in your Python source directory at Tools/scripts/ptags.py. -ptags For Perl, in Perl. It can be found at - http://www.eleves.ens.fr:8080/home/nthiery/Tags/. -gnatxref For Ada. See http://www.gnuada.org/. gnatxref is - part of the gnat package. The lines in the tags file must have one of these two formats: @@ -566,8 +561,8 @@ ctags). with Vi, it ignores the following fields. Example: APP file /^static int APP;$/;" v When {tagaddress} is not a line number or search pattern, then - {term} must be |;". Here the bar ends the command (excluding - the bar) and ;" is used to have Vi ignore the rest of the + {term} must be `|;"`. Here the bar ends the command (excluding + the bar) and `;"` is used to have Vi ignore the rest of the line. Example: APP file.c call cursor(3, 4)|;" v @@ -629,8 +624,7 @@ If the command is a normal search command (it starts and ends with "/" or "?"), some special handling is done: - Searching starts on line 1 of the file. The direction of the search is forward for "/", backward for "?". - Note that 'wrapscan' does not matter, the whole file is always searched. (Vi - does use 'wrapscan', which caused tags sometimes not be found.) + Note that 'wrapscan' does not matter, the whole file is always searched. - If the search fails, another try is done ignoring case. If that fails too, a search is done for: "^tagname[ \t]*(" @@ -834,10 +828,10 @@ CTRL-W d Open a new window, with the cursor on the first (default: whole file). See |:search-args| for [/] and [!]. - *:che* *:chec* *:check* *:checkpath* -:che[ckpath] List all the included files that could not be found. + *:checkp* *:checkpath* +:checkp[ath] List all the included files that could not be found. -:che[ckpath]! List all the included files. +:checkp[ath]! List all the included files. *:search-args* Common arguments for the commands above: @@ -854,9 +848,9 @@ Common arguments for the commands above: a comment (even though syntax highlighting does recognize it). Note: Since a macro definition mostly doesn't look like a comment, the [!] makes no difference for ":dlist", ":dsearch" and ":djump". -[/] A pattern can be surrounded by '/'. Without '/' only whole words are - matched, using the pattern "\<pattern\>". Only after the second '/' a - next command can be appended with '|'. Example: > +[/] A pattern can be surrounded by "/". Without "/" only whole words are + matched, using the pattern "\<pattern\>". Only after the second "/" a + next command can be appended with "|". Example: > :isearch /string/ | echo "the last one" < For a ":djump", ":dsplit", ":dlist" and ":dsearch" command the pattern is used as a literal string, not as a search pattern. @@ -914,6 +908,8 @@ If the function returns |v:null| instead of a List, a standard tag lookup will be performed instead. It is not allowed to change the tagstack from inside 'tagfunc'. *E986* +It is not allowed to close a window or change window from inside 'tagfunc'. +*E1299* The following is a hypothetical example of a function used for 'tagfunc'. It uses the output of |taglist()| to generate the result: a list of tags in the diff --git a/runtime/doc/term.txt b/runtime/doc/term.txt index cd6798a5de..847b4b6112 100644 --- a/runtime/doc/term.txt +++ b/runtime/doc/term.txt @@ -29,7 +29,7 @@ whole. Building your own terminfo is usually as simple as running this as a non-superuser: > - curl -LO http://invisible-island.net/datafiles/current/terminfo.src.gz + curl -LO https://invisible-island.net/datafiles/current/terminfo.src.gz gunzip terminfo.src.gz tic terminfo.src < @@ -76,7 +76,7 @@ supplying an external one with entries for the terminal type. Settings depending on terminal *term-dependent-settings* If you want to set terminal-dependent options or mappings, you can do this in -your init.vim. Example: > +your init.vim. Example: >vim if $TERM =~ '^\(rxvt\|screen\|interix\|putty\)\(-.*\)\?$' set notermguicolors @@ -222,9 +222,9 @@ are not in terminfo you must add them by setting "terminal-overrides" in ~/.tmux.conf . See the tmux(1) manual page for the details of how and what to do in the tmux -configuration file. It will look something like: > +configuration file. It will look something like: >bash set -ga terminal-overrides '*:Ss=\E[%p1%d q:Se=\E[ q' -<or (alas!) for Konsole 18.07.70 or older, something more complex like: > +<or (alas!) for Konsole 18.07.70 or older, something more complex like: >bash set -ga terminal-overrides 'xterm*:\E]50;CursorShape=%?%p1%{3}%<%t%{0}%e%{1}%;%d\007' < ============================================================================== @@ -262,7 +262,7 @@ See the "Options" chapter |options|. If you are using a color terminal that is slow when displaying lines beyond the end of a buffer, this is because Nvim is drawing the whitespace twice, in -two sets of colours and attributes. To prevent this, use this command: > +two sets of colours and attributes. To prevent this, use this command: >vim hi NonText cterm=NONE ctermfg=NONE This draws the spaces with the default colours and attributes, which allows the second pass of drawing to be optimized away. Note: Although in theory the @@ -372,7 +372,7 @@ that has a match selects until that match (like using "v%"). If the match is 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: > +An example, for using a double click to jump to the tag under the cursor: >vim :map <2-LeftMouse> :exe "tag " .. expand("<cword>")<CR> Dragging the mouse with a double click (button-down, button-up, button-down @@ -380,6 +380,8 @@ and then drag) will result in whole words to be selected. This continues until the button is released, at which point the selection is per character again. +For scrolling with the mouse see |scroll-mouse-wheel|. + In Insert mode, when a selection is started, Vim goes into Normal mode temporarily. When Visual or Select mode ends, it returns to Insert mode. This is like using CTRL-O in Insert mode. Select mode is used when the @@ -408,23 +410,23 @@ The X1 and X2 buttons refer to the extra buttons found on some mice. The 'Microsoft Explorer' mouse has these buttons available to the right thumb. Currently X1 and X2 only work on Win32 and X11 environments. -Examples: > +Examples: >vim :noremap <MiddleMouse> <LeftMouse><MiddleMouse> Paste at the position of the middle mouse button click (otherwise the paste -would be done at the cursor position). > +would be done at the cursor position). >vim :noremap <LeftRelease> <LeftRelease>y Immediately yank the selection, when using Visual mode. Note the use of ":noremap" instead of "map" to avoid a recursive mapping. -> +>vim :map <X1Mouse> <C-O> :map <X2Mouse> <C-I> Map the X1 and X2 buttons to go forwards and backwards in the jump list, see |CTRL-O| and |CTRL-I|. *mouse-swap-buttons* -To swap the meaning of the left and right mouse buttons: > +To swap the meaning of the left and right mouse buttons: >vim :noremap <LeftMouse> <RightMouse> :noremap <LeftDrag> <RightDrag> :noremap <LeftRelease> <RightRelease> diff --git a/runtime/doc/testing.txt b/runtime/doc/testing.txt index f4375c3363..ef5e179c86 100644 --- a/runtime/doc/testing.txt +++ b/runtime/doc/testing.txt @@ -20,17 +20,11 @@ and for testing plugins. Vim can be tested after building it, usually with "make test". The tests are located in the directory "src/testdir". -There are several types of tests added over time: - test33.in oldest, don't add any of these - test_something.in old style tests - test_something.vim new style tests - *new-style-testing* -New tests should be added as new style tests. These use functions such as -|assert_equal()| to keep the test commands and the expected result in one -place. - *old-style-testing* -In some cases an old style test needs to be used. +New tests should be added as new style tests. The test scripts are named +test_<feature>.vim (replace <feature> with the feature under test). These use +functions such as |assert_equal()| to keep the test commands and the expected +result in one place. Find more information in the file src/testdir/README.txt. @@ -98,18 +92,46 @@ assert_exception({error} [, {msg}]) *assert_exception()* catch call assert_exception('E492:') endtry - -assert_fails({cmd} [, {error} [, {msg}]]) *assert_fails()* +< + *assert_fails()* +assert_fails({cmd} [, {error} [, {msg} [, {lnum} [, {context}]]]]) Run {cmd} and add an error message to |v:errors| if it does - NOT produce an error. Also see |assert-return|. - When {error} is given it must match in |v:errmsg|. + NOT produce an error or when {error} is not found in the + error message. Also see |assert-return|. + + When {error} is a string it must be found literally in the + first reported error. Most often this will be the error code, + including the colon, e.g. "E123:". > + assert_fails('bad cmd', 'E987:') +< + When {error} is a |List| with one or two strings, these are + used as patterns. The first pattern is matched against the + first reported error: > + assert_fails('cmd', ['E987:.*expected bool']) +< The second pattern, if present, is matched against the last + reported error. To only match the last error use an empty + string for the first error: > + assert_fails('cmd', ['', 'E987:']) +< + If {msg} is empty then it is not used. Do this to get the + default message when passing the {lnum} argument. + + When {lnum} is present and not negative, and the {error} + argument is present and matches, then this is compared with + the line number at which the error was reported. That can be + the line number in a function or in a script. + + When {context} is present it is used as a pattern and matched + against the context (script name or function name) where + {lnum} is located in. + Note that beeping is not considered an error, and some failing commands only beep. Use |assert_beeps()| for those. Can also be used as a |method|: > GetCmd()->assert_fails('E99:') -assert_false({actual} [, {msg}]) *assert_false()* +assert_false({actual} [, {msg}]) *assert_false()* When {actual} is not false an error message is added to |v:errors|, like with |assert_equal()|. Also see |assert-return|. diff --git a/runtime/doc/tips.txt b/runtime/doc/tips.txt index d913b53c6b..5d5e89da77 100644 --- a/runtime/doc/tips.txt +++ b/runtime/doc/tips.txt @@ -8,7 +8,7 @@ Tips and ideas for using Vim *tips* These are just a few that we thought would be helpful for many users. You can find many more tips on the wiki. The URL can be found on -http://www.vim.org +https://www.vim.org Don't forget to browse the user manual, it also contains lots of useful tips |usr_toc.txt|. @@ -297,7 +297,7 @@ be able to give comments to the parts of the mapping. > (<> notation |<>|. Note that this is all typed literally. ^W is "^" "W", not CTRL-W.) -Note that the last comment starts with |", because the ":execute" command +Note that the last comment starts with `|"`, because the ":execute" command doesn't accept a comment directly. You also need to set 'textwidth' to a non-zero value, e.g., > @@ -454,7 +454,7 @@ the current window, try this custom `:HelpCurwin` command: > command -bar -nargs=? -complete=help HelpCurwin execute s:HelpCurwin(<q-args>) let s:did_open_help = v:false - + function s:HelpCurwin(subject) abort let mods = 'silent noautocmd keepalt' if !s:did_open_help diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index c836ccec8c..917863eef8 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -24,7 +24,7 @@ via a plugin like https://github.com/nvim-treesitter/nvim-treesitter. Parsers are searched for as `parser/{lang}.*` in any 'runtimepath' directory. If multiple parsers for the same language are found, the first one is used. (This typically implies the priority "user config > plugins > bundled". -A parser can also be loaded manually using a full path: > +A parser can also be loaded manually using a full path: >lua vim.treesitter.require_language("python", "/path/to/python.so") < @@ -37,7 +37,7 @@ file), multiple parsers may be needed to parse the full buffer. These are combined in a |LanguageTree| object. To create a LanguageTree (parser object) for a buffer and a given language, -use > +use >lua tsparser = vim.treesitter.get_parser(bufnr, lang) < @@ -46,7 +46,7 @@ Currently, the parser will be retained for the lifetime of a buffer but this is subject to change. A plugin should keep a reference to the parser object as long as it wants incremental updates. -Whenever you need to access the current syntax tree, parse the buffer: > +Whenever you need to access the current syntax tree, parse the buffer: >lua tstree = tsparser:parse() < @@ -56,11 +56,11 @@ current state of the buffer. When the plugin wants to access the state after a 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 -must call |get_parser()| before you register your callback. But preferably -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. +Note: To use the parser directly inside a |nvim_buf_attach()| Lua callback, +you must call |vim.treesitter.get_parser()| before you register your callback. +But preferably 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. See |lua-treesitter-languagetree| for the list of available methods. @@ -212,7 +212,7 @@ treesitter queries from Lua. TREESITTER QUERY PREDICATES *treesitter-predicates* Predicates are special scheme nodes that are evaluated to conditionally capture -nodes. For example, the |eq?| predicate can be used as follows: > +nodes. For example, the `eq?` predicate can be used as follows: > ((identifier) @foo (#eq? @foo "foo")) < @@ -239,7 +239,7 @@ The following predicates are built in: `contains?` *treesitter-predicate-contains?* Match a string against parts of the text corresponding to a node: > ((identifier) @foo (#contains? @foo "foo")) - ((identifier) @foo-bar (#contains @foo-bar "foo" "bar")) + ((identifier) @foo-bar (#contains? @foo-bar "foo" "bar")) < `any-of?` *treesitter-predicate-any-of?* Match any of the given strings against the text corresponding to @@ -253,15 +253,15 @@ The following predicates are built in: Each predicate has a `not-` prefixed predicate that is just the negation of the predicate. -Further predicates can be added via `vim.treesitter.query.`|add_predicate()|. -Use `vim.treesitter.query.`|list_predicates()| to list all available +Further predicates can be added via |vim.treesitter.query.add_predicate()|. +Use |vim.treesitter.query.list_predicates()| to list all available predicates. TREESITTER QUERY DIRECTIVES *treesitter-directives* Treesitter directives store metadata for a node or match and perform side -effects. For example, the |set!| predicate sets metadata on the match or node: > +effects. For example, the `set!` directive sets metadata on the match or node: > ((identifier) @foo (#set! "type" "parameter")) < @@ -297,8 +297,8 @@ The following directives are built in: ((identifier) @constant (#offset! @constant 0 1 0 -1)) < -Further directives can be added via `vim.treesitter.query.`|add_directive()|. -Use `vim.treesitter.query.`|list_directives()| to list all available +Further directives can be added via |vim.treesitter.query.add_directive()|. +Use |vim.treesitter.query.list_directives()| to list all available directives. @@ -366,12 +366,65 @@ queries that make them available. As an additional rule, capture highlights can always be specialized by language, by appending the language name after an additional dot. For -instance, to highlight comments differently per language: > +instance, to highlight comments differently per language: >vim hi @comment.c guifg=Blue - hi @comment.lua @guifg=DarkBlue + hi @comment.lua guifg=DarkBlue hi link @comment.doc.java String < +The following captures are linked by default to standard |group-name|s: +> + @text.literal Comment + @text.reference Identifier + @text.title Title + @text.uri Underlined + @text.underline Underlined + @text.todo Todo + + @comment Comment + @punctuation Delimiter + + @constant Constant + @constant.builtin Special + @constant.macro Define + @define Define + @macro Macro + @string String + @string.escape SpecialChar + @string.special SpecialChar + @character Character + @character.special SpecialChar + @number Number + @boolean Boolean + @float Float + + @function Function + @function.builtin Special + @function.macro Macro + @parameter Identifier + @method Function + @field Identifier + @property Identifier + @constructor Special + + @conditional Conditional + @repeat Repeat + @label Label + @operator Operator + @keyword Keyword + @exception Exception + + @variable Identifier + @type Type + @type.definition Typedef + @storageclass StorageClass + @structure Structure + @namespace Identifier + @include Include + @preproc PreProc + @debug Debug + @tag Tag +< *treesitter-highlight-spell* The special `@spell` capture can be used to indicate that a node should be spell checked by Nvim's builtin |spell| checker. For example, the following @@ -379,6 +432,9 @@ capture marks comments as to be checked: > (comment) @spell < + +There is also `@nospell` which disables spellchecking regions with `@spell`. + *treesitter-highlight-conceal* Treesitter highlighting supports |conceal| via the `conceal` metadata. By convention, nodes to be concealed are captured as `@conceal`, but any capture @@ -425,7 +481,8 @@ library. ============================================================================== Lua module: vim.treesitter *lua-treesitter-core* -get_captures_at_cursor({winnr}) *get_captures_at_cursor()* + *vim.treesitter.get_captures_at_cursor()* +get_captures_at_cursor({winnr}) Returns a list of highlight capture names under the cursor Parameters: ~ @@ -434,7 +491,8 @@ get_captures_at_cursor({winnr}) *get_captures_at_cursor()* Return: ~ string[] List of capture names -get_captures_at_pos({bufnr}, {row}, {col}) *get_captures_at_pos()* + *vim.treesitter.get_captures_at_pos()* +get_captures_at_pos({bufnr}, {row}, {col}) Returns a list of highlight captures at the given position Each capture is represented by a table containing the capture name as a @@ -450,7 +508,7 @@ get_captures_at_pos({bufnr}, {row}, {col}) *get_captures_at_pos()* table[] List of captures `{ capture = "capture name", metadata = { ... } }` -get_node_at_cursor({winnr}) *get_node_at_cursor()* +get_node_at_cursor({winnr}) *vim.treesitter.get_node_at_cursor()* Returns the smallest named node under the cursor Parameters: ~ @@ -459,7 +517,8 @@ get_node_at_cursor({winnr}) *get_node_at_cursor()* Return: ~ (string) Name of node under the cursor -get_node_at_pos({bufnr}, {row}, {col}, {opts}) *get_node_at_pos()* + *vim.treesitter.get_node_at_pos()* +get_node_at_pos({bufnr}, {row}, {col}, {opts}) Returns the smallest named node at the given position Parameters: ~ @@ -467,13 +526,14 @@ get_node_at_pos({bufnr}, {row}, {col}, {opts}) *get_node_at_pos()* • {row} (number) Position row • {col} (number) Position column • {opts} (table) Optional keyword arguments: + • lang string|nil Parser language • ignore_injections boolean Ignore injected languages (default true) Return: ~ - userdata |tsnode| under the cursor + userdata|nil |tsnode| under the cursor -get_node_range({node_or_range}) *get_node_range()* +get_node_range({node_or_range}) *vim.treesitter.get_node_range()* Returns the node's range or an unpacked range table Parameters: ~ @@ -482,7 +542,7 @@ get_node_range({node_or_range}) *get_node_range()* Return: ~ (table) `{ start_row, start_col, end_row, end_col }` -get_parser({bufnr}, {lang}, {opts}) *get_parser()* +get_parser({bufnr}, {lang}, {opts}) *vim.treesitter.get_parser()* Returns the parser for a specific buffer and filetype and attaches it to the buffer @@ -498,7 +558,8 @@ get_parser({bufnr}, {lang}, {opts}) *get_parser()* Return: ~ LanguageTree |LanguageTree| object to use for parsing -get_string_parser({str}, {lang}, {opts}) *get_string_parser()* + *vim.treesitter.get_string_parser()* +get_string_parser({str}, {lang}, {opts}) Returns a string parser Parameters: ~ @@ -509,7 +570,7 @@ get_string_parser({str}, {lang}, {opts}) *get_string_parser()* Return: ~ LanguageTree |LanguageTree| object to use for parsing -is_ancestor({dest}, {source}) *is_ancestor()* +is_ancestor({dest}, {source}) *vim.treesitter.is_ancestor()* Determines whether a node is the ancestor of another Parameters: ~ @@ -519,7 +580,8 @@ is_ancestor({dest}, {source}) *is_ancestor()* Return: ~ (boolean) True if {dest} is an ancestor of {source} -is_in_node_range({node}, {line}, {col}) *is_in_node_range()* + *vim.treesitter.is_in_node_range()* +is_in_node_range({node}, {line}, {col}) Determines whether (line, col) position is in node range Parameters: ~ @@ -530,7 +592,7 @@ is_in_node_range({node}, {line}, {col}) *is_in_node_range()* Return: ~ (boolean) True if the position is in node range -node_contains({node}, {range}) *node_contains()* +node_contains({node}, {range}) *vim.treesitter.node_contains()* Determines if a node contains a range Parameters: ~ @@ -540,7 +602,32 @@ node_contains({node}, {range}) *node_contains()* Return: ~ (boolean) True if the {node} contains the {range} -start({bufnr}, {lang}) *start()* +show_tree({opts}) *vim.treesitter.show_tree()* + Open a window that displays a textual representation of the nodes in the + language tree. + + While in the window, press "a" to toggle display of anonymous nodes, "I" + to toggle the display of the source language of each node, and press + <Enter> to jump to the node under the cursor in the source buffer. + + Parameters: ~ + • {opts} (table|nil) Optional options table with the following possible + keys: + • lang (string|nil): The language of the source buffer. If + omitted, the filetype of the source buffer is used. + • bufnr (number|nil): Buffer to draw the tree into. If + omitted, a new buffer is created. + • winid (number|nil): Window id to display the tree buffer in. + If omitted, a new window is created with {command}. + • command (string|nil): Vimscript command to create the + window. Default value is "topleft 60vnew". Only used when + {winid} is nil. + • title (string|fun(bufnr:number):string|nil): Title of the + window. If a function, it accepts the buffer number of the + source buffer as its only argument and should return a + string. + +start({bufnr}, {lang}) *vim.treesitter.start()* Starts treesitter highlighting for a buffer Can be used in an ftplugin or FileType autocommand. @@ -549,7 +636,7 @@ start({bufnr}, {lang}) *start()* required for some plugins. In this case, add `vim.bo.syntax = 'on'` after the call to `start`. - Example: > + Example: >lua vim.api.nvim_create_autocmd( 'FileType', { pattern = 'tex', callback = function(args) @@ -565,7 +652,7 @@ start({bufnr}, {lang}) *start()* • {lang} (string|nil) Language of the parser (default: buffer filetype) -stop({bufnr}) *stop()* +stop({bufnr}) *vim.treesitter.stop()* Stops treesitter highlighting for a buffer Parameters: ~ @@ -576,7 +663,7 @@ stop({bufnr}) *stop()* ============================================================================== Lua module: vim.treesitter.language *lua-treesitter-language* -inspect_language({lang}) *inspect_language()* +inspect_language({lang}) *vim.treesitter.language.inspect_language()* Inspects the provided language. Inspecting provides some useful information on the language like node @@ -588,7 +675,7 @@ inspect_language({lang}) *inspect_language()* Return: ~ (table) - *require_language()* + *vim.treesitter.language.require_language()* require_language({lang}, {path}, {silent}, {symbol_name}) Asserts that a parser for the language {lang} is installed. @@ -610,7 +697,8 @@ require_language({lang}, {path}, {silent}, {symbol_name}) ============================================================================== Lua module: vim.treesitter.query *lua-treesitter-query* -add_directive({name}, {handler}, {force}) *add_directive()* + *vim.treesitter.query.add_directive()* +add_directive({name}, {handler}, {force}) Adds a new directive to be used in queries Handlers can set match level data by setting directly on the metadata @@ -620,18 +708,29 @@ add_directive({name}, {handler}, {force}) *add_directive()* Parameters: ~ • {name} (string) Name of the directive, without leading # - • {handler} function(match:string, pattern:string, bufnr:number, - predicate:function, metadata:table) - -add_predicate({name}, {handler}, {force}) *add_predicate()* + • {handler} function(match:table, pattern:string, bufnr:number, + predicate:string[], metadata:table) + • match: see |treesitter-query| + • node-level data are accessible via `match[capture_id]` + + • pattern: see |treesitter-query| + • predicate: list of strings containing the full directive + being called, e.g. `(node (#set! conceal "-"))` would get + the predicate `{ "#set!", "conceal", "-" }` + + *vim.treesitter.query.add_predicate()* +add_predicate({name}, {handler}, {force}) Adds a new predicate to be used in queries Parameters: ~ • {name} (string) Name of the predicate, without leading # - • {handler} function(match:string, pattern:string, bufnr:number, - predicate:function) + • {handler} function(match:table, pattern:string, bufnr:number, + predicate:string[]) + • see |vim.treesitter.query.add_directive()| for argument + meanings -get_node_text({node}, {source}, {opts}) *get_node_text()* + *vim.treesitter.query.get_node_text()* +get_node_text({node}, {source}, {opts}) Gets the text corresponding to a given node Parameters: ~ @@ -645,7 +744,7 @@ get_node_text({node}, {source}, {opts}) *get_node_text()* Return: ~ (string[]|string) -get_query({lang}, {query_name}) *get_query()* +get_query({lang}, {query_name}) *vim.treesitter.query.get_query()* Returns the runtime query {query_name} for {lang}. Parameters: ~ @@ -655,7 +754,7 @@ get_query({lang}, {query_name}) *get_query()* Return: ~ Query Parsed query - *get_query_files()* + *vim.treesitter.query.get_query_files()* get_query_files({lang}, {query_name}, {is_included}) Gets the list of files used to make up a query @@ -669,19 +768,19 @@ get_query_files({lang}, {query_name}, {is_included}) string[] query_files List of files to load for given query and language -list_directives() *list_directives()* +list_directives() *vim.treesitter.query.list_directives()* Lists the currently available directives to use in queries. Return: ~ string[] List of supported directives. -list_predicates() *list_predicates()* +list_predicates() *vim.treesitter.query.list_predicates()* Lists the currently available predicates to use in queries. Return: ~ string[] List of supported predicates. -parse_query({lang}, {query}) *parse_query()* +parse_query({lang}, {query}) *vim.treesitter.query.parse_query()* Parse {query} as a string. (If the query is in a file, the caller should read the contents into a string before calling). @@ -714,7 +813,7 @@ Query:iter_captures({self}, {node}, {source}, {start}, {stop}) The iterator returns three values: a numeric id identifying the capture, the captured node, and metadata from any directives processing the match. - The following example shows how to get captures by name: > + The following example shows how to get captures by name: >lua for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do local name = query.captures[id] -- name of the capture in the query @@ -746,7 +845,7 @@ Query:iter_matches({self}, {node}, {source}, {start}, {stop}) (1-based) index of the pattern in the query, 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 is an example iterating over all captures in every match: > + and e.g. `pairs()` method should be used over `ipairs` . Here is an example iterating over all captures in every match: >lua for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do for id, node in pairs(match) do @@ -772,7 +871,8 @@ Query:iter_matches({self}, {node}, {source}, {start}, {stop}) (table) match (table) metadata -set_query({lang}, {query_name}, {text}) *set_query()* + *vim.treesitter.query.set_query()* +set_query({lang}, {query_name}, {text}) Sets the runtime query named {query_name} for {lang} This allows users to override any runtime files and/or configuration set @@ -787,7 +887,7 @@ set_query({lang}, {query_name}, {text}) *set_query()* ============================================================================== Lua module: vim.treesitter.highlighter *lua-treesitter-highlighter* -new({tree}, {opts}) *highlighter.new()* +new({tree}, {opts}) *vim.treesitter.highlighter.new()* Creates a new highlighter using Parameters: ~ @@ -958,7 +1058,7 @@ LanguageTree:trees({self}) *LanguageTree:trees()* Parameters: ~ • {self} -new({source}, {lang}, {opts}) *languagetree.new()* +new({source}, {lang}, {opts}) *vim.treesitter.languagetree.new()* A |LanguageTree| holds the treesitter parser for a given language {lang} used to parse a buffer. As the buffer may contain injected languages, the LanguageTree needs to store parsers for these child languages as well (which in turn may contain child languages themselves, hence the name). diff --git a/runtime/doc/uganda.txt b/runtime/doc/uganda.txt index 23dfa082a0..d8fc26ad17 100644 --- a/runtime/doc/uganda.txt +++ b/runtime/doc/uganda.txt @@ -11,9 +11,9 @@ Vim is Charityware. You can use and copy it as much as you like, but you are encouraged to make a donation for needy children in Uganda. Please see |kcc| below or visit the ICCF web site, available at these URLs: - http://iccf-holland.org/ - http://www.vim.org/iccf/ - http://www.iccf.nl/ + https://iccf-holland.org/ + https://www.vim.org/iccf/ + https://www.iccf.nl/ You can also sponsor the development of Vim. Vim sponsors can vote for features. See |sponsor|. The money goes to Uganda anyway. @@ -166,10 +166,11 @@ households are stimulated to build a proper latrine. I helped setting up a production site for cement slabs. These are used to build a good latrine. They are sold below cost price. -There is a small clinic at the project, which provides children and their -family with medical help. When needed, transport to a hospital is offered. -Immunization programs are carried out and help is provided when an epidemic is -breaking out (measles and cholera have been a problem). +There is a clinic at the project, which provides children and their family +medical help. Since 2020 a maternity ward was added and 24/7 service is +available. When needed, transport to a hospital is offered. Immunization +programs are carried out and help is provided when an epidemic is breaking out +(measles and cholera have been a problem). *donate* Summer 1994 to summer 1995 I spent a whole year at the centre, working as a volunteer. I have helped to expand the centre and worked in the area of water @@ -211,44 +212,29 @@ Check the ICCF web site for the latest information! See |iccf| for the URL. USA: The methods mentioned below can be used. - Sending a check to the Nehemiah Group Outreach Society (NGOS) - is no longer possible, unfortunately. We are looking for - another way to get you an IRS tax receipt. - For sponsoring a child contact KCF in Canada (see below). US - checks can be sent to them to lower banking costs. - -Canada: Contact Kibaale Children's Fund (KCF) in Surrey, Canada. They - take care of the Canadian sponsors for the children in - Kibaale. KCF forwards 100% of the money to the project in - Uganda. You can send them a one time donation directly. + If you must send a check send it to our Canadian partner: + https://www.kuwasha.net/ + +Canada: Contact Kuwasha in Surrey, Canada. They take care of the + Canadian sponsors for the children in Kibaale. Kuwasha + forwards 100% of the money to the project in Uganda. You can + send them a one time donation directly. Please send me a note so that I know what has been donated - because of Vim. Ask KCF for information about sponsorship. - Kibaale Children's Fund c/o Pacific Academy - 10238-168 Street - Surrey, B.C. V4N 1Z4 - Canada - Phone: 604-581-5353 - If you make a donation to Kibaale Children's Fund (KCF) you - will receive a tax receipt which can be submitted with your - tax return. - -Holland: Transfer to the account of "Stichting ICCF Holland" in Lisse. - This will allow for tax deduction if you live in Holland. - Postbank, nr. 4548774 - IBAN: NL95 INGB 0004 5487 74 + because of Vim. Look on their site for information about + sponsorship: https://www.kuwasha.net/ + If you make a donation to Kuwasha you will receive a tax + receipt which can be submitted with your tax return. + +Holland: Transfer to the account of "Stichting ICCF Holland" in + Amersfoort. This will allow for tax deduction if you live in + Holland. ING bank, IBAN: NL95 INGB 0004 5487 74 Germany: It is possible to make donations that allow for a tax return. Check the ICCF web site for the latest information: - http://iccf-holland.org/germany.html - -World: Use a postal money order. That should be possible from any - country, mostly from the post office. Use this name (which is - in my passport): "Abraham Moolenaar". Use Euro for the - currency if possible. + https://iccf-holland.org/germany.html -Europe: Use a bank transfer if possible. Your bank should have a form - that you can use for this. See "Others" below for the swift - code and IBAN number. +Europe: Use a bank transfer if possible. See "Others" below for the + swift code and IBAN number. Any other method should work. Ask for information about sponsorship. @@ -258,28 +244,12 @@ Credit Card: You can use PayPal to send money with a Credit card. This is https://www.paypal.com/en_US/mrb/pal=XAC62PML3GF8Q The e-mail address for sending the money to is: Bram@iccf-holland.org - For amounts above 400 Euro ($500) sending a check is - preferred. - -Others: Transfer to one of these accounts if possible: - Postbank, account 4548774 - Swift code: INGB NL 2A - IBAN: NL95 INGB 0004 5487 74 - under the name "stichting ICCF Holland", Lisse - If that doesn't work: - Rabobank Lisse, account 3765.05.117 - Swift code: RABO NL 2U - under the name "Bram Moolenaar", Lisse - Otherwise, send a check in euro or US dollars to the address - below. Minimal amount: $70 (my bank does not accept smaller - amounts for foreign check, sorry) - -Address to send checks to: - Bram Moolenaar - Finsterruetihof 1 - 8134 Adliswil - Switzerland - -This address is expected to be valid for a long time. + +Others: Transfer to this account if possible: + ING bank: IBAN: NL95 INGB 0004 5487 74 + Swift code: INGBNL2A + under the name "stichting ICCF Holland", Amersfoort + Checks are not accepted. + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index 5685979c82..3110d0817c 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -28,12 +28,14 @@ with these (optional) keys: - `rgb` Decides the color format. - true: (default) 24-bit RGB colors - false: Terminal colors (8-bit, max 256) + *ui-override* - `override` Decides how UI capabilities are resolved. - true: Enable requested UI capabilities, even if not supported by all connected UIs (including |TUI|). - false: (default) Disable UI capabilities not supported by all connected UIs (including TUI). + *ui-ext-options* - `ext_cmdline` Externalize the cmdline. |ui-cmdline| - `ext_hlstate` Detailed highlight state. |ui-hlstate| @@ -54,6 +56,8 @@ with these (optional) keys: - `stdin_fd` Read buffer from `fd` as if it was a stdin pipe This option can only used by |--embed| ui, see |ui-startup-stdin|. + `stdin_tty` Tells if `stdin` is a `tty` or not. + `stdout_tty` Tells if `stdout` is a `tty` or not. Specifying an unknown option is an error; UIs can check the |api-metadata| `ui_options` key for supported options. @@ -117,7 +121,7 @@ for forward-compatibility. |api-contract| UI startup *ui-startup* UI embedders (clients that start Nvim with |--embed| and later call -|nvim_ui_attach()|) must start Nvim without |--headless|: > +|nvim_ui_attach()|) must start Nvim without |--headless|: >bash nvim --embed Nvim will pause before loading startup files and reading buffers, so the UI has a chance to invoke requests and do early initialization. Startup will @@ -130,14 +134,18 @@ procedure: 1. Invoke |nvim_get_api_info()|, if needed to setup the client library and/or to get the list of supported UI extensions. + 2. Do any configuration that should be happen before user config is loaded. Buffers and windows are not available at this point, but this could be used to set |g:| variables visible to init.vim + 3. If the UI wants to do additional setup after user config is loaded, - register a VimEnter autocmd: > + register a VimEnter autocmd: >vim nvim_command("autocmd VimEnter * call rpcrequest(1, 'vimenter')") -<4. Now invoke |nvim_ui_attach()|. The UI must handle user input by now: + +4. Now invoke |nvim_ui_attach()|. The UI must handle user input by now: sourcing init.vim and loading buffers might lead to blocking prompts. + 5. If step 3 was used, Nvim will send a blocking "vimenter" request to the UI. Inside this request handler, the UI can safely do any initialization before entering normal mode, for example reading variables set by init.vim. @@ -316,6 +324,7 @@ numerical highlight ids to the actual attributes. `underdouble`: double underlined text. The lines have `special` color. `underdotted`: underdotted text. The dots have `special` color. `underdashed`: underdashed text. The dashes have `special` color. + `altfont`: alternative font. `blend`: Blend level (0-100). Could be used by UIs to support blending floating windows to the background or to signal a transparent cursor. @@ -716,7 +725,7 @@ For command-line 'wildmenu' UI events, activate |ui-popupmenu|. ["cmdline_block_show", lines] ~ Show a block of context to the current command line. For example if - the user defines a |:function| interactively: > + the user defines a |:function| interactively: >vim :function Foo() : echo "foo" : diff --git a/runtime/doc/userfunc.txt b/runtime/doc/userfunc.txt index 5462fa952c..9c428175bb 100644 --- a/runtime/doc/userfunc.txt +++ b/runtime/doc/userfunc.txt @@ -180,7 +180,16 @@ See |:verbose-cmd| for more information. the number 0 is returned. Note that there is no check for unreachable lines, thus there is no warning if commands follow ":return". - + Also, there is no check if the following + line contains a valid command. Forgetting the line + continuation backslash may go unnoticed: > + return 'some text' + .. ' some more text' +< Will happily return "some text" without an error. It + should have been: > + return 'some text' + \ .. ' some more text' +< If the ":return" is used after a |:try| but before the matching |:finally| (if present), the commands following the ":finally" up to the matching |:endtry| @@ -343,7 +352,6 @@ is used as a method: > ============================================================================== - 3. Automatically loading functions ~ *autoload-functions* When using many or large functions, it's possible to automatically define them @@ -388,6 +396,10 @@ file should then define the function like this: > echo "Done!" endfunction +If the file doesn't exist, Vim will also search in 'packpath' (under "start") +to allow calling packages' functions from your |vimrc| when the packages have +not been added to 'runtimepath' yet (see |packages|). + The file name and the name used before the # in the function must match exactly, and the defined function must have the name exactly as it will be called. diff --git a/runtime/doc/usr_01.txt b/runtime/doc/usr_01.txt index 52fbf06ec4..f0e2462fae 100644 --- a/runtime/doc/usr_01.txt +++ b/runtime/doc/usr_01.txt @@ -85,7 +85,7 @@ The Vim user manual and reference manual are Copyright (c) 1988-2003 by Bram Moolenaar. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later. The latest version is presently available at: - http://www.opencontent.org/openpub/ + https://www.opencontent.org/openpub/ People who contribute to the manuals must agree with the above copyright notice. diff --git a/runtime/doc/usr_02.txt b/runtime/doc/usr_02.txt index f822e7d4b8..11afe39742 100644 --- a/runtime/doc/usr_02.txt +++ b/runtime/doc/usr_02.txt @@ -33,7 +33,7 @@ On Unix you can type this at any command prompt. If you are running Microsoft Windows, open a Command Prompt and enter the command. In either case, Vim starts editing a file called file.txt. Because this is a new file, you get a blank window. This is what your screen will look like: - +> +---------------------------------------+ |# | |~ | @@ -43,7 +43,7 @@ blank window. This is what your screen will look like: |"file.txt" [New file] | +---------------------------------------+ ('#' is the cursor position.) - +< The tilde (~) lines indicate lines not in the file. In other words, when Vim runs out of file to display, it displays tilde lines. At the bottom of the screen, a message line indicates the file is named file.txt and shows that you @@ -83,7 +83,7 @@ limerick, this is what you type: > After typing "turtle" you press the <Enter> key to start a new line. Finally you press the <Esc> key to stop Insert mode and go back to Normal mode. You now have two lines of text in your Vim window: - +> +---------------------------------------+ |A very intelligent turtle | |Found programming Unix a hurdle | @@ -91,7 +91,7 @@ now have two lines of text in your Vim window: |~ | | | +---------------------------------------+ - +< WHAT IS THE MODE? @@ -105,7 +105,7 @@ with a colon). Finish this command by pressing the <Enter> key (all commands that start with a colon are finished this way). Now, if you type the "i" command Vim will display --INSERT-- at the bottom of the window. This indicates you are in Insert mode. - +> +---------------------------------------+ |A very intelligent turtle | |Found programming Unix a hurdle | @@ -113,7 +113,7 @@ of the window. This indicates you are in Insert mode. |~ | |-- INSERT -- | +---------------------------------------+ - +< If you press <Esc> to go back to Normal mode the last line will be made blank. @@ -182,7 +182,7 @@ throwback to the old days of the typewriter, when you deleted things by typing xxxx over them.) Move the cursor to the beginning of the first line, for example, and type xxxxxxx (seven x's) to delete "A very ". The result should look like this: - +> +---------------------------------------+ |intelligent turtle | |Found programming Unix a hurdle | @@ -190,14 +190,14 @@ look like this: |~ | | | +---------------------------------------+ - +< Now you can insert new text, for example by typing: > iA young <Esc> This begins an insert (the i), inserts the words "A young", and then exits insert mode (the final <Esc>). The result: - +> +---------------------------------------+ |A young intelligent turtle | |Found programming Unix a hurdle | @@ -205,13 +205,13 @@ insert mode (the final <Esc>). The result: |~ | | | +---------------------------------------+ - +< DELETING A LINE To delete a whole line use the "dd" command. The following line will then move up to fill the gap: - +> +---------------------------------------+ |Found programming Unix a hurdle | |~ | @@ -219,7 +219,7 @@ then move up to fill the gap: |~ | | | +---------------------------------------+ - +< DELETING A LINE BREAK diff --git a/runtime/doc/usr_03.txt b/runtime/doc/usr_03.txt index 74674fdb42..2b0d40ba32 100644 --- a/runtime/doc/usr_03.txt +++ b/runtime/doc/usr_03.txt @@ -224,7 +224,7 @@ you can see? This figure shows the three commands you can use: +---------------------------+ Hints: "H" stands for Home, "M" for Middle and "L" for Last. Alternatively, -"H" for high, "M" for Middle and "L" for low. +"H" for High, "M" for Middle and "L" for Low. ============================================================================== *03.6* Telling where you are diff --git a/runtime/doc/usr_05.txt b/runtime/doc/usr_05.txt index 0e94d9a1b1..24d6185eae 100644 --- a/runtime/doc/usr_05.txt +++ b/runtime/doc/usr_05.txt @@ -107,7 +107,7 @@ Display an incomplete command in the lower right corner of the Vim window, left of the ruler. For example, when you type "2f", Vim is waiting for you to type the character to find and "2f" is displayed. When you press "w" next, the "2fw" command is executed and the displayed "2f" is removed. - +> +-------------------------------------------------+ |text in the Vim window | |~ | @@ -119,7 +119,7 @@ the "2fw" command is executed and the displayed "2f" is removed. > set incsearch - +< Display matches for a search pattern while you type. > @@ -127,8 +127,8 @@ Display matches for a search pattern while you type. This defines a key mapping. More about that in the next section. This defines the "Q" command to do formatting with the "gq" operator. This is how -it worked before Vim 5.0. Otherwise the "Q" command starts Ex mode, but you -will not need it. +it worked before Vim 5.0. Otherwise the "Q" command repeats the last recorded +register. > vnoremap _g y:exe "grep /" .. escape(@", '\\/') .. "/ *.c *.h"<CR> @@ -320,7 +320,7 @@ Where can you find plugins? - Some are always loaded, you can see them in the directory $VIMRUNTIME/plugin. - Some come with Vim. You can find them in the directory $VIMRUNTIME/macros and its sub-directories and under $VIM/vimfiles/pack/dist/opt/. -- Download from the net. There is a large collection on http://www.vim.org. +- Download from the net. There is a large collection on https://www.vim.org. - They are sometimes posted in a Vim maillist. - You could write one yourself, see |write-plugin|. diff --git a/runtime/doc/usr_06.txt b/runtime/doc/usr_06.txt index 8eda33b4f0..755e6e816a 100644 --- a/runtime/doc/usr_06.txt +++ b/runtime/doc/usr_06.txt @@ -14,8 +14,7 @@ screen. |06.2| No or wrong colors? |06.3| Different colors |06.4| With colors or without colors -|06.5| Printing with colors -|06.6| Further reading +|06.5| Further reading Next chapter: |usr_07.txt| Editing more than one file Previous chapter: |usr_05.txt| Set your settings @@ -191,59 +190,7 @@ buffer, set the 'syntax' option: > :set syntax=ON < ============================================================================== -*06.5* Printing with colors *syntax-printing* - -In the MS-Windows version you can print the current file with this command: > - - :hardcopy - -You will get the usual printer dialog, where you can select the printer and a -few settings. If you have a color printer, the paper output should look the -same as what you see inside Vim. But when you use a dark background the -colors will be adjusted to look good on white paper. - -There are several options that change the way Vim prints: - 'printdevice' - 'printheader' - 'printfont' - 'printoptions' - -To print only a range of lines, use Visual mode to select the lines and then -type the command: > - - v100j:hardcopy - -"v" starts Visual mode. "100j" moves a hundred lines down, they will be -highlighted. Then ":hardcopy" will print those lines. You can use other -commands to move in Visual mode, of course. - -This also works on Unix, if you have a PostScript printer. Otherwise, you -will have to do a bit more work. You need to convert the text to HTML first, -and then print it from a web browser. - -Convert the current file to HTML with this command: > - - :TOhtml - -In case that doesn't work: > - - :source $VIMRUNTIME/syntax/2html.vim - -You will see it crunching away, this can take quite a while for a large file. -Some time later another window shows the HTML code. Now write this somewhere -(doesn't matter where, you throw it away later): -> - :write main.c.html - -Open this file in your favorite browser and print it from there. If all goes -well, the output should look exactly as it does in Vim. See |2html.vim| for -details. Don't forget to delete the HTML file when you are done with it. - -Instead of printing, you could also put the HTML file on a web server, and let -others look at the colored text. - -============================================================================== -*06.6* Further reading +*06.5* Further reading |usr_44.txt| Your own syntax highlighted. |syntax| All the details. diff --git a/runtime/doc/usr_08.txt b/runtime/doc/usr_08.txt index 1d20913a14..0ba03a4861 100644 --- a/runtime/doc/usr_08.txt +++ b/runtime/doc/usr_08.txt @@ -32,7 +32,7 @@ The easiest way to open a new window is to use the following command: > This command splits the screen into two windows and leaves the cursor in the top one: - +> +----------------------------------+ |/* file one.c */ | |~ | @@ -43,7 +43,7 @@ top one: |one.c=============================| | | +----------------------------------+ - +< What you see here is two windows on the same file. The line with "====" is the status line. It displays information about the window above it. (In practice the status line will be in reverse video.) @@ -87,7 +87,7 @@ The following command opens a second window and starts editing the given file: :split two.c If you were editing one.c, then the result looks like this: - +> +----------------------------------+ |/* file two.c */ | |~ | @@ -98,7 +98,7 @@ If you were editing one.c, then the result looks like this: |one.c=============================| | | +----------------------------------+ - +< To open a window on a new, empty file, use this: > :new @@ -170,7 +170,7 @@ or: > :vsplit two.c The result looks something like this: - +> +--------------------------------------+ |/* file two.c */ |/* file one.c */ | |~ |~ | @@ -179,7 +179,7 @@ The result looks something like this: |two.c===============one.c=============| | | +--------------------------------------+ - +< Actually, the | lines in the middle will be in reverse video. This is called the vertical separator. It separates the two windows left and right of it. @@ -218,7 +218,7 @@ cursor keys can also be used, if you like. You have split a few windows, but now they are in the wrong place. Then you need a command to move the window somewhere else. For example, you have three windows like this: - +> +----------------------------------+ |/* file two.c */ | |~ | @@ -233,7 +233,7 @@ windows like this: |one.c=============================| | | +----------------------------------+ - +< Clearly the last one should be at the top. Go to that window (using CTRL-W w) and then type this command: > @@ -244,7 +244,7 @@ the very top. You will notice that K is again used for moving upwards. When you have vertical splits, CTRL-W K will move the current window to the top and make it occupy the full width of the Vim window. If this is your layout: - +> +-------------------------------------------+ |/* two.c */ |/* three.c */ |/* one.c */ | |~ |~ |~ | @@ -255,9 +255,9 @@ layout: |two.c=========three.c=========one.c========| | | +-------------------------------------------+ - +< Then using CTRL-W K in the middle window (three.c) will result in: - +> +-------------------------------------------+ |/* three.c */ | |~ | @@ -268,7 +268,7 @@ Then using CTRL-W K in the middle window (three.c) will result in: |two.c==================one.c===============| | | +-------------------------------------------+ - +< The other three similar commands (you can probably guess these now): CTRL-W H move window to the far left @@ -316,7 +316,7 @@ To make Vim open a window for each file, start it with the "-o" argument: > vim -o one.txt two.txt three.txt This results in: - +> +-------------------------------+ |file one.txt | |~ | @@ -329,7 +329,7 @@ This results in: |three.txt======================| | | +-------------------------------+ - +< The "-O" argument is used to get vertically split windows. When Vim is already running, the ":all" command opens a window for each file in the argument list. ":vertical all" does it with vertical splits. @@ -347,7 +347,7 @@ Type this command in a shell to start Nvim in diff mode: > Vim will start, with two windows side by side. You will only see the line in which you added characters, and a few lines above and below it. - +> VV VV +-----------------------------------------+ |+ +--123 lines: /* a|+ +--123 lines: /* a| <- fold @@ -366,7 +366,7 @@ in which you added characters, and a few lines above and below it. |main.c~==============main.c==============| | | +-----------------------------------------+ - +< (This picture doesn't show the highlighting, use "nvim -d" for that.) The lines that were not modified have been collapsed into one line. This is @@ -519,7 +519,7 @@ Assume you are editing "thisfile". To create a new tab page use this command: > This will edit the file "thatfile" in a window that occupies the whole Vim window. And you will notice a bar at the top with the two file names: - +> +----------------------------------+ | thisfile | /thatfile/ __________X| (thatfile is bold) |/* thatfile */ | @@ -530,13 +530,13 @@ window. And you will notice a bar at the top with the two file names: |~ | | | +----------------------------------+ - +< You now have two tab pages. The first one has a window for "thisfile" and the second one a window for "thatfile". It's like two pages that are on top of each other, with a tab sticking out of each page showing the file name. Now use the mouse to click on "thisfile" in the top line. The result is - +> +----------------------------------+ | /thisfile/ | thatfile __________X| (thisfile is bold) |/* thisfile */ | @@ -547,7 +547,7 @@ Now use the mouse to click on "thisfile" in the top line. The result is |~ | | | +----------------------------------+ - +< Thus you can switch between tab pages by clicking on the label in the top line. If you don't have a mouse or don't want to use it, you can use the "gt" command. Mnemonic: Goto Tab. @@ -558,7 +558,7 @@ Now let's create another tab page with the command: > This makes a new tab page with one window that is editing the same buffer as the window we were in: - +> +-------------------------------------+ | thisfile | /thisfile/ | thatfile __X| (thisfile is bold) |/* thisfile */ | @@ -569,7 +569,7 @@ the window we were in: |~ | | | +-------------------------------------+ - +< You can put ":tab" before any Ex command that opens a window. The window will be opened in a new tab page. Another example: > diff --git a/runtime/doc/usr_10.txt b/runtime/doc/usr_10.txt index 8844671e01..3e45fda882 100644 --- a/runtime/doc/usr_10.txt +++ b/runtime/doc/usr_10.txt @@ -813,10 +813,10 @@ REDRAWING THE SCREEN If the external command produced an error message, the display may have been messed up. Vim is very efficient and only redraws those parts of the screen that it knows need redrawing. But it can't know about what another program -has written. To tell Vim to redraw the screen: > - +has written. To tell Vim to redraw the screen: +> CTRL-L - +< ============================================================================== Next chapter: |usr_11.txt| Recovering from a crash diff --git a/runtime/doc/usr_20.txt b/runtime/doc/usr_20.txt index 6a8836c8e8..2b69862fe1 100644 --- a/runtime/doc/usr_20.txt +++ b/runtime/doc/usr_20.txt @@ -338,7 +338,7 @@ Open the command line window with this command: > Vim now opens a (small) window at the bottom. It contains the command line history, and an empty line at the end: - +> +-------------------------------------+ |other window | |~ | @@ -353,7 +353,7 @@ history, and an empty line at the end: |command-line=========================| | | +-------------------------------------+ - +< You are now in Normal mode. You can use the "hjkl" keys to move around. For example, move up with "5k" to the ":e config.h.in" line. Type "$h" to go to the "i" of "in" and type "cwout". Now you have changed the line to: diff --git a/runtime/doc/usr_21.txt b/runtime/doc/usr_21.txt index add5d48073..191d333f3d 100644 --- a/runtime/doc/usr_21.txt +++ b/runtime/doc/usr_21.txt @@ -255,7 +255,8 @@ well stand for "source"). The windows that were open are restored, with the same position and size as before. Mappings and option values are like before. What exactly is restored depends on the 'sessionoptions' option. The -default value is "blank,buffers,curdir,folds,help,options,winsize". +default value is: +"blank,buffers,curdir,folds,help,options,tabpages,winsize,terminal". blank keep empty windows buffers all buffers, not only the ones in a window @@ -263,7 +264,9 @@ default value is "blank,buffers,curdir,folds,help,options,winsize". folds folds, also manually created ones help the help window options all options and mappings + tabpages all tab pages winsize window sizes + terminal include terminal windows Change this to your liking. To also restore the size of the Vim window, for example, use: > @@ -299,7 +302,7 @@ session file as a starting point. use, and save this in a session. Then you can go back to this layout whenever you want. For example, this is a nice layout to use: - +> +----------------------------------------+ | VIM - main help file | | | @@ -315,7 +318,7 @@ you want. |~/=========|[No File]===================| | | +----------------------------------------+ - +< This has a help window at the top, so that you can read this text. The narrow vertical window on the left contains a file explorer. This is a Vim plugin that lists the contents of a directory. You can select files to edit there. @@ -451,7 +454,7 @@ Use this format for the modeline: The "any-text" indicates that you can put any text before and after the part that Vim will use. This allows making it look like a comment, like what was -done above with /* and */. +done above with "/*" and "*/". The " vim:" part is what makes Vim recognize this line. There must be white space before "vim", or "vim" must be at the start of the line. Thus using something like "gvim:" will not work. diff --git a/runtime/doc/usr_25.txt b/runtime/doc/usr_25.txt index 2efb67e55f..955d2ae5f0 100644 --- a/runtime/doc/usr_25.txt +++ b/runtime/doc/usr_25.txt @@ -325,16 +325,16 @@ Let's attempt to show this with one line of text. The cursor is on the "w" of currently visible. The "window"s below the text indicate the text that is visible after the command left of it. - |<-- current window -->| + `|<-- current window -->|` some long text, part of which is visible in the window ~ - ze |<-- window -->| - zH |<-- window -->| - 4zh |<-- window -->| - zh |<-- window -->| - zl |<-- window -->| - 4zl |<-- window -->| - zL |<-- window -->| - zs |<-- window -->| + ze `|<-- window -->|` + zH `|<-- window -->|` + 4zh `|<-- window -->|` + zh `|<-- window -->|` + zl `|<-- window -->|` + 4zl `|<-- window -->|` + zL `|<-- window -->|` + zs `|<-- window -->|` MOVING WITH WRAP OFF @@ -350,7 +350,7 @@ scroll: gM to middle of the text in this line g$ to last visible character in this line - |<-- window -->| + `|<-- window -->|` some long text, part of which is visible in one line ~ g0 g^ gm gM g$ @@ -365,7 +365,7 @@ broken halfway, which makes them hard to read. 'linebreak' option. Vim then breaks lines at an appropriate place when displaying the line. The text in the file remains unchanged. Without 'linebreak' text might look like this: - +> +---------------------------------+ |letter generation program for a b| |ank. They wanted to send out a s| @@ -373,12 +373,13 @@ displaying the line. The text in the file remains unchanged. |eir richest 1000 customers. Unfo| |rtunately for the programmer, he | +---------------------------------+ +< After: > :set linebreak it looks like this: - +> +---------------------------------+ |letter generation program for a | |bank. They wanted to send out a | @@ -386,7 +387,7 @@ it looks like this: |their richest 1000 customers. | |Unfortunately for the programmer,| +---------------------------------+ - +< Related options: 'breakat' specifies the characters where a break can be inserted. 'showbreak' specifies a string to show at the start of broken line. @@ -425,7 +426,7 @@ That looks complicated. Let's break it up in pieces: into one line. Starting with this text, containing eight lines broken at column 30: - +> +----------------------------------+ |A letter generation program | |for a bank. They wanted to | @@ -436,9 +437,9 @@ Starting with this text, containing eight lines broken at column 30: |customers. Unfortunately for | |the programmer, | +----------------------------------+ - +< You end up with two lines: - +> +----------------------------------+ |A letter generation program for a | |bank. They wanted to send out a s| @@ -446,7 +447,7 @@ You end up with two lines: |To their richest 1000 customers. | |Unfortunately for the programmer, | +----------------------------------+ - +< Note that this doesn't work when the separating line is blank but not empty; when it contains spaces and/or tabs. This command does work with blank lines: > diff --git a/runtime/doc/usr_29.txt b/runtime/doc/usr_29.txt index d8c556c281..751cb9a902 100644 --- a/runtime/doc/usr_29.txt +++ b/runtime/doc/usr_29.txt @@ -33,9 +33,8 @@ 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 Universal/Exuberant ctags at: - http://ctags.io ~ - http://ctags.sf.net ~ +If you do not have it yet, you can find Universal ctags at: + https://ctags.io ~ Universal ctags is preferred, Exuberant ctags is no longer being developed. @@ -54,7 +53,7 @@ function. The "write_line" function calls "write_char". You need to figure out what it does. So you position the cursor over the call to "write_char" and press CTRL-]. Now you are at the definition of "write_char". - +> +-------------------------------------+ |void write_block(char **s; int cnt) | |{ | @@ -80,7 +79,7 @@ CTRL-]. Now you are at the definition of "write_char". | putchar((int)(unsigned char)c); | |} | +------------------------------------+ - +< The ":tags" command shows the list of tags that you traversed through: :tags @@ -268,9 +267,6 @@ doesn't work if the tags file isn't sorted. The 'taglength' option can be used to tell Vim the number of significant characters in a tag. -Cscope is a free program. It does not only find places where an identifier is -declared, but also where it is used. See |cscope|. - ============================================================================== *29.2* The preview window @@ -429,7 +425,7 @@ MOVING IN COMMENTS To move back to the start of a comment use "[/". Move forward to the end of a comment with "]/". This only works for /* - */ comments. - +> +-> +-> /* | [/ | * A comment about --+ [/ | +-- * wonderful life. | ]/ @@ -438,7 +434,7 @@ comment with "]/". This only works for /* - */ comments. +-- foo = bar * 3; --+ | ]/ /* a short comment */ <-+ - +< ============================================================================== *29.4* Finding global identifiers @@ -579,7 +575,7 @@ and jump to the first place where the word under the cursor is used: > Hint: Goto Definition. This command is very useful to find a variable or function that was declared locally ("static", in C terms). Example (cursor on "counter"): - +> +-> static int counter = 0; | | int get_counter(void) @@ -587,7 +583,7 @@ function that was declared locally ("static", in C terms). Example (cursor on | ++counter; +-- return counter; } - +< To restrict the search even further, and look only in the current function, use this command: > @@ -597,7 +593,7 @@ This will go back to the start of the current function and find the first occurrence of the word under the cursor. Actually, it searches backwards to an empty line above a "{" in the first column. From there it searches forward for the identifier. Example (cursor on "idx"): - +> int find_entry(char *name) { +-> int idx; @@ -606,7 +602,7 @@ for the identifier. Example (cursor on "idx"): | if (strcmp(table[idx].name, name) == 0) +-- return idx; } - +< ============================================================================== Next chapter: |usr_30.txt| Editing programs diff --git a/runtime/doc/usr_30.txt b/runtime/doc/usr_30.txt index 98d1780cc4..7e7b3b21f4 100644 --- a/runtime/doc/usr_30.txt +++ b/runtime/doc/usr_30.txt @@ -56,7 +56,7 @@ From this you can see that you have errors in the file "main.c". When you press <Enter>, Vim displays the file "main.c", with the cursor positioned on line 6, the first line with an error. You did not need to specify the file or the line number, Vim knew where to go by looking in the error messages. - +> +---------------------------------------------------+ |int main() | |{ | @@ -69,7 +69,7 @@ the line number, Vim knew where to go by looking in the error messages. | ~ | |(3 of 12): too many arguments to function 'do_sub' | +---------------------------------------------------+ - +< The following command goes to where the next error occurs: > :cnext diff --git a/runtime/doc/usr_32.txt b/runtime/doc/usr_32.txt index 8b489ea1e0..324efccf25 100644 --- a/runtime/doc/usr_32.txt +++ b/runtime/doc/usr_32.txt @@ -169,10 +169,10 @@ To travel forward in time again use the |:later| command: > The arguments are "s", "m" and "h", just like with |:earlier|. If you want even more details, or want to manipulate the information, you can -use the |undotree()| function. To see what it returns: > - +use the |undotree()| function. To see what it returns: +> :echo undotree() - +< ============================================================================== Next chapter: |usr_40.txt| Make new commands diff --git a/runtime/doc/usr_40.txt b/runtime/doc/usr_40.txt index f47c933124..8befb15528 100644 --- a/runtime/doc/usr_40.txt +++ b/runtime/doc/usr_40.txt @@ -226,7 +226,7 @@ When using a space inside a mapping, use <Space> (seven characters): > This makes the spacebar move a blank-separated word forward. It is not possible to put a comment directly after a mapping, because the " -character is considered to be part of the mapping. You can use |", this +character is considered to be part of the mapping. You can use `|"`, this starts a new, empty command with a comment. Example: > :map <Space> W| " Use spacebar to move forward a word @@ -657,10 +657,10 @@ To ignore all events, use the following command: > :set eventignore=all -To set it back to the normal behavior, make 'eventignore' empty: > - +To set it back to the normal behavior, make 'eventignore' empty: +> :set eventignore= - +< ============================================================================== Next chapter: |usr_41.txt| Write a Vim script diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 6690dad4a7..910aebae70 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -619,10 +619,12 @@ String manipulation: *string-functions* stridx() first index of a short string in a long string strridx() last index of a short string in a long string strlen() length of a string in bytes - strchars() length of a string in characters + strcharlen() length of a string in characters + strchars() number of characters in a string strwidth() size of string when displayed strdisplaywidth() size of string when displayed, deals with tabs setcellwidths() set character cell width overrides + getcellwidths() get character cell width overrides substitute() substitute a pattern match with a string submatch() get a specific match in ":s" and substitute() strpart() get part of a string using byte index @@ -639,6 +641,7 @@ String manipulation: *string-functions* execute() execute an Ex command and get the output win_execute() like execute() but in a specified window trim() trim characters from a string + gettext() lookup message translation List manipulation: *list-functions* get() get an item without error for wrong index @@ -783,6 +786,13 @@ Working with text in the current buffer: *text-functions* getcharsearch() return character search information setcharsearch() set character search information +Working with text in another buffer: + getbufline() get a list of lines from the specified buffer + getbufoneline() get a one line from the specified buffer + setbufline() replace a line in the specified buffer + appendbufline() append a list of lines in the specified buffer + deletebufline() delete lines from a specified buffer + *system-functions* *file-functions* System functions and manipulation of files: glob() expand wildcards @@ -817,6 +827,7 @@ System functions and manipulation of files: setenv() set an environment variable hostname() name of the system readfile() read a file into a List of lines + readblob() read a file into a Blob readdir() get a List of file names in a directory writefile() write a List of lines or Blob into a file @@ -835,8 +846,10 @@ Buffers, windows and the argument list: argidx() current position in the argument list arglistid() get id of the argument list argv() get one entry from the argument list + bufadd() add a file to the list of buffers bufexists() check if a buffer exists buflisted() check if a buffer exists and is listed + bufload() ensure a buffer is loaded bufloaded() check if a buffer exists and is loaded bufname() get the name of a specific buffer bufnr() get the buffer number of a specific buffer @@ -847,10 +860,6 @@ Buffers, windows and the argument list: bufwinid() get the window ID of a specific buffer bufwinnr() get the window number of a specific buffer winbufnr() get the buffer number of a specific window - getbufline() get a list of lines from the specified buffer - setbufline() replace a line in the specified buffer - appendbufline() append a list of lines in the specified buffer - deletebufline() delete lines from a specified buffer win_findbuf() find windows containing a buffer win_getid() get window ID of a window win_gettype() get type of window @@ -1051,7 +1060,6 @@ Various: *various-functions* exists() check if a variable, function, etc. exists has() check if a feature is supported in Vim changenr() return number of most recent change - cscope_connection() check if a cscope connection exists did_filetype() check if a FileType autocommand was used eventhandler() check if invoked by an event handler getpid() get process ID of Vim @@ -2624,7 +2632,7 @@ Further reading: |autoload|. ============================================================================== *41.16* Distributing Vim scripts *distribute-script* -Vim users will look for scripts on the Vim website: http://www.vim.org. +Vim users will look for scripts on the Vim website: https://www.vim.org. If you made something that is useful for others, share it! Vim scripts can be used on any system. There might not be a tar or gzip diff --git a/runtime/doc/usr_42.txt b/runtime/doc/usr_42.txt index 470f4e0fe5..9c5e3db72c 100644 --- a/runtime/doc/usr_42.txt +++ b/runtime/doc/usr_42.txt @@ -81,7 +81,7 @@ the far right. The second number (340) determines the location of the item within the pull-down menu. Lower numbers go on top, higher number on the bottom. These are the priorities in the File menu: - +> +-----------------+ 10.310 |Open... | 10.320 |Split-Open... | @@ -99,7 +99,7 @@ are the priorities in the File menu: 10.610 |Save-Exit | 10.620 |Exit | +-----------------+ - +< Notice that there is room in between the numbers. This is where you can insert your own items, if you really want to (it's often better to leave the standard menus alone and add a new menu for your own items). @@ -168,11 +168,11 @@ inserts a CTRL-C or CTRL-O for you. For example, if you use this command: Then the resulting menu commands will be: - Normal mode: * - Visual mode: CTRL-C * - Operator-pending mode: CTRL-C * - Insert mode: CTRL-O * - Command-line mode: CTRL-C * + Normal mode: `*` + Visual mode: CTRL-C `*` + Operator-pending mode: CTRL-C `*` + Insert mode: CTRL-O `*` + Command-line mode: CTRL-C `*` When in Command-line mode the CTRL-C will abandon the command typed so far. In Visual and Operator-pending mode CTRL-C will stop the mode. The CTRL-O in diff --git a/runtime/doc/usr_45.txt b/runtime/doc/usr_45.txt index 0d23ef50fd..95a2bc8f79 100644 --- a/runtime/doc/usr_45.txt +++ b/runtime/doc/usr_45.txt @@ -71,8 +71,8 @@ directory src/po/README.txt. programmer. You must know both English and the language you are translating to, of course. When you are satisfied with the translation, consider making it available -to others. Upload it at vim-online (http://vim.sf.net) or e-mail it to -the Vim maintainer <maintainer@vim.org>. Or both. +to others. Upload it to https://github.com/vim/vim or e-mail it to the Vim +maintainer <maintainer@vim.org>. Or both. ============================================================================== *45.2* Language for Menus @@ -166,10 +166,7 @@ script files, etc. You can regard 'encoding' as the setting for the internals of Vim. This example assumes you have this font on your system. The name in the example is for the X Window System. This font is in a package that is used to -enhance xterm with Unicode support. If you don't have this font, you might -find it here: - - http://www.cl.cam.ac.uk/~mgk25/download/ucs-fonts.tar.gz ~ +enhance xterm with Unicode support. For MS-Windows, some fonts have a limited number of Unicode characters. Try using the "Courier New" font. You can use the Edit/Select Font... menu to @@ -178,10 +175,7 @@ though. Example: > :set guifont=courier_new:h12 -If it doesn't work well, try getting a fontpack. If Microsoft didn't move it, -you can find it here: - - http://www.microsoft.com/typography/fonts/default.aspx ~ +If it doesn't work well, try getting a fontpack. Now you have told Vim to use Unicode internally and display text with a Unicode font. diff --git a/runtime/doc/usr_toc.txt b/runtime/doc/usr_toc.txt index c61bb55c26..dd0d5784f5 100644 --- a/runtime/doc/usr_toc.txt +++ b/runtime/doc/usr_toc.txt @@ -111,8 +111,7 @@ Read this from start to end to learn the essential commands. |06.2| No or wrong colors? |06.3| Different colors |06.4| With colors or without colors - |06.5| Printing with colors - |06.6| Further reading + |06.5| Further reading |usr_07.txt| Editing more than one file |07.1| Edit another file diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index cd178cfbbb..e13d892fd6 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -102,9 +102,7 @@ g8 Print the hex values of the bytes used in the *:p* *:pr* *:print* *E749* :[range]p[rint] [flags] Print [range] lines (default current line). - Note: If you are looking for a way to print your text - on paper see |:hardcopy|. In the GUI you can use the - File.Print menu entry. + In the GUI you can use the File.Print menu entry. See |ex-flags| for [flags]. The |:filter| command can be used to only show lines matching a pattern. diff --git a/runtime/doc/vi_diff.txt b/runtime/doc/vi_diff.txt index cef2859eb5..afabddb7f9 100644 --- a/runtime/doc/vi_diff.txt +++ b/runtime/doc/vi_diff.txt @@ -337,10 +337,6 @@ Viminfo. The 'viminfo' option can be set to select which items to store in the .viminfo file. This is off by default. -Printing. |printing| - The |:hardcopy| command sends text to the printer. This can include - syntax highlighting. - Mouse support. |mouse-using| The mouse is supported in the GUI version, in an xterm for Unix, for BSDs with sysmouse, for Linux with gpm, and for Win32. It can be used diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 62b755d64b..bb3b670b24 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -36,7 +36,6 @@ centralized reference of the differences. - 'belloff' defaults to "all" - 'compatible' is always disabled - 'complete' excludes "i" -- 'cscopeverbose' is enabled - 'directory' defaults to ~/.local/state/nvim/swap// (|xdg|), auto-created - 'display' defaults to "lastline" - 'encoding' is UTF-8 (cf. 'fileencoding' for file-content encoding) @@ -74,12 +73,11 @@ centralized reference of the differences. - 'wildoptions' defaults to "pum,tagfile" - |man.lua| plugin is enabled, so |:Man| is available by default. -- |matchit| plugin is enabled. To disable it in your config: > +- |matchit| plugin is enabled. To disable it in your config: >vim :let loaded_matchit = 1 - |g:vimsyn_embed| defaults to "l" to enable Lua highlighting - Default Mouse ~ *default-mouse* *disable-mouse* By default the mouse is enabled, and <RightMouse> opens a |popup-menu| with @@ -89,23 +87,26 @@ typing ":". If you don't like this you can disable the mouse in your |config| using any of the following: -- Disable mouse completely by unsetting the 'mouse' option: > +- Disable mouse completely by unsetting the 'mouse' option: >vim set mouse= -- Pressing <RightMouse> extends selection instead of showing popup-menu: > +- Pressing <RightMouse> extends selection instead of showing popup-menu: >vim set mousemodel=extend -- Pressing <A-LeftMouse> releases mouse until the cursor moves: > +- Pressing <A-LeftMouse> releases mouse until the cursor moves: >vim nnoremap <A-LeftMouse> <Cmd> \ set mouse=<Bar> \ echo 'mouse OFF until next cursor-move'<Bar> \ autocmd CursorMoved * ++once set mouse&<Bar> \ echo 'mouse ON'<CR> < - +To remove the "How-to disable mouse" menu item and the separator above it: >vim + aunmenu PopUp.How-to\ disable\ mouse + aunmenu PopUp.-1- +< Default Mappings ~ *default-mappings* 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". -> +>vim nnoremap Y y$ nnoremap <C-L> <Cmd>nohlsearch<Bar>diffupdate<Bar>normal! <C-L><CR> inoremap <C-U> <C-G>u<C-U> @@ -128,7 +129,7 @@ nvim_cmdwin: ============================================================================== 3. New Features *nvim-features* -MAJOR COMPONENTS ~ +MAJOR COMPONENTS API |API| Job control |job-control| @@ -146,7 +147,7 @@ Terminal emulator |terminal| Vimscript parser |nvim_parse_expression()| XDG base directories |xdg| -USER EXPERIENCE ~ +USER EXPERIENCE Working intuitively and consistently is a major goal of Nvim. @@ -159,7 +160,7 @@ Working intuitively and consistently is a major goal of Nvim. - Nvim avoids features that cannot be provided on all platforms; instead that is delegated to external plugins/extensions. E.g. the `-X` platform-specific option is "sometimes" available in Vim (with potential surprises: - http://stackoverflow.com/q/14635295). + https://stackoverflow.com/q/14635295). - Vim's internal test functions (test_autochdir(), test_settime(), etc.) are not exposed (nor implemented); instead Nvim has a robust API. @@ -177,7 +178,7 @@ Some features are built in that otherwise required external plugins: - Highlighting the yanked region, see |lua-highlight|. -ARCHITECTURE ~ +ARCHITECTURE External plugins run in separate processes. |remote-plugin| This improves stability and allows those plugins to work without blocking the editor. Even @@ -188,7 +189,7 @@ Platform and I/O facilities are built upon libuv. Nvim benefits from libuv features and bug fixes, and other projects benefit from improvements to libuv by Nvim developers. -FEATURES ~ +FEATURES Command-line highlighting: The expression prompt (|@=|, |c_CTRL-R_=|, |i_CTRL-R_=|) is highlighted @@ -207,6 +208,7 @@ Commands: |:match| can be invoked before highlight group is defined |:source| works with Lua User commands can support |:command-preview| to show results as you type + |:write| with "++p" flag creates parent directories. Events: |RecordingEnter| @@ -227,6 +229,7 @@ Functions: |stdpath()| |system()|, |systemlist()| can run {cmd} directly (without 'shell') |matchadd()| can be called before highlight group is defined + |writefile()| with "p" flag creates parent directories. Highlight groups: |highlight-blend| controls blend level for a highlight group @@ -268,6 +271,7 @@ Options: 'tabline' %@Func@foo%X can call any function on mouse-click 'winblend' pseudo-transparency in floating windows |api-floatwin| 'winhighlight' window-local highlights + 'diffopt' has the option `linematch`. Signs: Signs are removed if the associated line is deleted. @@ -277,7 +281,20 @@ Variables: |v:windowid| is always available (for use by external UIs) ============================================================================== -4. Changed features *nvim-features-changed* +4. Upstreamed features *nvim-upstreamed* + +These Nvim features were later integrated into Vim. + +- 'fillchars' flags: "eob" +- 'wildoptions' flags: "pum" enables popupmenu for wildmode completion +- |<Cmd>| +- |WinClosed| +- |WinScrolled| +- |:sign-define| "numhl" argument +- |:source| works with anonymous (no file) scripts + +============================================================================== +5. Changed features *nvim-features-changed* Nvim always builds with all features, in contrast to Vim which may have certain features removed/added at compile-time. |feature-compile| @@ -302,7 +319,7 @@ are always available and may be used simultaneously. See |provider-python|. structures. 2. |string()| fails immediately on nested containers, not when recursion limit was exceeded. -2. When |:echo| encounters duplicate containers like > +2. When |:echo| encounters duplicate containers like >vim let l = [] echo [l, l] @@ -417,6 +434,8 @@ Options: 'jumpoptions' "view" tries to restore the |mark-view| when moving through the |jumplist|, |changelist|, |alternate-file| or using |mark-motions|. 'shortmess' the "F" flag does not affect output from autocommands + 'exrc' searches for ".nvim.lua", ".nvimrc", or ".exrc" files. The user is + prompted whether to trust the file. Shell: Shell output (|:!|, |:make|, …) is always routed through the UI, so it @@ -460,7 +479,7 @@ TUI: < *'term'* *E529* *E530* *E531* 'term' reflects the terminal type derived from |$TERM| and other environment - checks. For debugging only; not reliable during startup. > + checks. For debugging only; not reliable during startup. >vim :echo &term < "builtin_x" means one of the |builtin-terms| was chosen, because the expected terminfo file was not found on the system. @@ -497,7 +516,7 @@ Working directory (Vim implemented some of these later than Nvim): working directory. Use `getcwd(-1, -1)` to get the global working directory. ============================================================================== -5. Missing legacy features *nvim-features-missing* +6. Missing legacy features *nvim-features-missing* Some legacy Vim features are not yet implemented: @@ -510,7 +529,7 @@ Some legacy Vim features are not yet implemented: *:gvim* ============================================================================== -6. Removed features *nvim-features-removed* +7. Removed features *nvim-features-removed* These Vim features were intentionally removed from Nvim. @@ -530,6 +549,7 @@ Aliases: Commands: :fixdel + :hardcopy :helpfind :mode (no longer accepts an argument) :open @@ -541,6 +561,10 @@ Commands: :sleep! (does not hide the cursor; same as :sleep) :smile :tearoff + :cstag + :cscope + :lcscope + :scscope Compile-time features: Emacs tags support @@ -548,6 +572,7 @@ Compile-time features: Eval: Vim9script + *cscope_connection()* *js_encode()* *js_decode()* *v:none* (used by Vim to represent JavaScript "undefined"); use |v:null| instead. @@ -561,7 +586,7 @@ Events: Highlight groups: *hl-StatusLineTerm* *hl-StatusLineTermNC* are unnecessary because Nvim supports 'winhighlight' window-local highlights. - For example, to mimic Vim's StatusLineTerm: > + For example, to mimic Vim's StatusLineTerm: >vim hi StatusLineTerm ctermfg=black ctermbg=green hi StatusLineTermNC ctermfg=green autocmd TermOpen,WinEnter * if &buftype=='terminal' @@ -579,6 +604,13 @@ Options: *'cp'* *'nocompatible'* *'nocp'* *'compatible'* (Nvim is always "nocompatible".) 'cpoptions' (gjkHw<*- and all POSIX flags were removed) *'cryptmethod'* *'cm'* *'key'* (Vim encryption implementation) + cscopepathcomp + cscopeprg + cscopequickfix + cscoperelative + cscopetag + cscopetagorder + cscopeverbose *'ed'* *'edcompatible'* *'noed'* *'noedcompatible'* 'encoding' ("utf-8" is always used) esckeys @@ -590,7 +622,7 @@ Options: *'imactivatekey'* *'imak'* *'imstatusfunc'* *'imsf'* *'insertmode'* *'im'* Use the following script to emulate 'insertmode': -> +>vim autocmd BufWinEnter * startinsert inoremap <Esc> <C-X><C-Z><C-]> inoremap <C-C> <C-X><C-Z> @@ -624,9 +656,18 @@ Options: Use |g8| or |ga|. See |mbyte-combining|. *'maxmem'* Nvim delegates memory-management to the OS. *'maxmemtot'* Nvim delegates memory-management to the OS. + *'printdevice'* + *'printencoding'* + *'printexpr'* + *'printfont'* + *'printheader'* + *'printmbcharset'* *'prompt'* *'noprompt'* *'remap'* *'noremap'* *'restorescreen'* *'rs'* *'norestorescreen'* *'nors'* + *'secure'* + Everything is allowed in 'exrc' files since they must be explicitly marked + trusted. *'shelltype'* *'shortname'* *'sn'* *'noshortname'* *'nosn'* *'swapsync'* *'sws'* @@ -694,5 +735,14 @@ TUI: at how the terminal is sending CSI. Nvim does not issue such a sequence and always uses 7-bit control sequences. +Cscope: + *cscope* + Cscope support has been removed in favour of LSP based solutions. + +Hardcopy: + *hardcopy* + `:hardcopy` was removed. Instead, use `:TOhtml` and print the resulting HTML + using a web browser or some other HTML viewer. + ============================================================================== vim:tw=78:ts=8:sw=2:et:ft=help:norl: diff --git a/runtime/doc/visual.txt b/runtime/doc/visual.txt index 5383ea4f72..0c6bd4f3a1 100644 --- a/runtime/doc/visual.txt +++ b/runtime/doc/visual.txt @@ -103,7 +103,7 @@ gn Search forward for the last used search pattern, like E.g., "dgn" deletes the text of the next match. If Visual mode is active, extends the selection until the end of the next match. - 'wrapscan' applies + 'wrapscan' applies. Note: Unlike `n` the search direction does not depend on the previous search command. @@ -501,11 +501,11 @@ mode Vim automatically switches to Visual mode, so that the same behavior as in Visual mode is effective. If you don't want this use |:xmap| or |:smap|. One particular edge case: > - :vnoremap <C-K> <Esc> + :vnoremap <C-K> <Esc> This ends Visual mode when in Visual mode, but in Select mode it does not work, because Select mode is restored after executing the mapped keys. You need to use: > - :snoremap <C-K> <Esc> + :snoremap <C-K> <Esc> < Users will expect printable characters to replace the selected area. Therefore avoid mapping printable characters in Select mode. Or use diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt index 45cedd2cd8..61f5013f47 100644 --- a/runtime/doc/windows.txt +++ b/runtime/doc/windows.txt @@ -284,8 +284,8 @@ Opens a vertically split, full-height window on the "tags" file at the far left of the Vim window. +------------------------------------------------------------------------------ Closing a window ----------------- :q[uit] :{count}q[uit] *:count_quit* @@ -537,9 +537,9 @@ CTRL-W = Make all windows (almost) equally high and wide, but use Windows with 'winfixheight' set keep their height and windows with 'winfixwidth' set keep their width. To equalize only vertically (make window equally high) use - `vertical wincmd =` + `vertical wincmd =` . To equalize only horizontally (make window equally wide) use - `horizontal wincmd =` + `horizontal wincmd =` . :res[ize] -N *:res* *:resize* *CTRL-W_-* CTRL-W - Decrease current window height by N (default 1). @@ -607,6 +607,53 @@ it). The minimal height and width of a window is set with 'winminheight' and 'winminwidth'. These are hard values, a window will never become smaller. + +WinScrolled and WinResized autocommands ~ + *win-scrolled-resized* +If you want to get notified of changes in window sizes, the |WinResized| +autocommand event can be used. +If you want to get notified of text in windows scrolling vertically or +horizontally, the |WinScrolled| autocommand event can be used. This will also +trigger in window size changes. +Exception: the events will not be triggered when the text scrolls for +'incsearch'. + *WinResized-event* +The |WinResized| event is triggered after updating the display, several +windows may have changed size then. A list of the IDs of windows that changed +since last time is provided in the v:event.windows variable, for example: + [1003, 1006] + *WinScrolled-event* +The |WinScrolled| event is triggered after |WinResized|, and also if a window +was scrolled. That can be vertically (the text at the top of the window +changed) or horizontally (when 'wrap' is off or when the first displayed part +of the first line changes). Note that |WinScrolled| will trigger many more +times than |WinResized|, it may slow down editing a bit. + +The information provided by |WinScrolled| is a dictionary for each window that +has changes, using the window ID as the key, and a total count of the changes +with the key "all". Example value for |v:event|: + { + all: {width: 0, height: 2, leftcol: 0, skipcol: 0, topline: 1, topfill: 0}, + 1003: {width: 0, height: -1, leftcol: 0, skipcol: 0, topline: 0, topfill: 0}, + 1006: {width: 0, height: 1, leftcol: 0, skipcol: 0, topline: 1, topfill: 0}, + } + +Note that the "all" entry has the absolute values of the individual windows +accumulated. + +If you need more information about what changed, or you want to "debounce" the +events (not handle every event to avoid doing too much work), you may want to +use the `winlayout()` and `getwininfo()` functions. + +|WinScrolled| and |WinResized| do not trigger when the first autocommand is +added, only after the first scroll or resize. They may trigger when switching +to another tab page. + +The commands executed are expected to not cause window size or scroll changes. +If this happens anyway, the event will trigger again very soon. In other +words: Just before triggering the event, the current sizes and scroll +positions are stored and used to decide whether there was a change. + ============================================================================== 7. Argument and buffer list commands *buffer-list* @@ -646,8 +693,8 @@ Note: ":next" is an exception, because it must accept a list of file names for compatibility with Vi. +------------------------------------------------------------------------------ The argument list and multiple windows --------------------------------------- The current position in the argument list can be different for each window. Remember that when doing ":e file", the position in the argument list stays diff --git a/runtime/filetype.lua b/runtime/filetype.lua index ee9d5a0a75..f772785d21 100644 --- a/runtime/filetype.lua +++ b/runtime/filetype.lua @@ -1,5 +1,4 @@ --- Skip if legacy filetype is enabled or filetype detection is disabled -if vim.g.do_legacy_filetype or vim.g.did_load_filetypes then +if vim.g.did_load_filetypes then return end vim.g.did_load_filetypes = 1 diff --git a/runtime/filetype.vim b/runtime/filetype.vim deleted file mode 100644 index b6673c1762..0000000000 --- a/runtime/filetype.vim +++ /dev/null @@ -1,2670 +0,0 @@ -" Vim support file to detect file types -" -" Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2022 Sep 27 - -" Only run this if enabled -if !exists("do_legacy_filetype") - finish -endif - -" Listen very carefully, I will say this only once -if exists("did_load_filetypes") - finish -endif -let did_load_filetypes = 1 - -" Line continuation is used here, remove 'C' from 'cpoptions' -let s:cpo_save = &cpo -set cpo&vim - -augroup filetypedetect - -" Ignored extensions -if exists("*fnameescape") -au BufNewFile,BufRead ?\+.orig,?\+.bak,?\+.old,?\+.new,?\+.dpkg-dist,?\+.dpkg-old,?\+.dpkg-new,?\+.dpkg-bak,?\+.rpmsave,?\+.rpmnew,?\+.pacsave,?\+.pacnew - \ exe "doau filetypedetect BufRead " . fnameescape(expand("<afile>:r")) -au BufNewFile,BufRead *~ - \ let s:name = expand("<afile>") | - \ let s:short = substitute(s:name, '\~$', '', '') | - \ if s:name != s:short && s:short != "" | - \ exe "doau filetypedetect BufRead " . fnameescape(s:short) | - \ endif | - \ unlet! s:name s:short -au BufNewFile,BufRead ?\+.in - \ if expand("<afile>:t") != "configure.in" | - \ exe "doau filetypedetect BufRead " . fnameescape(expand("<afile>:r")) | - \ endif -elseif &verbose > 0 - echomsg "Warning: some filetypes will not be recognized because this version of Vim does not have fnameescape()" -endif - -" Pattern used to match file names which should not be inspected. -" Currently finds compressed files. -if !exists("g:ft_ignore_pat") - let g:ft_ignore_pat = '\.\(Z\|gz\|bz2\|zip\|tgz\)$' -endif - -" Function used for patterns that end in a star: don't set the filetype if the -" 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) - if expand("<amatch>") !~ g:ft_ignore_pat - exe 'setf ' . a:ft - endif -endfunc - -" Vim help file -au BufNewFile,BufRead $VIMRUNTIME/doc/*.txt setf help - -" Abaqus or Trasys -au BufNewFile,BufRead *.inp call dist#ft#Check_inp() - -" 8th (Firth-derivative) -au BufNewFile,BufRead *.8th setf 8th - -" A-A-P recipe -au BufNewFile,BufRead *.aap setf aap - -" A2ps printing utility -au BufNewFile,BufRead */etc/a2ps.cfg,*/etc/a2ps/*.cfg,a2psrc,.a2psrc setf a2ps - -" ABAB/4 -au BufNewFile,BufRead *.abap setf abap - -" ABC music notation -au BufNewFile,BufRead *.abc setf abc - -" ABEL -au BufNewFile,BufRead *.abl setf abel - -" AceDB -au BufNewFile,BufRead *.wrm setf acedb - -" Ada (83, 9X, 95) -au BufNewFile,BufRead *.adb,*.ads,*.ada setf ada -au BufNewFile,BufRead *.gpr setf ada - -" AHDL -au BufNewFile,BufRead *.tdf setf ahdl - -" AIDL -au BufNewFile,BufRead *.aidl setf aidl - -" AMPL -au BufNewFile,BufRead *.run setf ampl - -" Ant -au BufNewFile,BufRead build.xml setf ant - -" Arduino -au BufNewFile,BufRead *.ino,*.pde setf arduino - -" Apache config file -au BufNewFile,BufRead .htaccess,*/etc/httpd/*.conf setf apache -au BufNewFile,BufRead */etc/apache2/sites-*/*.com setf apache - -" XA65 MOS6510 cross assembler -au BufNewFile,BufRead *.a65 setf a65 - -" Applescript -au BufNewFile,BufRead *.scpt setf applescript - -" Applix ELF -au BufNewFile,BufRead *.am - \ if expand("<afile>") !~? 'Makefile.am\>' | setf elf | endif - -" ALSA configuration -au BufNewFile,BufRead .asoundrc,*/usr/share/alsa/alsa.conf,*/etc/asound.conf setf alsaconf - -" Arc Macro Language -au BufNewFile,BufRead *.aml setf aml - -" APT config file -au BufNewFile,BufRead apt.conf setf aptconf -au BufNewFile,BufRead */.aptitude/config setf aptconf -" more generic pattern far down - -" Arch Inventory file -au BufNewFile,BufRead .arch-inventory,=tagging-method setf arch - -" ART*Enterprise (formerly ART-IM) -au BufNewFile,BufRead *.art setf art - -" AsciiDoc -au BufNewFile,BufRead *.asciidoc,*.adoc setf asciidoc - -" ASN.1 -au BufNewFile,BufRead *.asn,*.asn1 setf asn - -" Active Server Pages (with Visual Basic Script) -au BufNewFile,BufRead *.asa - \ if exists("g:filetype_asa") | - \ exe "setf " . g:filetype_asa | - \ else | - \ setf aspvbs | - \ endif - -" Active Server Pages (with Perl or Visual Basic Script) -au BufNewFile,BufRead *.asp - \ if exists("g:filetype_asp") | - \ exe "setf " . g:filetype_asp | - \ elseif getline(1) . getline(2) . getline(3) =~? "perlscript" | - \ setf aspperl | - \ else | - \ setf aspvbs | - \ endif - -" Grub (must be before pattern *.lst) -au BufNewFile,BufRead */boot/grub/menu.lst,*/boot/grub/grub.conf,*/etc/grub.conf setf grub - -" Maxima, see: -" https://maxima.sourceforge.io/docs/manual/maxima_71.html#file_005ftype_005fmaxima -" Must be before the pattern *.mac. -" *.dem omitted - also used by gnuplot demos -" *.mc omitted - used by dist#ft#McSetf() -au BufNewFile,BufRead *.demo,*.dm{1,2,3,t},*.wxm,maxima-init.mac setf maxima - -" Assembly (all kinds) -" *.lst is not pure assembly, it has two extra columns (address, byte codes) -au BufNewFile,BufRead *.asm,*.[sS],*.[aA],*.mac,*.lst call dist#ft#FTasm() - -" Assembly - Macro (VAX) -au BufNewFile,BufRead *.mar setf vmasm - -" Astro -au BufNewFile,BufRead *.astro setf astro - -" Atlas -au BufNewFile,BufRead *.atl,*.as setf atlas - -" Atom is based on XML -au BufNewFile,BufRead *.atom setf xml - -" Autoit v3 -au BufNewFile,BufRead *.au3 setf autoit - -" Autohotkey -au BufNewFile,BufRead *.ahk setf autohotkey - -" Automake -au BufNewFile,BufRead [mM]akefile.am,GNUmakefile.am setf automake - -" Autotest .at files are actually m4 -au BufNewFile,BufRead *.at setf m4 - -" Avenue -au BufNewFile,BufRead *.ave setf ave - -" Awk -au BufNewFile,BufRead *.awk,*.gawk setf awk - -" B -au BufNewFile,BufRead *.mch,*.ref,*.imp setf b - -" BASIC or Visual 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 - -" IBasic file (similar to QBasic) -au BufNewFile,BufRead *.iba,*.ibi setf ibasic - -" FreeBasic file (similar to QBasic) -au BufNewFile,BufRead *.fb setf freebasic - -" 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() - -" BC calculator -au BufNewFile,BufRead *.bc setf bc - -" BDF font -au BufNewFile,BufRead *.bdf setf bdf - -" Beancount -au BufNewFile,BufRead *.beancount setf beancount - -" BibTeX bibliography database file -au BufNewFile,BufRead *.bib setf bib - -" 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 - -" BIND zone -au BufNewFile,BufRead named.root setf bindzone -au BufNewFile,BufRead *.db call dist#ft#BindzoneCheck('') - -" Blank -au BufNewFile,BufRead *.bl setf blank - -" Bitbake -au BufNewFile,BufRead *.bb,*.bbappend,*.bbclass,*/build/conf/*.conf,*/meta{-*,}/conf/*.conf setf bitbake - -" Blkid cache file -au BufNewFile,BufRead */etc/blkid.tab,*/etc/blkid.tab.old setf xml - -" BSDL -au BufNewFile,BufRead *.bsd,*.bsdl setf bsdl - -" Bazel (http://bazel.io) -autocmd BufRead,BufNewFile *.bzl,*.bazel,WORKSPACE setf bzl -if has("fname_case") - " There is another check for BUILD further below. - autocmd BufRead,BufNewFile *.BUILD,BUILD setf bzl -endif - -" C or lpc -au BufNewFile,BufRead *.c call dist#ft#FTlpc() -au BufNewFile,BufRead *.lpc,*.ulpc setf lpc - -" Calendar -au BufNewFile,BufRead calendar setf calendar - -" C# -au BufNewFile,BufRead *.cs,*.csx setf cs - -" CSDL -au BufNewFile,BufRead *.csdl setf csdl - -" Cabal -au BufNewFile,BufRead *.cabal setf cabal - -" Cdrdao TOC -au BufNewFile,BufRead *.toc setf cdrtoc - -" Cdrdao config -au BufNewFile,BufRead */etc/cdrdao.conf,*/etc/defaults/cdrdao,*/etc/default/cdrdao,.cdrdao setf cdrdaoconf - -" Cfengine -au BufNewFile,BufRead cfengine.conf setf cfengine - -" ChaiScript -au BufRead,BufNewFile *.chai setf chaiscript - -" Chatito -au BufNewFile,BufRead *.chatito setf chatito - -" Comshare Dimension Definition Language -au BufNewFile,BufRead *.cdl setf cdl - -" Conary Recipe -au BufNewFile,BufRead *.recipe setf conaryrecipe - -" Controllable Regex Mutilator -au BufNewFile,BufRead *.crm setf crm - -" Cyn++ -au BufNewFile,BufRead *.cyn setf cynpp - -" Cynlib -" .cc and .cpp files can be C++ or Cynlib. -au BufNewFile,BufRead *.cc - \ if exists("cynlib_syntax_for_cc")|setf cynlib|else|setf cpp|endif -au BufNewFile,BufRead *.cpp - \ if exists("cynlib_syntax_for_cpp")|setf cynlib|else|setf cpp|endif - -" C++ -au BufNewFile,BufRead *.cxx,*.c++,*.hh,*.hxx,*.hpp,*.ipp,*.moc,*.tcc,*.inl setf cpp -if has("fname_case") - au BufNewFile,BufRead *.C,*.H setf cpp -endif - -" .h files can be C, Ch C++, ObjC or ObjC++. -" Set c_syntax_for_h if you want C, ch_syntax_for_h if you want Ch. ObjC is -" detected automatically. -au BufNewFile,BufRead *.h call dist#ft#FTheader() - -" Ch (CHscript) -au BufNewFile,BufRead *.chf setf ch - -" TLH files are C++ headers generated by Visual C++'s #import from typelibs -au BufNewFile,BufRead *.tlh setf cpp - -" Cascading Style Sheets -au BufNewFile,BufRead *.css setf css - -" Century Term Command Scripts (*.cmd too) -au BufNewFile,BufRead *.con setf cterm - -" Changelog -au BufNewFile,BufRead changelog.Debian,changelog.dch,NEWS.Debian,NEWS.dch,*/debian/changelog - \ setf debchangelog - -au BufNewFile,BufRead [cC]hange[lL]og - \ if getline(1) =~ '; urgency=' - \| setf debchangelog - \| else - \| setf changelog - \| endif - -au BufNewFile,BufRead NEWS - \ if getline(1) =~ '; urgency=' - \| setf debchangelog - \| endif - -" CHILL -au BufNewFile,BufRead *..ch setf chill - -" Changes for WEB and CWEB or CHILL -au BufNewFile,BufRead *.ch call dist#ft#FTchange() - -" ChordPro -au BufNewFile,BufRead *.chopro,*.crd,*.cho,*.crdpro,*.chordpro setf chordpro - -" Clean -au BufNewFile,BufRead *.dcl,*.icl setf clean - -" Clever -au BufNewFile,BufRead *.eni setf cl - -" Clever or dtd -au BufNewFile,BufRead *.ent call dist#ft#FTent() - -" 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 - -" Cmake -au BufNewFile,BufRead CMakeLists.txt,*.cmake,*.cmake.in setf cmake - -" Cmusrc -au BufNewFile,BufRead */.cmus/{autosave,rc,command-history,*.theme} setf cmusrc -au BufNewFile,BufRead */cmus/{rc,*.theme} setf cmusrc - -" Cobol -au BufNewFile,BufRead *.cbl,*.cob,*.lib setf cobol -" cobol or zope form controller python script? (heuristic) -au BufNewFile,BufRead *.cpy - \ if getline(1) =~ '^##' | - \ setf python | - \ else | - \ setf cobol | - \ endif - -" Coco/R -au BufNewFile,BufRead *.atg setf coco - -" Cold Fusion -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 - -" CSV Files -au BufNewFile,BufRead *.csv setf csv - -" CUDA Compute Unified Device Architecture -au BufNewFile,BufRead *.cu,*.cuh setf cuda - -" Dockerfile; Podman uses the same syntax with name Containerfile -" Also see Dockerfile.* below. -au BufNewFile,BufRead Containerfile,Dockerfile,dockerfile,*.[dD]ockerfile setf dockerfile - -" WildPackets EtherPeek Decoder -au BufNewFile,BufRead *.dcd setf dcd - -" Enlightenment configuration files -au BufNewFile,BufRead *enlightenment/*.cfg setf c - -" Eterm -au BufNewFile,BufRead *Eterm/*.cfg setf eterm - -" Elixir or Euphoria -au BufNewFile,BufRead *.ex call dist#ft#ExCheck() - -" Elixir -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") - au BufNewFile,BufRead *.EU,*.EW,*.EX,*.EXU,*.EXW call dist#ft#EuphoriaCheck() -endif - -" Lynx config files -au BufNewFile,BufRead lynx.cfg setf lynx - -" LyRiCs -au BufNewFile,BufRead *.lrc setf lyrics - -" Modula-3 configuration language (must be before *.cfg and *makefile) -au BufNewFile,BufRead *.quake,cm3.cfg setf m3quake -au BufNewFile,BufRead m3makefile,m3overrides setf m3build - -" Quake -au BufNewFile,BufRead *baseq[2-3]/*.cfg,*id1/*.cfg setf quake -au BufNewFile,BufRead *quake[1-3]/*.cfg setf quake - -" Quake C -au BufNewFile,BufRead *.qc setf c - -" Configure files -au BufNewFile,BufRead *.cfg\c call dist#ft#FTcfg() - -" Cucumber -au BufNewFile,BufRead *.feature setf cucumber - -" Communicating Sequential Processes -au BufNewFile,BufRead *.csp,*.fdr setf csp - -" CUPL logic description and simulation -au BufNewFile,BufRead *.pld setf cupl -au BufNewFile,BufRead *.si setf cuplsim - -" Dart -au BufRead,BufNewfile *.dart,*.drt setf dart - -" Debian Control -au BufNewFile,BufRead */debian/control setf debcontrol -au BufNewFile,BufRead control - \ if getline(1) =~ '^Source:' - \| setf debcontrol - \| endif - -" Debian Copyright -au BufNewFile,BufRead */debian/copyright setf debcopyright -au BufNewFile,BufRead copyright - \ if getline(1) =~ '^Format:' - \| setf debcopyright - \| endif - -" Debian Sources.list -au BufNewFile,BufRead */etc/apt/sources.list setf debsources -au BufNewFile,BufRead */etc/apt/sources.list.d/*.list setf debsources - -" Deny hosts -au BufNewFile,BufRead denyhosts.conf setf denyhosts - -" dnsmasq(8) configuration files -au BufNewFile,BufRead */etc/dnsmasq.conf setf dnsmasq - -" ROCKLinux package description -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 -au BufNewFile,BufRead *.desktop,*.directory setf desktop - -" Dict config -au BufNewFile,BufRead dict.conf,.dictrc setf dictconf - -" Dictd config -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$' | - \ setf gitsendemail | - \ else | - \ setf diff | - \ endif - -" Dircolors -au BufNewFile,BufRead .dir_colors,.dircolors,*/etc/DIR_COLORS setf dircolors - -" Diva (with Skill) or InstallShield -au BufNewFile,BufRead *.rul - \ if getline(1).getline(2).getline(3).getline(4).getline(5).getline(6) =~? 'InstallShield' | - \ setf ishd | - \ else | - \ setf diva | - \ endif - -" DCL (Digital Command Language - vms) or DNS zone file -au BufNewFile,BufRead *.com call dist#ft#BindzoneCheck('dcl') - -" DOT -au BufNewFile,BufRead *.dot,*.gv setf dot - -" Dune -au BufNewFile,BufRead jbuild,dune,dune-project,dune-workspace setf dune - -" Dylan - lid files -au BufNewFile,BufRead *.lid setf dylanlid - -" Dylan - intr files (melange) -au BufNewFile,BufRead *.intr setf dylanintr - -" Dylan -au BufNewFile,BufRead *.dylan setf dylan - -" Microsoft Module Definition -au BufNewFile,BufRead *.def setf def - -" Dracula -au BufNewFile,BufRead *.drac,*.drc,*lvs,*lpe setf dracula - -" Datascript -au BufNewFile,BufRead *.ds setf datascript - -" dsl: DSSSL or Structurizr -au BufNewFile,BufRead *.dsl - \ if getline(1) =~ '^\s*<\!' | - \ setf dsl | - \ else | - \ setf structurizr | - \ endif - -" DTD (Document Type Definition for XML) -au BufNewFile,BufRead *.dtd setf dtd - -" DTS/DSTI (device tree files) -au BufNewFile,BufRead *.dts,*.dtsi setf dts - -" EDIF (*.edf,*.edif,*.edn,*.edo) or edn -au BufNewFile,BufRead *.ed\(f\|if\|o\) setf edif -au BufNewFile,BufRead *.edn - \ if getline(1) =~ '^\s*(\s*edif\>' | - \ setf edif | - \ else | - \ setf clojure | - \ endif - -" EditorConfig (close enough to dosini) -au BufNewFile,BufRead .editorconfig setf dosini - -" Embedix Component Description -au BufNewFile,BufRead *.ecd setf ecd - -" Eiffel or Specman or Euphoria -au BufNewFile,BufRead *.e,*.E call dist#ft#FTe() - -" Elinks configuration -au BufNewFile,BufRead elinks.conf setf elinks - -" ERicsson LANGuage; Yaws is erlang too -au BufNewFile,BufRead *.erl,*.hrl,*.yaws setf erlang - -" Elm -au BufNewFile,BufRead *.elm setf elm - -" Elm Filter Rules file -au BufNewFile,BufRead filter-rules setf elmfilt - -" ESMTP rc file -au BufNewFile,BufRead *esmtprc setf esmtprc - -" ESQL-C -au BufNewFile,BufRead *.ec,*.EC setf esqlc - -" Esterel -au BufNewFile,BufRead *.strl setf esterel - -" Essbase script -au BufNewFile,BufRead *.csc setf csc - -" Exim -au BufNewFile,BufRead exim.conf setf exim - -" Expect -au BufNewFile,BufRead *.exp setf expect - -" Exports -au BufNewFile,BufRead exports setf exports - -" Falcon -au BufNewFile,BufRead *.fal setf falcon - -" Fantom -au BufNewFile,BufRead *.fan,*.fwt setf fan - -" Factor -au BufNewFile,BufRead *.factor setf factor - -" Fennel -autocmd BufRead,BufNewFile *.fnl setf fennel - -" Fetchmail RC file -au BufNewFile,BufRead .fetchmailrc setf fetchmail - -" Fish shell -au BufNewFile,BufRead *.fish setf fish - -" FlexWiki - disabled, because it has side effects when a .wiki file -" is not actually FlexWiki -"au BufNewFile,BufRead *.wiki setf flexwiki - -" Focus Executable -au BufNewFile,BufRead *.fex,*.focexec setf focexec - -" Focus Master file (but not for auto.master) -au BufNewFile,BufRead auto.master setf conf -au BufNewFile,BufRead *.mas,*.master setf master - -" Forth -au BufNewFile,BufRead *.ft,*.fth setf forth - -" Reva Forth -au BufNewFile,BufRead *.frt setf reva - -" Fortran -if has("fname_case") - au BufNewFile,BufRead *.F,*.FOR,*.FPP,*.FTN,*.F77,*.F90,*.F95,*.F03,*.F08 setf fortran -endif -au BufNewFile,BufRead *.f,*.for,*.fortran,*.fpp,*.ftn,*.f77,*.f90,*.f95,*.f03,*.f08 setf fortran - -" Framescript -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,.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 - -" Godot shader -au BufRead,BufNewFile *.gdshader,*.shader setf gdshader - -" Gedcom -au BufNewFile,BufRead *.ged,lltxxxxx.txt setf gedcom - -" Gemtext -au BufNewFile,BufRead *.gmi,*.gemini setf gemtext - -" Gift (Moodle) -autocmd BufRead,BufNewFile *.gift setf gift - -" Git -au BufNewFile,BufRead COMMIT_EDITMSG,MERGE_MSG,TAG_EDITMSG setf gitcommit -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 - au BufNewFile,BufRead $XDG_CONFIG_HOME/git/attributes setf gitattributes - au BufNewFile,BufRead $XDG_CONFIG_HOME/git/ignore setf gitignore -endif -au BufNewFile,BufRead .gitattributes,*.git/info/attributes setf gitattributes -au BufNewFile,BufRead */.config/git/attributes setf gitattributes -au BufNewFile,BufRead */etc/gitattributes setf gitattributes -au BufNewFile,BufRead .gitignore,*.git/info/exclude setf gitignore -au BufNewFile,BufRead */.config/git/ignore setf gitignore -au BufNewFile,BufRead git-rebase-todo setf gitrebase -au BufRead,BufNewFile .gitsendemail.msg.?????? setf gitsendemail -au BufNewFile,BufRead *.git/* - \ if getline(1) =~# '^\x\{40,\}\>\|^ref: ' | - \ setf git | - \ endif - -" Gkrellmrc -au BufNewFile,BufRead gkrellmrc,gkrellmrc_? setf gkrellmrc - -" Gleam -au BufNewFile,BufRead *.gleam setf gleam - -" GLSL -au BufNewFile,BufRead *.glsl setf glsl - -" GP scripts (2.0 and onward) -au BufNewFile,BufRead *.gp,.gprc setf gp - -" GPG -au BufNewFile,BufRead */.gnupg/options setf gpg -au BufNewFile,BufRead */.gnupg/gpg.conf setf gpg -au BufNewFile,BufRead */usr/*/gnupg/options.skel setf gpg -if !empty($GNUPGHOME) - au BufNewFile,BufRead $GNUPGHOME/options setf gpg - au BufNewFile,BufRead $GNUPGHOME/gpg.conf setf gpg -endif - -" gnash(1) configuration files -au BufNewFile,BufRead gnashrc,.gnashrc,gnashpluginrc,.gnashpluginrc setf gnash - -" Gitolite -au BufNewFile,BufRead gitolite.conf setf gitolite -au BufNewFile,BufRead {,.}gitolite.rc,example.gitolite.rc setf perl - -" Glimmer-flavored TypeScript and JavaScript -au BufNewFile,BufRead *.gts setf typescript.glimmer -au BufNewFile,BufRead *.gjs setf javascript.glimmer - -" Gnuplot scripts -au BufNewFile,BufRead *.gpi,.gnuplot setf gnuplot - -" 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 - -" Groovy -au BufNewFile,BufRead *.gradle,*.groovy setf groovy - -" GNU Server Pages -au BufNewFile,BufRead *.gsp setf gsp - -" Group file -au BufNewFile,BufRead */etc/group,*/etc/group-,*/etc/group.edit,*/etc/gshadow,*/etc/gshadow-,*/etc/gshadow.edit,*/var/backups/group.bak,*/var/backups/gshadow.bak setf group - -" GTK RC -au BufNewFile,BufRead .gtkrc,gtkrc setf gtkrc - -" GYP -au BufNewFile,BufRead *.gyp,*.gypi setf gyp - -" 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 - -" Hare -au BufNewFile,BufRead *.ha setf hare - -" Haskell -au BufNewFile,BufRead *.hs,*.hsc,*.hs-boot,*.hsig setf haskell -au BufNewFile,BufRead *.lhs setf lhaskell -au BufNewFile,BufRead *.chs setf chaskell -au BufNewFile,BufRead cabal.project setf cabalproject -au BufNewFile,BufRead $HOME/.cabal/config setf cabalconfig -au BufNewFile,BufRead cabal.config setf cabalconfig - -" Haste -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 - -" HLS Playlist (or another form of playlist) -au BufNewFile,BufRead *.m3u,*.m3u8 setf hlsplaylist - -" Hollywood -au BufRead,BufNewFile *.hws setf hollywood - -" Hoon -au BufRead,BufNewFile *.hoon setf hoon - -" Tilde (must be before HTML) -au BufNewFile,BufRead *.t.html setf tilde - -" HTML (.shtml and .stm for server side) -au BufNewFile,BufRead *.html,*.htm,*.shtml,*.stm call dist#ft#FThtml() -au BufNewFile,BufRead *.cshtml setf html - -" HTML with Ruby - eRuby -au BufNewFile,BufRead *.erb,*.rhtml setf eruby - -" HTML with M4 -au BufNewFile,BufRead *.html.m4 setf htmlm4 - -" Some template. Used to be HTML Cheetah. -au BufNewFile,BufRead *.tmpl setf template - -" Host config -au BufNewFile,BufRead */etc/host.conf setf hostconf - -" Hosts access -au BufNewFile,BufRead */etc/hosts.allow,*/etc/hosts.deny setf hostsaccess - -" Hyper Builder -au BufNewFile,BufRead *.hb setf hb - -" Httest -au BufNewFile,BufRead *.htt,*.htb setf httest - -" i3 -au BufNewFile,BufRead */i3/config setf i3config -au BufNewFile,BufRead */.i3/config setf i3config - -" sway -au BufNewFile,BufRead */sway/config setf swayconfig -au BufNewFile,BufRead */.sway/config setf swayconfig - -" Icon -au BufNewFile,BufRead *.icn setf icon - -" IDL (Interface Description Language) -au BufNewFile,BufRead *.idl call dist#ft#FTidl() - -" Microsoft IDL (Interface Description Language) Also *.idl -" MOF = WMI (Windows Management Instrumentation) Managed Object Format -au BufNewFile,BufRead *.odl,*.mof setf msidl - -" Icewm menu -au BufNewFile,BufRead */.icewm/menu setf icemenu - -" Indent profile (must come before IDL *.pro!) -au BufNewFile,BufRead .indent.pro setf indent -au BufNewFile,BufRead indent.pro call dist#ft#ProtoCheck('indent') - -" IDL (Interactive Data Language) -au BufNewFile,BufRead *.pro call dist#ft#ProtoCheck('idlang') - -" Indent RC -au BufNewFile,BufRead indentrc setf indent - -" Inform -au BufNewFile,BufRead *.inf,*.INF setf inform - -" Initng -au BufNewFile,BufRead */etc/initng/*/*.i,*.ii setf initng - -" Innovation Data Processing -au BufRead,BufNewFile upstream.dat\c,upstream.*.dat\c,*.upstream.dat\c setf upstreamdat -au BufRead,BufNewFile fdrupstream.log,upstream.log\c,upstream.*.log\c,*.upstream.log\c,UPSTREAM-*.log\c setf upstreamlog -au BufRead,BufNewFile upstreaminstall.log\c,upstreaminstall.*.log\c,*.upstreaminstall.log\c setf upstreaminstalllog -au BufRead,BufNewFile usserver.log\c,usserver.*.log\c,*.usserver.log\c setf usserverlog -au BufRead,BufNewFile usw2kagt.log\c,usw2kagt.*.log\c,*.usw2kagt.log\c setf usw2kagtlog - -" Ipfilter -au BufNewFile,BufRead ipf.conf,ipf6.conf,ipf.rules setf ipfilter - -" Informix 4GL (source - canonical, include file, I4GL+M4 preproc.) -au BufNewFile,BufRead *.4gl,*.4gh,*.m4gl setf fgl - -" .INI file for MSDOS -au BufNewFile,BufRead *.ini setf dosini - -" SysV Inittab -au BufNewFile,BufRead inittab setf inittab - -" Inno Setup -au BufNewFile,BufRead *.iss setf iss - -" J -au BufNewFile,BufRead *.ijs setf j - -" JAL -au BufNewFile,BufRead *.jal,*.JAL setf jal - -" Jam -au BufNewFile,BufRead *.jpl,*.jpr setf jam - -" Java -au BufNewFile,BufRead *.java,*.jav setf java - -" JavaCC -au BufNewFile,BufRead *.jj,*.jjt setf javacc - -" JavaScript, ECMAScript, ES module script, CommonJS script -au BufNewFile,BufRead *.js,*.jsm,*.javascript,*.es,*.mjs,*.cjs setf javascript - -" JavaScript with React -au BufNewFile,BufRead *.jsx setf javascriptreact - -" Java Server Pages -au BufNewFile,BufRead *.jsp setf jsp - -" Java Properties resource file (note: doesn't catch font.properties.pl) -au BufNewFile,BufRead *.properties,*.properties_??,*.properties_??_?? setf jproperties - -" Jess -au BufNewFile,BufRead *.clp setf jess - -" Jgraph -au BufNewFile,BufRead *.jgr setf jgraph - -" Jovial -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 - -" Jupyter Notebook is also json -au BufNewFile,BufRead *.ipynb setf json - -" Other files that look like json -au BufNewFile,BufRead .babelrc,.eslintrc,.prettierrc,.firebaserc setf json - -" JSONC -au BufNewFile,BufRead *.jsonc setf jsonc - -" Jsonnet -au BufNewFile,BufRead *.jsonnet,*.libjsonnet setf jsonnet - -" Julia -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 - -" Kivy -au BufNewFile,BufRead *.kv setf kivy - -" Kotlin -au BufNewFile,BufRead *.kt,*.ktm,*.kts setf kotlin - -" KDE script -au BufNewFile,BufRead *.ks setf kscript - -" Kconfig -au BufNewFile,BufRead Kconfig,Kconfig.debug setf kconfig - -" Lace (ISE) -au BufNewFile,BufRead *.ace,*.ACE setf lace - -" Latexmkrc -au BufNewFile,BufRead .latexmkrc,latexmkrc setf perl - -" Latte -au BufNewFile,BufRead *.latte,*.lte setf latte - -" Limits -au BufNewFile,BufRead */etc/limits,*/etc/*limits.conf,*/etc/*limits.d/*.conf setf limits - -" LambdaProlog or SML (see dist#ft#FTmod for *.mod) -au BufNewFile,BufRead *.sig call dist#ft#FTsig() - -" LDAP LDIF -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 - -" Lex -au BufNewFile,BufRead *.lex,*.l,*.lxx,*.l++ setf lex - -" Libao -au BufNewFile,BufRead */etc/libao.conf,*/.libao setf libao - -" Libsensors -au BufNewFile,BufRead */etc/sensors.conf,*/etc/sensors3.conf setf sensors - -" LFTP -au BufNewFile,BufRead lftp.conf,.lftprc,*lftp/rc setf lftp - -" Lifelines (or Lex for C++!) -au BufNewFile,BufRead *.ll setf lifelines - -" Lilo: Linux loader -au BufNewFile,BufRead lilo.conf setf lilo - -" Lilypond -au BufNewFile,BufRead *.ly,*.ily setf lilypond - -" 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,*.asd,*.el,*.cl,*.L,.emacs,.sawfishrc setf lisp -else - au BufNewFile,BufRead *.lsp,*.lisp,*.asd,*.el,*.cl,.emacs,.sawfishrc setf lisp -endif - -" SBCL implementation of Common Lisp -au BufNewFile,BufRead sbclrc,.sbclrc setf lisp - -" Liquid -au BufNewFile,BufRead *.liquid setf liquid - -" Lite -au BufNewFile,BufRead *.lite,*.lt setf lite - -" LiteStep RC files -au BufNewFile,BufRead */LiteStep/*/*.rc setf litestep - -" Login access -au BufNewFile,BufRead */etc/login.access setf loginaccess - -" Login defs -au BufNewFile,BufRead */etc/login.defs setf logindefs - -" Logtalk -au BufNewFile,BufRead *.lgt setf logtalk - -" LOTOS -au BufNewFile,BufRead *.lot,*.lotos setf lotos - -" Lout (also: *.lt) -au BufNewFile,BufRead *.lou,*.lout setf lout - -" Lua -au BufNewFile,BufRead *.lua setf lua - -" Luacheck -au BufNewFile,BufRead .luacheckrc setf lua - -" Luarocks -au BufNewFile,BufRead *.rockspec setf lua - -" Linden Scripting Language (Second Life) -au BufNewFile,BufRead *.lsl setf lsl - -" Lynx style file (or LotusScript!) -au BufNewFile,BufRead *.lss setf lss - -" M4 -au BufNewFile,BufRead *.m4 - \ if expand("<afile>") !~? 'html.m4$\|fvwm2rc' | setf m4 | endif - -" MaGic Point -au BufNewFile,BufRead *.mgp setf mgp - -" Mail (for Elm, trn, mutt, muttng, rn, slrn, neomutt) -au BufNewFile,BufRead snd.\d\+,.letter,.letter.\d\+,.followup,.article,.article.\d\+,pico.\d\+,mutt{ng,}-*-\w\+,mutt[[:alnum:]_-]\\\{6\},neomutt-*-\w\+,neomutt[[:alnum:]_-]\\\{6\},ae\d\+.txt,/tmp/SLRN[0-9A-Z.]\+,*.eml setf mail - -" Mail aliases -au BufNewFile,BufRead */etc/mail/aliases,*/etc/aliases setf mailaliases - -" Mailcap configuration file -au BufNewFile,BufRead .mailcap,mailcap setf mailcap - -" Makefile -au BufNewFile,BufRead *[mM]akefile,*.mk,*.mak,*.dsp setf make - -" MakeIndex -au BufNewFile,BufRead *.ist,*.mst setf ist - -" Mallard -au BufNewFile,BufRead *.page setf mallard - -" Manpage -au BufNewFile,BufRead *.man setf nroff - -" Man config -au BufNewFile,BufRead */etc/man.conf,man.config setf manconf - -" Maple V -au BufNewFile,BufRead *.mv,*.mpl,*.mws setf maple - -" Map (UMN mapserver config file) -au BufNewFile,BufRead *.map setf map - -" Markdown -au BufNewFile,BufRead *.markdown,*.mdown,*.mkd,*.mkdn,*.mdwn,*.md setf markdown - -" Mason -au BufNewFile,BufRead *.mason,*.mhtml,*.comp setf mason - -" Mathematica, Matlab, Murphi, Objective C or Octave -au BufNewFile,BufRead *.m call dist#ft#FTm() - -" Mathematica notebook -au BufNewFile,BufRead *.nb setf mma - -" Maya Extension Language -au BufNewFile,BufRead *.mel setf mel - -" Mercurial (hg) commit file -au BufNewFile,BufRead hg-editor-*.txt setf hgcommit - -" Mercurial config (looks like generic config file) -au BufNewFile,BufRead *.hgrc,*hgrc setf cfg - -" Meson Build system config -au BufNewFile,BufRead meson.build,meson_options.txt setf meson -au BufNewFile,BufRead *.wrap setf dosini - -" Messages (logs mostly) -au BufNewFile,BufRead */log/{auth,cron,daemon,debug,kern,lpr,mail,messages,news/news,syslog,user}{,.log,.err,.info,.warn,.crit,.notice}{,.[0-9]*,-[0-9]*} setf messages - -" Metafont -au BufNewFile,BufRead *.mf setf mf - -" MetaPost -au BufNewFile,BufRead *.mp setf mp -au BufNewFile,BufRead *.mpxl,*.mpiv,*.mpvi let b:mp_metafun = 1 | setf mp - -" MGL -au BufNewFile,BufRead *.mgl setf mgl - -" MIX - Knuth assembly -au BufNewFile,BufRead *.mix,*.mixal setf mix - -" MMIX or VMS makefile -au BufNewFile,BufRead *.mms call dist#ft#FTmms() - -" Symbian meta-makefile definition (MMP) -au BufNewFile,BufRead *.mmp setf mmp - -" 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, 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 - -" Monk -au BufNewFile,BufRead *.isc,*.monk,*.ssc,*.tsc setf monk - -" MOO -au BufNewFile,BufRead *.moo setf moo - -" Moonscript -au BufNewFile,BufRead *.moon setf moonscript - -" 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 - -" Motorola S record -au BufNewFile,BufRead *.s19,*.s28,*.s37,*.mot,*.srec setf srec - -" Mrxvtrc -au BufNewFile,BufRead mrxvtrc,.mrxvtrc setf mrxvtrc - -" Msql -au BufNewFile,BufRead *.msql setf msql - -" Mysql -au BufNewFile,BufRead *.mysql setf mysql - -" Tcl Shell RC file -au BufNewFile,BufRead tclsh.rc setf tcl - -" M$ Resource files -" /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 - -" Mush -au BufNewFile,BufRead *.mush setf mush - -" Mutt setup file (also for Muttng) -au BufNewFile,BufRead Mutt{ng,}rc setf muttrc - -" N1QL -au BufRead,BufNewfile *.n1ql,*.nql setf n1ql - -" Nano -au BufNewFile,BufRead */etc/nanorc,*.nanorc setf nanorc - -" Nastran input/DMAP -"au BufNewFile,BufRead *.dat setf nastran - -" Natural -au BufNewFile,BufRead *.NS[ACGLMNPS] setf natural - -" Noemutt setup file -au BufNewFile,BufRead Neomuttrc setf neomuttrc - -" Netrc -au BufNewFile,BufRead .netrc setf netrc - -" Nginx -au BufNewFile,BufRead *.nginx,nginx*.conf,*nginx.conf,*/etc/nginx/*,*/usr/local/nginx/conf/*,*/nginx/*.conf setf nginx - -" Nim file -au BufNewFile,BufRead *.nim,*.nims,*.nimble setf nim - -" 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 - -" Novell netware batch files -au BufNewFile,BufRead *.ncf setf ncf - -" Nroff/Troff (*.ms and *.t are checked below) -au BufNewFile,BufRead *.me - \ if expand("<afile>") != "read.me" && expand("<afile>") != "click.me" | - \ setf nroff | - \ endif -au BufNewFile,BufRead *.tr,*.nr,*.roff,*.tmac,*.mom setf nroff -au BufNewFile,BufRead *.[1-9] call dist#ft#FTnroff() - -" Nroff or Objective C++ -au BufNewFile,BufRead *.mm call dist#ft#FTmm() - -" Not Quite C -au BufNewFile,BufRead *.nqc setf nqc - -" NSE - Nmap Script Engine - uses Lua syntax -au BufNewFile,BufRead *.nse setf lua - -" NSIS -au BufNewFile,BufRead *.nsi,*.nsh setf nsis - -" OCaml -au BufNewFile,BufRead *.ml,*.mli,*.mll,*.mly,.ocamlinit,*.mlt,*.mlp,*.mlip,*.mli.cppo,*.ml.cppo setf ocaml - -" Occam -au BufNewFile,BufRead *.occ setf occam - -" Octave -au BufNewFile,BufRead octave.conf,.octaverc,octaverc setf octave - -" Omnimark -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 - -" OPL -au BufNewFile,BufRead *.[Oo][Pp][Ll] setf opl - -" OpenSCAD -au BufNewFile,BufRead *.scad setf openscad - -" 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 - -" ini style config files, using # comments -au BufNewFile,BufRead */etc/pacman.conf,mpv.conf setf confini - -" Pacman hooks -au BufNewFile,BufRead *.hook - \ if getline(1) == '[Trigger]' | - \ setf conf | - \ endif - -" Pam conf -au BufNewFile,BufRead */etc/pam.conf setf pamconf - -" Pam environment -au BufNewFile,BufRead pam_env.conf,.pam_environment setf pamenv - -" PApp -au BufNewFile,BufRead *.papp,*.pxml,*.pxsl setf papp - -" Password file -au BufNewFile,BufRead */etc/passwd,*/etc/passwd-,*/etc/passwd.edit,*/etc/shadow,*/etc/shadow-,*/etc/shadow.edit,*/var/backups/passwd.bak,*/var/backups/shadow.bak setf passwd - -" Pascal (also *.p, *.pp, *.inc) -au BufNewFile,BufRead *.pas setf pascal - -" Pascal or Puppet manifest -au BufNewFile,BufRead *.pp call dist#ft#FTpp() - -" Delphi or Lazarus program file -au BufNewFile,BufRead *.dpr,*.lpr setf pascal - -" Free Pascal makefile definition file -au BufNewFile,BufRead *.fpc setf fpcmake - -" Path of Exile item filter -au BufNewFile,BufRead *.filter setf poefilter - -" PDF -au BufNewFile,BufRead *.pdf setf pdf - -" PCMK - HAE - crm configure edit -au BufNewFile,BufRead *.pcmk setf pcmk - -" Perl -if has("fname_case") - au BufNewFile,BufRead *.pl,*.PL call dist#ft#FTpl() -else - au BufNewFile,BufRead *.pl call dist#ft#FTpl() -endif -au BufNewFile,BufRead *.plx,*.al,*.psgi setf perl - -" Perl, XPM or XPM2 -au BufNewFile,BufRead *.pm - \ if getline(1) =~ "XPM2" | - \ setf xpm2 | - \ elseif getline(1) =~ "XPM" | - \ setf xpm | - \ else | - \ setf perl | - \ endif - -" Perl POD -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. -" Also .phpt for php tests. -" Also .theme for Drupal theme files. -au BufNewFile,BufRead *.php,*.php\d,*.phtml,*.ctp,*.phpt,*.theme setf php - -" PHP config -au BufNewFile,BufRead php.ini-* setf dosini - -" Pike and Cmod -au BufNewFile,BufRead *.pike,*.pmod setf pike -au BufNewFile,BufRead *.cmod setf cmod - -" Pinfo config -au BufNewFile,BufRead */etc/pinforc,*/.pinforc setf pinfo - -" Palm Resource compiler -au BufNewFile,BufRead *.rcp setf pilrc - -" Pine config -au BufNewFile,BufRead .pinerc,pinerc,.pinercex,pinercex setf pine - -" Pipenv Pipfiles -au BufNewFile,BufRead Pipfile setf toml -au BufNewFile,BufRead Pipfile.lock setf json - -" PL/1, PL/I -au BufNewFile,BufRead *.pli,*.pl1 setf pli - -" PL/M (also: *.inp) -au BufNewFile,BufRead *.plm,*.p36,*.pac setf plm - -" PL/SQL -au BufNewFile,BufRead *.pls,*.plsql setf plsql - -" PLP -au BufNewFile,BufRead *.plp setf plp - -" PO and PO template (GNU gettext) -au BufNewFile,BufRead *.po,*.pot setf po - -" Postfix main config -au BufNewFile,BufRead main.cf setf pfmain - -" PostScript (+ font files, encapsulated PostScript, Adobe Illustrator) -au BufNewFile,BufRead *.ps,*.pfa,*.afm,*.eps,*.epsf,*.epsi,*.ai setf postscr - -" PostScript Printer Description -au BufNewFile,BufRead *.ppd setf ppd - -" Povray -au BufNewFile,BufRead *.pov setf pov - -" Povray configuration -au BufNewFile,BufRead .povrayrc setf povini - -" Povray, Pascal, PHP or assembly -au BufNewFile,BufRead *.inc call dist#ft#FTinc() - -" PowerShell -au BufNewFile,BufRead *.ps1,*.psd1,*.psm1,*.pssc setf ps1 -au BufNewFile,BufRead *.ps1xml setf ps1xml -au BufNewFile,BufRead *.cdxml,*.psc1 setf xml - -" Printcap and Termcap -au BufNewFile,BufRead *printcap - \ let b:ptcap_type = "print" | setf ptcap -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 - -" PPWizard -au BufNewFile,BufRead *.it,*.ih setf ppwiz - -" Pug -au BufRead,BufNewFile *.pug setf pug - -" Puppet -au BufNewFile,BufRead Puppetfile setf ruby - -" Embedded Puppet -au BufNewFile,BufRead *.epp setf epuppet - -" Obj 3D file format -" TODO: is there a way to avoid MS-Windows Object files? -au BufNewFile,BufRead *.obj setf obj - -" Oracle Pro*C/C++ -au BufNewFile,BufRead *.pc setf proc - -" Privoxy actions file -au BufNewFile,BufRead *.action setf privoxy - -" Procmail -au BufNewFile,BufRead .procmail,.procmailrc setf procmail - -" Progress or CWEB -au BufNewFile,BufRead *.w call dist#ft#FTprogress_cweb() - -" Progress or assembly -au BufNewFile,BufRead *.i call dist#ft#FTprogress_asm() - -" Progress or Pascal -au BufNewFile,BufRead *.p call dist#ft#FTprogress_pascal() - -" Software Distributor Product Specification File (POSIX 1387.2-1995) -au BufNewFile,BufRead *.psf setf psf -au BufNewFile,BufRead INDEX,INFO - \ if getline(1) =~ '^\s*\(distribution\|installed_software\|root\|bundle\|product\)\s*$' | - \ setf psf | - \ endif - -" Prolog -au BufNewFile,BufRead *.pdb setf prolog - -" Promela -au BufNewFile,BufRead *.pml setf promela - -" Property Specification Language (PSL) -au BufNewFile,BufRead *.psl setf psl - -" Google protocol buffers -au BufNewFile,BufRead *.proto setf proto -au BufNewFile,BufRead *.pbtxt setf pbtxt - -" Poke -au BufNewFile,BufRead *.pk setf poke - -" Protocols -au BufNewFile,BufRead */etc/protocols setf protocols - -" Pyret -au BufNewFile,BufRead *.arr setf pyret - -" Pyrex -au BufNewFile,BufRead *.pyx,*.pxd setf pyrex - -" Python, Python Shell Startup and Python Stub Files -" Quixote (Python-based web framework) -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 - -" Quarto -au BufRead,BufNewFile *.qmd setf quarto - -" Radiance -au BufNewFile,BufRead *.rad,*.mat setf radiance - -" Raku (formerly Perl6) -au BufNewFile,BufRead *.pm6,*.p6,*.t6,*.pod6,*.raku,*.rakumod,*.rakudoc,*.rakutest setf raku - -" Ratpoison config/command files -au BufNewFile,BufRead .ratpoisonrc,ratpoisonrc setf ratpoison - -" RCS file -au BufNewFile,BufRead *\,v setf rcs - -" Readline -au BufNewFile,BufRead .inputrc,inputrc setf readline - -" Registry for MS-Windows -au BufNewFile,BufRead *.reg - \ if getline(1) =~? '^REGEDIT[0-9]*\s*$\|^Windows Registry Editor Version \d*\.\d*\s*$' | setf registry | endif - -" Renderman Interface Bytestream -au BufNewFile,BufRead *.rib setf rib - -" Rego Policy Language -au BufNewFile,BufRead *.rego setf rego - -" Rexx -au BufNewFile,BufRead *.rex,*.orx,*.rxo,*.rxj,*.jrexx,*.rexxj,*.rexx,*.testGroup,*.testUnit setf rexx - -" R Help file -if has("fname_case") - au BufNewFile,BufRead *.rd,*.Rd setf rhelp -else - au BufNewFile,BufRead *.rd setf rhelp -endif - -" R noweb file -if has("fname_case") - au BufNewFile,BufRead *.Rnw,*.rnw,*.Snw,*.snw setf rnoweb -else - au BufNewFile,BufRead *.rnw,*.snw setf rnoweb -endif - -" R Markdown file -if has("fname_case") - au BufNewFile,BufRead *.Rmd,*.rmd,*.Smd,*.smd setf rmd -else - au BufNewFile,BufRead *.rmd,*.smd setf rmd -endif - -" RSS looks like XML -au BufNewFile,BufRead *.rss setf xml - -" R reStructuredText file -if has("fname_case") - au BufNewFile,BufRead *.Rrst,*.rrst,*.Srst,*.srst setf rrst -else - au BufNewFile,BufRead *.rrst,*.srst setf rrst -endif - -" Rexx, Rebol or R -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 - -" Relax NG Compact -au BufNewFile,BufRead *.rnc setf rnc - -" Relax NG XML -au BufNewFile,BufRead *.rng setf rng - -" RPL/2 -au BufNewFile,BufRead *.rpl setf rpl - -" Robot Framework -au BufNewFile,BufRead *.robot,*.resource setf robot - -" Robots.txt -au BufNewFile,BufRead robots.txt setf robots - -" Rpcgen -au BufNewFile,BufRead *.x setf rpcgen - -" MikroTik RouterOS script -au BufRead,BufNewFile *.rsc setf routeros - -" reStructuredText Documentation Format -au BufNewFile,BufRead *.rst setf rst - -" RTF -au BufNewFile,BufRead *.rtf setf rtf - -" Interactive Ruby shell -au BufNewFile,BufRead .irbrc,irbrc setf ruby - -" Ruby -au BufNewFile,BufRead *.rb,*.rbw setf ruby - -" RubyGems -au BufNewFile,BufRead *.gemspec setf ruby - -" RBS (Ruby Signature) -au BufNewFile,BufRead *.rbs setf rbs - -" Rackup -au BufNewFile,BufRead *.ru setf ruby - -" Bundler -au BufNewFile,BufRead Gemfile setf ruby - -" Ruby on Rails -au BufNewFile,BufRead *.builder,*.rxml,*.rjs setf ruby - -" Rantfile and Rakefile is like Ruby -au BufNewFile,BufRead [rR]antfile,*.rant,[rR]akefile,*.rake setf ruby - -" Rust -au BufNewFile,BufRead *.rs setf rust -au BufNewFile,BufRead Cargo.lock,*/.cargo/config,*/.cargo/credentials setf toml - -" S-lang (or shader language, or SmallLisp) -au BufNewFile,BufRead *.sl setf slang - -" Samba config -au BufNewFile,BufRead smb.conf setf samba - -" SAS script -au BufNewFile,BufRead *.sas setf sas - -" Sass -au BufNewFile,BufRead *.sass setf sass - -" Sather -au BufNewFile,BufRead *.sa setf sather - -" 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 - - -" SCSS -au BufNewFile,BufRead *.scss setf scss - -" SD: Streaming Descriptors -au BufNewFile,BufRead *.sd setf sd - -" SDL -au BufNewFile,BufRead *.sdl,*.pr setf sdl - -" sed -au BufNewFile,BufRead *.sed setf sed - -" SubRip -au BufNewFile,BufRead *.srt setf srt - -" SubStation Alpha -au BufNewFile,BufRead *.ass,*.ssa setf ssa - -" svelte -au BufNewFile,BufRead *.svelte setf svelte - -" Sieve (RFC 3028, 5228) -au BufNewFile,BufRead *.siv,*.sieve setf sieve - -" Sendmail -au BufNewFile,BufRead sendmail.cf setf sm - -" Sendmail .mc files are actually m4. Could also be MS Message text file or -" Maxima. -au BufNewFile,BufRead *.mc call dist#ft#McSetf() - -" Services -au BufNewFile,BufRead */etc/services setf services - -" Service Location config -au BufNewFile,BufRead */etc/slp.conf setf slpconf - -" Service Location registration -au BufNewFile,BufRead */etc/slp.reg setf slpreg - -" Service Location SPI -au BufNewFile,BufRead */etc/slp.spi setf slpspi - -" Setserial config -au BufNewFile,BufRead */etc/serial.conf setf setserial - -" SGML -au BufNewFile,BufRead *.sgm,*.sgml - \ if getline(1).getline(2).getline(3).getline(4).getline(5) =~? 'linuxdoc' | - \ setf sgmllnx | - \ elseif getline(1) =~ '<!DOCTYPE.*DocBook' || getline(2) =~ '<!DOCTYPE.*DocBook' | - \ let b:docbk_type = "sgml" | - \ let b:docbk_ver = 4 | - \ setf docbk | - \ else | - \ setf sgml | - \ endif - -" SGMLDECL -au BufNewFile,BufRead *.decl,*.dcl,*.dec - \ if getline(1).getline(2).getline(3) =~? '^<!SGML' | - \ setf sgmldecl | - \ endif - -" SGML catalog file -au BufNewFile,BufRead catalog setf catalog - -" Shell scripts (sh, ksh, bash, bash2, csh); Allow .profile_foo etc. -" Gentoo ebuilds, Arch Linux PKGBUILDs and Alpine Linux APKBUILDs are actually -" bash scripts. -" NOTE: Patterns ending in a star are further down, these have lower priority. -au BufNewFile,BufRead .bashrc,bashrc,bash.bashrc,.bash[_-]profile,.bash[_-]logout,.bash[_-]aliases,bash-fc[-.],*.ebuild,*.bash,*.eclass,PKGBUILD,APKBUILD call dist#ft#SetFileTypeSH("bash") -au BufNewFile,BufRead .kshrc,*.ksh call dist#ft#SetFileTypeSH("ksh") -au BufNewFile,BufRead */etc/profile,.profile,*.sh,*.env call dist#ft#SetFileTypeSH(getline(1)) - -" Shell script (Arch Linux) or PHP file (Drupal) -au BufNewFile,BufRead *.install - \ if getline(1) =~ '<?php' | - \ setf php | - \ else | - \ call dist#ft#SetFileTypeSH("bash") | - \ endif - -" tcsh scripts (patterns ending in a star further below) -au BufNewFile,BufRead .tcshrc,*.tcsh,tcsh.tcshrc,tcsh.login call dist#ft#SetFileTypeShell("tcsh") - -" csh scripts, but might also be tcsh scripts (on some systems csh is tcsh) -" (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,*.sld,*.rkt,*.rktd,*.rktl setf scheme - -" Screen RC -au BufNewFile,BufRead .screenrc,screenrc setf screen - -" Sexplib -au BufNewFile,BufRead *.sexp setf sexplib - -" Simula -au BufNewFile,BufRead *.sim setf simula - -" SINDA -au BufNewFile,BufRead *.sin,*.s85 setf sinda - -" SiSU -au BufNewFile,BufRead *.sst,*.ssm,*.ssi,*.-sst,*._sst setf sisu -au BufNewFile,BufRead *.sst.meta,*.-sst.meta,*._sst.meta setf sisu - -" SKILL -au BufNewFile,BufRead *.il,*.ils,*.cdf setf skill - -" SLRN -au BufNewFile,BufRead .slrnrc setf slrnrc -au BufNewFile,BufRead *.score setf slrnsc - -" Smalltalk -au BufNewFile,BufRead *.st setf st - -" Smalltalk (and Rexx, TeX, and Visual Basic) -au BufNewFile,BufRead *.cls call dist#ft#FTcls() - -" Smarty templates -au BufNewFile,BufRead *.tpl setf smarty - -" SMIL or XML -au BufNewFile,BufRead *.smil - \ if getline(1) =~ '<?\s*xml.*?>' | - \ setf xml | - \ else | - \ setf smil | - \ endif - -" SMIL or SNMP MIB file -au BufNewFile,BufRead *.smi - \ if getline(1) =~ '\<smil\>' | - \ setf smil | - \ else | - \ setf mib | - \ endif - -" SMITH -au BufNewFile,BufRead *.smt,*.smith setf smith - -" Snobol4 and spitbol -au BufNewFile,BufRead *.sno,*.spt setf snobol4 - -" SNMP MIB files -au BufNewFile,BufRead *.mib,*.my setf mib - -" Snort Configuration -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 - -" Spec (Linux RPM) -au BufNewFile,BufRead *.spec setf spec - -" Speedup (AspenTech plant simulator) -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 - -" Spyce -au BufNewFile,BufRead *.spy,*.spi setf spyce - -" Squid -au BufNewFile,BufRead squid.conf setf squid - -" SQL for Oracle Designer -au BufNewFile,BufRead *.tyb,*.typ,*.tyc,*.pkb,*.pks setf sql - -" SQL -au BufNewFile,BufRead *.sql call dist#ft#SQL() - -" SQLJ -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,*/.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 -au BufNewFile,BufRead */etc/ssh/sshd_config.d/*.conf setf sshdconfig - -" Stata -au BufNewFile,BufRead *.ado,*.do,*.imata,*.mata setf stata -" Also *.class, but not when it's a Java bytecode file -au BufNewFile,BufRead *.class - \ if getline(1) !~ "^\xca\xfe\xba\xbe" | setf stata | endif - -" SMCL -au BufNewFile,BufRead *.hlp,*.ihlp,*.smcl setf smcl - -" Stored Procedures -au BufNewFile,BufRead *.stp setf stp - -" Standard ML -au BufNewFile,BufRead *.sml setf sml - -" Sratus VOS command macro -au BufNewFile,BufRead *.cm setf voscm - -" Swift -au BufNewFile,BufRead *.swift setf swift -au BufNewFile,BufRead *.swift.gyb setf swiftgyb - -" Swift Intermediate Language or SILE -au BufNewFile,BufRead *.sil call dist#ft#FTsil() - -" Sysctl -au BufNewFile,BufRead */etc/sysctl.conf,*/etc/sysctl.d/*.conf setf sysctl - -" Systemd unit files -au BufNewFile,BufRead */systemd/*.{automount,dnssd,link,mount,netdev,network,nspawn,path,service,slice,socket,swap,target,timer} setf systemd -" Systemd overrides -au BufNewFile,BufRead */etc/systemd/*.conf.d/*.conf setf systemd -au BufNewFile,BufRead */etc/systemd/system/*.d/*.conf setf systemd -au BufNewFile,BufRead */.config/systemd/user/*.d/*.conf setf systemd -" Systemd temp files -au BufNewFile,BufRead */etc/systemd/system/*.d/.#* setf systemd -au BufNewFile,BufRead */etc/systemd/system/.#* setf systemd -au BufNewFile,BufRead */.config/systemd/user/*.d/.#* setf systemd -au BufNewFile,BufRead */.config/systemd/user/.#* setf systemd - -" Synopsys Design Constraints -au BufNewFile,BufRead *.sdc setf sdc - -" Sudoers -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 - -" Tags -au BufNewFile,BufRead tags setf tags - -" TAK -au BufNewFile,BufRead *.tak setf tak - -" Task -au BufRead,BufNewFile {pending,completed,undo}.data setf taskdata -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 - -" Telix Salt -au BufNewFile,BufRead *.slt setf tsalt - -" Tera Term Language or Turtle -au BufRead,BufNewFile *.ttl - \ if getline(1) =~ '^@\?\(prefix\|base\)' | - \ setf turtle | - \ else | - \ setf teraterm | - \ endif - -" Terminfo -au BufNewFile,BufRead *.ti setf terminfo - -" Terraform variables -au BufRead,BufNewFile *.tfvars setf terraform-vars - -" TeX -au BufNewFile,BufRead *.latex,*.sty,*.dtx,*.ltx,*.bbl setf tex -au BufNewFile,BufRead *.tex call dist#ft#FTtex() - -" ConTeXt -au BufNewFile,BufRead *.mkii,*.mkiv,*.mkvi,*.mkxl,*.mklx setf context - -" Texinfo -au BufNewFile,BufRead *.texinfo,*.texi,*.txi setf texinfo - -" TeX configuration -au BufNewFile,BufRead texmf.cnf setf texmf - -" Tidy config -au BufNewFile,BufRead .tidyrc,tidyrc,tidy.conf setf tidy - -" TF mud client -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 - -" TOML -au BufNewFile,BufRead *.toml setf toml - -" TPP - Text Presentation Program -au BufNewFile,BufRead *.tpp setf tpp - -" Treetop -au BufRead,BufNewFile *.treetop setf treetop - -" Trustees -au BufNewFile,BufRead trustees.conf setf trustees - -" TSS - Geometry -au BufNewFile,BufReadPost *.tssgm setf tssgm - -" TSS - Optics -au BufNewFile,BufReadPost *.tssop setf tssop - -" TSS - Command Line (temporary) -au BufNewFile,BufReadPost *.tsscl setf tsscl - -" TSV Files -au BufNewFile,BufRead *.tsv setf tsv - -" Tutor mode -au BufNewFile,BufReadPost *.tutor setf tutor - -" TWIG files -au BufNewFile,BufReadPost *.twig setf twig - -" TypeScript or Qt translation file (which is XML) -au BufNewFile,BufReadPost *.ts - \ if getline(1) =~ '<?xml' | - \ setf xml | - \ else | - \ setf typescript | - \ endif - -" TypeScript module and common -au BufNewFile,BufRead *.mts,*.cts setf typescript - -" TypeScript with React -au BufNewFile,BufRead *.tsx setf typescriptreact - -" Motif UIT/UIL files -au BufNewFile,BufRead *.uit,*.uil setf uil - -" Udev conf -au BufNewFile,BufRead */etc/udev/udev.conf setf udevconf - -" Udev permissions -au BufNewFile,BufRead */etc/udev/permissions.d/*.permissions setf udevperm -" -" Udev symlinks config -au BufNewFile,BufRead */etc/udev/cdsymlinks.conf setf sh - -" UnrealScript -au BufNewFile,BufRead *.uc setf uc - -" Updatedb -au BufNewFile,BufRead */etc/updatedb.conf setf updatedb - -" Upstart (init(8)) config files -au BufNewFile,BufRead */usr/share/upstart/*.conf setf upstart -au BufNewFile,BufRead */usr/share/upstart/*.override setf upstart -au BufNewFile,BufRead */etc/init/*.conf,*/etc/init/*.override setf upstart -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 - -" VDF -au BufNewFile,BufRead *.vdf setf vdf - -" VDM -au BufRead,BufNewFile *.vdmpp,*.vpp setf vdmpp -au BufRead,BufNewFile *.vdmrt setf vdmrt -au BufRead,BufNewFile *.vdmsl,*.vdm setf vdmsl - -" 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 - -" Verilog-AMS HDL -au BufNewFile,BufRead *.va,*.vams setf verilogams - -" SystemVerilog -au BufNewFile,BufRead *.sv,*.svh setf systemverilog - -" VHDL -au BufNewFile,BufRead *.hdl,*.vhd,*.vhdl,*.vbe,*.vst,*.vho setf vhdl - -" Vim script -au BufNewFile,BufRead *.vim,*.vba,.exrc,_exrc setf vim - -" Viminfo file -au BufNewFile,BufRead .viminfo,_viminfo setf viminfo - -" Virata Config Script File or Drupal module -au BufRead,BufNewFile *.hw,*.module,*.pkg - \ if getline(1) =~ '<?php' | - \ setf php | - \ else | - \ setf virata | - \ endif - -" Visual Basic (also uses *.bas) or FORM -au BufNewFile,BufRead *.frm call dist#ft#FTfrm() - -" SaxBasic is close to Visual Basic -au BufNewFile,BufRead *.sba setf vb - -" Vgrindefs file -au BufNewFile,BufRead vgrindefs setf vgrindefs - -" VRML V1.0c -au BufNewFile,BufRead *.wrl setf vrml - -" Vroom (vim testing and executable documentation) -au BufNewFile,BufRead *.vroom setf vroom - -" Vue.js Single File Component -au BufNewFile,BufRead *.vue setf vue - -" WebAssembly -au BufNewFile,BufRead *.wast,*.wat setf wast - -" Webmacro -au BufNewFile,BufRead *.wm setf webmacro - -" Wget config -au BufNewFile,BufRead .wgetrc,wgetrc setf wget - -" Wget2 config -au BufNewFile,BufRead .wget2rc,wget2rc setf wget2 - -" Website MetaLanguage -au BufNewFile,BufRead *.wml setf wml - -" Winbatch -au BufNewFile,BufRead *.wbt setf winbatch - -" WSML -au BufNewFile,BufRead *.wsml setf wsml - -" WPL -au BufNewFile,BufRead *.wpl setf xml - -" WvDial -au BufNewFile,BufRead wvdial.conf,.wvdialrc setf wvdial - -" CVS RC file -au BufNewFile,BufRead .cvsrc setf cvsrc - -" CVS commit file -au BufNewFile,BufRead cvs\d\+ setf cvs - -" WEB (*.web is also used for Winbatch: Guess, based on expecting "%" comment -" lines in a WEB file). -au BufNewFile,BufRead *.web - \ if getline(1)[0].getline(2)[0].getline(3)[0].getline(4)[0].getline(5)[0] =~ "%" | - \ setf web | - \ else | - \ setf winbatch | - \ endif - -" Windows Scripting Host and Windows Script Component -au BufNewFile,BufRead *.ws[fc] setf wsh - -" XHTML -au BufNewFile,BufRead *.xhtml,*.xht setf xhtml - -" X Pixmap (dynamically sets colors, this used to trigger on BufEnter to make -" it work better, but that breaks setting 'filetype' manually) -au BufNewFile,BufRead *.xpm - \ if getline(1) =~ "XPM2" | - \ setf xpm2 | - \ else | - \ setf xpm | - \ endif -au BufNewFile,BufRead *.xpm2 setf xpm2 - -" XFree86 config -au BufNewFile,BufRead XF86Config - \ if getline(1) =~ '\<XConfigurator\>' | - \ let b:xf86conf_xfree86_version = 3 | - \ endif | - \ setf xf86conf -au BufNewFile,BufRead */xorg.conf.d/*.conf - \ let b:xf86conf_xfree86_version = 4 | - \ setf xf86conf - -" Xorg config -au BufNewFile,BufRead xorg.conf,xorg.conf-4 let b:xf86conf_xfree86_version = 4 | setf xf86conf - -" Xinetd conf -au BufNewFile,BufRead */etc/xinetd.conf setf xinetd - -" XS Perl extension interface language -au BufNewFile,BufRead *.xs setf xs - -" X resources file -au BufNewFile,BufRead .Xdefaults,.Xpdefaults,.Xresources,xdm-config,*.ad setf xdefaults - -" Xmath -au BufNewFile,BufRead *.msc,*.msf setf xmath -au BufNewFile,BufRead *.ms - \ if !dist#ft#FTnroff() | setf xmath | endif - -" XML specific variants: docbk and xbl -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 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 - -" TPM's are RDF-based descriptions of TeX packages (Nikolai Weibull) -au BufNewFile,BufRead *.tpm setf xml - -" Xdg menus -au BufNewFile,BufRead */etc/xdg/menus/*.menu setf xml - -" ATI graphics driver configuration -au BufNewFile,BufRead fglrxrc setf xml - -" Web Services Description Language (WSDL) -au BufNewFile,BufRead *.wsdl setf xml - -" XLIFF (XML Localisation Interchange File Format) is also XML -au BufNewFile,BufRead *.xlf setf xml -au BufNewFile,BufRead *.xliff setf xml - -" XML User Interface Language -au BufNewFile,BufRead *.xul setf xml - -" X11 xmodmap (also see below) -au BufNewFile,BufRead *Xmodmap setf xmodmap - -" Xquery -au BufNewFile,BufRead *.xq,*.xql,*.xqm,*.xquery,*.xqy setf xquery - -" XSD -au BufNewFile,BufRead *.xsd setf xsd - -" Xslt -au BufNewFile,BufRead *.xsl,*.xslt setf xslt - -" Yacc -au BufNewFile,BufRead *.yy,*.yxx,*.y++ setf yacc - -" Yacc or racc -au BufNewFile,BufRead *.y call dist#ft#FTy() - -" Yaml -au BufNewFile,BufRead *.yaml,*.yml setf yaml - -" Raml -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 -au BufNewFile,BufRead *.zut setf zimbutempl - -" Zope -" dtml (zope dynamic template markup language), pt (zope page template), -" cpt (zope form controller page template) -au BufNewFile,BufRead *.dtml,*.pt,*.cpt call dist#ft#FThtml() -" zsql (zope sql method) -au BufNewFile,BufRead *.zsql call dist#ft#SQL() - -" Z80 assembler asz80 -au BufNewFile,BufRead *.z8a setf z8a - -augroup END - - -" Source the user-specified filetype file, for backwards compatibility with -" Vim 5.x. -if exists("myfiletypefile") && filereadable(expand(myfiletypefile)) - execute "source " . myfiletypefile -endif - - -" Check for "*" after loading myfiletypefile, so that scripts.vim is only used -" when there are no matching file name extensions. -" Don't do this for compressed files. -augroup filetypedetect -au BufNewFile,BufRead * - \ if !did_filetype() && expand("<amatch>") !~ g:ft_ignore_pat - \ | runtime! scripts.vim | endif -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. -" Most of these should call s:StarSetf() to avoid names ending in .gz and the -" like are used. - -" More Apache style config files -au BufNewFile,BufRead */etc/proftpd/*.conf*,*/etc/proftpd/conf.*/* call s:StarSetf('apachestyle') -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.*/*,*/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') -au BufNewFile,BufRead *asterisk*/*voicemail.conf* call s:StarSetf('asteriskvm') - -" Bazaar version control -au BufNewFile,BufRead bzr_log.* setf bzr - -" Bazel build file -if !has("fname_case") - au BufNewFile,BufRead *.BUILD,BUILD setf bzl -endif - -" BIND zone -au BufNewFile,BufRead */named/db.*,*/bind/db.* call s:StarSetf('bindzone') - -au BufNewFile,BufRead cabal.project.* call s:StarSetf('cabalproject') - -" Calendar -au BufNewFile,BufRead */.calendar/*, - \*/share/calendar/*/calendar.*,*/share/calendar/calendar.* - \ call s:StarSetf('calendar') - -" Changelog -au BufNewFile,BufRead [cC]hange[lL]og* - \ if getline(1) =~ '; urgency=' - \| call s:StarSetf('debchangelog') - \|else - \| call s:StarSetf('changelog') - \|endif - -" Crontab -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') - -" Fvwm -au BufNewFile,BufRead */.fvwm/* call s:StarSetf('fvwm') -au BufNewFile,BufRead *fvwmrc*,*fvwm95*.hook - \ let b:fvwm_version = 1 | call s:StarSetf('fvwm') -au BufNewFile,BufRead *fvwm2rc* - \ if expand("<afile>:e") == "m4" - \| call s:StarSetf('fvwm2m4') - \|else - \| let b:fvwm_version = 2 | call s:StarSetf('fvwm') - \|endif - -" Gedcom -au BufNewFile,BufRead */tmp/lltmp* call s:StarSetf('gedcom') - -" Git -au BufNewFile,BufRead */.gitconfig.d/*,*/etc/gitconfig.d/* call s:StarSetf('gitconfig') - -" Gitolite -au BufNewFile,BufRead */gitolite-admin/conf/* call s:StarSetf('gitolite') - -" GTK RC -au BufNewFile,BufRead .gtkrc*,gtkrc* call s:StarSetf('gtkrc') - -" Jam -au BufNewFile,BufRead Prl*.*,JAM*.* call s:StarSetf('jam') - -" Jargon -au! BufNewFile,BufRead *jarg* - \ if getline(1).getline(2).getline(3).getline(4).getline(5) =~? 'THIS IS THE JARGON FILE' - \| call s:StarSetf('jargon') - \|endif - -" Java Properties resource file (note: doesn't catch font.properties.pl) -au BufNewFile,BufRead *.properties_??_??_* call s:StarSetf('jproperties') - -" Kconfig -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') - -" Makefile -au BufNewFile,BufRead [mM]akefile* call s:StarSetf('make') - -" Ruby Makefile -au BufNewFile,BufRead [rR]akefile* call s:StarSetf('ruby') - -" Mail (also matches muttrc.vim, so this is below the other checks) -au BufNewFile,BufRead {neo,}mutt[[:alnum:]._-]\\\{6\} setf mail - -au BufNewFile,BufRead reportbug-* call s:StarSetf('mail') - -" Modconf -au BufNewFile,BufRead */etc/modutils/* - \ if executable(expand("<afile>")) != 1 - \| call s:StarSetf('modconf') - \|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') - -" Neomutt setup file -au BufNewFile,BufRead .neomuttrc*,*/.neomutt/neomuttrc* call s:StarSetf('neomuttrc') -au BufNewFile,BufRead neomuttrc*,Neomuttrc* call s:StarSetf('neomuttrc') - -" Nroff macros -au BufNewFile,BufRead tmac.* call s:StarSetf('nroff') - -" OpenBSD hostname.if -au BufNewFile,BufRead */etc/hostname.* call s:StarSetf('config') - -" Pam conf -au BufNewFile,BufRead */etc/pam.d/* call s:StarSetf('pamconf') - -" Printcap and Termcap -au BufNewFile,BufRead *printcap* - \ if !did_filetype() - \| let b:ptcap_type = "print" | call s:StarSetf('ptcap') - \|endif -au BufNewFile,BufRead *termcap* - \ if !did_filetype() - \| let b:ptcap_type = "term" | call s:StarSetf('ptcap') - \|endif - -" ReDIF -" Only used when the .rdf file was not detected to be XML. -au BufRead,BufNewFile *.rdf call dist#ft#Redif() - -" Remind -au BufNewFile,BufRead .reminders* call s:StarSetf('remind') - -" SGML catalog file -au BufNewFile,BufRead sgml.catalog* call s:StarSetf('catalog') - -" avoid doc files being recognized a shell files -au BufNewFile,BufRead */doc/{,.}bash[_-]completion{,.d,.sh}{,/*} setf text - -" Shell scripts ending in a star -au BufNewFile,BufRead .bashrc*,.bash[_-]profile*,.bash[_-]logout*,.bash[_-]aliases*,bash-fc[-.]*,PKGBUILD*,APKBUILD*,*/{,.}bash[_-]completion{,.d,.sh}{,/*} call dist#ft#SetFileTypeSH("bash") -au BufNewFile,BufRead .kshrc* call dist#ft#SetFileTypeSH("ksh") -au BufNewFile,BufRead .profile* call dist#ft#SetFileTypeSH(getline(1)) - -" Sudoers -au BufNewFile,BufRead */etc/sudoers.d/* call s:StarSetf('sudoers') - -" tcsh scripts ending in a star -au BufNewFile,BufRead .tcshrc* call dist#ft#SetFileTypeShell("tcsh") - -" csh scripts ending in a star -au BufNewFile,BufRead .login*,.cshrc* call dist#ft#CSH() - -" tmux configuration with arbitrary extension -au BufNewFile,BufRead {.,}tmux*.conf* setf tmux - -" VHDL -au BufNewFile,BufRead *.vhdl_[0-9]* call s:StarSetf('vhdl') - -" Vim script -au BufNewFile,BufRead *vimrc* call s:StarSetf('vim') - -" Subversion commit file -au BufNewFile,BufRead svn-commit*.tmp setf svn - -" X resources file -au BufNewFile,BufRead Xresources*,*/app-defaults/*,*/Xresources/* call s:StarSetf('xdefaults') - -" XFree86 config -au BufNewFile,BufRead XF86Config-4* - \ let b:xf86conf_xfree86_version = 4 | call s:StarSetf('xf86conf') -au BufNewFile,BufRead XF86Config* - \ if getline(1) =~ '\<XConfigurator\>' - \| let b:xf86conf_xfree86_version = 3 - \|endif - \|call s:StarSetf('xf86conf') - -" X11 xmodmap -au BufNewFile,BufRead *xmodmap* call s:StarSetf('xmodmap') - -" Xinetd conf -au BufNewFile,BufRead */etc/xinetd.d/* call s:StarSetf('xinetd') - -" yum conf (close enough to dosini) -au BufNewFile,BufRead */etc/yum.repos.d/* call s:StarSetf('dosini') - -" Z-Shell script ending in a star -au BufNewFile,BufRead .zsh*,.zlog*,.zcompdump* call s:StarSetf('zsh') -au BufNewFile,BufRead zsh*,zlog* call s:StarSetf('zsh') - - -" 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 - -" Blueprint markup files -au BufNewFile,BufRead *.blp setf blueprint - -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 -" state. -augroup END - -" Generic configuration file. Use FALLBACK, it's just guessing! -au filetypedetect BufNewFile,BufRead,StdinReadPost * - \ if !did_filetype() && expand("<amatch>") !~ g:ft_ignore_pat - \ && (getline(1) =~ '^#' || getline(2) =~ '^#' || getline(3) =~ '^#' - \ || getline(4) =~ '^#' || getline(5) =~ '^#') | - \ setf FALLBACK conf | - \ endif - - -" If the GUI is already running, may still need to install the Syntax menu. -" Don't do it when the 'M' flag is included in 'guioptions'. -if has("menu") && has("gui_running") - \ && !exists("did_install_syntax_menu") && &guioptions !~# "M" - source <sfile>:p:h/menu.vim -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) - let output = '' - for f in a:testlist - try - exe f - catch - let output = output . "\n" . f . ": " . v:exception - endtry - endfor - return output -endfunc - -" Restore 'cpoptions' -let &cpo = s:cpo_save -unlet s:cpo_save diff --git a/runtime/ftplugin/abaqus.vim b/runtime/ftplugin/abaqus.vim index 3faeff621a..5931cd921d 100644 --- a/runtime/ftplugin/abaqus.vim +++ b/runtime/ftplugin/abaqus.vim @@ -1,7 +1,7 @@ " Vim filetype plugin file " Language: Abaqus finite element input file (www.abaqus.com) " Maintainer: Carl Osterwisch <costerwi@gmail.com> -" Last Change: 2022 Aug 03 +" Last Change: 2022 Oct 08 " Only do this when not done yet for this buffer if exists("b:did_ftplugin") | finish | endif @@ -66,25 +66,44 @@ if exists("loaded_matchit") && !exists("b:match_words") endif if !exists("no_plugin_maps") && !exists("no_abaqus_maps") - " Define keys used to move [count] keywords backward or forward. - noremap <silent><buffer> [[ ?^\*\a<CR>:nohlsearch<CR> - noremap <silent><buffer> ]] /^\*\a<CR>:nohlsearch<CR> + " Map [[ and ]] keys to move [count] keywords backward or forward + nnoremap <silent><buffer> ]] :call <SID>Abaqus_NextKeyword(1)<CR> + nnoremap <silent><buffer> [[ :call <SID>Abaqus_NextKeyword(-1)<CR> + function! <SID>Abaqus_NextKeyword(direction) + .mark ' + if a:direction < 0 + let flags = 'b' + else + let flags = '' + endif + let l:count = abs(a:direction) * v:count1 + while l:count > 0 && search("^\\*\\a", flags) + let l:count -= 1 + endwhile + endfunction - " Define key to toggle commenting of the current line or range + " Map \\ to toggle commenting of the current line or range noremap <silent><buffer> <LocalLeader><LocalLeader> \ :call <SID>Abaqus_ToggleComment()<CR>j function! <SID>Abaqus_ToggleComment() range - if strpart(getline(a:firstline), 0, 2) == "**" - " Un-comment all lines in range - silent execute a:firstline . ',' . a:lastline . 's/^\*\*//' - else - " Comment all lines in range - silent execute a:firstline . ',' . a:lastline . 's/^/**/' - endif + if strpart(getline(a:firstline), 0, 2) == "**" + " Un-comment all lines in range + silent execute a:firstline . ',' . a:lastline . 's/^\*\*//' + else + " Comment all lines in range + silent execute a:firstline . ',' . a:lastline . 's/^/**/' + endif + endfunction + + " Map \s to swap first two comma separated fields + noremap <silent><buffer> <LocalLeader>s :call <SID>Abaqus_Swap()<CR> + function! <SID>Abaqus_Swap() range + silent execute a:firstline . ',' . a:lastline . 's/\([^*,]*\),\([^,]*\)/\2,\1/' endfunction let b:undo_ftplugin .= "|unmap <buffer> [[|unmap <buffer> ]]" \ . "|unmap <buffer> <LocalLeader><LocalLeader>" + \ . "|unmap <buffer> <LocalLeader>s" endif " Undo must be done in nocompatible mode for <LocalLeader>. diff --git a/runtime/ftplugin/apache.vim b/runtime/ftplugin/apache.vim new file mode 100644 index 0000000000..9f612f5447 --- /dev/null +++ b/runtime/ftplugin/apache.vim @@ -0,0 +1,16 @@ +" Vim filetype plugin +" Language: apache configuration file +" Maintainer: Per Juchtmans <dubgeiser+vimNOSPAM@gmail.com> +" Last Change: 2022 Oct 22 + +if exists("b:did_ftplugin") + finish +endif +let b:did_ftplugin = 1 + +setlocal comments=:# +setlocal commentstring=#\ %s + +let b:undo_ftplugin = "setlocal comments< commentstring<" + +" vim: nowrap sw=2 sts=2 ts=8 noet: diff --git a/runtime/ftplugin/checkhealth.vim b/runtime/ftplugin/checkhealth.vim index 3d8e9ace1a..62a1970b4a 100644 --- a/runtime/ftplugin/checkhealth.vim +++ b/runtime/ftplugin/checkhealth.vim @@ -1,20 +1,18 @@ " Vim filetype plugin -" Language: Neovim checkhealth buffer -" Last Change: 2021 Dec 15 +" Language: Nvim :checkhealth buffer +" Last Change: 2022 Nov 10 if exists("b:did_ftplugin") finish endif -runtime! ftplugin/markdown.vim ftplugin/markdown_*.vim ftplugin/markdown/*.vim +runtime! ftplugin/help.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<" + let b:undo_ftplugin .= "|setl wrap< bri< lbr< kp< isk<" else - let b:undo_ftplugin = "setl wrap< bri< lbr< cole< cocu< kp< isk<" + let b:undo_ftplugin = "setl wrap< bri< lbr< kp< isk<" endif diff --git a/runtime/ftplugin/cs.vim b/runtime/ftplugin/cs.vim index f53ffcbc8e..0734d11d22 100644 --- a/runtime/ftplugin/cs.vim +++ b/runtime/ftplugin/cs.vim @@ -2,7 +2,7 @@ " Language: C# " Maintainer: Nick Jensen <nickspoon@gmail.com> " Former Maintainer: Johannes Zellner <johannes@zellner.org> -" Last Change: 2021-12-07 +" Last Change: 2022-11-16 " License: Vim (see :h license) " Repository: https://github.com/nickspoons/vim-cs @@ -25,8 +25,9 @@ let b:undo_ftplugin = 'setl com< fo<' if exists('loaded_matchit') && !exists('b:match_words') " #if/#endif support included by default + let b:match_ignorecase = 0 let b:match_words = '\%(^\s*\)\@<=#\s*region\>:\%(^\s*\)\@<=#\s*endregion\>,' - let b:undo_ftplugin .= ' | unlet! b:match_words' + let b:undo_ftplugin .= ' | unlet! b:match_ignorecase b:match_words' endif if (has('gui_win32') || has('gui_gtk')) && !exists('b:browsefilter') diff --git a/runtime/ftplugin/lua.vim b/runtime/ftplugin/lua.vim index aaa61f71d9..88b1fc9d44 100644 --- a/runtime/ftplugin/lua.vim +++ b/runtime/ftplugin/lua.vim @@ -3,7 +3,8 @@ " Maintainer: Doug Kearns <dougkearns@gmail.com> " Previous Maintainer: Max Ischenko <mfi@ukr.net> " Contributor: Dorai Sitaram <ds26@gte.com> -" Last Change: 2022 Sep 05 +" C.D. MacEachern <craig.daniel.maceachern@gmail.com> +" Last Change: 2022 Nov 19 if exists("b:did_ftplugin") finish @@ -19,9 +20,11 @@ setlocal formatoptions-=t formatoptions+=croql let &l:define = '\<function\|\<local\%(\s\+function\)\=' +" TODO: handle init.lua +setlocal includeexpr=tr(v:fname,'.','/') setlocal suffixesadd=.lua -let b:undo_ftplugin = "setlocal cms< com< def< fo< sua<" +let b:undo_ftplugin = "setlocal cms< com< def< fo< inex< sua<" if exists("loaded_matchit") && !exists("b:match_words") let b:match_ignorecase = 0 diff --git a/runtime/ftplugin/markdown.vim b/runtime/ftplugin/markdown.vim index fc1d9e068b..2b963f139d 100644 --- a/runtime/ftplugin/markdown.vim +++ b/runtime/ftplugin/markdown.vim @@ -1,7 +1,7 @@ " Vim filetype plugin -" Language: Markdown -" Maintainer: Tim Pope <vimNOSPAM@tpope.org> -" Last Change: 2019 Dec 05 +" Language: Markdown +" Maintainer: Tim Pope <https://github.com/tpope/vim-markdown> +" Last Change: 2022 Oct 13 if exists("b:did_ftplugin") finish @@ -9,18 +9,33 @@ endif runtime! ftplugin/html.vim ftplugin/html_*.vim ftplugin/html/*.vim +let s:keepcpo= &cpo +set cpo&vim + setlocal comments=fb:*,fb:-,fb:+,n:> commentstring=<!--%s--> setlocal formatoptions+=tcqln formatoptions-=r formatoptions-=o -setlocal formatlistpat=^\\s*\\d\\+\\.\\s\\+\\\|^[-*+]\\s\\+\\\|^\\[^\\ze[^\\]]\\+\\]: +setlocal formatlistpat=^\\s*\\d\\+\\.\\s\\+\\\|^\\s*[-*+]\\s\\+\\\|^\\[^\\ze[^\\]]\\+\\]:\\&^.\\{4\\} if exists('b:undo_ftplugin') - let b:undo_ftplugin .= "|setl cms< com< fo< flp<" + let b:undo_ftplugin .= "|setl cms< com< fo< flp< et< ts< sts< sw<" else - let b:undo_ftplugin = "setl cms< com< fo< flp<" + let b:undo_ftplugin = "setl cms< com< fo< flp< et< ts< sts< sw<" +endif + +if get(g:, 'markdown_recommended_style', 1) + setlocal expandtab tabstop=4 softtabstop=4 shiftwidth=4 +endif + +if !exists("g:no_plugin_maps") && !exists("g:no_markdown_maps") + nnoremap <silent><buffer> [[ :<C-U>call search('\%(^#\{1,5\}\s\+\S\\|^\S.*\n^[=-]\+$\)', "bsW")<CR> + nnoremap <silent><buffer> ]] :<C-U>call search('\%(^#\{1,5\}\s\+\S\\|^\S.*\n^[=-]\+$\)', "sW")<CR> + xnoremap <silent><buffer> [[ :<C-U>exe "normal! gv"<Bar>call search('\%(^#\{1,5\}\s\+\S\\|^\S.*\n^[=-]\+$\)', "bsW")<CR> + xnoremap <silent><buffer> ]] :<C-U>exe "normal! gv"<Bar>call search('\%(^#\{1,5\}\s\+\S\\|^\S.*\n^[=-]\+$\)', "sW")<CR> + let b:undo_ftplugin .= '|sil! nunmap <buffer> [[|sil! nunmap <buffer> ]]|sil! xunmap <buffer> [[|sil! xunmap <buffer> ]]' endif function! s:NotCodeBlock(lnum) abort - return synIDattr(synID(v:lnum, 1, 1), 'name') !=# 'markdownCode' + return synIDattr(synID(a:lnum, 1, 1), 'name') !=# 'markdownCode' endfunction function! MarkdownFold() abort @@ -64,11 +79,14 @@ function! MarkdownFoldText() abort return hash_indent.' '.title.' '.linecount endfunction -if has("folding") && exists("g:markdown_folding") +if has("folding") && get(g:, "markdown_folding", 0) setlocal foldexpr=MarkdownFold() setlocal foldmethod=expr setlocal foldtext=MarkdownFoldText() - let b:undo_ftplugin .= " foldexpr< foldmethod< foldtext<" + let b:undo_ftplugin .= "|setl foldexpr< foldmethod< foldtext<" endif +let &cpo = s:keepcpo +unlet s:keepcpo + " vim:set sw=2: diff --git a/runtime/ftplugin/mermaid.vim b/runtime/ftplugin/mermaid.vim new file mode 100644 index 0000000000..fe84ab37cf --- /dev/null +++ b/runtime/ftplugin/mermaid.vim @@ -0,0 +1,49 @@ +" Vim filetype plugin +" Language: Mermaid +" Maintainer: Craig MacEachern <https://github.com/craigmac/vim-mermaid> +" Last Change: 2022 Oct 13 + +if exists("b:did_ftplugin") + finish +endif + +let s:keepcpo= &cpo +set cpo&vim + +" Use mermaid live editor's style +setlocal expandtab +setlocal shiftwidth=2 +setlocal softtabstop=-1 +setlocal tabstop=4 + +" TODO: comments, formatlist stuff, based on what? +setlocal comments=b:#,fb:- +setlocal commentstring=#\ %s +setlocal formatoptions+=tcqln formatoptions-=r formatoptions-=o +setlocal formatlistpat=^\\s*\\d\\+\\.\\s\\+\\\|^\\s*[-*+]\\s\\+\\\|^\\[^\\ze[^\\]]\\+\\]:\\&^.\\{4\\} + +if exists('b:undo_ftplugin') + let b:undo_ftplugin .= "|setl cms< com< fo< flp< et< ts< sts< sw<" +else + let b:undo_ftplugin = "setl cms< com< fo< flp< et< ts< sts< sw<" +endif + +if !exists("g:no_plugin_maps") && !exists("g:no_markdown_maps") + nnoremap <silent><buffer> [[ :<C-U>call search('\%(^#\{1,5\}\s\+\S\\|^\S.*\n^[=-]\+$\)', "bsW")<CR> + nnoremap <silent><buffer> ]] :<C-U>call search('\%(^#\{1,5\}\s\+\S\\|^\S.*\n^[=-]\+$\)', "sW")<CR> + xnoremap <silent><buffer> [[ :<C-U>exe "normal! gv"<Bar>call search('\%(^#\{1,5\}\s\+\S\\|^\S.*\n^[=-]\+$\)', "bsW")<CR> + xnoremap <silent><buffer> ]] :<C-U>exe "normal! gv"<Bar>call search('\%(^#\{1,5\}\s\+\S\\|^\S.*\n^[=-]\+$\)', "sW")<CR> + let b:undo_ftplugin .= '|sil! nunmap <buffer> [[|sil! nunmap <buffer> ]]|sil! xunmap <buffer> [[|sil! xunmap <buffer> ]]' +endif + +" if has("folding") && get(g:, "markdown_folding", 0) +" setlocal foldexpr=MarkdownFold() +" setlocal foldmethod=expr +" setlocal foldtext=MarkdownFoldText() +" let b:undo_ftplugin .= "|setl foldexpr< foldmethod< foldtext<" +" endif + +let &cpo = s:keepcpo +unlet s:keepcpo + +" vim:set sw=2: diff --git a/runtime/ftplugin/obse.vim b/runtime/ftplugin/obse.vim new file mode 100644 index 0000000000..6d865f05ee --- /dev/null +++ b/runtime/ftplugin/obse.vim @@ -0,0 +1,70 @@ +" Vim filetype plugin file +" Language: Oblivion Language (obl) +" Original Creator: Kat <katisntgood@gmail.com> +" Maintainer: Kat <katisntgood@gmail.com> +" Created: August 08, 2021 +" Last Change: 13 November 2022 + +if exists("b:did_ftplugin") + finish +endif + +let s:cpo_save = &cpo +set cpo&vim + +let b:undo_ftplugin = "setl com< cms<" + +noremap <script> <buffer> <silent> [[ <nop> +noremap <script> <buffer> <silent> ]] <nop> + +noremap <script> <buffer> <silent> [] <nop> +noremap <script> <buffer> <silent> ][ <nop> + +setlocal commentstring=;%s +setlocal comments=:; + +function s:NextSection(type, backwards, visual) + if a:visual + normal! gv + endif + + if a:type == 1 + let pattern = '\v(\n\n^\S|%^)' + let flags = 'e' + elseif a:type == 2 + let pattern = '\v^\S.*' + let flags = '' + endif + + if a:backwards + let dir = '?' + else + let dir = '/' + endif + + execute 'silent normal! ' . dir . pattern . dir . flags . "\r" +endfunction + +noremap <script> <buffer> <silent> ]] + \ :call <SID>NextSection(1, 0, 0)<cr> + +noremap <script> <buffer> <silent> [[ + \ :call <SID>NextSection(1, 1, 0)<cr> + +noremap <script> <buffer> <silent> ][ + \ :call <SID>NextSection(2, 0, 0)<cr> + +noremap <script> <buffer> <silent> [] + \ :call <SID>NextSection(2, 1, 0)<cr> + +vnoremap <script> <buffer> <silent> ]] + \ :<c-u>call <SID>NextSection(1, 0, 1)<cr> +vnoremap <script> <buffer> <silent> [[ + \ :<c-u>call <SID>NextSection(1, 1, 1)<cr> +vnoremap <script> <buffer> <silent> ][ + \ :<c-u>call <SID>NextSection(2, 0, 1)<cr> +vnoremap <script> <buffer> <silent> [] + \ :<c-u>call <SID>NextSection(2, 1, 1)<cr> + +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/runtime/ftplugin/openvpn.vim b/runtime/ftplugin/openvpn.vim new file mode 100644 index 0000000000..56c0f25b39 --- /dev/null +++ b/runtime/ftplugin/openvpn.vim @@ -0,0 +1,14 @@ +" Vim filetype plugin +" Language: OpenVPN +" Maintainer: ObserverOfTime <chronobserver@disroot.org> +" Last Change: 2022 Oct 16 + +if exists('b:did_ftplugin') + finish +endif +let b:did_ftplugin = 1 + +setlocal iskeyword+=-,.,/ +setlocal comments=:#,:; commentstring=#%s + +let b:undo_ftplugin = 'setl isk< com< cms<' diff --git a/runtime/ftplugin/poefilter.vim b/runtime/ftplugin/poefilter.vim new file mode 100644 index 0000000000..92c2def630 --- /dev/null +++ b/runtime/ftplugin/poefilter.vim @@ -0,0 +1,13 @@ +" Vim filetype plugin +" Language: PoE item filter +" Maintainer: ObserverOfTime <chronobserver@disroot.org> +" Last Change: 2022 Oct 07 + +if exists('b:did_ftplugin') + finish +endif +let b:did_ftplugin = 1 + +setlocal comments=:# commentstring=#\ %s + +let b:undo_ftplugin = 'setl com< cms<' diff --git a/runtime/ftplugin/readline.vim b/runtime/ftplugin/readline.vim index e9ef93ec7f..eba7122347 100644 --- a/runtime/ftplugin/readline.vim +++ b/runtime/ftplugin/readline.vim @@ -1,7 +1,8 @@ " Vim filetype plugin file -" Language: readline(3) configuration file -" Previous Maintainer: Nikolai Weibull <now@bitwi.se> -" Latest Revision: 2008-07-09 +" Language: readline(3) configuration file +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Previous Maintainer: Nikolai Weibull <now@bitwi.se> +" Last Change: 2022 Dec 09 if exists("b:did_ftplugin") finish @@ -11,9 +12,25 @@ let b:did_ftplugin = 1 let s:cpo_save = &cpo set cpo&vim +setlocal comments=:# +setlocal commentstring=#\ %s +setlocal formatoptions-=t formatoptions+=croql + let b:undo_ftplugin = "setl com< cms< fo<" -setlocal comments=:# commentstring=#\ %s formatoptions-=t formatoptions+=croql +if exists("loaded_matchit") && !exists("b:match_words") + let b:match_ignorecase = 0 + let b:match_words = '$if:$else:$endif' + let b:undo_ftplugin ..= " | unlet! b:match_ignorecase b:match_words" +endif + +if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter") + let b:browsefilter = "Readline Intialization Files (inputrc .inputrc)\tinputrc;*.inputrc\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/ssa.vim b/runtime/ftplugin/ssa.vim new file mode 100644 index 0000000000..04cc7a9bde --- /dev/null +++ b/runtime/ftplugin/ssa.vim @@ -0,0 +1,13 @@ +" Vim filetype plugin +" Language: SubStation Alpha +" Maintainer: ObserverOfTime <chronobserver@disroot.org> +" Last Change: 2022 Oct 10 + +if exists('b:did_ftplugin') + finish +endif +let b:did_ftplugin = 1 + +setlocal comments=:;,:!: commentstring=;\ %s + +let b:undo_ftplugin = 'setl com< cms<' diff --git a/runtime/ftplugin/vim.vim b/runtime/ftplugin/vim.vim index 82a4b13f9f..b64bb55d68 100644 --- a/runtime/ftplugin/vim.vim +++ b/runtime/ftplugin/vim.vim @@ -1,7 +1,7 @@ " Vim filetype plugin " Language: Vim " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2022 Sep 09 +" Last Change: 2022 Nov 27 " Only do this when not done yet for this buffer if exists("b:did_ftplugin") @@ -98,7 +98,7 @@ if exists("loaded_matchit") " func name " require a parenthesis following, then there can be an "endfunc". let b:match_words = - \ '\<\%(fu\%[nction]\|def\)!\=\s\+\S\+(:\%(\%(^\||\)\s*\)\@<=\<retu\%[rn]\>:\%(\%(^\||\)\s*\)\@<=\<\%(endf\%[unction]\|enddef\)\>,' . + \ '\<\%(fu\%[nction]\|def\)!\=\s\+\S\+\s*(:\%(\%(^\||\)\s*\)\@<=\<retu\%[rn]\>:\%(\%(^\||\)\s*\)\@<=\<\%(endf\%[unction]\|enddef\)\>,' . \ '\<\(wh\%[ile]\|for\)\>:\%(\%(^\||\)\s*\)\@<=\<brea\%[k]\>:\%(\%(^\||\)\s*\)\@<=\<con\%[tinue]\>:\%(\%(^\||\)\s*\)\@<=\<end\(w\%[hile]\|fo\%[r]\)\>,' . \ '\<if\>:\%(\%(^\||\)\s*\)\@<=\<el\%[seif]\>:\%(\%(^\||\)\s*\)\@<=\<en\%[dif]\>,' . \ '{:},' . diff --git a/runtime/ftplugin/zig.vim b/runtime/ftplugin/zig.vim new file mode 100644 index 0000000000..e740a52849 --- /dev/null +++ b/runtime/ftplugin/zig.vim @@ -0,0 +1,66 @@ +" Vim filetype plugin file +" Language: Zig +" Upstream: https://github.com/ziglang/zig.vim + +" Only do this when not done yet for this buffer +if exists("b:did_ftplugin") + finish +endif + +let b:did_ftplugin = 1 + +let s:cpo_orig = &cpo +set cpo&vim + +compiler zig_build + +" Match Zig builtin fns +setlocal iskeyword+=@-@ + +" Recomended code style, no tabs and 4-space indentation +setlocal expandtab +setlocal tabstop=8 +setlocal softtabstop=4 +setlocal shiftwidth=4 + +setlocal formatoptions-=t formatoptions+=croql + +setlocal suffixesadd=.zig,.zir + +if has('comments') + setlocal comments=:///,://!,://,:\\\\ + setlocal commentstring=//\ %s +endif + +if has('find_in_path') + let &l:includeexpr='substitute(v:fname, "^([^.])$", "\1.zig", "")' + let &l:include='\v(\@import>|\@cInclude>|^\s*\#\s*include)' +endif + +let &l:define='\v(<fn>|<const>|<var>|^\s*\#\s*define)' + +if !exists('g:zig_std_dir') && exists('*json_decode') && executable('zig') + silent let s:env = system('zig env') + if v:shell_error == 0 + let g:zig_std_dir = json_decode(s:env)['std_dir'] + endif + unlet! s:env +endif + +if exists('g:zig_std_dir') + let &l:path = &l:path . ',' . g:zig_std_dir +endif + +let b:undo_ftplugin = + \ 'setl isk< et< ts< sts< sw< fo< sua< mp< com< cms< inex< inc< pa<' + +augroup vim-zig + autocmd! * <buffer> + autocmd BufWritePre <buffer> if get(g:, 'zig_fmt_autosave', 1) | call zig#fmt#Format() | endif +augroup END + +let b:undo_ftplugin .= '|au! vim-zig * <buffer>' + +let &cpo = s:cpo_orig +unlet s:cpo_orig +" vim: tabstop=8 shiftwidth=4 softtabstop=4 expandtab diff --git a/runtime/indent/nginx.vim b/runtime/indent/nginx.vim index 8cef7662e0..65506099f4 100644 --- a/runtime/indent/nginx.vim +++ b/runtime/indent/nginx.vim @@ -1,19 +1,78 @@ " Vim indent file " Language: nginx.conf " Maintainer: Chris Aumann <me@chr4.org> -" Last Change: 2022 Apr 06 +" Last Change: 2022 Dec 01 -if exists("b:did_indent") - finish +" Only load this indent file when no other was loaded. +if exists('b:did_indent') + finish endif let b:did_indent = 1 -setlocal indentexpr= +setlocal indentexpr=GetNginxIndent() -" cindent actually works for nginx' simple file structure -setlocal cindent +setlocal indentkeys=0{,0},0#,!^F,o,O -" Just make sure that the comments are not reset as defs would be. -setlocal cinkeys-=0# +let b:undo_indent = 'setl inde< indk<' -let b:undo_indent = "setl inde< cin< cink<" +" Only define the function once. +if exists('*GetNginxIndent') + finish +endif + +function GetNginxIndent() abort + let plnum = s:PrevNotAsBlank(v:lnum - 1) + + " Hit the start of the file, use zero indent. + if plnum == 0 + return 0 + endif + + let ind = indent(plnum) + + " Add a 'shiftwidth' after '{' + if s:AsEndWith(getline(plnum), '{') + let ind = ind + shiftwidth() + end + + " Subtract a 'shiftwidth' on '}' + " This is the part that requires 'indentkeys'. + if getline(v:lnum) =~ '^\s*}' + let ind = ind - shiftwidth() + endif + + let pplnum = s:PrevNotAsBlank(plnum - 1) + + if s:IsLineContinuation(plnum) + if !s:IsLineContinuation(pplnum) + let ind = ind + shiftwidth() + end + else + if s:IsLineContinuation(pplnum) + let ind = ind - shiftwidth() + end + endif + + return ind +endfunction + +" Find the first line at or above {lnum} that is non-blank and not a comment. +function s:PrevNotAsBlank(lnum) abort + let lnum = prevnonblank(a:lnum) + while lnum > 0 + if getline(lnum) !~ '^\s*#' + break + endif + let lnum = prevnonblank(lnum - 1) + endwhile + return lnum +endfunction + +" Check whether {line} ends with {pat}, ignoring trailing comments. +function s:AsEndWith(line, pat) abort + return a:line =~ a:pat . '\m\s*\%(#.*\)\?$' +endfunction + +function s:IsLineContinuation(lnum) abort + return a:lnum > 0 && !s:AsEndWith(getline(a:lnum), '[;{}]') +endfunction diff --git a/runtime/indent/obse.vim b/runtime/indent/obse.vim new file mode 100644 index 0000000000..6603723dba --- /dev/null +++ b/runtime/indent/obse.vim @@ -0,0 +1,55 @@ +" Vim indent file +" Language: Oblivion Language (obl) +" Original Creator: Kat <katisntgood@gmail.com> +" Maintainer: Kat <katisntgood@gmail.com> +" Created: 01 November 2021 +" Last Change: 13 November 2022 + +if exists("b:did_indent") + finish +endif +let b:did_indent = 1 +let b:undo_indent = 'setlocal indentkeys< indentexpr<' + +setlocal indentexpr=GetOblIndent() +setlocal indentkeys+==~endif,=~else,=~loop,=~end + +if exists("*GetOblIndent") + finish +endif +let s:keepcpo = &cpo +set cpo&vim + +let s:SKIP_LINES = '^\s*\(;.*\)' +function! GetOblIndent() + + let lnum = prevnonblank(v:lnum - 1) + let cur_text = getline(v:lnum) + if lnum == 0 + return 0 + endif + let prev_text = getline(lnum) + let found_cont = 0 + let ind = indent(lnum) + + " indent next line on start terms + let i = match(prev_text, '\c^\s*\(\s\+\)\?\(\(if\|while\|foreach\|begin\|else\%[if]\)\>\)') + if i >= 0 + let ind += shiftwidth() + if strpart(prev_text, i, 1) == '|' && has('syntax_items') + \ && synIDattr(synID(lnum, i, 1), "name") =~ '\(Comment\|String\)$' + let ind -= shiftwidth() + endif + endif + " indent current line on end/else terms + if cur_text =~ '\c^\s*\(\s\+\)\?\(\(loop\|endif\|else\%[if]\)\>\)' + let ind = ind - shiftwidth() + " if we are at a begin block just go to column 0 + elseif cur_text =~ '\c^\s*\(\s\+\)\?\(\(begin\|end\)\>\)' + let ind = 0 + endif + return ind +endfunction + +let &cpo = s:keepcpo +unlet s:keepcpo diff --git a/runtime/indent/testdir/vb.in b/runtime/indent/testdir/vb.in new file mode 100644 index 0000000000..1653ae6f80 --- /dev/null +++ b/runtime/indent/testdir/vb.in @@ -0,0 +1,134 @@ +' vim: filetype=vb shiftwidth=4 expandtab +' +' START_INDENT +Public Type GEmployeeRecord ' Create user-defined type. +ID As Integer ' Define elements of data type. +Name As String * 20 +Address As String * 30 +Phone As Long +HireDate As Date +End Type + +Public Enum InterfaceColors +icMistyRose = &HE1E4FF& +icSlateGray = &H908070& +icDodgerBlue = &HFF901E& +icDeepSkyBlue = &HFFBF00& +icSpringGreen = &H7FFF00& +icForestGreen = &H228B22& +icGoldenrod = &H20A5DA& +icFirebrick = &H2222B2& +End Enum + +Enum SecurityLevel +IllegalEntry = -1 +SecurityLevel1 = 0 +SecurityLevel2 = 1 +End Enum + +Public Function TestConditional (number As Integer, ext As String) As Boolean +Dim inRange As Boolean + +Select Case number +Case <= 0 +inRange = False +Case > 10 +inRange = False +Case Else +inRange = True +End Select + +' This is a special case identified in the indent script. +Select Case number +End Select + +If ext = ".xlm" Then +If inRange Then +TestConditional = True +Else +TestConditional = False +End If +ElseIf ext = ".xlsx" Then +If inRange Then +TestConditional = False +Else +TestConditional = True +End If +Else +TestConditional = False +End If +End Function + +Private Sub TestIterators (lLimit As Integer, uLimit As Integer) +Dim a() As Variant +Dim elmt As Variant +Dim found As Boolean +Dim indx As Integer +Const specialValue As Integer = 5 + +If uLimit < lLimit Then +Exit Sub +End If + +ReDim a(lLimit To uLimit) +For indx=lLimit To Ulimit +a(indx) = 2 * indx +Next indx + +found = False +For Each elmt in a +If elmt = specialValue Then +found = True +End If +Next elmt + +If found then +indx = uLimit +Do While indx >= lLimit +indx = indx - 1 +Loop +End If + +End Sub + +Public Sub TestMultiline (cellAddr As String, rowNbr As Long) +Dim rng As Range + +Set rng = Range(cellAddr) +With rng +.Cells(1,1).Value = _ +"Line 1 of multiline string; " & _ +"Line 2 of multiline string; " & _ +"Line 3 of multiline string" +End With + +' The following lines have whitespace after the underscore character +' and therefore do not form a valid multiline statement. The indent +' script correctly treats them as four single line statements contrary +' to the author's obvious indent. +rng..Cells(1,1).Value = _ +"Line 1 of multiline string; " & _ +"Line 2 of multiline string; " & _ +"Line 3 of multiline string" + +End Sub + +Private Sub TestStmtLabel() +GoTo stmtLabel + +' Statement labels are never indented +stmtLabel: + +End Sub + +Sub TestTypeKeyword() +Type EmployeeRecord ' Create user-defined type. +ID As Integer ' Define elements of data type. +Name As String * 20 +Address As String * 30 +Phone As Long +HireDate As Date +End Type +Dim varType As EmployeeRecord +End Sub +' END_INDENT diff --git a/runtime/indent/testdir/vb.ok b/runtime/indent/testdir/vb.ok new file mode 100644 index 0000000000..143c688708 --- /dev/null +++ b/runtime/indent/testdir/vb.ok @@ -0,0 +1,134 @@ +' vim: filetype=vb shiftwidth=4 expandtab +' +' START_INDENT +Public Type GEmployeeRecord ' Create user-defined type. + ID As Integer ' Define elements of data type. + Name As String * 20 + Address As String * 30 + Phone As Long + HireDate As Date +End Type + +Public Enum InterfaceColors + icMistyRose = &HE1E4FF& + icSlateGray = &H908070& + icDodgerBlue = &HFF901E& + icDeepSkyBlue = &HFFBF00& + icSpringGreen = &H7FFF00& + icForestGreen = &H228B22& + icGoldenrod = &H20A5DA& + icFirebrick = &H2222B2& +End Enum + +Enum SecurityLevel + IllegalEntry = -1 + SecurityLevel1 = 0 + SecurityLevel2 = 1 +End Enum + +Public Function TestConditional (number As Integer, ext As String) As Boolean + Dim inRange As Boolean + + Select Case number + Case <= 0 + inRange = False + Case > 10 + inRange = False + Case Else + inRange = True + End Select + + ' This is a special case identified in the indent script. + Select Case number + End Select + + If ext = ".xlm" Then + If inRange Then + TestConditional = True + Else + TestConditional = False + End If + ElseIf ext = ".xlsx" Then + If inRange Then + TestConditional = False + Else + TestConditional = True + End If + Else + TestConditional = False + End If +End Function + +Private Sub TestIterators (lLimit As Integer, uLimit As Integer) + Dim a() As Variant + Dim elmt As Variant + Dim found As Boolean + Dim indx As Integer + Const specialValue As Integer = 5 + + If uLimit < lLimit Then + Exit Sub + End If + + ReDim a(lLimit To uLimit) + For indx=lLimit To Ulimit + a(indx) = 2 * indx + Next indx + + found = False + For Each elmt in a + If elmt = specialValue Then + found = True + End If + Next elmt + + If found then + indx = uLimit + Do While indx >= lLimit + indx = indx - 1 + Loop + End If + +End Sub + +Public Sub TestMultiline (cellAddr As String, rowNbr As Long) + Dim rng As Range + + Set rng = Range(cellAddr) + With rng + .Cells(1,1).Value = _ + "Line 1 of multiline string; " & _ + "Line 2 of multiline string; " & _ + "Line 3 of multiline string" + End With + + ' The following lines have whitespace after the underscore character + ' and therefore do not form a valid multiline statement. The indent + ' script correctly treats them as four single line statements contrary + ' to the author's obvious indent. + rng..Cells(1,1).Value = _ + "Line 1 of multiline string; " & _ + "Line 2 of multiline string; " & _ + "Line 3 of multiline string" + +End Sub + +Private Sub TestStmtLabel() + GoTo stmtLabel + + ' Statement labels are never indented +stmtLabel: + +End Sub + +Sub TestTypeKeyword() + Type EmployeeRecord ' Create user-defined type. + ID As Integer ' Define elements of data type. + Name As String * 20 + Address As String * 30 + Phone As Long + HireDate As Date + End Type + Dim varType As EmployeeRecord +End Sub +' END_INDENT diff --git a/runtime/indent/vb.vim b/runtime/indent/vb.vim index 4d05345565..bc7142f428 100644 --- a/runtime/indent/vb.vim +++ b/runtime/indent/vb.vim @@ -1,8 +1,12 @@ " Vim indent file " Language: VisualBasic (ft=vb) / Basic (ft=basic) / SaxBasic (ft=vb) " Author: Johannes Zellner <johannes@zellner.org> +" Maintainer: Michael Soyka (mssr953@gmail.com) " Last Change: Fri, 18 Jun 2004 07:22:42 CEST " Small update 2010 Jul 28 by Maxim Kim +" 2022/12/15: add support for multiline statements. +" 2022/12/21: move VbGetIndent from global to script-local scope +" 2022/12/26: recognize "Type" keyword if exists("b:did_indent") finish @@ -10,28 +14,33 @@ endif let b:did_indent = 1 setlocal autoindent -setlocal indentexpr=VbGetIndent(v:lnum) +setlocal indentexpr=s:VbGetIndent(v:lnum) setlocal indentkeys& -setlocal indentkeys+==~else,=~elseif,=~end,=~wend,=~case,=~next,=~select,=~loop,<:> +setlocal indentkeys+==~else,=~elseif,=~end,=~wend,=~case,=~next,=~select,=~loop let b:undo_indent = "set ai< indentexpr< indentkeys<" " Only define the function once. -if exists("*VbGetIndent") +if exists("*s:VbGetIndent") finish endif -fun! VbGetIndent(lnum) +function s:VbGetIndent(lnum) + let this_lnum = a:lnum + let this_line = getline(this_lnum) + " labels and preprocessor get zero indent immediately - let this_line = getline(a:lnum) let LABELS_OR_PREPROC = '^\s*\(\<\k\+\>:\s*$\|#.*\)' if this_line =~? LABELS_OR_PREPROC return 0 endif + + " Get the current value of "shiftwidth" + let bShiftwidth = shiftwidth() " Find a non-blank line above the current line. " Skip over labels and preprocessor directives. - let lnum = a:lnum + let lnum = this_lnum while lnum > 0 let lnum = prevnonblank(lnum - 1) let previous_line = getline(lnum) @@ -45,34 +54,102 @@ fun! VbGetIndent(lnum) return 0 endif - let ind = indent(lnum) + " Variable "previous_line" now contains the text in buffer line "lnum". + + " Multi-line statements have the underscore character at end-of-line: + " + " object.method(arguments, _ + " arguments, _ + " arguments) + " + " and require extra logic to determine the correct indentation. + " + " Case 1: Line "lnum" is the first line of a multiline statement. + " Line "lnum" will have a trailing underscore character + " but the preceding non-blank line does not. + " Line "this_lnum" will be indented relative to "lnum". + " + " Case 2: Line "lnum" is the last line of a multiline statement. + " Line "lnum" will not have a trailing underscore character + " but the preceding non-blank line will. + " Line "this_lnum" will have the same indentation as the starting + " line of the multiline statement. + " + " Case 3: Line "lnum" is neither the first nor last line. + " Lines "lnum" and "lnum-1" will have a trailing underscore + " character. + " Line "this_lnum" will have the same indentation as the preceding + " line. + " + " No matter which case it is, the starting line of the statement must be + " found. It will be assumed that multiline statements cannot have + " intermingled comments, statement labels, preprocessor directives or + " blank lines. + " + let lnum_is_continued = (previous_line =~ '_$') + if lnum > 1 + let before_lnum = prevnonblank(lnum-1) + let before_previous_line = getline(before_lnum) + else + let before_lnum = 0 + let before_previous_line = "" + endif + + if before_previous_line !~ '_$' + " Variable "previous_line" contains the start of a statement. + " + let ind = indent(lnum) + if lnum_is_continued + let ind += bShiftwidth + endif + elseif ! lnum_is_continued + " Line "lnum" contains the last line of a multiline statement. + " Need to find where this multiline statement begins + " + while before_lnum > 0 + let before_lnum -= 1 + if getline(before_lnum) !~ '_$' + let before_lnum += 1 + break + endif + endwhile + if before_lnum == 0 + let before_lnum = 1 + endif + let previous_line = getline(before_lnum) + let ind = indent(before_lnum) + else + " Line "lnum" is not the first or last line of a multiline statement. + " + let ind = indent(lnum) + endif " Add - if previous_line =~? '^\s*\<\(begin\|\%(\%(private\|public\|friend\)\s\+\)\=\%(function\|sub\|property\)\|select\|case\|default\|if\|else\|elseif\|do\|for\|while\|enum\|with\)\>' - let ind = ind + shiftwidth() + if previous_line =~? '^\s*\<\(begin\|\%(\%(private\|public\|friend\)\s\+\)\=\%(function\|sub\|property\|enum\|type\)\|select\|case\|default\|if\|else\|elseif\|do\|for\|while\|with\)\>' + let ind = ind + bShiftwidth endif " Subtract if this_line =~? '^\s*\<end\>\s\+\<select\>' if previous_line !~? '^\s*\<select\>' - let ind = ind - 2 * shiftwidth() + let ind = ind - 2 * bShiftwidth else " this case is for an empty 'select' -- 'end select' " (w/o any case statements) like: " " select case readwrite " end select - let ind = ind - shiftwidth() + let ind = ind - bShiftwidth endif elseif this_line =~? '^\s*\<\(end\|else\|elseif\|until\|loop\|next\|wend\)\>' - let ind = ind - shiftwidth() + let ind = ind - bShiftwidth elseif this_line =~? '^\s*\<\(case\|default\)\>' if previous_line !~? '^\s*\<select\>' - let ind = ind - shiftwidth() + let ind = ind - bShiftwidth endif endif return ind -endfun +endfunction " vim:sw=4 diff --git a/runtime/indent/vue.vim b/runtime/indent/vue.vim index 7ff623b3d1..f6fe350a8f 100644 --- a/runtime/indent/vue.vim +++ b/runtime/indent/vue.vim @@ -1,12 +1,14 @@ " Vim indent file placeholder " Language: Vue " Maintainer: None, please volunteer if you have a real Vue indent script +" Last Change: 2022 Dec 24 " Only load this indent file when no other was loaded. if exists("b:did_indent") finish endif -let b:did_indent = 1 +" don't set b:did_indent, otherwise html indenting won't be activated +" let b:did_indent = 1 " Html comes closest runtime! indent/html.vim diff --git a/runtime/indent/zig.vim b/runtime/indent/zig.vim new file mode 100644 index 0000000000..e3ce8aa410 --- /dev/null +++ b/runtime/indent/zig.vim @@ -0,0 +1,80 @@ +" Vim filetype indent file +" Language: Zig +" Upstream: https://github.com/ziglang/zig.vim + +" Only load this indent file when no other was loaded. +if exists("b:did_indent") + finish +endif +let b:did_indent = 1 + +if (!has("cindent") || !has("eval")) + finish +endif + +setlocal cindent + +" L0 -> 0 indent for jump labels (i.e. case statement in c). +" j1 -> indenting for "javascript object declarations" +" J1 -> see j1 +" w1 -> starting a new line with `(` at the same indent as `(` +" m1 -> if `)` starts a line, match its indent with the first char of its +" matching `(` line +" (s -> use one indent, when starting a new line after a trailing `(` +setlocal cinoptions=L0,m1,(s,j1,J1,l1 + +" cinkeys: controls what keys trigger indent formatting +" 0{ -> { +" 0} -> } +" 0) -> ) +" 0] -> ] +" !^F -> make CTRL-F (^F) reindent the current line when typed +" o -> when <CR> or `o` is used +" O -> when the `O` command is used +setlocal cinkeys=0{,0},0),0],!^F,o,O + +setlocal indentexpr=GetZigIndent(v:lnum) + +let b:undo_indent = "setlocal cindent< cinkeys< cinoptions< indentexpr<" + +function! GetZigIndent(lnum) + let curretLineNum = a:lnum + let currentLine = getline(a:lnum) + + " cindent doesn't handle multi-line strings properly, so force no indent + if currentLine =~ '^\s*\\\\.*' + return -1 + endif + + let prevLineNum = prevnonblank(a:lnum-1) + let prevLine = getline(prevLineNum) + + " for lines that look like + " }, + " }; + " try treating them the same as a } + if prevLine =~ '\v^\s*},$' + if currentLine =~ '\v^\s*};$' || currentLine =~ '\v^\s*}$' + return indent(prevLineNum) - 4 + endif + return indent(prevLineNum-1) - 4 + endif + if currentLine =~ '\v^\s*},$' + return indent(prevLineNum) - 4 + endif + if currentLine =~ '\v^\s*};$' + return indent(prevLineNum) - 4 + endif + + + " cindent doesn't handle this case correctly: + " switch (1): { + " 1 => true, + " ~ + " ^---- indents to here + if prevLine =~ '.*=>.*,$' && currentLine !~ '.*}$' + return indent(prevLineNum) + endif + + return cindent(a:lnum) +endfunction diff --git a/runtime/lua/_vim9script.lua b/runtime/lua/_vim9script.lua new file mode 100644 index 0000000000..363d061451 --- /dev/null +++ b/runtime/lua/_vim9script.lua @@ -0,0 +1,650 @@ +------------------------------------------------------------------------------- +-- This file is auto generated by vim9jit. Do not edit by hand. +-- All content is in the source repository. +-- Bugs should be reported to: github.com/tjdevries/vim9jit +-- +-- In addition, this file is considered "private" by neovim. You should +-- not expect any of the APIs, functions, etc to be stable. They are subject +-- to change at any time. +------------------------------------------------------------------------------- + +local vim9 = (function() + local M = {} + + M.ternary = function(cond, if_true, if_false) + if cond then + if type(if_true) == 'function' then + return if_true() + else + return if_true + end + else + if type(if_false) == 'function' then + return if_false() + else + return if_false + end + end + end + + M.fn_ref = function(module, name, copied, ...) + for _, val in ipairs({ ... }) do + table.insert(copied, val) + end + + local funcref = name + if type(funcref) == 'function' then + return funcref(unpack(copied)) + elseif type(funcref) == 'string' then + if vim.fn.exists('*' .. funcref) == 1 then + return vim.fn[funcref](unpack(copied)) + end + + if module[funcref] then + module[funcref](unpack(copied)) + end + + error('unknown function: ' .. funcref) + else + error(string.format('unable to call funcref: %s', funcref)) + end + end + + M.fn_mut = function(name, args, info) + local result = vim.fn._Vim9ScriptFn(name, args) + for idx, val in pairs(result[2]) do + M.replace(args[idx], val) + end + + -- Substitute returning the reference to the + -- returned value + if info.replace then + return args[info.replace + 1] + end + + return result[1] + end + + M.replace = function(orig, new) + if type(orig) == 'table' and type(new) == 'table' then + for k in pairs(orig) do + orig[k] = nil + end + + for k, v in pairs(new) do + orig[k] = v + end + + return orig + end + + return new + end + + M.index = function(obj, idx) + if vim.tbl_islist(obj) then + if idx < 0 then + return obj[#obj + idx + 1] + else + return obj[idx + 1] + end + elseif type(obj) == 'table' then + return obj[idx] + elseif type(obj) == 'string' then + return string.sub(obj, idx + 1, idx + 1) + end + + error('invalid type for indexing: ' .. vim.inspect(obj)) + end + + M.index_expr = function(idx) + if type(idx) == 'string' then + return idx + elseif type(idx) == 'number' then + return idx + 1 + else + error(string.format('not yet handled: %s', vim.inspect(idx))) + end + end + + M.slice = function(obj, start, finish) + if start == nil then + start = 0 + end + + if start < 0 then + start = #obj + start + end + assert(type(start) == 'number') + + if finish == nil then + finish = #obj + end + + if finish < 0 then + finish = #obj + finish + end + assert(type(finish) == 'number') + + local slicer + if vim.tbl_islist(obj) then + slicer = vim.list_slice + elseif type(obj) == 'string' then + slicer = string.sub + else + error('invalid type for slicing: ' .. vim.inspect(obj)) + end + + return slicer(obj, start + 1, finish + 1) + end + + -- Currently unused, but this could be used to embed vim9jit within a + -- running nvim application and transpile "on the fly" as files are + -- sourced. There would still need to be some work done to make that + -- work correctly with imports and what not, but overall it could + -- work well for calling ":source X" from within a vimscript/vim9script + -- function + M.make_source_cmd = function() + local group = vim.api.nvim_create_augroup('vim9script-source', {}) + vim.api.nvim_create_autocmd('SourceCmd', { + pattern = '*.vim', + group = group, + callback = function(a) + local file = vim.fn.readfile(a.file) + for _, line in ipairs(file) do + -- TODO: Or starts with def <something> + -- You can use def in legacy vim files + if vim.startswith(line, 'vim9script') then + -- TODO: Use the rust lib to actually + -- generate the corresponding lua code and then + -- execute that (instead of sourcing it directly) + return + end + end + + vim.api.nvim_exec(table.concat(file, '\n'), false) + end, + }) + end + + M.iter = function(expr) + if vim.tbl_islist(expr) then + return ipairs(expr) + else + return pairs(expr) + end + end + + M.ITER_DEFAULT = 0 + M.ITER_CONTINUE = 1 + M.ITER_BREAK = 2 + M.ITER_RETURN = 3 + + return M +end)() + +vim.cmd([[ +function! _Vim9ScriptFn(name, args) abort + try + let ret = function(a:name, a:args)() + catch + echo "Failed..." + echo a:name + echo a:args + + throw v:errmsg + endtry + + return [ret, a:args] +endfunction +]]) + +vim9['autoload'] = (function() + return function(path) + return loadfile(path)() + end +end)() +vim9['bool'] = (function() + return function(...) + return vim9.convert.to_vim_bool(...) + end +end)() +vim9['convert'] = (function() + local M = {} + + M.decl_bool = function(val) + if type(val) == 'boolean' then + return val + elseif type(val) == 'number' then + if val == 0 then + return false + elseif val == 1 then + return true + else + error(string.format('bad number passed to bool declaration: %s', val)) + end + end + + error(string.format('invalid bool declaration: %s', vim.inspect(val))) + end + + M.decl_dict = function(val) + if type(val) == 'nil' then + return vim.empty_dict() + elseif type(val) == 'table' then + if vim.tbl_isempty(val) then + return vim.empty_dict() + elseif vim.tbl_islist(val) then + error(string.format('Cannot pass list to dictionary? %s', vim.inspect(val))) + else + return val + end + end + + error(string.format('invalid dict declaration: %s', vim.inspect(val))) + end + + M.to_vim_bool = function(val) + if type(val) == 'boolean' then + return val + elseif type(val) == 'number' then + return val ~= 0 + elseif type(val) == 'string' then + return string.len(val) ~= 0 + elseif type(val) == 'table' then + return not vim.tbl_isempty(val) + elseif val == nil then + return false + end + + error('unhandled type: ' .. vim.inspect(val)) + end + + return M +end)() +vim9['fn'] = (function() + local M = {} + + M.insert = function(list, item, idx) + if idx == nil then + idx = 1 + end + + table.insert(list, idx + 1, item) + + return list + end + + M.extend = function(left, right, expr3) + if expr3 ~= nil then + error("haven't written this code yet") + end + + if vim.tbl_islist(right) then + vim.list_extend(left, right) + return left + else + -- local result = vim.tbl_extend(left, right) + for k, v in pairs(right) do + left[k] = v + end + + return left + end + end + + M.add = function(list, item) + table.insert(list, item) + return list + end + + M.has_key = function(obj, key) + return not not obj[key] + end + + M.prop_type_add = function(...) + local args = { ... } + print('[prop_type_add]', vim.inspect(args)) + end + + do + local has_overrides = { + -- We do have vim9script ;) that's this plugin + ['vim9script'] = true, + + -- Include some vim patches that are sometimes required by variuos vim9script plugins + -- that we implement via vim9jit + [ [[patch-8.2.2261]] ] = true, + [ [[patch-8.2.4257]] ] = true, + } + + M.has = function(patch) + if has_overrides[patch] then + return true + end + + return vim.fn.has(patch) + end + end + + --[=[ +Currently missing patch, can be removed in the future. + +readdirex({directory} [, {expr} [, {dict}]]) *readdirex()* + Extended version of |readdir()|. + Return a list of Dictionaries with file and directory + information in {directory}. + This is useful if you want to get the attributes of file and + directory at the same time as getting a list of a directory. + This is much faster than calling |readdir()| then calling + |getfperm()|, |getfsize()|, |getftime()| and |getftype()| for + each file and directory especially on MS-Windows. + The list will by default be sorted by name (case sensitive), + the sorting can be changed by using the optional {dict} + argument, see |readdir()|. + + The Dictionary for file and directory information has the + following items: + group Group name of the entry. (Only on Unix) + name Name of the entry. + perm Permissions of the entry. See |getfperm()|. + size Size of the entry. See |getfsize()|. + time Timestamp of the entry. See |getftime()|. + type Type of the entry. + On Unix, almost same as |getftype()| except: + Symlink to a dir "linkd" + Other symlink "link" + On MS-Windows: + Normal file "file" + Directory "dir" + Junction "junction" + Symlink to a dir "linkd" + Other symlink "link" + Other reparse point "reparse" + user User name of the entry's owner. (Only on Unix) + On Unix, if the entry is a symlink, the Dictionary includes + the information of the target (except the "type" item). + On MS-Windows, it includes the information of the symlink + itself because of performance reasons. +--]=] + M.readdirex = function(dir) + local files = vim.fn.readdir(dir) + local direx = {} + for _, f in ipairs(files) do + table.insert(direx, { + name = f, + type = vim.fn.getftype(f), + }) + end + + return direx + end + + M.mapnew = function(tbl, expr) + return vim.fn.map(tbl, expr) + end + + M.typename = function(val) + local ty = type(val) + if ty == 'string' then + return 'string' + elseif ty == 'boolean' then + return 'bool' + elseif ty == 'number' then + return 'number' + else + error(string.format('typename: %s', val)) + end + end + + -- Popup menu stuff: Could be rolled into other plugin later + -- but currently is here for testing purposes (and implements + -- some very simple compat layers at the moment) + do + local pos_map = { + topleft = 'NW', + topright = 'NE', + botleft = 'SW', + botright = 'SE', + } + + M.popup_menu = function(_, options) + -- print "OPTIONS:" + + local buf = vim.api.nvim_create_buf(false, true) + local win = vim.api.nvim_open_win(buf, true, { + relative = 'editor', + style = 'minimal', + anchor = pos_map[options.pos], + height = options.maxheight or options.minheight, + width = options.maxwidth or options.minwidth, + row = options.line, + col = options.col, + }) + + if options.filter then + local loop + loop = function() + vim.cmd([[redraw!]]) + local ok, ch = pcall(vim.fn.getcharstr) + if not ok then + return + end -- interrupted + + if ch == '<C-C>' then + return + end + + if not require('vim9script').bool(options.filter(nil, ch)) then + vim.cmd.normal(ch) + end + + vim.schedule(loop) + end + + vim.schedule(loop) + end + + return win + end + + M.popup_settext = function(id, text) + if type(text) == 'string' then + -- text = vim.split(text, "\n") + error("Haven't handled string yet") + end + + local lines = {} + for _, obj in ipairs(text) do + table.insert(lines, obj.text) + end + + vim.api.nvim_buf_set_lines(vim.api.nvim_win_get_buf(id), 0, -1, false, lines) + end + + M.popup_filter_menu = function() + print('ok, just pretend we filtered the menu') + end + + M.popup_setoptions = function(id, _) + print('setting options...', id) + end + end + + M = setmetatable(M, { + __index = vim.fn, + }) + + return M +end)() +vim9['heredoc'] = (function() + local M = {} + + M.trim = function(lines) + local min_whitespace = 9999 + for _, line in ipairs(lines) do + local _, finish = string.find(line, '^%s*') + min_whitespace = math.min(min_whitespace, finish) + end + + local trimmed_lines = {} + for _, line in ipairs(lines) do + table.insert(trimmed_lines, string.sub(line, min_whitespace + 1)) + end + + return trimmed_lines + end + + return M +end)() +vim9['import'] = (function() + local imported = {} + imported.autoload = setmetatable({}, { + __index = function(_, name) + local luaname = 'autoload/' .. string.gsub(name, '%.vim$', '.lua') + local runtime_file = vim.api.nvim_get_runtime_file(luaname, false)[1] + if not runtime_file then + error('unable to find autoload file:' .. name) + end + + return imported.absolute[vim.fn.fnamemodify(runtime_file, ':p')] + end, + }) + + imported.absolute = setmetatable({}, { + __index = function(self, name) + if vim.loop.fs_stat(name) then + local result = loadfile(name)() + rawset(self, name, result) + + return result + end + + error(string.format('unabled to find absolute file: %s', name)) + end, + }) + + return function(info) + local name = info.name + + if info.autoload then + return imported.autoload[info.name] + end + + local debug_info = debug.getinfo(2, 'S') + local sourcing_path = vim.fn.fnamemodify(string.sub(debug_info.source, 2), ':p') + + -- Relative paths + if vim.startswith(name, '../') or vim.startswith(name, './') then + local luaname = string.gsub(name, '%.vim$', '.lua') + local directory = vim.fn.fnamemodify(sourcing_path, ':h') + local search = directory .. '/' .. luaname + return imported.absolute[search] + end + + if vim.startswith(name, '/') then + error('absolute path') + -- local luaname = string.gsub(name, "%.vim", ".lua") + -- local runtime_file = vim.api.nvim_get_runtime_file(luaname, false)[1] + -- if runtime_file then + -- runtime_file = vim.fn.fnamemodify(runtime_file, ":p") + -- return loadfile(runtime_file)() + -- end + end + + error('Unhandled case' .. vim.inspect(info) .. vim.inspect(debug_info)) + end +end)() +vim9['ops'] = (function() + local lib = vim9 + + local M = {} + + M['And'] = function(left, right) + return lib.bool(left) and lib.bool(right) + end + + M['Or'] = function(left, right) + return lib.bool(left) or lib.bool(right) + end + + M['Plus'] = function(left, right) + return left + right + end + + M['Multiply'] = function(left, right) + return left * right + end + + M['Divide'] = function(left, right) + return left / right + end + + M['StringConcat'] = function(left, right) + return left .. right + end + + M['EqualTo'] = function(left, right) + return left == right + end + + M['NotEqualTo'] = function(left, right) + return not M['EqualTo'](left, right) + end + + M['LessThan'] = function(left, right) + return left < right + end + + M['LessThanOrEqual'] = function(left, right) + return left <= right + end + + M['GreaterThan'] = function(left, right) + return left > right + end + + M['GreaterThanOrEqual'] = function(left, right) + return left >= right + end + + M['RegexpMatches'] = function(left, right) + return not not vim.regex(right):match_str(left) + end + + M['RegexpMatchesIns'] = function(left, right) + return not not vim.regex('\\c' .. right):match_str(left) + end + + M['NotRegexpMatches'] = function(left, right) + return not M['RegexpMatches'](left, right) + end + + M['Modulo'] = function(left, right) + return left % right + end + + M['Minus'] = function(left, right) + -- TODO: This is not right :) + return left - right + end + + return M +end)() +vim9['prefix'] = (function() + local lib = vim9 + + local M = {} + + M['Minus'] = function(right) + return -right + end + + M['Bang'] = function(right) + return not lib.bool(right) + end + + return M +end)() + +return vim9 diff --git a/runtime/lua/editorconfig.lua b/runtime/lua/editorconfig.lua new file mode 100644 index 0000000000..2079006234 --- /dev/null +++ b/runtime/lua/editorconfig.lua @@ -0,0 +1,246 @@ +local M = {} + +M.properties = {} + +--- Modified version of the builtin assert that does not include error position information +--- +---@param v any Condition +---@param message string Error message to display if condition is false or nil +---@return any v if not false or nil, otherwise an error is displayed +--- +---@private +local function assert(v, message) + return v or error(message, 0) +end + +--- Show a warning message +--- +---@param msg string Message to show +--- +---@private +local function warn(msg, ...) + vim.notify(string.format(msg, ...), vim.log.levels.WARN, { + title = 'editorconfig', + }) +end + +function M.properties.charset(bufnr, val) + assert( + vim.tbl_contains({ 'utf-8', 'utf-8-bom', 'latin1', 'utf-16be', 'utf-16le' }, val), + 'charset must be one of "utf-8", "utf-8-bom", "latin1", "utf-16be", or "utf-16le"' + ) + if val == 'utf-8' or val == 'utf-8-bom' then + vim.bo[bufnr].fileencoding = 'utf-8' + vim.bo[bufnr].bomb = val == 'utf-8-bom' + elseif val == 'utf-16be' then + vim.bo[bufnr].fileencoding = 'utf-16' + else + vim.bo[bufnr].fileencoding = val + end +end + +function M.properties.end_of_line(bufnr, val) + vim.bo[bufnr].fileformat = assert( + ({ lf = 'unix', crlf = 'dos', cr = 'mac' })[val], + 'end_of_line must be one of "lf", "crlf", or "cr"' + ) +end + +function M.properties.indent_style(bufnr, val, opts) + assert(val == 'tab' or val == 'space', 'indent_style must be either "tab" or "space"') + vim.bo[bufnr].expandtab = val == 'space' + if val == 'tab' and not opts.indent_size then + vim.bo[bufnr].shiftwidth = 0 + vim.bo[bufnr].softtabstop = 0 + end +end + +function M.properties.indent_size(bufnr, val, opts) + if val == 'tab' then + vim.bo[bufnr].shiftwidth = 0 + vim.bo[bufnr].softtabstop = 0 + else + local n = assert(tonumber(val), 'indent_size must be a number') + vim.bo[bufnr].shiftwidth = n + vim.bo[bufnr].softtabstop = -1 + if not opts.tab_width then + vim.bo[bufnr].tabstop = n + end + end +end + +function M.properties.tab_width(bufnr, val) + vim.bo[bufnr].tabstop = assert(tonumber(val), 'tab_width must be a number') +end + +function M.properties.max_line_length(bufnr, val) + local n = tonumber(val) + if n then + vim.bo[bufnr].textwidth = n + else + assert(val == 'off', 'max_line_length must be a number or "off"') + vim.bo[bufnr].textwidth = 0 + end +end + +function M.properties.trim_trailing_whitespace(bufnr, val) + assert( + val == 'true' or val == 'false', + 'trim_trailing_whitespace must be either "true" or "false"' + ) + if val == 'true' then + vim.api.nvim_create_autocmd('BufWritePre', { + group = 'editorconfig', + buffer = bufnr, + callback = function() + local view = vim.fn.winsaveview() + vim.api.nvim_command('silent! undojoin') + vim.api.nvim_command('silent keepjumps keeppatterns %s/\\s\\+$//e') + vim.fn.winrestview(view) + end, + }) + else + vim.api.nvim_clear_autocmds({ + event = 'BufWritePre', + group = 'editorconfig', + buffer = bufnr, + }) + end +end + +function M.properties.insert_final_newline(bufnr, val) + assert(val == 'true' or val == 'false', 'insert_final_newline must be either "true" or "false"') + vim.bo[bufnr].fixendofline = val == 'true' + vim.bo[bufnr].endofline = val == 'true' +end + +--- Modified version of |glob2regpat()| that does not match path separators on *. +--- +--- This function replaces single instances of * with the regex pattern [^/]*. However, the star in +--- the replacement pattern also gets interpreted by glob2regpat, so we insert a placeholder, pass +--- it through glob2regpat, then replace the placeholder with the actual regex pattern. +--- +---@param glob string Glob to convert into a regular expression +---@return string Regular expression +--- +---@private +local function glob2regpat(glob) + local placeholder = '@@PLACEHOLDER@@' + return ( + string.gsub( + vim.fn.glob2regpat( + vim.fn.substitute( + string.gsub(glob, '{(%d+)%.%.(%d+)}', '[%1-%2]'), + '\\*\\@<!\\*\\*\\@!', + placeholder, + 'g' + ) + ), + placeholder, + '[^/]*' + ) + ) +end + +--- Parse a single line in an EditorConfig file +--- +---@param line string Line +---@return string|nil If the line contains a pattern, the glob pattern +---@return string|nil If the line contains a key-value pair, the key +---@return string|nil If the line contains a key-value pair, the value +--- +---@private +local function parse_line(line) + if line:find('^%s*[^ #;]') then + local glob = (line:match('%b[]') or ''):match('^%s*%[(.*)%]%s*$') + if glob then + return glob, nil, nil + end + + local key, val = line:match('^%s*([^:= ][^:=]-)%s*[:=]%s*(.-)%s*$') + if key ~= nil and val ~= nil then + return nil, key:lower(), val:lower() + end + end +end + +--- Parse options from an .editorconfig file +--- +---@param filepath string File path of the file to apply EditorConfig settings to +---@param dir string Current directory +---@return table Table of options to apply to the given file +--- +---@private +local function parse(filepath, dir) + local pat = nil + local opts = {} + local f = io.open(dir .. '/.editorconfig') + if f then + for line in f:lines() do + local glob, key, val = parse_line(line) + if glob then + glob = glob:find('/') and (dir .. '/' .. glob:gsub('^/', '')) or ('**/' .. glob) + local ok, regpat = pcall(glob2regpat, glob) + if ok then + pat = vim.regex(regpat) + else + pat = nil + warn('editorconfig: Error occurred while parsing glob pattern "%s": %s', glob, regpat) + end + elseif key ~= nil and val ~= nil then + if key == 'root' then + opts.root = val == 'true' + elseif pat and pat:match_str(filepath) then + opts[key] = val + end + end + end + f:close() + end + return opts +end + +--- Configure the given buffer with options from an .editorconfig file +--- +---@param bufnr number Buffer number to configure +--- +---@private +function M.config(bufnr) + bufnr = bufnr or vim.api.nvim_get_current_buf() + local path = vim.fs.normalize(vim.api.nvim_buf_get_name(bufnr)) + if vim.bo[bufnr].buftype ~= '' or not vim.bo[bufnr].modifiable or path == '' then + return + end + + local opts = {} + for parent in vim.fs.parents(path) do + for k, v in pairs(parse(path, parent)) do + if opts[k] == nil then + opts[k] = v + end + end + + if opts.root then + break + end + end + + local applied = {} + for opt, val in pairs(opts) do + if val ~= 'unset' then + local func = M.properties[opt] + if func then + local ok, err = pcall(func, bufnr, val, opts) + if ok then + applied[opt] = val + else + warn('editorconfig: invalid value for option %s: %s. %s', opt, val, err) + end + end + end + end + + vim.b[bufnr].editorconfig = applied +end + +return M diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua index 6477786dbb..732a4ab92e 100644 --- a/runtime/lua/man.lua +++ b/runtime/lua/man.lua @@ -12,7 +12,7 @@ local function man_error(msg) end -- Run a system command and timeout after 30 seconds. -local function system(cmd, silent, env) +local function system(cmd_, silent, env) local stdout_data = {} local stderr_data = {} local stdout = vim.loop.new_pipe(false) @@ -21,11 +21,23 @@ local function system(cmd, silent, env) local done = false local exit_code + -- We use the `env` command here rather than the env option to vim.loop.spawn since spawn will + -- completely overwrite the environment when we just want to modify the existing one. + -- + -- Overwriting mainly causes problems NixOS which relies heavily on a non-standard environment. + local cmd + if env then + cmd = { 'env' } + vim.list_extend(cmd, env) + vim.list_extend(cmd, cmd_) + else + cmd = cmd_ + end + local handle handle = vim.loop.spawn(cmd[1], { args = vim.list_slice(cmd, 2), stdio = { nil, stdout, stderr }, - env = env, }, function(code) exit_code = code stdout:close() @@ -283,6 +295,14 @@ local function get_path(sect, name, silent) return end + -- `man -w /some/path` will return `/some/path` for any existent file, which + -- stops us from actually determining if a path has a corresponding man file. + -- Since `:Man /some/path/to/man/file` isn't supported anyway, we should just + -- error out here if we detect this is the case. + if sect == '' and #results == 1 and results[1] == name then + return + end + -- find any that match the specified name local namematches = vim.tbl_filter(function(v) return fn.fnamemodify(v, ':t'):match(name) @@ -400,6 +420,10 @@ local function extract_sect_and_name_path(path) end local function find_man() + if vim.bo.filetype == 'man' then + return true + end + local win = 1 while win <= fn.winnr('$') do local buf = fn.winbufnr(win) @@ -449,7 +473,7 @@ local function get_page(path, silent) end local function put_page(page) - vim.bo.modified = true + vim.bo.modifiable = true vim.bo.readonly = false vim.bo.swapfile = false @@ -604,11 +628,6 @@ function M.goto_tag(pattern, _, _) end end - if vim.o.cscopetag then - -- return only a single entry so we work well with :cstag (#11675) - structured = { structured[1] } - end - return vim.tbl_map(function(entry) return { name = entry.name, diff --git a/runtime/lua/man/health.lua b/runtime/lua/man/health.lua deleted file mode 100644 index 11d7148216..0000000000 --- a/runtime/lua/man/health.lua +++ /dev/null @@ -1,20 +0,0 @@ -local M = {} - -local report_ok = vim.fn['health#report_ok'] -local report_error = vim.fn['health#report_error'] - -local function check_runtime_file(name) - local path = vim.env.VIMRUNTIME .. '/' .. name - if vim.loop.fs_stat(path) then - report_error(string.format('%s detected. Please delete %s', name, path)) - else - report_ok(string.format('%s not in $VIMRUNTIME', name)) - end -end - -function M.check() - check_runtime_file('plugin/man.vim') - check_runtime_file('autoload/man.vim') -end - -return M diff --git a/runtime/lua/nvim/health.lua b/runtime/lua/nvim/health.lua new file mode 100644 index 0000000000..b76106f241 --- /dev/null +++ b/runtime/lua/nvim/health.lua @@ -0,0 +1,406 @@ +local M = {} +local health = require('vim.health') + +local fn_bool = function(key) + return function(...) + return vim.fn[key](...) == 1 + end +end + +local has = fn_bool('has') +local executable = fn_bool('executable') +local empty = fn_bool('empty') +local filereadable = fn_bool('filereadable') +local filewritable = fn_bool('filewritable') + +local shell_error = function() + return vim.v.shell_error ~= 0 +end + +local suggest_faq = 'https://github.com/neovim/neovim/wiki/Building-Neovim#optimized-builds' + +local function check_runtime() + health.report_start('Runtime') + -- Files from an old installation. + local bad_files = { + ['plugin/man.vim'] = false, + ['scripts.vim'] = false, + ['autoload/man.vim'] = false, + } + local bad_files_msg = '' + for k, _ in pairs(bad_files) do + local path = ('%s/%s'):format(vim.env.VIMRUNTIME, k) + if vim.loop.fs_stat(path) then + bad_files[k] = true + bad_files_msg = ('%s%s\n'):format(bad_files_msg, path) + end + end + + local ok = (bad_files_msg == '') + local info = ok and health.report_ok or health.report_info + info(string.format('$VIMRUNTIME: %s', vim.env.VIMRUNTIME)) + if not ok then + health.report_error( + string.format( + '$VIMRUNTIME has files from an old installation (this can cause weird behavior):\n%s', + bad_files_msg + ), + { 'Delete $VIMRUNTIME (or uninstall Nvim), then reinstall Nvim.' } + ) + end +end + +local function check_config() + health.report_start('Configuration') + local ok = true + + local vimrc = ( + empty(vim.env.MYVIMRC) and vim.fn.stdpath('config') .. '/init.vim' or vim.env.MYVIMRC + ) + if not filereadable(vimrc) then + ok = false + local has_vim = filereadable(vim.fn.expand('~/.vimrc')) + health.report_warn( + (-1 == vim.fn.getfsize(vimrc) and 'Missing' or 'Unreadable') .. ' user config file: ' .. vimrc, + { has_vim and ':help nvim-from-vim' or ':help init.vim' } + ) + end + + -- If $VIM is empty we don't care. Else make sure it is valid. + if not empty(vim.env.VIM) and not filereadable(vim.env.VIM .. '/runtime/doc/nvim.txt') then + ok = false + health.report_error('$VIM is invalid: ' .. vim.env.VIM) + end + + if vim.env.NVIM_TUI_ENABLE_CURSOR_SHAPE then + ok = false + health.report_warn('$NVIM_TUI_ENABLE_CURSOR_SHAPE is ignored in Nvim 0.2+', { + "Use the 'guicursor' option to configure cursor shape. :help 'guicursor'", + 'https://github.com/neovim/neovim/wiki/Following-HEAD#20170402', + }) + end + + if vim.v.ctype == 'C' then + ok = false + health.report_error( + 'Locale does not support UTF-8. Unicode characters may not display correctly.' + .. ('\n$LANG=%s $LC_ALL=%s $LC_CTYPE=%s'):format( + vim.env.LANG, + vim.env.LC_ALL, + vim.env.LC_CTYPE + ), + { + 'If using tmux, try the -u option.', + 'Ensure that your terminal/shell/tmux/etc inherits the environment, or set $LANG explicitly.', + 'Configure your system locale.', + } + ) + end + + if vim.o.paste == 1 then + ok = false + health.report_error( + "'paste' is enabled. This option is only for pasting text.\nIt should not be set in your config.", + { + 'Remove `set paste` from your init.vim, if applicable.', + 'Check `:verbose set paste?` to see if a plugin or script set the option.', + } + ) + end + + local writeable = true + local shadaopt = vim.fn.split(vim.o.shada, ',') + local shadafile = ( + empty(vim.o.shada) and vim.o.shada + or vim.fn.substitute(vim.fn.matchstr(shadaopt[#shadaopt], '^n.\\+'), '^n', '', '') + ) + shadafile = ( + empty(vim.o.shadafile) + and (empty(shadafile) and vim.fn.stdpath('state') .. '/shada/main.shada' or vim.fn.expand( + shadafile + )) + or (vim.o.shadafile == 'NONE' and '' or vim.o.shadafile) + ) + if not empty(shadafile) and empty(vim.fn.glob(shadafile)) then + -- Since this may be the first time Nvim has been run, try to create a shada file. + if not pcall(vim.cmd.wshada) then + writeable = false + end + end + if + not writeable + or (not empty(shadafile) and (not filereadable(shadafile) or not filewritable(shadafile))) + then + ok = false + health.report_error( + 'shada file is not ' + .. ((not writeable or filereadable(shadafile)) and 'writeable' or 'readable') + .. ':\n' + .. shadafile + ) + end + + if ok then + health.report_ok('no issues found') + end +end + +local function check_performance() + health.report_start('Performance') + + -- Check buildtype + local buildtype = vim.fn.matchstr(vim.fn.execute('version'), [[\v\cbuild type:?\s*[^\n\r\t ]+]]) + if empty(buildtype) then + health.report_error('failed to get build type from :version') + elseif vim.regex([[\v(MinSizeRel|Release|RelWithDebInfo)]]):match_str(buildtype) then + health.report_ok(buildtype) + else + health.report_info(buildtype) + health.report_warn( + 'Non-optimized ' .. (has('debug') and '(DEBUG) ' or '') .. 'build. Nvim will be slower.', + { + 'Install a different Nvim package, or rebuild with `CMAKE_BUILD_TYPE=RelWithDebInfo`.', + suggest_faq, + } + ) + end + + -- check for slow shell invocation + local slow_cmd_time = 1.5 + local start_time = vim.fn.reltime() + vim.fn.system('echo') + local elapsed_time = vim.fn.reltimefloat(vim.fn.reltime(start_time)) + if elapsed_time > slow_cmd_time then + health.report_warn( + 'Slow shell invocation (took ' .. vim.fn.printf('%.2f', elapsed_time) .. ' seconds).' + ) + end +end + +-- Load the remote plugin manifest file and check for unregistered plugins +local function check_rplugin_manifest() + health.report_start('Remote Plugins') + + local existing_rplugins = {} + for _, item in ipairs(vim.fn['remote#host#PluginsForHost']('python')) do + existing_rplugins[item.path] = 'python' + end + + for item in ipairs(vim.fn['remote#host#PluginsForHost']('python3')) do + existing_rplugins[item.path] = 'python3' + end + + local require_update = false + local handle_path = function(path) + local python_glob = vim.fn.glob(path .. '/rplugin/python*', true, true) + if empty(python_glob) then + return + end + + local python_dir = python_glob[1] + local python_version = vim.fn.fnamemodify(python_dir, ':t') + + local scripts = vim.fn.glob(python_dir .. '/*.py', true, true) + vim.list_extend(scripts, vim.fn.glob(python_dir .. '/*/__init__.py', true, true)) + + for script in ipairs(scripts) do + local contents = vim.fn.join(vim.fn.readfile(script)) + if vim.regex([[\<\%(from\|import\)\s\+neovim\>]]):match_str(contents) then + if vim.regex([[[\/]__init__\.py$]]):match_str(script) then + script = vim.fn.tr(vim.fn.fnamemodify(script, ':h'), '\\', '/') + end + if not existing_rplugins[script] then + local msg = vim.fn.printf('"%s" is not registered.', vim.fn.fnamemodify(path, ':t')) + if python_version == 'pythonx' then + if not has('python3') then + msg = msg .. ' (python3 not available)' + end + elseif not has(python_version) then + msg = msg .. vim.fn.printf(' (%s not available)', python_version) + else + require_update = true + end + + health.report_warn(msg) + end + + break + end + end + end + + for _, path in ipairs(vim.fn.map(vim.fn.split(vim.o.runtimepath, ','), 'resolve(v:val)')) do + handle_path(path) + end + + if require_update then + health.report_warn('Out of date', { 'Run `:UpdateRemotePlugins`' }) + else + health.report_ok('Up to date') + end +end + +local function check_tmux() + if empty(vim.env.TMUX) or not executable('tmux') then + return + end + + local get_tmux_option = function(option) + local cmd = 'tmux show-option -qvg ' .. option -- try global scope + local out = vim.fn.system(vim.fn.split(cmd)) + local val = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g') + if shell_error() then + health.report_error('command failed: ' .. cmd .. '\n' .. out) + return 'error' + elseif empty(val) then + cmd = 'tmux show-option -qvgs ' .. option -- try session scope + out = vim.fn.system(vim.fn.split(cmd)) + val = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g') + if shell_error() then + health.report_error('command failed: ' .. cmd .. '\n' .. out) + return 'error' + end + end + return val + end + + health.report_start('tmux') + + -- check escape-time + local suggestions = + { 'set escape-time in ~/.tmux.conf:\nset-option -sg escape-time 10', suggest_faq } + local tmux_esc_time = get_tmux_option('escape-time') + if tmux_esc_time ~= 'error' then + if empty(tmux_esc_time) then + health.report_error('`escape-time` is not set', suggestions) + elseif tonumber(tmux_esc_time) > 300 then + health.report_error( + '`escape-time` (' .. tmux_esc_time .. ') is higher than 300ms', + suggestions + ) + else + health.report_ok('escape-time: ' .. tmux_esc_time) + end + end + + -- check focus-events + local tmux_focus_events = get_tmux_option('focus-events') + if tmux_focus_events ~= 'error' then + if empty(tmux_focus_events) or tmux_focus_events ~= 'on' then + health.report_warn( + "`focus-events` is not enabled. |'autoread'| may not work.", + { '(tmux 1.9+ only) Set `focus-events` in ~/.tmux.conf:\nset-option -g focus-events on' } + ) + else + health.report_ok('focus-events: ' .. tmux_focus_events) + end + end + + -- check default-terminal and $TERM + health.report_info('$TERM: ' .. vim.env.TERM) + local cmd = 'tmux show-option -qvg default-terminal' + local out = vim.fn.system(vim.fn.split(cmd)) + local tmux_default_term = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g') + if empty(tmux_default_term) then + cmd = 'tmux show-option -qvgs default-terminal' + out = vim.fn.system(vim.fn.split(cmd)) + tmux_default_term = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g') + end + + if shell_error() then + health.report_error('command failed: ' .. cmd .. '\n' .. out) + elseif tmux_default_term ~= vim.env.TERM then + health.report_info('default-terminal: ' .. tmux_default_term) + health.report_error( + '$TERM differs from the tmux `default-terminal` setting. Colors might look wrong.', + { '$TERM may have been set by some rc (.bashrc, .zshrc, ...).' } + ) + elseif not vim.regex([[\v(tmux-256color|screen-256color)]]):match_str(vim.env.TERM) then + health.report_error( + '$TERM should be "screen-256color" or "tmux-256color" in tmux. Colors might look wrong.', + { + 'Set default-terminal in ~/.tmux.conf:\nset-option -g default-terminal "screen-256color"', + suggest_faq, + } + ) + end + + -- check for RGB capabilities + local info = vim.fn.system({ 'tmux', 'display-message', '-p', '#{client_termfeatures}' }) + info = vim.split(vim.trim(info), ',', { trimempty = true }) + if not vim.tbl_contains(info, 'RGB') then + local has_rgb = false + if #info == 0 then + -- client_termfeatures may not be supported; fallback to checking show-messages + info = vim.fn.system({ 'tmux', 'show-messages', '-JT' }) + has_rgb = info:find(' Tc: (flag) true', 1, true) or info:find(' RGB: (flag) true', 1, true) + end + if not has_rgb then + health.report_warn( + "Neither Tc nor RGB capability set. True colors are disabled. |'termguicolors'| won't work properly.", + { + "Put this in your ~/.tmux.conf and replace XXX by your $TERM outside of tmux:\nset-option -sa terminal-features ',XXX:RGB'", + "For older tmux versions use this instead:\nset-option -ga terminal-overrides ',XXX:Tc'", + } + ) + end + end +end + +local function check_terminal() + if not executable('infocmp') then + return + end + + health.report_start('terminal') + local cmd = 'infocmp -L' + local out = vim.fn.system(vim.fn.split(cmd)) + local kbs_entry = vim.fn.matchstr(out, 'key_backspace=[^,[:space:]]*') + local kdch1_entry = vim.fn.matchstr(out, 'key_dc=[^,[:space:]]*') + + if + shell_error() + and ( + not has('win32') + or empty( + vim.fn.matchstr( + out, + [[infocmp: couldn't open terminfo file .\+\%(conemu\|vtpcon\|win32con\)]] + ) + ) + ) + then + health.report_error('command failed: ' .. cmd .. '\n' .. out) + else + health.report_info( + vim.fn.printf( + 'key_backspace (kbs) terminfo entry: `%s`', + (empty(kbs_entry) and '? (not found)' or kbs_entry) + ) + ) + + health.report_info( + vim.fn.printf( + 'key_dc (kdch1) terminfo entry: `%s`', + (empty(kbs_entry) and '? (not found)' or kdch1_entry) + ) + ) + end + + for env_var in ipairs({ 'XTERM_VERSION', 'VTE_VERSION', 'TERM_PROGRAM', 'COLORTERM', 'SSH_TTY' }) do + if vim.env[env_var] then + health.report_info(vim.fn.printf('$%s="%s"', env_var, vim.env[env_var])) + end + end +end + +function M.check() + check_config() + check_runtime() + check_performance() + check_rplugin_manifest() + check_terminal() + check_tmux() +end + +return M diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 28e5897aa9..da8764fbd4 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -36,6 +36,7 @@ for k, v in pairs({ ui = true, health = true, fs = true, + secure = true, }) do vim._submodules[k] = v end @@ -122,7 +123,7 @@ do --- (such as the |TUI|) pastes text into the editor. --- --- Example: To remove ANSI color codes when pasting: - --- <pre> + --- <pre>lua --- vim.paste = (function(overridden) --- return function(lines, phase) --- for i,line in ipairs(lines) do @@ -167,7 +168,8 @@ do local line1 = lines[1]:gsub('(%c)', '\022%1') -- nvim_input() is affected by mappings, -- so use nvim_feedkeys() with "n" flag to ignore mappings. - vim.api.nvim_feedkeys(line1, 'n', true) + -- "t" flag is also needed so the pasted text is saved in cmdline history. + vim.api.nvim_feedkeys(line1, 'nt', true) end return true end @@ -278,7 +280,7 @@ end --- command. --- --- Example: ---- <pre> +--- <pre>lua --- vim.cmd('echo 42') --- vim.cmd([[ --- augroup My_group @@ -389,7 +391,7 @@ end ---@param pos2 integer[] (line, column) tuple marking end of region ---@param regtype string type of selection, see |setreg()| ---@param inclusive boolean indicating whether the selection is end-inclusive ----@return table<integer, {}> region lua table of the form {linenr = {startcol,endcol}} +---@return table region Table of the form `{linenr = {startcol,endcol}}` function vim.region(bufnr, pos1, pos2, regtype, inclusive) if not vim.api.nvim_buf_is_loaded(bufnr) then vim.fn.bufload(bufnr) @@ -416,11 +418,16 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive) c2 = c1 + regtype:sub(2) -- and adjust for non-ASCII characters bufline = vim.api.nvim_buf_get_lines(bufnr, l, l + 1, true)[1] - if c1 < #bufline then + local utflen = vim.str_utfindex(bufline, #bufline) + if c1 <= utflen then c1 = vim.str_byteindex(bufline, c1) + else + c1 = #bufline + 1 end - if c2 < #bufline then + if c2 <= utflen then c2 = vim.str_byteindex(bufline, c2) + else + c2 = #bufline + 1 end else c1 = (l == pos1[1]) and pos1[2] or 0 @@ -739,7 +746,7 @@ end ---Prints given arguments in human-readable format. ---Example: ----<pre> +---<pre>lua --- -- 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> diff --git a/runtime/lua/vim/_init_packages.lua b/runtime/lua/vim/_init_packages.lua index 19c8608732..0c4ee8636d 100644 --- a/runtime/lua/vim/_init_packages.lua +++ b/runtime/lua/vim/_init_packages.lua @@ -56,6 +56,9 @@ setmetatable(vim, { if vim._submodules[key] then t[key] = require('vim.' .. key) return t[key] + elseif key == 'inspect_pos' or key == 'show_pos' then + require('vim._inspector') + return t[key] elseif vim.startswith(key, 'uri_') then local val = require('vim.uri')[key] if val ~= nil then diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua new file mode 100644 index 0000000000..f46a525910 --- /dev/null +++ b/runtime/lua/vim/_inspector.lua @@ -0,0 +1,238 @@ +---@class InspectorFilter +---@field syntax boolean include syntax based highlight groups (defaults to true) +---@field treesitter boolean include treesitter based highlight groups (defaults to true) +---@field extmarks boolean|"all" include extmarks. When `all`, then extmarks without a `hl_group` will also be included (defaults to true) +---@field semantic_tokens boolean include semantic tokens (defaults to true) +local defaults = { + syntax = true, + treesitter = true, + extmarks = true, + semantic_tokens = true, +} + +---Get all the items at a given buffer position. +--- +---Can also be pretty-printed with `:Inspect!`. *:Inspect!* +--- +---@param bufnr? number defaults to the current buffer +---@param row? number row to inspect, 0-based. Defaults to the row of the current cursor +---@param col? number col to inspect, 0-based. Defaults to the col of the current cursor +---@param filter? InspectorFilter (table|nil) a table with key-value pairs to filter the items +--- - syntax (boolean): include syntax based highlight groups (defaults to true) +--- - treesitter (boolean): include treesitter based highlight groups (defaults to true) +--- - extmarks (boolean|"all"): include extmarks. When `all`, then extmarks without a `hl_group` will also be included (defaults to true) +--- - semantic_tokens (boolean): include semantic tokens (defaults to true) +---@return {treesitter:table,syntax:table,extmarks:table,semantic_tokens:table,buffer:number,col:number,row:number} (table) a table with the following key-value pairs. Items are in "traversal order": +--- - treesitter: a list of treesitter captures +--- - syntax: a list of syntax groups +--- - semantic_tokens: a list of semantic tokens +--- - extmarks: a list of extmarks +--- - buffer: the buffer used to get the items +--- - row: the row used to get the items +--- - col: the col used to get the items +function vim.inspect_pos(bufnr, row, col, filter) + filter = vim.tbl_deep_extend('force', defaults, filter or {}) + + bufnr = bufnr or 0 + if row == nil or col == nil then + -- get the row/col from the first window displaying the buffer + local win = bufnr == 0 and vim.api.nvim_get_current_win() or vim.fn.bufwinid(bufnr) + if win == -1 then + error('row/col is required for buffers not visible in a window') + end + local cursor = vim.api.nvim_win_get_cursor(win) + row, col = cursor[1] - 1, cursor[2] + end + bufnr = bufnr == 0 and vim.api.nvim_get_current_buf() or bufnr + + local results = { + treesitter = {}, + syntax = {}, + extmarks = {}, + semantic_tokens = {}, + buffer = bufnr, + row = row, + col = col, + } + + -- resolve hl links + ---@private + local function resolve_hl(data) + if data.hl_group then + local hlid = vim.api.nvim_get_hl_id_by_name(data.hl_group) + local name = vim.fn.synIDattr(vim.fn.synIDtrans(hlid), 'name') + data.hl_group_link = name + end + return data + end + + -- treesitter + if filter.treesitter then + for _, capture in pairs(vim.treesitter.get_captures_at_pos(bufnr, row, col)) do + capture.hl_group = '@' .. capture.capture + table.insert(results.treesitter, resolve_hl(capture)) + end + end + + -- syntax + if filter.syntax then + for _, i1 in ipairs(vim.fn.synstack(row + 1, col + 1)) do + table.insert(results.syntax, resolve_hl({ hl_group = vim.fn.synIDattr(i1, 'name') })) + end + end + + -- semantic tokens + if filter.semantic_tokens then + for _, token in ipairs(vim.lsp.semantic_tokens.get_at_pos(bufnr, row, col) or {}) do + token.hl_groups = { + type = resolve_hl({ hl_group = '@' .. token.type }), + modifiers = vim.tbl_map(function(modifier) + return resolve_hl({ hl_group = '@' .. modifier }) + end, token.modifiers or {}), + } + table.insert(results.semantic_tokens, token) + end + end + + -- extmarks + if filter.extmarks then + for ns, nsid in pairs(vim.api.nvim_get_namespaces()) do + if ns:find('vim_lsp_semantic_tokens') ~= 1 then + local extmarks = vim.api.nvim_buf_get_extmarks(bufnr, nsid, 0, -1, { details = true }) + for _, extmark in ipairs(extmarks) do + extmark = { + ns_id = nsid, + ns = ns, + id = extmark[1], + row = extmark[2], + col = extmark[3], + opts = resolve_hl(extmark[4]), + } + local end_row = extmark.opts.end_row or extmark.row -- inclusive + local end_col = extmark.opts.end_col or (extmark.col + 1) -- exclusive + if + (filter.extmarks == 'all' or extmark.opts.hl_group) -- filter hl_group + and (row >= extmark.row and row <= end_row) -- within the rows of the extmark + and (row > extmark.row or col >= extmark.col) -- either not the first row, or in range of the col + and (row < end_row or col < end_col) -- either not in the last row or in range of the col + then + table.insert(results.extmarks, extmark) + end + end + end + end + end + return results +end + +---Show all the items at a given buffer position. +--- +---Can also be shown with `:Inspect`. *:Inspect* +--- +---@param bufnr? number defaults to the current buffer +---@param row? number row to inspect, 0-based. Defaults to the row of the current cursor +---@param col? number col to inspect, 0-based. Defaults to the col of the current cursor +---@param filter? InspectorFilter (table|nil) see |vim.inspect_pos()| +function vim.show_pos(bufnr, row, col, filter) + local items = vim.inspect_pos(bufnr, row, col, filter) + + local lines = { {} } + + ---@private + local function append(str, hl) + table.insert(lines[#lines], { str, hl }) + end + + ---@private + local function nl() + table.insert(lines, {}) + end + + ---@private + local function item(data, comment) + append(' - ') + append(data.hl_group, data.hl_group) + append(' ') + if data.hl_group ~= data.hl_group_link then + append('links to ', 'MoreMsg') + append(data.hl_group_link, data.hl_group_link) + append(' ') + end + if comment then + append(comment, 'Comment') + end + nl() + end + + -- treesitter + if #items.treesitter > 0 then + append('Treesitter', 'Title') + nl() + for _, capture in ipairs(items.treesitter) do + item(capture, capture.lang) + end + nl() + end + + if #items.semantic_tokens > 0 then + append('Semantic Tokens', 'Title') + nl() + for _, token in ipairs(items.semantic_tokens) do + local client = vim.lsp.get_client_by_id(token.client_id) + client = client and (' (' .. client.name .. ')') or '' + item(token.hl_groups.type, 'type' .. client) + for _, modifier in ipairs(token.hl_groups.modifiers) do + item(modifier, 'modifier' .. client) + end + end + nl() + end + + -- syntax + if #items.syntax > 0 then + append('Syntax', 'Title') + nl() + for _, syn in ipairs(items.syntax) do + item(syn) + end + nl() + end + -- extmarks + if #items.extmarks > 0 then + append('Extmarks', 'Title') + nl() + for _, extmark in ipairs(items.extmarks) do + if extmark.opts.hl_group then + item(extmark.opts, extmark.ns) + else + append(' - ') + append(extmark.ns, 'Comment') + nl() + end + end + nl() + end + + if #lines[#lines] == 0 then + table.remove(lines) + end + + local chunks = {} + for _, line in ipairs(lines) do + vim.list_extend(chunks, line) + table.insert(chunks, { '\n' }) + end + if #chunks == 0 then + chunks = { + { + 'No items found at position ' + .. items.row + .. ',' + .. items.col + .. ' in buffer ' + .. items.buffer, + }, + } + end + vim.api.nvim_echo(chunks, false, {}) +end diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index 9c7972873e..104f29c4c0 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -526,7 +526,7 @@ local function create_option_accessor(scope) return setmetatable({}, { __index = function(_, k) - return make_option(k, a.nvim_get_option_value(k, { scope = scope })) + return make_option(k, a.nvim_get_option_value(k, {})) end, __newindex = function(_, k, v) diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 84091fbf0e..6fd000a029 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -1,7 +1,8 @@ -local if_nil = vim.F.if_nil +local api, if_nil = vim.api, vim.F.if_nil local M = {} +---@enum DiagnosticSeverity M.severity = { ERROR = 1, WARN = 2, @@ -47,11 +48,11 @@ local bufnr_and_namespace_cacher_mt = { local diagnostic_cache do - local group = vim.api.nvim_create_augroup('DiagnosticBufWipeout', {}) + local group = api.nvim_create_augroup('DiagnosticBufWipeout', {}) diagnostic_cache = setmetatable({}, { __index = function(t, bufnr) assert(bufnr > 0, 'Invalid buffer number') - vim.api.nvim_create_autocmd('BufWipeout', { + api.nvim_create_autocmd('BufWipeout', { group = group, buffer = bufnr, callback = function() @@ -245,25 +246,12 @@ end)() ---@private local function get_bufnr(bufnr) if not bufnr or bufnr == 0 then - return vim.api.nvim_get_current_buf() + return api.nvim_get_current_buf() end return bufnr end ---@private -local function is_disabled(namespace, bufnr) - local ns = M.get_namespace(namespace) - if ns.disabled then - return true - end - - if type(diagnostic_disabled[bufnr]) == 'table' then - return diagnostic_disabled[bufnr][namespace] - end - return diagnostic_disabled[bufnr] -end - ----@private local function diagnostic_lines(diagnostics) if not diagnostics then return {} @@ -299,7 +287,7 @@ 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 }) + local extmarks_current = api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { details = true }) local found = {} for _, extmark in ipairs(extmarks_current) do -- nvim_buf_set_lines will move any extmark to the line after the last @@ -312,7 +300,7 @@ local function restore_extmarks(bufnr, last) if not found[extmark[1]] then local opts = extmark[4] opts.id = extmark[1] - pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, extmark[2], extmark[3], opts) + pcall(api.nvim_buf_set_extmark, bufnr, ns, extmark[2], extmark[3], opts) end end end @@ -322,7 +310,7 @@ end local function save_extmarks(namespace, bufnr) bufnr = get_bufnr(bufnr) if not diagnostic_attached_buffers[bufnr] then - vim.api.nvim_buf_attach(bufnr, false, { + api.nvim_buf_attach(bufnr, false, { on_lines = function(_, _, _, _, _, last) restore_extmarks(bufnr, last - 1) end, @@ -333,7 +321,7 @@ local function save_extmarks(namespace, bufnr) diagnostic_attached_buffers[bufnr] = true end diagnostic_cache_extmarks[bufnr][namespace] = - vim.api.nvim_buf_get_extmarks(bufnr, namespace, 0, -1, { details = true }) + api.nvim_buf_get_extmarks(bufnr, namespace, 0, -1, { details = true }) end local registered_autocmds = {} @@ -366,8 +354,8 @@ local function schedule_display(namespace, bufnr, args) local key = make_augroup_key(namespace, bufnr) if not registered_autocmds[key] then - local group = vim.api.nvim_create_augroup(key, { clear = true }) - vim.api.nvim_create_autocmd(insert_leave_auto_cmds, { + local group = api.nvim_create_augroup(key, { clear = true }) + api.nvim_create_autocmd(insert_leave_auto_cmds, { group = group, buffer = bufnr, callback = function() @@ -384,7 +372,7 @@ local function clear_scheduled_display(namespace, bufnr) local key = make_augroup_key(namespace, bufnr) if registered_autocmds[key] then - vim.api.nvim_del_augroup_by_name(key) + api.nvim_del_augroup_by_name(key) registered_autocmds[key] = nil end end @@ -399,7 +387,7 @@ local function get_diagnostics(bufnr, opts, clamp) -- 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) + t[k] = api.nvim_buf_line_count(k) return rawget(t, k) end, }) @@ -407,7 +395,7 @@ local function get_diagnostics(bufnr, opts, clamp) ---@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 + if clamp and api.nvim_buf_is_loaded(b) then local line_count = buf_line_count[b] - 1 if d.lnum > line_count @@ -428,32 +416,31 @@ local function get_diagnostics(bufnr, opts, clamp) end end + ---@private + local function add_all_diags(buf, diags) + for _, diagnostic in pairs(diags) do + add(buf, diagnostic) + 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 + add_all_diags(b, v) 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 + add_all_diags(bufnr, diagnostic_cache[bufnr][iter_namespace]) 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 + add_all_diags(b, t[namespace] or {}) end else bufnr = get_bufnr(bufnr) - for _, diagnostic in pairs(diagnostic_cache[bufnr][namespace] or {}) do - add(bufnr, diagnostic) - end + add_all_diags(bufnr, diagnostic_cache[bufnr][namespace] or {}) end if opts.severity then @@ -471,7 +458,7 @@ local function set_list(loclist, opts) local winnr = opts.winnr or 0 local bufnr if loclist then - bufnr = vim.api.nvim_win_get_buf(winnr) + bufnr = api.nvim_win_get_buf(winnr) end -- Don't clamp line numbers since the quickfix list can already handle line -- numbers beyond the end of the buffer @@ -483,7 +470,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 'botright copen') + api.nvim_command(loclist and 'lwindow' or 'botright cwindow') end end @@ -492,7 +479,7 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace) position[1] = position[1] - 1 bufnr = get_bufnr(bufnr) local wrap = vim.F.if_nil(opts.wrap, true) - local line_count = vim.api.nvim_buf_line_count(bufnr) + local line_count = api.nvim_buf_line_count(bufnr) local diagnostics = get_diagnostics(bufnr, vim.tbl_extend('keep', opts, { namespace = namespace }), true) local line_diagnostics = diagnostic_lines(diagnostics) @@ -506,7 +493,7 @@ 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 line_length = #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) @@ -542,17 +529,17 @@ local function diagnostic_move_pos(opts, pos) opts = opts or {} local float = vim.F.if_nil(opts.float, true) - local win_id = opts.win_id or vim.api.nvim_get_current_win() + local win_id = opts.win_id or api.nvim_get_current_win() if not pos then - vim.api.nvim_echo({ { 'No more valid diagnostics to move to', 'WarningMsg' } }, true, {}) + api.nvim_echo({ { 'No more valid diagnostics to move to', 'WarningMsg' } }, true, {}) return end - vim.api.nvim_win_call(win_id, function() + 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] }) + api.nvim_win_set_cursor(win_id, { pos[1] + 1, pos[2] }) -- Open folds under the cursor vim.cmd('normal! zv') end) @@ -561,7 +548,7 @@ local function diagnostic_move_pos(opts, pos) local float_opts = type(float) == 'table' and float or {} vim.schedule(function() M.open_float(vim.tbl_extend('keep', float_opts, { - bufnr = vim.api.nvim_win_get_buf(win_id), + bufnr = api.nvim_win_get_buf(win_id), scope = 'cursor', focus = false, })) @@ -578,12 +565,12 @@ end --- followed by namespace configuration, and finally global configuration. --- --- For example, if a user enables virtual text globally with ---- <pre> +--- <pre>lua --- vim.diagnostic.config({ virtual_text = true }) --- </pre> --- --- and a diagnostic producer sets diagnostics with ---- <pre> +--- <pre>lua --- vim.diagnostic.set(ns, 0, diagnostics, { virtual_text = false }) --- </pre> --- @@ -613,16 +600,20 @@ end --- * spacing: (number) Amount of empty spaces inserted at the beginning --- of the virtual text. --- * prefix: (string) Prepend diagnostic message with prefix. +--- * suffix: (string or function) Append diagnostic message with suffix. +--- If a function, it must have the signature (diagnostic) -> +--- string, where {diagnostic} is of type |diagnostic-structure|. +--- This can be used to render an LSP diagnostic error code. --- * 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: ---- <pre> ---- function(diagnostic) ---- if diagnostic.severity == vim.diagnostic.severity.ERROR then ---- return string.format("E: %s", diagnostic.message) +--- <pre>lua +--- function(diagnostic) +--- if diagnostic.severity == vim.diagnostic.severity.ERROR then +--- return string.format("E: %s", diagnostic.message) +--- end +--- return diagnostic.message --- end ---- return diagnostic.message ---- end --- </pre> --- - signs: (default true) Use signs for diagnostics. Options: --- * severity: Only show signs for diagnostics matching the given severity @@ -666,13 +657,13 @@ function M.config(opts, namespace) if namespace then for bufnr, v in pairs(diagnostic_cache) do - if vim.api.nvim_buf_is_loaded(bufnr) and v[namespace] then + if api.nvim_buf_is_loaded(bufnr) and v[namespace] then M.show(namespace, bufnr) end end else for bufnr, v in pairs(diagnostic_cache) do - if vim.api.nvim_buf_is_loaded(bufnr) then + if api.nvim_buf_is_loaded(bufnr) then for ns in pairs(v) do M.show(ns, bufnr) end @@ -707,11 +698,11 @@ function M.set(namespace, bufnr, diagnostics, opts) set_diagnostic_cache(namespace, bufnr, diagnostics) end - if vim.api.nvim_buf_is_loaded(bufnr) then + if api.nvim_buf_is_loaded(bufnr) then M.show(namespace, bufnr, nil, opts) end - vim.api.nvim_exec_autocmds('DiagnosticChanged', { + api.nvim_exec_autocmds('DiagnosticChanged', { modeline = false, buffer = bufnr, data = { diagnostics = diagnostics }, @@ -726,7 +717,7 @@ function M.get_namespace(namespace) vim.validate({ namespace = { namespace, 'n' } }) if not all_namespaces[namespace] then local name - for k, v in pairs(vim.api.nvim_get_namespaces()) do + for k, v in pairs(api.nvim_get_namespaces()) do if namespace == v then name = k break @@ -751,6 +742,18 @@ function M.get_namespaces() return vim.deepcopy(all_namespaces) end +---@class Diagnostic +---@field buffer number +---@field lnum number 0-indexed +---@field end_lnum nil|number 0-indexed +---@field col number 0-indexed +---@field end_col nil|number 0-indexed +---@field severity DiagnosticSeverity +---@field message string +---@field source nil|string +---@field code nil|string +---@field user_data nil|any arbitrary data plugins can add + --- Get current diagnostics. --- ---@param bufnr number|nil Buffer number to get diagnostics from. Use 0 for @@ -759,7 +762,7 @@ end --- - namespace: (number) Limit diagnostics to the given namespace. --- - lnum: (number) Limit diagnostics to the given line number. --- - severity: See |diagnostic-severity|. ----@return table A list of diagnostic items |diagnostic-structure|. +---@return Diagnostic[] table A list of diagnostic items |diagnostic-structure|. function M.get(bufnr, opts) vim.validate({ bufnr = { bufnr, 'n', true }, @@ -771,22 +774,23 @@ end --- Get the previous diagnostic closest to the cursor position. --- ----@param opts table See |vim.diagnostic.goto_next()| ----@return table Previous diagnostic +---@param opts nil|table See |vim.diagnostic.goto_next()| +---@return Diagnostic|nil Previous diagnostic function M.get_prev(opts) opts = opts or {} - local win_id = opts.win_id or vim.api.nvim_get_current_win() - local bufnr = vim.api.nvim_win_get_buf(win_id) - local cursor_position = opts.cursor_position or vim.api.nvim_win_get_cursor(win_id) + local win_id = opts.win_id or api.nvim_get_current_win() + local bufnr = api.nvim_win_get_buf(win_id) + local cursor_position = opts.cursor_position or api.nvim_win_get_cursor(win_id) return next_diagnostic(cursor_position, false, bufnr, opts, opts.namespace) end --- Return the position of the previous diagnostic in the current buffer. --- ----@param opts table See |vim.diagnostic.goto_next()| ----@return table Previous diagnostic position as a (row, col) tuple. +---@param opts table|nil See |vim.diagnostic.goto_next()| +---@return table|false Previous diagnostic position as a (row, col) tuple or false if there is no +--- prior diagnostic function M.get_prev_pos(opts) local prev = M.get_prev(opts) if not prev then @@ -797,29 +801,30 @@ function M.get_prev_pos(opts) end --- Move to the previous diagnostic in the current buffer. ----@param opts table See |vim.diagnostic.goto_next()| +---@param opts table|nil See |vim.diagnostic.goto_next()| function M.goto_prev(opts) return diagnostic_move_pos(opts, M.get_prev_pos(opts)) end --- Get the next diagnostic closest to the cursor position. --- ----@param opts table See |vim.diagnostic.goto_next()| ----@return table Next diagnostic +---@param opts table|nil See |vim.diagnostic.goto_next()| +---@return Diagnostic|nil Next diagnostic function M.get_next(opts) opts = opts or {} - local win_id = opts.win_id or vim.api.nvim_get_current_win() - local bufnr = vim.api.nvim_win_get_buf(win_id) - local cursor_position = opts.cursor_position or vim.api.nvim_win_get_cursor(win_id) + local win_id = opts.win_id or api.nvim_get_current_win() + local bufnr = api.nvim_win_get_buf(win_id) + local cursor_position = opts.cursor_position or api.nvim_win_get_cursor(win_id) return next_diagnostic(cursor_position, true, bufnr, opts, opts.namespace) end --- Return the position of the next diagnostic in the current buffer. --- ----@param opts table See |vim.diagnostic.goto_next()| ----@return table Next diagnostic position as a (row, col) tuple. +---@param opts table|nil See |vim.diagnostic.goto_next()| +---@return table|false Next diagnostic position as a (row, col) tuple or false if no next +--- diagnostic. function M.get_next_pos(opts) local next = M.get_next(opts) if not next then @@ -903,7 +908,7 @@ M.handlers.signs = { end, hide = function(namespace, bufnr) local ns = M.get_namespace(namespace) - if ns.user_data.sign_group then + if ns.user_data.sign_group and api.nvim_buf_is_valid(bufnr) then vim.fn.sign_unplace(ns.user_data.sign_group, { buffer = bufnr }) end end, @@ -931,7 +936,7 @@ M.handlers.underline = { local ns = M.get_namespace(namespace) if not ns.user_data.underline_ns then - ns.user_data.underline_ns = vim.api.nvim_create_namespace('') + ns.user_data.underline_ns = api.nvim_create_namespace('') end local underline_ns = ns.user_data.underline_ns @@ -958,7 +963,9 @@ M.handlers.underline = { local ns = M.get_namespace(namespace) if ns.user_data.underline_ns then diagnostic_cache_extmarks[bufnr][ns.user_data.underline_ns] = {} - vim.api.nvim_buf_clear_namespace(bufnr, ns.user_data.underline_ns, 0, -1) + if api.nvim_buf_is_valid(bufnr) then + api.nvim_buf_clear_namespace(bufnr, ns.user_data.underline_ns, 0, -1) + end end end, } @@ -997,7 +1004,7 @@ M.handlers.virtual_text = { local ns = M.get_namespace(namespace) if not ns.user_data.virt_text_ns then - ns.user_data.virt_text_ns = vim.api.nvim_create_namespace('') + ns.user_data.virt_text_ns = api.nvim_create_namespace('') end local virt_text_ns = ns.user_data.virt_text_ns @@ -1009,7 +1016,7 @@ M.handlers.virtual_text = { local virt_texts = M._get_virt_text_chunks(line_diagnostics, opts.virtual_text) if virt_texts then - vim.api.nvim_buf_set_extmark(bufnr, virt_text_ns, line, 0, { + api.nvim_buf_set_extmark(bufnr, virt_text_ns, line, 0, { hl_mode = 'combine', virt_text = virt_texts, }) @@ -1021,7 +1028,9 @@ M.handlers.virtual_text = { local ns = M.get_namespace(namespace) if ns.user_data.virt_text_ns then diagnostic_cache_extmarks[bufnr][ns.user_data.virt_text_ns] = {} - vim.api.nvim_buf_clear_namespace(bufnr, ns.user_data.virt_text_ns, 0, -1) + if api.nvim_buf_is_valid(bufnr) then + api.nvim_buf_clear_namespace(bufnr, ns.user_data.virt_text_ns, 0, -1) + end end end, } @@ -1039,6 +1048,7 @@ function M._get_virt_text_chunks(line_diags, opts) opts = opts or {} local prefix = opts.prefix or '■' + local suffix = opts.suffix or '' local spacing = opts.spacing or 4 -- Create a little more space between virtual text and contents @@ -1052,8 +1062,11 @@ function M._get_virt_text_chunks(line_diags, opts) -- TODO(tjdevries): Allow different servers to be shown first somehow? -- TODO(tjdevries): Display server name associated with these? if last.message then + if type(suffix) == 'function' then + suffix = suffix(last) or '' + end table.insert(virt_texts, { - string.format('%s %s', prefix, last.message:gsub('\r', ''):gsub('\n', ' ')), + string.format('%s %s%s', prefix, last.message:gsub('\r', ''):gsub('\n', ' '), suffix), virtual_text_highlight_map[last.severity], }) @@ -1093,6 +1106,27 @@ function M.hide(namespace, bufnr) end end +--- Check whether diagnostics are disabled in a given buffer. +--- +---@param bufnr number|nil Buffer number, or 0 for current buffer. +---@param namespace number|nil Diagnostic namespace. When omitted, checks if +--- all diagnostics are disabled in {bufnr}. +--- Otherwise, only checks if diagnostics from +--- {namespace} are disabled. +---@return boolean +function M.is_disabled(bufnr, namespace) + bufnr = get_bufnr(bufnr) + if namespace and M.get_namespace(namespace).disabled then + return true + end + + if type(diagnostic_disabled[bufnr]) == 'table' then + return diagnostic_disabled[bufnr][namespace] + end + + return diagnostic_disabled[bufnr] ~= nil +end + --- Display diagnostics for the given namespace and buffer. --- ---@param namespace number|nil Diagnostic namespace. When omitted, show @@ -1136,7 +1170,7 @@ function M.show(namespace, bufnr, diagnostics, opts) return end - if is_disabled(namespace, bufnr) then + if M.is_disabled(bufnr, namespace) then return end @@ -1153,7 +1187,7 @@ function M.show(namespace, bufnr, diagnostics, opts) if opts.update_in_insert then clear_scheduled_display(namespace, bufnr) else - local mode = vim.api.nvim_get_mode() + local mode = api.nvim_get_mode() if string.sub(mode.mode, 1, 1) == 'i' then schedule_display(namespace, bufnr, opts) return @@ -1220,7 +1254,9 @@ end --- 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}) +--- - suffix: Same as {prefix}, but appends the text to the diagnostic instead of +--- prepending it. Overrides the setting from |vim.diagnostic.config()|. +---@return number|nil, number|nil: ({float_bufnr}, {win_id}) function M.open_float(opts, ...) -- Support old (bufnr, opts) signature local bufnr @@ -1251,7 +1287,7 @@ function M.open_float(opts, ...) local lnum, col if scope == 'line' or scope == 'cursor' then if not opts.pos then - local pos = vim.api.nvim_win_get_cursor(0) + local pos = api.nvim_win_get_cursor(0) lnum = pos[1] - 1 col = pos[2] elseif type(opts.pos) == 'number' then @@ -1273,7 +1309,7 @@ function M.open_float(opts, ...) end, diagnostics) elseif scope == 'cursor' then -- LSP servers can send diagnostics with `end_col` past the length of the line - local line_length = #vim.api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1] + local line_length = #api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1] diagnostics = vim.tbl_filter(function(d) return d.lnum == lnum and math.min(d.col, line_length - 1) <= col @@ -1305,9 +1341,7 @@ function M.open_float(opts, ...) vim.validate({ header = { header, - function(v) - return type(v) == 'string' or type(v) == 'table' - end, + { 'string', 'table' }, "'string' or 'table'", }, }) @@ -1315,11 +1349,11 @@ function M.open_float(opts, ...) -- Don't insert any lines for an empty string if string.len(if_nil(header[1], '')) > 0 then table.insert(lines, header[1]) - table.insert(highlights, { 0, header[2] or 'Bold' }) + table.insert(highlights, { hlname = header[2] or 'Bold' }) end elseif #header > 0 then table.insert(lines, header) - table.insert(highlights, { 0, 'Bold' }) + table.insert(highlights, { hlname = 'Bold' }) end end @@ -1341,9 +1375,7 @@ function M.open_float(opts, ...) vim.validate({ prefix = { prefix_opt, - function(v) - return type(v) == 'string' or type(v) == 'table' or type(v) == 'function' - end, + { 'string', 'table', 'function' }, "'string' or 'table' or 'function'", }, }) @@ -1354,18 +1386,52 @@ function M.open_float(opts, ...) end end + local suffix_opt = if_nil(opts.suffix, function(diagnostic) + return diagnostic.code and string.format(' [%s]', diagnostic.code) or '' + end) + + local suffix, suffix_hl_group + if suffix_opt then + vim.validate({ + suffix = { + suffix_opt, + { 'string', 'table', 'function' }, + "'string' or 'table' or 'function'", + }, + }) + if type(suffix_opt) == 'string' then + suffix, suffix_hl_group = suffix_opt, 'NormalFloat' + elseif type(suffix_opt) == 'table' then + suffix, suffix_hl_group = suffix_opt[1] or '', suffix_opt[2] or 'NormalFloat' + end + end + for i, diagnostic in ipairs(diagnostics) do if prefix_opt and type(prefix_opt) == 'function' then prefix, prefix_hl_group = prefix_opt(diagnostic, i, #diagnostics) prefix, prefix_hl_group = prefix or '', prefix_hl_group or 'NormalFloat' end + if suffix_opt and type(suffix_opt) == 'function' then + suffix, suffix_hl_group = suffix_opt(diagnostic, i, #diagnostics) + suffix, suffix_hl_group = suffix or '', suffix_hl_group or 'NormalFloat' + end local hiname = floating_highlight_map[diagnostic.severity] local message_lines = vim.split(diagnostic.message, '\n') - table.insert(lines, prefix .. message_lines[1]) - table.insert(highlights, { #prefix, hiname, prefix_hl_group }) - for j = 2, #message_lines do - table.insert(lines, string.rep(' ', #prefix) .. message_lines[j]) - table.insert(highlights, { 0, hiname }) + for j = 1, #message_lines do + local pre = j == 1 and prefix or string.rep(' ', #prefix) + local suf = j == #message_lines and suffix or '' + table.insert(lines, pre .. message_lines[j] .. suf) + table.insert(highlights, { + hlname = hiname, + prefix = { + length = j == 1 and #prefix or 0, + hlname = prefix_hl_group, + }, + suffix = { + length = j == #message_lines and #suffix or 0, + hlname = suffix_hl_group, + }, + }) end end @@ -1374,12 +1440,17 @@ function M.open_float(opts, ...) opts.focus_id = scope end local float_bufnr, winnr = require('vim.lsp.util').open_floating_preview(lines, 'plaintext', opts) - for i, hi in ipairs(highlights) do - local prefixlen, hiname, prefix_hiname = unpack(hi) - if prefix_hiname then - vim.api.nvim_buf_add_highlight(float_bufnr, -1, prefix_hiname, i - 1, 0, prefixlen) + for i, hl in ipairs(highlights) do + local line = lines[i] + local prefix_len = hl.prefix and hl.prefix.length or 0 + local suffix_len = hl.suffix and hl.suffix.length or 0 + if prefix_len > 0 then + api.nvim_buf_add_highlight(float_bufnr, -1, hl.prefix.hlname, i - 1, 0, prefix_len) + end + api.nvim_buf_add_highlight(float_bufnr, -1, hl.hlname, i - 1, prefix_len, #line - suffix_len) + if suffix_len > 0 then + api.nvim_buf_add_highlight(float_bufnr, -1, hl.suffix.hlname, i - 1, #line - suffix_len, -1) end - vim.api.nvim_buf_add_highlight(float_bufnr, -1, hiname, i - 1, prefixlen, -1) end return float_bufnr, winnr @@ -1410,11 +1481,15 @@ function M.reset(namespace, bufnr) M.hide(iter_namespace, iter_bufnr) end - vim.api.nvim_exec_autocmds('DiagnosticChanged', { - modeline = false, - buffer = iter_bufnr, - data = { diagnostics = {} }, - }) + if api.nvim_buf_is_valid(iter_bufnr) then + api.nvim_exec_autocmds('DiagnosticChanged', { + modeline = false, + buffer = iter_bufnr, + data = { diagnostics = {} }, + }) + else + diagnostic_cache[iter_bufnr] = nil + end end end @@ -1517,11 +1592,11 @@ end --- --- This can be parsed into a diagnostic |diagnostic-structure| --- with: ---- <pre> ---- local s = "WARNING filename:27:3: Variable 'foo' does not exist" ---- local pattern = "^(%w+) %w+:(%d+):(%d+): (.+)$" ---- local groups = { "severity", "lnum", "col", "message" } ---- vim.diagnostic.match(s, pattern, groups, { WARNING = vim.diagnostic.WARN }) +--- <pre>lua +--- local s = "WARNING filename:27:3: Variable 'foo' does not exist" +--- local pattern = "^(%w+) %w+:(%d+):(%d+): (.+)$" +--- local groups = { "severity", "lnum", "col", "message" } +--- vim.diagnostic.match(s, pattern, groups, { WARNING = vim.diagnostic.WARN }) --- </pre> --- ---@param str string String to parse diagnostics from. @@ -1533,7 +1608,7 @@ end ---@param defaults table|nil Table of default values for any fields not listed in {groups}. --- When omitted, numeric values default to 0 and "severity" defaults to --- ERROR. ----@return diagnostic |diagnostic-structure| or `nil` if {pat} fails to match {str}. +---@return Diagnostic|nil: |diagnostic-structure| or `nil` if {pat} fails to match {str}. function M.match(str, pat, groups, severity_map, defaults) vim.validate({ str = { str, 's' }, @@ -1580,7 +1655,7 @@ local errlist_type_map = { --- passed to |setqflist()| or |setloclist()|. --- ---@param diagnostics table List of diagnostics |diagnostic-structure|. ----@return array of quickfix list items |setqflist-what| +---@return table[] of quickfix list items |setqflist-what| function M.toqflist(diagnostics) vim.validate({ diagnostics = { @@ -1605,7 +1680,11 @@ function M.toqflist(diagnostics) end table.sort(list, function(a, b) if a.bufnr == b.bufnr then - return a.lnum < b.lnum + if a.lnum == b.lnum then + return a.col < b.col + else + return a.lnum < b.lnum + end else return a.bufnr < b.bufnr end @@ -1617,7 +1696,7 @@ end --- ---@param list table A list of quickfix items from |getqflist()| or --- |getloclist()|. ----@return array of diagnostics |diagnostic-structure| +---@return Diagnostic[] array of |diagnostic-structure| function M.fromqflist(list) vim.validate({ list = { diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index e204fe8ae2..9293c828b8 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -170,7 +170,7 @@ local extension = { return require('vim.filetype.detect').bindzone(bufnr, 'dcl') end, db = function(path, bufnr) - return require('vim.filetype.detect').bindzone(bufnr, '') + return require('vim.filetype.detect').bindzone(bufnr) end, bicep = 'bicep', bb = 'bitbake', @@ -190,6 +190,7 @@ local extension = { BUILD = 'bzl', qc = 'c', cabal = 'cabal', + capnp = 'capnp', cdl = 'cdl', toc = 'cdrtoc', cfc = 'cf', @@ -257,6 +258,7 @@ local extension = { cc = function(path, bufnr) return vim.g.cynlib_syntax_for_cc and 'cynlib' or 'cpp' end, + cql = 'cqlang', crm = 'crm', csx = 'cs', cs = 'cs', @@ -326,11 +328,7 @@ local extension = { end, eex = 'eelixir', leex = 'eelixir', - am = function(path, bufnr) - if not path:lower():find('makefile%.am$') then - return 'elf' - end - end, + am = 'elf', exs = 'elixir', elm = 'elm', elv = 'elvish', @@ -418,6 +416,7 @@ local extension = { fs = function(path, bufnr) return require('vim.filetype.detect').fs(bufnr) end, + fsh = 'fsh', fsi = 'fsharp', fsx = 'fsharp', fusion = 'fusion', @@ -555,6 +554,7 @@ local extension = { jov = 'jovial', jovial = 'jovial', properties = 'jproperties', + jq = 'jq', slnf = 'json', json = 'json', jsonp = 'json', @@ -564,7 +564,7 @@ local extension = { json5 = 'json5', jsonc = 'jsonc', jsonnet = 'jsonnet', - libjsonnet = 'jsonnet', + libsonnet = 'jsonnet', jsp = 'jsp', jl = 'julia', kv = 'kivy', @@ -611,7 +611,9 @@ local extension = { c = function(path, bufnr) return require('vim.filetype.detect').lpc(bufnr) end, - lsl = 'lsl', + lsl = function(path, bufnr) + return require('vim.filetype.detect').lsl(bufnr) + end, lss = 'lss', nse = 'lua', rockspec = 'lua', @@ -656,6 +658,9 @@ local extension = { dmt = 'maxima', wxm = 'maxima', mel = 'mel', + mmd = 'mermaid', + mmdc = 'mermaid', + mermaid = 'mermaid', mf = 'mf', mgl = 'mgl', mgp = 'mgp', @@ -674,6 +679,7 @@ local extension = { DEF = 'modula2', ['m2'] = 'modula2', mi = 'modula2', + lm3 = 'modula3', ssc = 'monk', monk = 'monk', tsc = 'monk', @@ -722,6 +728,10 @@ local extension = { nsi = 'nsis', nsh = 'nsis', obj = 'obj', + obl = 'obse', + obse = 'obse', + oblivion = 'obse', + obscript = 'obse', mlt = 'ocaml', mly = 'ocaml', mll = 'ocaml', @@ -735,6 +745,7 @@ local extension = { opam = 'opam', ['or'] = 'openroad', scad = 'openscad', + ovpn = 'openvpn', ora = 'ora', org = 'org', org_archive = 'org', @@ -935,12 +946,14 @@ local extension = { ice = 'slice', score = 'slrnsc', sol = 'solidity', + smali = 'smali', tpl = 'smarty', ihlp = 'smcl', smcl = 'smcl', hlp = 'smcl', smith = 'smith', smt = 'smith', + smithy = 'smithy', sml = 'sml', spt = 'snobol4', sno = 'snobol4', @@ -1009,6 +1022,7 @@ local extension = { texinfo = 'texinfo', text = 'text', tfvars = 'terraform-vars', + thrift = 'thrift', tla = 'tla', tli = 'tli', toml = 'toml', @@ -1053,6 +1067,7 @@ local extension = { hdl = 'vhdl', vho = 'vhdl', vbe = 'vhdl', + tape = 'vhs', vim = 'vim', vba = 'vim', mar = 'vmasm', @@ -1062,6 +1077,7 @@ local extension = { vue = 'vue', wat = 'wast', wast = 'wast', + wdl = 'wdl', wm = 'webmacro', wbt = 'winbatch', wml = 'wml', @@ -1108,6 +1124,7 @@ local extension = { yang = 'yang', ['z8a'] = 'z8a', zig = 'zig', + zir = 'zir', zu = 'zimbu', zut = 'zimbutempl', zsh = 'zsh', @@ -1306,6 +1323,7 @@ local filename = { ['GNUmakefile.am'] = 'automake', ['named.root'] = 'bindzone', WORKSPACE = 'bzl', + ['WORKSPACE.bzlmod'] = 'bzl', BUILD = 'bzl', ['cabal.project'] = 'cabalproject', ['cabal.config'] = 'cabalconfig', @@ -1361,13 +1379,13 @@ local filename = { npmrc = 'dosini', ['/etc/yum.conf'] = 'dosini', ['.npmrc'] = 'dosini', - ['.editorconfig'] = 'dosini', ['/etc/pacman.conf'] = 'confini', ['mpv.conf'] = 'confini', dune = 'dune', jbuild = 'dune', ['dune-workspace'] = 'dune', ['dune-project'] = 'dune', + ['.editorconfig'] = 'editorconfig', ['elinks.conf'] = 'elinks', ['mix.lock'] = 'elixir', ['filter-rules'] = 'elmfilt', @@ -1410,6 +1428,7 @@ local filename = { gnashpluginrc = 'gnash', gnashrc = 'gnash', ['.gnuplot'] = 'gnuplot', + ['go.sum'] = 'gosum', ['go.work'] = 'gowork', ['.gprc'] = 'gp', ['/.gnupg/gpg.conf'] = 'gpg', @@ -1439,11 +1458,15 @@ local filename = { ['ipf.conf'] = 'ipfilter', ['ipf6.conf'] = 'ipfilter', ['ipf.rules'] = 'ipfilter', - ['.eslintrc'] = 'json', - ['.babelrc'] = 'json', ['Pipfile.lock'] = 'json', ['.firebaserc'] = 'json', ['.prettierrc'] = 'json', + ['.babelrc'] = 'jsonc', + ['.eslintrc'] = 'jsonc', + ['.hintrc'] = 'jsonc', + ['.jsfmtrc'] = 'jsonc', + ['.jshintrc'] = 'jsonc', + ['.swrc'] = 'jsonc', Kconfig = 'kconfig', ['Kconfig.debug'] = 'kconfig', ['lftp.conf'] = 'lftp', @@ -1458,6 +1481,9 @@ local filename = { ['.sawfishrc'] = 'lisp', ['/etc/login.access'] = 'loginaccess', ['/etc/login.defs'] = 'logindefs', + ['.lsl'] = function(path, bufnr) + return require('vim.filetype.detect').lsl(bufnr) + end, ['.luacheckrc'] = 'lua', ['lynx.cfg'] = 'lynx', ['m3overrides'] = 'm3build', @@ -1508,6 +1534,7 @@ local filename = { ['.latexmkrc'] = 'perl', ['pf.conf'] = 'pf', ['main.cf'] = 'pfmain', + ['main.cf.proto'] = 'pfmain', pinerc = 'pine', ['.pinercex'] = 'pine', ['.pinerc'] = 'pine', @@ -1540,6 +1567,9 @@ local filename = { ['.pythonstartup'] = 'python', ['.pythonrc'] = 'python', SConstruct = 'python', + ['.Rprofile'] = 'r', + ['Rprofile'] = 'r', + ['Rprofile.site'] = 'r', ratpoisonrc = 'ratpoison', ['.ratpoisonrc'] = 'ratpoison', inputrc = 'readline', @@ -1666,6 +1696,8 @@ local filename = { fglrxrc = 'xml', ['/etc/blkid.tab'] = 'xml', ['/etc/blkid.tab.old'] = 'xml', + ['.clang-format'] = 'yaml', + ['.clang-tidy'] = 'yaml', ['/etc/zprofile'] = 'zsh', ['.zlogin'] = 'zsh', ['.zlogout'] = 'zsh', @@ -1779,6 +1811,8 @@ local pattern = { ['.*/etc/DIR_COLORS'] = 'dircolors', ['.*/etc/dnsmasq%.conf'] = 'dnsmasq', ['php%.ini%-.*'] = 'dosini', + ['.*/%.aws/config'] = 'confini', + ['.*/%.aws/credentials'] = 'confini', ['.*/etc/pacman%.conf'] = 'confini', ['.*/etc/yum%.conf'] = 'dosini', ['.*lvs'] = 'dracula', @@ -1885,7 +1919,9 @@ local pattern = { ['Prl.*%..*'] = starsetf('jam'), ['.*%.properties_..'] = 'jproperties', ['.*%.properties_.._..'] = 'jproperties', + ['org%.eclipse%..*%.prefs'] = 'jproperties', ['.*%.properties_.._.._.*'] = starsetf('jproperties'), + ['[jt]sconfig.*%.json'] = 'jsonc', ['Kconfig%..*'] = starsetf('kconfig'), ['.*%.[Ss][Uu][Bb]'] = 'krl', ['lilo%.conf.*'] = starsetf('lilo'), @@ -2039,6 +2075,7 @@ local pattern = { ['.*%.ml%.cppo'] = 'ocaml', ['.*%.mli%.cppo'] = 'ocaml', ['.*%.opam%.template'] = 'opam', + ['.*/openvpn/.*/.*%.conf'] = 'openvpn', ['.*%.[Oo][Pp][Ll]'] = 'opl', ['.*/etc/pam%.conf'] = 'pamconf', ['.*/etc/pam%.d/.*'] = starsetf('pamconf'), @@ -2281,10 +2318,8 @@ end --- --- See $VIMRUNTIME/lua/vim/filetype.lua for more examples. --- ---- Note that Lua filetype detection is disabled when |g:do_legacy_filetype| is set. ---- --- Example: ---- <pre> +--- <pre>lua --- vim.filetype.add({ --- extension = { --- foo = 'fooscript', @@ -2319,8 +2354,8 @@ end --- }) --- </pre> --- ---- To add a fallback match on contents (see |new-filetype-scripts|), use ---- <pre> +--- To add a fallback match on contents, use +--- <pre>lua --- vim.filetype.add { --- pattern = { --- ['.*'] = { @@ -2432,7 +2467,7 @@ end --- Each of the three options is specified using a key to the single argument of this function. --- Example: --- ---- <pre> +--- <pre>lua --- -- Using a buffer number --- vim.filetype.match({ buf = 42 }) --- diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 7fc7f1b7ca..edffdde9c7 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -181,7 +181,7 @@ function M.cls(bufnr) return vim.g.filetype_cls end local line = getlines(bufnr, 1) - if line:find('^%%') then + if line:find('^[%%\\]') then return 'tex' elseif line:find('^#') and line:lower():find('rexx') then return 'rexx' @@ -634,6 +634,19 @@ function M.lpc(bufnr) return 'c' end +function M.lsl(bufnr) + if vim.g.filetype_lsl then + return vim.g.filetype_lsl + end + + local line = nextnonblank(bufnr, 1) + if findany(line, { '^%s*%%', ':%s*trait%s*$' }) then + return 'larch' + else + return 'lsl' + end +end + function M.m(bufnr) if vim.g.filetype_m then return vim.g.filetype_m @@ -1084,11 +1097,10 @@ function M.sc(bufnr) for _, line in ipairs(getlines(bufnr, 1, 25)) do if findany(line, { - '[A-Za-z0-9]*%s:%s[A-Za-z0-9]', 'var%s<', 'classvar%s<', '%^this.*', - '|%w*|', + '|%w+|', '%+%s%w*%s{', '%*ar%s', }) @@ -1153,13 +1165,14 @@ function M.sh(path, contents, name) vim.b[b].is_bash = nil vim.b[b].is_sh = nil end - elseif vim.g.bash_is_sh or matchregex(name, [[\<bash\>]]) or matchregex(name, [[\<bash2\>]]) then + elseif vim.g.bash_is_sh or matchregex(name, [[\<\(bash\|bash2\)\>]]) then on_detect = function(b) vim.b[b].is_bash = 1 vim.b[b].is_kornshell = nil vim.b[b].is_sh = nil end - elseif matchregex(name, [[\<sh\>]]) then + -- Ubuntu links sh to dash + elseif matchregex(name, [[\<\(sh\|dash\)\>]]) then on_detect = function(b) vim.b[b].is_sh = 1 vim.b[b].is_kornshell = nil @@ -1243,17 +1256,15 @@ end -- 2. Check the first 1000 non-comment lines for LaTeX or ConTeXt keywords. -- 3. Default to "plain" or to g:tex_flavor, can be set in user's vimrc. function M.tex(path, bufnr) - local format = getlines(bufnr, 1):find('^%%&%s*(%a+)') - if format then + local matched, _, format = getlines(bufnr, 1):find('^%%&%s*(%a+)') + if matched then format = format:lower():gsub('pdf', '', 1) - if format == 'tex' then - return 'tex' - elseif format == 'plaintex' then - return 'plaintex' - end elseif path:lower():find('tex/context/.*/.*%.tex') then return 'context' else + -- Default value, may be changed later: + format = vim.g.tex_flavor or 'plaintex' + local lpat = [[documentclass\>\|usepackage\>\|begin{\|newcommand\>\|renewcommand\>]] local 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\>]] @@ -1262,26 +1273,25 @@ function M.tex(path, bufnr) -- Find first non-comment line if not l:find('^%s*%%%S') then -- Check the next thousand lines for a LaTeX or ConTeXt keyword. - for _, line in ipairs(getlines(bufnr, i + 1, i + 1000)) do - local lpat_match, cpat_match = - matchregex(line, [[\c^\s*\\\%(]] .. lpat .. [[\)\|^\s*\\\(]] .. cpat .. [[\)]]) - if lpat_match then + for _, line in ipairs(getlines(bufnr, i, i + 1000)) do + if matchregex(line, [[\c^\s*\\\%(]] .. lpat .. [[\)]]) then return 'tex' - elseif cpat_match then + elseif matchregex(line, [[\c^\s*\\\%(]] .. cpat .. [[\)]]) then return 'context' end end end end - -- TODO: add AMSTeX, RevTex, others? - if not vim.g.tex_flavor or vim.g.tex_flavor == 'plain' then - return 'plaintex' - elseif vim.g.tex_flavor == 'context' then - return 'context' - else - -- Probably LaTeX - return 'tex' - end + end -- if matched + + -- Translation from formats to file types. TODO: add AMSTeX, RevTex, others? + if format == 'plain' then + return 'plaintex' + elseif format == 'plaintex' or format == 'context' then + return format + else + -- Probably LaTeX + return 'tex' end end @@ -1447,8 +1457,8 @@ local function match_from_hashbang(contents, path) name = 'wish' end - if matchregex(name, [[^\(bash\d*\|\|ksh\d*\|sh\)\>]]) then - -- Bourne-like shell scripts: bash bash2 ksh ksh93 sh + if matchregex(name, [[^\(bash\d*\|dash\|ksh\d*\|sh\)\>]]) then + -- Bourne-like shell scripts: bash bash2 dash ksh ksh93 sh return require('vim.filetype.detect').sh(path, contents, first_line) elseif matchregex(name, [[^csh\>]]) then return require('vim.filetype.detect').shell(path, contents, vim.g.filetype_csh or 'csh') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 7bd635d8b6..a0d2c4c339 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -1,9 +1,11 @@ local M = {} +local iswin = vim.loop.os_uname().sysname == 'Windows_NT' + --- Iterate over all the parents of the given file or directory. --- --- Example: ---- <pre> +--- <pre>lua --- local root_dir --- for dir in vim.fs.parents(vim.api.nvim_buf_get_name(0)) do --- if vim.fn.isdirectory(dir .. "/.git") == 1 then @@ -40,7 +42,19 @@ function M.dirname(file) if file == nil then return nil end - return vim.fn.fnamemodify(file, ':h') + vim.validate({ file = { file, 's' } }) + if iswin and file:match('^%w:[\\/]?$') then + return (file:gsub('\\', '/')) + elseif not file:match('[\\/]') then + return '.' + elseif file == '/' or file:match('^/[^/]+$') then + return '/' + end + local dir = file:match('[/\\]$') and file:sub(1, #file - 1) or file:match('^([/\\]?.+)[/\\]') + if iswin and dir:match('^%w:$') then + return dir .. '/' + end + return (dir:gsub('\\', '/')) end --- Return the basename of the given file or directory @@ -48,21 +62,75 @@ end ---@param file (string) File or directory ---@return (string) Basename of {file} function M.basename(file) - return vim.fn.fnamemodify(file, ':t') + if file == nil then + return nil + end + vim.validate({ file = { file, 's' } }) + if iswin and file:match('^%w:[\\/]?$') then + return '' + end + return file:match('[/\\]$') and '' or (file:match('[^\\/]*$'):gsub('\\', '/')) +end + +---@private +local function join_paths(...) + return (table.concat({ ... }, '/'):gsub('//+', '/')) end --- Return an iterator over the files and directories located in {path} --- ---@param path (string) An absolute or relative path to the directory to iterate --- over. The path is first normalized |vim.fs.normalize()|. +--- @param opts table|nil Optional keyword arguments: +--- - depth: integer|nil How deep the traverse (default 1) +--- - skip: (fun(dir_name: string): boolean)|nil Predicate +--- to control traversal. Return false to stop searching the current directory. +--- Only useful when depth > 1 +--- ---@return Iterator over files and directories in {path}. Each iteration yields --- two values: name and type. Each "name" is the basename of the file or --- directory relative to {path}. Type is one of "file" or "directory". -function M.dir(path) - return function(fs) - return vim.loop.fs_scandir_next(fs) - end, - vim.loop.fs_scandir(M.normalize(path)) +function M.dir(path, opts) + opts = opts or {} + + vim.validate({ + path = { path, { 'string' } }, + depth = { opts.depth, { 'number' }, true }, + skip = { opts.skip, { 'function' }, true }, + }) + + if not opts.depth or opts.depth == 1 then + return function(fs) + return vim.loop.fs_scandir_next(fs) + end, + vim.loop.fs_scandir(M.normalize(path)) + end + + --- @async + return coroutine.wrap(function() + local dirs = { { path, 1 } } + while #dirs > 0 do + local dir0, level = unpack(table.remove(dirs, 1)) + local dir = level == 1 and dir0 or join_paths(path, dir0) + local fs = vim.loop.fs_scandir(M.normalize(dir)) + while fs do + local name, t = vim.loop.fs_scandir_next(fs) + if not name then + break + end + local f = level == 1 and name or join_paths(dir0, name) + coroutine.yield(f, t) + if + opts.depth + and level < opts.depth + and t == 'directory' + and (not opts.skip or opts.skip(f) ~= false) + then + dirs[#dirs + 1] = { f, level + 1 } + end + end + end + end) end --- Find files or directories in the given path. @@ -73,17 +141,18 @@ end --- searches are recursive and may search through many directories! If {stop} --- is non-nil, then the search stops when the directory given in {stop} is --- reached. The search terminates when {limit} (default 1) matches are found. ---- The search can be narrowed to find only files or or only directories by +--- The search can be narrowed to find only files or only directories by --- specifying {type} to be "file" or "directory", respectively. --- ---@param names (string|table|fun(name: string): boolean) Names of the files --- and directories to find. --- Must be base names, paths and globs are not supported. ---- If a function it is called per file and dir within the ---- traversed directories to test if they match. +--- The function is called per file and directory within the +--- traversed directories to test if they match {names}. +--- ---@param opts (table) Optional keyword arguments: --- - path (string): Path to begin searching from. If ---- omitted, the current working directory is used. +--- omitted, the |current-directory| is used. --- - upward (boolean, default false): If true, search --- upward through parent directories. Otherwise, --- search through child directories @@ -92,12 +161,12 @@ end --- reached. The directory itself is not searched. --- - type (string): Find only files ("file") or --- directories ("directory"). If omitted, both ---- files and directories that match {name} are +--- files and directories that match {names} are --- included. --- - limit (number, default 1): Stop the search after --- finding this many matches. Use `math.huge` to --- place no limit on the number of matches. ----@return (table) The paths of all matching files or directories +---@return (table) Normalized paths |vim.fs.normalize()| of all matching files or directories function M.find(names, opts) opts = opts or {} vim.validate({ @@ -133,7 +202,7 @@ function M.find(names, opts) local t = {} for name, type in M.dir(p) do if names(name) and (not opts.type or opts.type == type) then - table.insert(t, p .. '/' .. name) + table.insert(t, join_paths(p, name)) end end return t @@ -142,7 +211,7 @@ function M.find(names, opts) test = function(p) local t = {} for _, name in ipairs(names) do - local f = p .. '/' .. name + local f = join_paths(p, name) local stat = vim.loop.fs_stat(f) if stat and (not opts.type or opts.type == stat.type) then t[#t + 1] = f @@ -179,7 +248,7 @@ function M.find(names, opts) end for other, type_ in M.dir(dir) do - local f = dir .. '/' .. other + local f = join_paths(dir, other) if type(names) == 'function' then if names(other) and (not opts.type or opts.type == type_) then if add(f) then @@ -211,23 +280,29 @@ end --- backslash (\\) characters are converted to forward slashes (/). Environment --- variables are also expanded. --- ---- Example: ---- <pre> ---- vim.fs.normalize('C:\\Users\\jdoe') ---- => 'C:/Users/jdoe' +--- Examples: +--- <pre>lua +--- vim.fs.normalize('C:\\\\Users\\\\jdoe') +--- --> 'C:/Users/jdoe' --- ---- vim.fs.normalize('~/src/neovim') ---- => '/home/jdoe/src/neovim' +--- vim.fs.normalize('~/src/neovim') +--- --> '/home/jdoe/src/neovim' --- ---- vim.fs.normalize('$XDG_CONFIG_HOME/nvim/init.vim') ---- => '/Users/jdoe/.config/nvim/init.vim' +--- vim.fs.normalize('$XDG_CONFIG_HOME/nvim/init.vim') +--- --> '/Users/jdoe/.config/nvim/init.vim' --- </pre> --- ---@param path (string) Path to normalize ---@return (string) Normalized path function M.normalize(path) vim.validate({ path = { path, 's' } }) - return (path:gsub('^~/', vim.env.HOME .. '/'):gsub('%$([%w_]+)', vim.env):gsub('\\', '/')) + return ( + path + :gsub('^~$', vim.loop.os_homedir()) + :gsub('^~/', vim.loop.os_homedir() .. '/') + :gsub('%$([%w_]+)', vim.loop.os_getenv) + :gsub('\\', '/') + ) end return M diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index b875da0abc..044880e076 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -23,7 +23,20 @@ end local path2name = function(path) if path:match('%.lua$') then -- Lua: transform "../lua/vim/lsp/health.lua" into "vim.lsp" - return path:gsub('.-lua[%\\%/]', '', 1):gsub('[%\\%/]', '.'):gsub('%.health.-$', '') + + -- Get full path, make sure all slashes are '/' + path = vim.fs.normalize(path) + + -- Remove everything up to the last /lua/ folder + path = path:gsub('^.*/lua/', '') + + -- Remove the filename (health.lua) + path = vim.fn.fnamemodify(path, ':h') + + -- Change slashes to dots + path = path:gsub('/', '.') + + return path else -- Vim: transform "../autoload/health/provider.vim" into "provider" return vim.fn.fnamemodify(path, ':t:r') diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index 0fde515bd9..20ad48dd27 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -5,6 +5,7 @@ local M = {} M.priorities = { syntax = 50, treesitter = 100, + semantic_tokens = 125, diagnostics = 150, user = 200, } diff --git a/runtime/lua/vim/keymap.lua b/runtime/lua/vim/keymap.lua index af41794c53..ef1c66ea20 100644 --- a/runtime/lua/vim/keymap.lua +++ b/runtime/lua/vim/keymap.lua @@ -2,7 +2,7 @@ local keymap = {} --- Add a new |mapping|. --- Examples: ---- <pre> +--- <pre>lua --- -- Can add mapping to Lua functions --- vim.keymap.set('n', 'lhs', function() print("real lua function") end) --- @@ -21,13 +21,13 @@ local keymap = {} --- </pre> --- --- Note that in a mapping like: ---- <pre> +--- <pre>lua --- 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> +--- <pre>lua --- vim.keymap.set('n', 'asdf', function() return require('jkl').my_fun() end) --- </pre> --- @@ -93,7 +93,7 @@ end --- Remove an existing mapping. --- Examples: ---- <pre> +--- <pre>lua --- vim.keymap.del('n', 'lhs') --- --- vim.keymap.del({'n', 'i', 'v'}, '<leader>w', { buffer = 5 }) diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 199964e24e..c5392ac154 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -4,6 +4,7 @@ local lsp_rpc = require('vim.lsp.rpc') local protocol = require('vim.lsp.protocol') local util = require('vim.lsp.util') local sync = require('vim.lsp.sync') +local semantic_tokens = require('vim.lsp.semantic_tokens') local api = vim.api local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option, nvim_exec_autocmds = @@ -25,6 +26,7 @@ local lsp = { buf = require('vim.lsp.buf'), diagnostic = require('vim.lsp.diagnostic'), codelens = require('vim.lsp.codelens'), + semantic_tokens = semantic_tokens, util = util, -- Allow raw RPC access. @@ -56,6 +58,8 @@ lsp._request_name_to_capability = { ['textDocument/formatting'] = { 'documentFormattingProvider' }, ['textDocument/completion'] = { 'completionProvider' }, ['textDocument/documentHighlight'] = { 'documentHighlightProvider' }, + ['textDocument/semanticTokens/full'] = { 'semanticTokensProvider' }, + ['textDocument/semanticTokens/full/delta'] = { 'semanticTokensProvider' }, } -- TODO improve handling of scratch buffers with LSP attached. @@ -63,7 +67,7 @@ lsp._request_name_to_capability = { ---@private --- Concatenates and writes a list of strings to the Vim error buffer. --- ----@param {...} (List of strings) List to write to the buffer +---@param {...} table[] List to write to the buffer local function err_message(...) nvim_err_writeln(table.concat(vim.tbl_flatten({ ... }))) nvim_command('redraw') @@ -72,7 +76,7 @@ end ---@private --- Returns the buffer number for the given {bufnr}. --- ----@param bufnr (number) Buffer number to resolve. Defaults to the current +---@param bufnr (number|nil) Buffer number to resolve. Defaults to the current ---buffer if not given. ---@returns bufnr (number) Number of requested buffer local function resolve_bufnr(bufnr) @@ -240,9 +244,9 @@ end ---@private --- Augments a validator function with support for optional (nil) values. --- ----@param fn (function(v)) The original validator function; should return a +---@param fn (fun(v)) The original validator function; should return a ---bool. ----@returns (function(v)) The augmented function. Also returns true if {v} is +---@returns (fun(v)) The augmented function. Also returns true if {v} is ---`nil`. local function optional_validator(fn) return function(v) @@ -813,8 +817,7 @@ end --- Attaches the current buffer to the client. --- --- Example: ---- ---- <pre> +--- <pre>lua --- vim.lsp.start({ --- name = 'my-server-name', --- cmd = {'name-of-language-server-executable'}, @@ -824,23 +827,16 @@ end --- --- See |vim.lsp.start_client()| for all available options. The most important are: --- ---- `name` is an arbitrary name for the LSP client. It should be unique per ---- language server. ---- ---- `cmd` the command as list - used to start the language server. ---- The command must be present in the `$PATH` environment variable or an ---- absolute path to the executable. Shell constructs like `~` are *NOT* expanded. ---- ---- `root_dir` path to the project root. ---- By default this is used to decide if an existing client should be re-used. ---- The example above uses |vim.fs.find()| and |vim.fs.dirname()| to detect the ---- root by traversing the file system upwards starting ---- from the current directory until either a `pyproject.toml` or `setup.py` ---- file is found. ---- ---- `workspace_folders` a list of { uri:string, name: string } tables. ---- The project root folders used by the language server. ---- If `nil` the property is derived from the `root_dir` for convenience. +--- - `name` arbitrary name for the LSP client. Should be unique per language server. +--- - `cmd` command (in list form) used to start the language server. Must be absolute, or found on +--- `$PATH`. Shell constructs like `~` are not expanded. +--- - `root_dir` path to the project root. By default this is used to decide if an existing client +--- should be re-used. The example above uses |vim.fs.find()| and |vim.fs.dirname()| to detect the +--- root by traversing the file system upwards starting from the current directory until either +--- a `pyproject.toml` or `setup.py` file is found. +--- - `workspace_folders` list of `{ uri:string, name: string }` tables specifying the project root +--- folders used by the language server. If `nil` the property is derived from `root_dir` for +--- convenience. --- --- Language servers use this information to discover metadata like the --- dependencies of your project and they tend to index the contents within the @@ -901,115 +897,114 @@ end -- --- Starts and initializes a client with the given configuration. --- ---- Parameter `cmd` is required. ---- ---- The following parameters describe fields in the {config} table. ---- ---- ----@param cmd: (table|string|fun(dispatchers: table):table) command string or ---- list treated like |jobstart()|. The command must launch the language server ---- process. `cmd` can also be a function that creates an RPC client. ---- The function receives a dispatchers table and must return a table with the ---- functions `request`, `notify`, `is_closing` and `terminate` ---- See |vim.lsp.rpc.request()| and |vim.lsp.rpc.notify()| ---- For TCP there is a built-in rpc client factory: |vim.lsp.rpc.connect()| ---- ----@param cmd_cwd: (string, default=|getcwd()|) Directory to launch ---- the `cmd` process. Not related to `root_dir`. ---- ----@param cmd_env: (table) Environment flags to pass to the LSP on ---- spawn. Can be specified using keys like a map or as a list with `k=v` ---- pairs or both. Non-string values are coerced to string. ---- Example: ---- <pre> ---- { "PRODUCTION=true"; "TEST=123"; PORT = 8080; HOST = "0.0.0.0"; } ---- </pre> ---- ----@param detached: (boolean, default true) Daemonize the server process so that it runs in a ---- separate process group from Nvim. Nvim will shutdown the process on exit, but if Nvim fails to ---- exit cleanly this could leave behind orphaned server processes. ---- ----@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. +--- Field `cmd` in {config} is required. +--- +---@param config (table) Configuration for the server: +--- - cmd: (table|string|fun(dispatchers: table):table) command string or +--- list treated like |jobstart()|. The command must launch the language server +--- process. `cmd` can also be a function that creates an RPC client. +--- The function receives a dispatchers table and must return a table with the +--- functions `request`, `notify`, `is_closing` and `terminate` +--- See |vim.lsp.rpc.request()| and |vim.lsp.rpc.notify()| +--- For TCP there is a built-in rpc client factory: |vim.lsp.rpc.connect()| +--- +--- - cmd_cwd: (string, default=|getcwd()|) Directory to launch +--- the `cmd` 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 with `k=v` +--- pairs or both. Non-string values are coerced to string. +--- Example: +--- <pre> +--- { "PRODUCTION=true"; "TEST=123"; PORT = 8080; HOST = "0.0.0.0"; } +--- </pre> +--- +--- - detached: (boolean, default true) Daemonize the server process so that it runs in a +--- separate process group from Nvim. Nvim will shutdown the process on exit, but if Nvim fails to +--- exit cleanly this could leave behind orphaned server processes. +--- +--- - 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()|, passed to the language +--- server on initialization. Hint: use make_client_capabilities() and modify +--- its result. +--- - Note: To send an empty dictionary use +--- `{[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 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 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`. See `initialize` in the LSP spec. +--- +--- - name: (string, default=client-id) Name in log messages. +--- +--- - get_language_id: function(bufnr, filetype) -> language ID as string. +--- Defaults to the filetype. +--- +--- - offset_encoding: (default="utf-16") One of "utf-8", "utf-16", +--- or "utf-32" which is the encoding that the LSP server expects. Client does +--- not verify this is correct. +--- +--- - 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.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, where `params` contains the +--- parameters being sent to the server and `config` is the config that was +--- passed to |vim.lsp.start_client()|. You can use this to modify parameters before +--- they are sent. +--- +--- - on_init: Callback (client, initialize_result) invoked after LSP +--- "initialize", where `result` is a table of `capabilities` and anything else +--- the server may send. For example, clangd sends +--- `initialize_result.offsetEncoding` if `capabilities.offsetEncoding` was +--- sent to it. You can only modify the `client.offset_encoding` here before +--- any notifications are sent. Most language servers expect to be sent client specified settings after +--- initialization. Neovim does not make this assumption. A +--- `workspace/didChangeConfiguration` notification should be sent +--- to the server during on_init. +--- +--- - on_exit Callback (code, signal, client_id) invoked on client +--- exit. +--- - code: exit code of the process +--- - signal: number describing the signal used to terminate (if any) +--- - client_id: client handle --- ----@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 ---- its result. ---- - Note: To send an empty dictionary use ---- `{[vim.type_idx]=vim.types.dictionary}`, else it will be encoded as an ---- array. +--- - on_attach: Callback (client, bufnr) invoked when client +--- attaches to a buffer. --- ----@param handlers Map of language server method names to |lsp-handler| +--- - trace: ("off" | "messages" | "verbose" | nil) passed directly to the language +--- server in the initialize request. Invalid/empty values will default to "off" --- ----@param settings Map with language server specific settings. These are ---- returned to the language server if requested via `workspace/configuration`. ---- Keys are case-sensitive. +--- - flags: A table with flags for the client. The current (experimental) flags are: +--- - allow_incremental_sync (bool, default true): Allow using incremental sync for buffer edits +--- - debounce_text_changes (number, default 150): Debounce didChange +--- notifications to the server by the given number in milliseconds. No debounce +--- occurs if nil +--- - exit_timeout (number|boolean, default false): 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. --- ----@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 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 get_language_id function(bufnr, filetype) -> language ID as string. ---- Defaults to the filetype. ---- ----@param offset_encoding (default="utf-16") One of "utf-8", "utf-16", ---- or "utf-32" which is the encoding that the LSP server expects. Client does ---- not verify this is correct. ---- ----@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.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 ---- parameters being sent to the server and `config` is the config that was ---- passed to |vim.lsp.start_client()|. You can use this to modify parameters before ---- they are sent. ---- ----@param on_init Callback (client, initialize_result) invoked after LSP ---- "initialize", where `result` is a table of `capabilities` and anything else ---- the server may send. For example, clangd sends ---- `initialize_result.offsetEncoding` if `capabilities.offsetEncoding` was ---- sent to it. You can only modify the `client.offset_encoding` here before ---- any notifications are sent. Most language servers expect to be sent client specified settings after ---- initialization. Neovim does not make this assumption. A ---- `workspace/didChangeConfiguration` notification should be sent ---- to the server during on_init. ---- ----@param on_exit Callback (code, signal, client_id) invoked on client ---- exit. ---- - code: exit code of the process ---- - signal: number describing the signal used to terminate (if any) ---- - client_id: client handle ---- ----@param on_attach Callback (client, bufnr) invoked when client ---- attaches to a buffer. ---- ----@param trace: "off" | "messages" | "verbose" | nil passed directly to the language ---- server in the initialize request. Invalid/empty values will default to "off" ----@param flags: A table with flags for the client. The current (experimental) flags are: ---- - allow_incremental_sync (bool, default true): Allow using incremental sync for buffer edits ---- - debounce_text_changes (number, default 150): Debounce didChange ---- notifications to the server by the given number in milliseconds. No debounce ---- occurs if nil ---- - exit_timeout (number|boolean, default false): 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. ---- ----@param root_dir string Directory where the LSP ---- server will base its workspaceFolders, rootUri, and rootPath ---- on initialization. +--- - 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 @@ -1293,8 +1288,6 @@ function lsp.start_client(config) client.initialized = true uninitialized_clients[client_id] = nil client.workspace_folders = workspace_folders - -- TODO(mjlbach): Backwards compatibility, to be removed in 0.7 - client.workspaceFolders = client.workspace_folders -- These are the cleaned up capabilities we use for dynamically deciding -- when to send certain events to clients. @@ -1372,7 +1365,7 @@ function lsp.start_client(config) --- ---@param method (string) LSP method name. ---@param params (table) LSP request params. - ---@param handler (function, optional) Response |lsp-handler| for this method. + ---@param handler (function|nil) Response |lsp-handler| for this method. ---@param bufnr (number) Buffer handle (0 for current). ---@returns ({status}, [request_id]): {status} is a bool indicating ---whether the request was successful. If it is `false`, then it will @@ -1383,8 +1376,10 @@ function lsp.start_client(config) ---@see |vim.lsp.buf_request()| function client.request(method, params, handler, bufnr) if not handler then - handler = resolve_handler(method) - or error(string.format('not found: %q request handler for client %q.', method, client.name)) + handler = assert( + resolve_handler(method), + 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, bufnr) @@ -1402,7 +1397,7 @@ function lsp.start_client(config) nvim_exec_autocmds('User', { pattern = 'LspRequest', modeline = false }) end) - if success then + if success and request_id then client.requests[request_id] = { type = 'pending', bufnr = bufnr, method = method } nvim_exec_autocmds('User', { pattern = 'LspRequest', modeline = false }) end @@ -1417,8 +1412,8 @@ function lsp.start_client(config) --- ---@param method (string) LSP method name. ---@param params (table) LSP request params. - ---@param timeout_ms (number, optional, default=1000) Maximum time in - ---milliseconds to wait for a result. + ---@param timeout_ms (number|nil) Maximum time in milliseconds to wait for + --- a result. Defaults to 1000 ---@param bufnr (number) Buffer handle (0 for current). ---@returns { err=err, result=result }, a dictionary, where `err` and `result` come from the |lsp-handler|. ---On timeout, cancel or error, returns `(nil, err)` where `err` is a @@ -1441,7 +1436,9 @@ function lsp.start_client(config) end, 10) if not wait_result then - client.cancel_request(request_id) + if request_id then + client.cancel_request(request_id) + end return nil, wait_result_reason[reason] end return request_result @@ -1536,6 +1533,16 @@ function lsp.start_client(config) -- TODO(ashkan) handle errors. pcall(config.on_attach, client, bufnr) end + + -- schedule the initialization of semantic tokens to give the above + -- on_attach and LspAttach callbacks the ability to schedule wrap the + -- opt-out (deleting the semanticTokensProvider from capabilities) + vim.schedule(function() + if vim.tbl_get(client.server_capabilities, 'semanticTokensProvider', 'full') then + semantic_tokens.start(bufnr, client.id) + end + end) + client.attached_buffers[bufnr] = true end @@ -1549,15 +1556,21 @@ end --- Notify all attached clients that a buffer has changed. local text_document_did_change_handler do - text_document_did_change_handler = - function(_, bufnr, changedtick, firstline, lastline, new_lastline) - -- 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 true - end - util.buf_versions[bufnr] = changedtick - changetracking.send_changes(bufnr, firstline, lastline, new_lastline) + text_document_did_change_handler = function( + _, + bufnr, + changedtick, + firstline, + lastline, + new_lastline + ) + -- 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 true end + util.buf_versions[bufnr] = changedtick + changetracking.send_changes(bufnr, firstline, lastline, new_lastline) + end end ---@private @@ -1621,9 +1634,37 @@ function lsp.buf_attach_client(bufnr, client_id) all_buffer_active_clients[bufnr] = buffer_client_ids local uri = vim.uri_from_bufnr(bufnr) - local augroup = ('lsp_c_%d_b_%d_did_save'):format(client_id, bufnr) + local augroup = ('lsp_c_%d_b_%d_save'):format(client_id, bufnr) + local group = api.nvim_create_augroup(augroup, { clear = true }) + api.nvim_create_autocmd('BufWritePre', { + group = group, + buffer = bufnr, + desc = 'vim.lsp: textDocument/willSave', + callback = function(ctx) + for_each_buffer_client(ctx.buf, function(client) + local params = { + textDocument = { + uri = uri, + }, + reason = protocol.TextDocumentSaveReason.Manual, + } + if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSave') then + client.notify('textDocument/willSave', params) + end + if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSaveWaitUntil') then + local result, err = + client.request_sync('textDocument/willSaveWaitUntil', params, 1000, ctx.buf) + if result and result.result then + util.apply_text_edits(result.result, ctx.buf, client.offset_encoding) + elseif err then + log.error(vim.inspect(err)) + end + end + end) + end, + }) api.nvim_create_autocmd('BufWritePost', { - group = api.nvim_create_augroup(augroup, { clear = true }), + group = group, buffer = bufnr, desc = 'vim.lsp: textDocument/didSave handler', callback = function(ctx) @@ -1761,16 +1802,15 @@ end --- --- You can also use the `stop()` function on a |vim.lsp.client| object. --- To stop all clients: ---- ---- <pre> +--- <pre>lua --- vim.lsp.stop_client(vim.lsp.get_active_clients()) --- </pre> --- --- By default asks the server to shutdown, unless stop was requested --- already for this client, then force-shutdown is attempted. --- ----@param client_id client id or |vim.lsp.client| object, or list thereof ----@param force boolean (optional) shutdown forcefully +---@param client_id number|table id or |vim.lsp.client| object, or list thereof +---@param force boolean|nil shutdown forcefully function lsp.stop_client(client_id, force) local ids = type(client_id) == 'table' and client_id or { client_id } for _, id in ipairs(ids) do @@ -1784,10 +1824,16 @@ function lsp.stop_client(client_id, force) end end +---@class vim.lsp.get_active_clients.filter +---@field id number|nil Match clients by id +---@field bufnr number|nil match clients attached to the given buffer +---@field name string|nil match clients by name + --- Get active clients. --- ----@param filter (table|nil) A table with key-value pairs used to filter the ---- returned clients. The available keys are: +---@param filter vim.lsp.get_active_clients.filter|nil (table|nil) A table with +--- key-value pairs used to filter the returned clients. +--- The available keys are: --- - id (number): Only return clients with the given id --- - bufnr (number): Only return clients attached to this buffer --- - name (string): Only return clients with the given name @@ -1804,7 +1850,8 @@ function lsp.get_active_clients(filter) for client_id in pairs(t) do local client = active_clients[client_id] if - (filter.id == nil or client.id == filter.id) + client + and (filter.id == nil or client.id == filter.id) and (filter.name == nil or client.name == filter.name) then clients[#clients + 1] = client @@ -1935,7 +1982,7 @@ end --- ---@param bufnr (number) Buffer handle, or 0 for current. ---@param method (string) LSP method name ----@param params (optional, table) Parameters to send to the server +---@param params (table|nil) Parameters to send to the server ---@param callback (function) The callback to call when all requests are finished. -- Unlike `buf_request`, this will collect all the responses from each server instead of handling them. -- A map of client_id:request_result will be provided to the callback @@ -1977,9 +2024,9 @@ end --- ---@param bufnr (number) Buffer handle, or 0 for current. ---@param method (string) LSP method name ----@param params (optional, table) Parameters to send to the server ----@param timeout_ms (optional, number, default=1000) Maximum time in ---- milliseconds to wait for a result. +---@param params (table|nil) Parameters to send to the server +---@param timeout_ms (number|nil) Maximum time in milliseconds to wait for a +--- result. Defaults to 1000 --- ---@returns Map of client_id:request_result. On timeout, cancel or error, --- returns `(nil, err)` where `err` is a string describing the failure @@ -2004,9 +2051,9 @@ function lsp.buf_request_sync(bufnr, method, params, timeout_ms) end --- Send a notification to a server ----@param bufnr [number] (optional): The number of the buffer ----@param method [string]: Name of the request method ----@param params [string]: Arguments to send to the server +---@param bufnr (number|nil) The number of the buffer +---@param method (string) Name of the request method +---@param params (any) Arguments to send to the server --- ---@returns true if any client returns true; false otherwise function lsp.buf_notify(bufnr, method, params) @@ -2047,8 +2094,8 @@ end ---@see |complete-items| ---@see |CompleteDone| --- ----@param findstart 0 or 1, decides behavior ----@param base If findstart=0, text to match against +---@param findstart number 0 or 1, decides behavior +---@param base number findstart=0, text to match against --- ---@returns (number) Decided by {findstart}: --- - findstart=0: column where the completion starts, or -2 or -3 @@ -2177,8 +2224,8 @@ end --- Otherwise, uses "workspace/symbol". If no results are returned from --- any LSP servers, falls back to using built-in tags. --- ----@param pattern Pattern used to find a workspace symbol ----@param flags See |tag-function| +---@param pattern string Pattern used to find a workspace symbol +---@param flags string See |tag-function| --- ---@returns A list of matching tags function lsp.tagfunc(...) @@ -2187,7 +2234,7 @@ end ---Checks whether a client is stopped. --- ----@param client_id (Number) +---@param client_id (number) ---@returns true if client is stopped, false otherwise. function lsp.client_is_stopped(client_id) return active_clients[client_id] == nil @@ -2196,7 +2243,7 @@ end --- Gets a map of client_id:client pairs for the given buffer, where each value --- is a |vim.lsp.client| object. --- ----@param bufnr (optional, number): Buffer handle, or 0 for current +---@param bufnr (number|nil): Buffer handle, or 0 for current ---@returns (table) Table of (client_id, client) pairs ---@deprecated Use |vim.lsp.get_active_clients()| instead. function lsp.buf_get_clients(bufnr) @@ -2225,7 +2272,7 @@ lsp.log_levels = log.levels --- ---@see |vim.lsp.log_levels| --- ----@param level [number|string] the case insensitive level name or number +---@param level (number|string) the case insensitive level name or number function lsp.set_log_level(level) if type(level) == 'string' or type(level) == 'number' then log.set_level(level) @@ -2246,7 +2293,7 @@ end ---@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> +--- <pre>lua --- vim.lsp.for_each_buffer_client(0, function(client, client_id, bufnr) --- print(vim.inspect(client)) --- end) diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index c593e72d62..6ac885c78f 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -162,11 +162,11 @@ end --- Predicate used to filter clients. Receives a client as argument and must return a --- boolean. Clients matching the predicate are included. Example: --- ---- <pre> ---- -- Never request typescript-language-server for formatting ---- vim.lsp.buf.format { ---- filter = function(client) return client.name ~= "tsserver" end ---- } +--- <pre>lua +--- -- Never request typescript-language-server for formatting +--- vim.lsp.buf.format { +--- filter = function(client) return client.name ~= "tsserver" end +--- } --- </pre> --- --- - async boolean|nil @@ -197,20 +197,21 @@ function M.format(options) clients = vim.tbl_filter(options.filter, clients) end + local mode = api.nvim_get_mode().mode + local range = options.range + if not range and mode == 'v' or mode == 'V' then + range = range_from_selection() + end + local method = range and 'textDocument/rangeFormatting' or 'textDocument/formatting' + clients = vim.tbl_filter(function(client) - return client.supports_method('textDocument/formatting') + return client.supports_method(method) end, clients) if #clients == 0 then vim.notify('[LSP] Format request failed, no matching language servers.') end - local mode = api.nvim_get_mode().mode - local range = options.range - if not range and mode == 'v' or mode == 'V' then - range = range_from_selection() - end - ---@private local function set_range(client, params) if range then @@ -221,7 +222,6 @@ function M.format(options) return params end - local method = range and 'textDocument/rangeFormatting' or 'textDocument/formatting' if options.async then local do_format do_format = function(idx, client) @@ -383,7 +383,7 @@ end --- Lists all the references to the symbol under the cursor in the quickfix window. --- ----@param context (table) Context for the request +---@param context (table|nil) Context for the request ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references ---@param options table|nil additional options --- - on_list: (function) handler for list results. See |lsp-on-list-handler| @@ -464,7 +464,7 @@ end --- function M.list_workspace_folders() local workspace_folders = {} - for _, client in pairs(vim.lsp.buf_get_clients()) do + for _, client in pairs(vim.lsp.get_active_clients({ bufnr = 0 })) do for _, folder in pairs(client.workspace_folders or {}) do table.insert(workspace_folders, folder.name) end @@ -487,9 +487,9 @@ function M.add_workspace_folder(workspace_folder) 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 _, client in pairs(vim.lsp.get_active_clients({ bufnr = 0 })) do local found = false for _, folder in pairs(client.workspace_folders or {}) do if folder.name == workspace_folder then @@ -522,7 +522,7 @@ function M.remove_workspace_folder(workspace_folder) { {} }, { { uri = vim.uri_from_fname(workspace_folder), name = workspace_folder } } ) - for _, client in pairs(vim.lsp.buf_get_clients()) do + for _, client in pairs(vim.lsp.get_active_clients({ bufnr = 0 })) do for idx, folder in pairs(client.workspace_folders) do if folder.name == workspace_folder then vim.lsp.buf_notify(0, 'workspace/didChangeWorkspaceFolders', params) @@ -555,11 +555,10 @@ 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`, e.g.: ---- ---- <pre> ---- 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>vim +--- 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 @@ -734,6 +733,7 @@ end --- List of LSP `CodeActionKind`s used to filter the code actions. --- Most language servers support values like `refactor` --- or `quickfix`. +--- - triggerKind (number|nil): The reason why code actions were requested. --- - filter: (function|nil) --- Predicate taking an `CodeAction` and returning a boolean. --- - apply: (boolean|nil) @@ -747,6 +747,7 @@ end --- using mark-like indexing. See |api-indexing| --- ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction +---@see vim.lsp.protocol.constants.CodeActionTriggerKind function M.code_action(options) validate({ options = { options, 't', true } }) options = options or {} @@ -756,6 +757,9 @@ function M.code_action(options) options = { options = options } end local context = options.context or {} + if not context.triggerKind then + context.triggerKind = vim.lsp.protocol.CodeActionTriggerKind.Invoked + end if not context.diagnostics then local bufnr = api.nvim_get_current_buf() context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics(bufnr) diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua index 4fa02c8db2..17489ed84d 100644 --- a/runtime/lua/vim/lsp/codelens.lua +++ b/runtime/lua/vim/lsp/codelens.lua @@ -108,13 +108,36 @@ function M.run() end end +---@private +local function resolve_bufnr(bufnr) + return bufnr == 0 and api.nvim_get_current_buf() or bufnr +end + +--- Clear the lenses +--- +---@param client_id number|nil filter by client_id. All clients if nil +---@param bufnr number|nil filter by buffer. All buffers if nil +function M.clear(client_id, bufnr) + local buffers = bufnr and { resolve_bufnr(bufnr) } or vim.tbl_keys(lens_cache_by_buf) + for _, iter_bufnr in pairs(buffers) do + local client_ids = client_id and { client_id } or vim.tbl_keys(namespaces) + for _, iter_client_id in pairs(client_ids) do + local ns = namespaces[iter_client_id] + lens_cache_by_buf[iter_bufnr][iter_client_id] = {} + api.nvim_buf_clear_namespace(iter_bufnr, ns, 0, -1) + end + end +end + --- Display the lenses using virtual text --- ---@param lenses table of lenses to display (`CodeLens[] | null`) ---@param bufnr number ---@param client_id number function M.display(lenses, bufnr, client_id) + local ns = namespaces[client_id] if not lenses or not next(lenses) then + api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) return end local lenses_by_lnum = {} @@ -126,7 +149,6 @@ function M.display(lenses, bufnr, client_id) end table.insert(line_lenses, lens) end - local ns = namespaces[client_id] local num_lines = api.nvim_buf_line_count(bufnr) for i = 0, num_lines do local line_lenses = lenses_by_lnum[i] or {} @@ -241,7 +263,8 @@ end --- --- It is recommended to trigger this using an autocmd or via keymap. --- ---- <pre> +--- Example: +--- <pre>vim --- autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh() --- </pre> --- diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index 1f9d084e2b..5e2bf75f1b 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -150,7 +150,7 @@ end --- --- See |vim.diagnostic.config()| for configuration options. Handler-specific --- configuration can be set using |vim.lsp.with()|: ---- <pre> +--- <pre>lua --- vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with( --- vim.lsp.diagnostic.on_publish_diagnostics, { --- -- Enable underline, use default values diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 93fd621161..5096100a60 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -81,22 +81,38 @@ M['window/workDoneProgress/create'] = function(_, result, ctx) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessageRequest +---@param result lsp.ShowMessageRequestParams M['window/showMessageRequest'] = function(_, result) - local actions = result.actions - print(result.message) - local option_strings = { result.message, '\nRequest Actions:' } - for i, action in ipairs(actions) do - local title = action.title:gsub('\r\n', '\\r\\n') - title = title:gsub('\n', '\\n') - table.insert(option_strings, string.format('%d. %s', i, title)) - end - - -- window/showMessageRequest can return either MessageActionItem[] or null. - local choice = vim.fn.inputlist(option_strings) - if choice < 1 or choice > #actions then - return vim.NIL + local actions = result.actions or {} + local co, is_main = coroutine.running() + if co and not is_main then + local opts = { + prompt = result.message .. ': ', + format_item = function(action) + return (action.title:gsub('\r\n', '\\r\\n')):gsub('\n', '\\n') + end, + } + vim.ui.select(actions, opts, function(choice) + -- schedule to ensure resume doesn't happen _before_ yield with + -- default synchronous vim.ui.select + vim.schedule(function() + coroutine.resume(co, choice or vim.NIL) + end) + end) + return coroutine.yield() else - return actions[choice] + local option_strings = { result.message, '\nRequest Actions:' } + for i, action in ipairs(actions) do + local title = action.title:gsub('\r\n', '\\r\\n') + title = title:gsub('\n', '\\n') + table.insert(option_strings, string.format('%d. %s', i, title)) + end + local choice = vim.fn.inputlist(option_strings) + if choice < 1 or choice > #actions then + return vim.NIL + else + return actions[choice] + end end end @@ -115,9 +131,10 @@ end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit M['workspace/applyEdit'] = function(_, workspace_edit, ctx) - if not workspace_edit then - return - end + assert( + workspace_edit, + 'workspace/applyEdit must be called with `ApplyWorkspaceEditParams`. Server is violating the specification' + ) -- TODO(ashkan) Do something more with label? local client_id = ctx.client_id local client = vim.lsp.get_client_by_id(client_id) @@ -297,13 +314,15 @@ M['textDocument/completion'] = function(_, result, _, _) end --- |lsp-handler| for the method "textDocument/hover" ---- <pre> ---- vim.lsp.handlers["textDocument/hover"] = vim.lsp.with( ---- vim.lsp.handlers.hover, { ---- -- Use a sharp border with `FloatBorder` highlights ---- border = "single" ---- } ---- ) +--- <pre>lua +--- vim.lsp.handlers["textDocument/hover"] = vim.lsp.with( +--- vim.lsp.handlers.hover, { +--- -- Use a sharp border with `FloatBorder` highlights +--- border = "single", +--- -- add the title in hover float window +--- title = "hover" +--- } +--- ) --- </pre> ---@param config table Configuration table. --- - border: (default=nil) @@ -312,8 +331,14 @@ end function M.hover(_, result, ctx, config) config = config or {} config.focus_id = ctx.method + if api.nvim_get_current_buf() ~= ctx.bufnr then + -- Ignore result since buffer changed. This happens for slow language servers. + return + end if not (result and result.contents) then - vim.notify('No information available') + if config.silent ~= true then + vim.notify('No information available') + end return end local markdown_lines = util.convert_input_to_markdown_lines(result.contents) @@ -377,13 +402,13 @@ M['textDocument/implementation'] = location_handler --- |lsp-handler| for the method "textDocument/signatureHelp". --- The active parameter is highlighted with |hl-LspSignatureActiveParameter|. ---- <pre> ---- vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with( ---- vim.lsp.handlers.signature_help, { ---- -- Use a sharp border with `FloatBorder` highlights ---- border = "single" ---- } ---- ) +--- <pre>lua +--- vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with( +--- vim.lsp.handlers.signature_help, { +--- -- Use a sharp border with `FloatBorder` highlights +--- border = "single" +--- } +--- ) --- </pre> ---@param config table Configuration table. --- - border: (default=nil) @@ -392,6 +417,10 @@ M['textDocument/implementation'] = location_handler function M.signature_help(_, result, ctx, config) config = config or {} config.focus_id = ctx.method + if api.nvim_get_current_buf() ~= ctx.bufnr then + -- Ignore result since buffer changed. This happens for slow language servers. + return + end -- When use `autocmd CompleteDone <silent><buffer> lua vim.lsp.buf.signature_help()` to call signatureHelp handler -- If the completion item doesn't have signatures It will make noise. Change to use `print` that can use `<silent>` to ignore if not (result and result.signatures and result.signatures[1]) then @@ -519,15 +548,15 @@ M['window/showDocument'] = function(_, result, ctx, _) -- TODO(lvimuser): ask the user for confirmation local cmd if vim.fn.has('win32') == 1 then - cmd = { 'cmd.exe', '/c', 'start', '""', vim.fn.shellescape(uri) } + cmd = { 'cmd.exe', '/c', 'start', '""', uri } elseif vim.fn.has('macunix') == 1 then - cmd = { 'open', vim.fn.shellescape(uri) } + cmd = { 'open', uri } else - cmd = { 'xdg-open', vim.fn.shellescape(uri) } + cmd = { 'xdg-open', uri } end local ret = vim.fn.system(cmd) - if vim.v.shellerror ~= 0 then + if vim.v.shell_error ~= 0 then return { success = false, error = { @@ -553,7 +582,10 @@ M['window/showDocument'] = function(_, result, ctx, _) range = result.selection, } - local success = util.show_document(location, client.offset_encoding, true, result.takeFocus) + local success = util.show_document(location, client.offset_encoding, { + reuse_win = true, + focus = result.takeFocus, + }) return { success = success or false } end diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua index ba730e3d6d..987707e661 100644 --- a/runtime/lua/vim/lsp/health.lua +++ b/runtime/lua/vim/lsp/health.lua @@ -2,8 +2,8 @@ local M = {} --- Performs a healthcheck for LSP function M.check() - local report_info = vim.fn['health#report_info'] - local report_warn = vim.fn['health#report_warn'] + local report_info = vim.health.report_info + local report_warn = vim.health.report_warn local log = require('vim.lsp.log') local current_log_level = log.get_level() @@ -27,6 +27,18 @@ function M.check() local report_fn = (log_size / 1000000 > 100 and report_warn or report_info) report_fn(string.format('Log size: %d KB', log_size / 1000)) + + local clients = vim.lsp.get_active_clients() + vim.health.report_start('vim.lsp: Active Clients') + if next(clients) then + for _, client in pairs(clients) do + report_info( + string.format('%s (id=%s, root_dir=%s)', client.name, client.id, client.config.root_dir) + ) + end + else + report_info('No active clients') + end end return M diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index 6c6ba0f206..d1a78572aa 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -20,6 +20,17 @@ local format_func = function(arg) end do + ---@private + local function notify(msg, level) + if vim.in_fast_event() then + vim.schedule(function() + vim.notify(msg, level) + end) + else + vim.notify(msg, level) + end + end + local path_sep = vim.loop.os_uname().version:match('Windows') and '\\' or '/' ---@private local function path_join(...) @@ -53,7 +64,7 @@ do logfile, openerr = io.open(logfilename, 'a+') if not logfile then local err_msg = string.format('Failed to open LSP client log file: %s', openerr) - vim.notify(err_msg, vim.log.levels.ERROR) + notify(err_msg, vim.log.levels.ERROR) return false end @@ -64,7 +75,7 @@ do log_info.size / (1000 * 1000), logfilename ) - vim.notify(warn_msg) + notify(warn_msg) end -- Start message for logging @@ -130,7 +141,7 @@ end vim.tbl_add_reverse_lookup(log.levels) --- Sets the current log level. ----@param level (string or number) One of `vim.lsp.log.levels` +---@param level (string|number) One of `vim.lsp.log.levels` function log.set_level(level) if type(level) == 'string' then current_log_level = diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 4034753322..12345b6c8c 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -20,6 +20,14 @@ function transform_schema_to_table() end --]=] +---@class lsp.ShowMessageRequestParams +---@field type lsp.MessageType +---@field message string +---@field actions nil|lsp.MessageActionItem[] + +---@class lsp.MessageActionItem +---@field title string + local constants = { DiagnosticSeverity = { -- Reports an error. @@ -39,6 +47,7 @@ local constants = { Deprecated = 2, }, + ---@enum lsp.MessageType MessageType = { -- An error message. Error = 1, @@ -142,6 +151,7 @@ local constants = { }, -- Represents reasons why a text document is saved. + ---@enum lsp.TextDocumentSaveReason TextDocumentSaveReason = { -- Manually triggered, e.g. by the user pressing save, by starting debugging, -- or by an API call. @@ -294,6 +304,17 @@ local constants = { -- Base kind for an organize imports source action SourceOrganizeImports = 'source.organizeImports', }, + -- The reason why code actions were requested. + ---@enum lsp.CodeActionTriggerKind + CodeActionTriggerKind = { + -- Code actions were explicitly requested by the user or by an extension. + Invoked = 1, + -- Code actions were requested automatically. + -- + -- This typically happens when current selection in a file changes, but can + -- also be triggered when file content changes. + Automatic = 2, + }, } for k, v in pairs(constants) do @@ -619,14 +640,63 @@ export interface WorkspaceClientCapabilities { function protocol.make_client_capabilities() return { textDocument = { - synchronization = { + semanticTokens = { dynamicRegistration = false, + tokenTypes = { + 'namespace', + 'type', + 'class', + 'enum', + 'interface', + 'struct', + 'typeParameter', + 'parameter', + 'variable', + 'property', + 'enumMember', + 'event', + 'function', + 'method', + 'macro', + 'keyword', + 'modifier', + 'comment', + 'string', + 'number', + 'regexp', + 'operator', + 'decorator', + }, + tokenModifiers = { + 'declaration', + 'definition', + 'readonly', + 'static', + 'deprecated', + 'abstract', + 'async', + 'modification', + 'documentation', + 'defaultLibrary', + }, + formats = { 'relative' }, + requests = { + -- TODO(jdrouhard): Add support for this + range = false, + full = { delta = true }, + }, - -- TODO(ashkan) Send textDocument/willSave before saving (BufWritePre) - willSave = false, + overlappingTokenSupport = true, + -- TODO(jdrouhard): Add support for this + multilineTokenSupport = false, + serverCancelSupport = false, + augmentsSyntaxTokens = true, + }, + synchronization = { + dynamicRegistration = false, - -- TODO(ashkan) Implement textDocument/willSaveWaitUntil - willSaveWaitUntil = false, + willSave = true, + willSaveWaitUntil = true, -- Send textDocument/didSave after saving (BufWritePost) didSave = true, @@ -637,7 +707,7 @@ function protocol.make_client_capabilities() codeActionLiteralSupport = { codeActionKind = { valueSet = (function() - local res = vim.tbl_values(protocol.CodeActionKind) + local res = vim.tbl_values(constants.CodeActionKind) table.sort(res) return res end)(), @@ -742,6 +812,9 @@ function protocol.make_client_capabilities() end)(), }, }, + callHierarchy = { + dynamicRegistration = false, + }, }, workspace = { symbol = { @@ -765,9 +838,9 @@ function protocol.make_client_capabilities() workspaceEdit = { resourceOperations = { 'rename', 'create', 'delete' }, }, - }, - callHierarchy = { - dynamicRegistration = false, + semanticTokens = { + refreshSupport = true, + }, }, experimental = nil, window = { @@ -861,8 +934,8 @@ function protocol._resolve_capabilities_compat(server_capabilities) text_document_sync_properties = { text_document_open_close = if_nil(textDocumentSync.openClose, false), text_document_did_change = if_nil(textDocumentSync.change, TextDocumentSyncKind.None), - text_document_will_save = if_nil(textDocumentSync.willSave, false), - text_document_will_save_wait_until = if_nil(textDocumentSync.willSaveWaitUntil, false), + text_document_will_save = if_nil(textDocumentSync.willSave, true), + text_document_will_save_wait_until = if_nil(textDocumentSync.willSaveWaitUntil, true), text_document_save = if_nil(textDocumentSync.save, false), text_document_save_include_text = if_nil( type(textDocumentSync.save) == 'table' and textDocumentSync.save.includeText, diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index ff62623544..f1492601ff 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -293,7 +293,7 @@ end ---@private --- Sends a notification to the LSP server. ---@param method (string) The invoked LSP method ----@param params (table|nil): Parameters for the invoked LSP method +---@param params (any): Parameters for the invoked LSP method ---@returns (bool) `true` if notification could be sent, `false` if not function Client:notify(method, params) return self:encode_and_send({ @@ -391,44 +391,46 @@ function Client:handle_body(body) -- Schedule here so that the users functions don't trigger an error and -- we can still use the result. schedule(function() - local status, result - status, result, err = self:try_call( - client_errors.SERVER_REQUEST_HANDLER_ERROR, - self.dispatchers.server_request, - decoded.method, - decoded.params - ) - local _ = log.debug() - and log.debug( - 'server_request: callback result', - { status = status, result = result, err = err } + coroutine.wrap(function() + local status, result + status, result, err = self:try_call( + client_errors.SERVER_REQUEST_HANDLER_ERROR, + self.dispatchers.server_request, + decoded.method, + decoded.params ) - if status then - if result == nil and err == nil then - error( - string.format( - 'method %q: either a result or an error must be sent to the server in response', - decoded.method - ) - ) - end - if err then - assert( - type(err) == 'table', - 'err must be a table. Use rpc_response_error to help format errors.' - ) - local code_name = assert( - protocol.ErrorCodes[err.code], - 'Errors must use protocol.ErrorCodes. Use rpc_response_error to help format errors.' + local _ = log.debug() + and log.debug( + 'server_request: callback result', + { status = status, result = result, err = err } ) - err.message = err.message or code_name + if status then + if result == nil and err == nil then + error( + string.format( + 'method %q: either a result or an error must be sent to the server in response', + decoded.method + ) + ) + end + if err then + assert( + type(err) == 'table', + 'err must be a table. Use rpc_response_error to help format errors.' + ) + local code_name = assert( + protocol.ErrorCodes[err.code], + 'Errors must use protocol.ErrorCodes. Use rpc_response_error to help format errors.' + ) + err.message = err.message or code_name + end + else + -- On an exception, result will contain the error message. + err = rpc_response_error(protocol.ErrorCodes.InternalError, result) + result = nil end - else - -- On an exception, result will contain the error message. - err = rpc_response_error(protocol.ErrorCodes.InternalError, result) - result = nil - end - self:send_response(decoded.id, err, result) + self:send_response(decoded.id, err, result) + end)() end) -- This works because we are expecting vim.NIL here elseif decoded.id and (decoded.result ~= vim.NIL or decoded.error ~= vim.NIL) then diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua new file mode 100644 index 0000000000..b1bc48dac6 --- /dev/null +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -0,0 +1,702 @@ +local api = vim.api +local handlers = require('vim.lsp.handlers') +local util = require('vim.lsp.util') + +--- @class STTokenRange +--- @field line number line number 0-based +--- @field start_col number start column 0-based +--- @field end_col number end column 0-based +--- @field type string token type as string +--- @field modifiers string[] token modifiers as strings +--- @field extmark_added boolean whether this extmark has been added to the buffer yet +--- +--- @class STCurrentResult +--- @field version number document version associated with this result +--- @field result_id string resultId from the server; used with delta requests +--- @field highlights STTokenRange[] cache of highlight ranges for this document version +--- @field tokens number[] raw token array as received by the server. used for calculating delta responses +--- @field namespace_cleared boolean whether the namespace was cleared for this result yet +--- +--- @class STActiveRequest +--- @field request_id number the LSP request ID of the most recent request sent to the server +--- @field version number the document version associated with the most recent request +--- +--- @class STClientState +--- @field namespace number +--- @field active_request STActiveRequest +--- @field current_result STCurrentResult + +---@class STHighlighter +---@field active table<number, STHighlighter> +---@field bufnr number +---@field augroup number augroup for buffer events +---@field debounce number milliseconds to debounce requests for new tokens +---@field timer table uv_timer for debouncing requests for new tokens +---@field client_state table<number, STClientState> +local STHighlighter = { active = {} } + +---@private +local function binary_search(tokens, line) + local lo = 1 + local hi = #tokens + while lo < hi do + local mid = math.floor((lo + hi) / 2) + if tokens[mid].line < line then + lo = mid + 1 + else + hi = mid + end + end + return lo +end + +--- Extracts modifier strings from the encoded number in the token array +--- +---@private +---@return string[] +local function modifiers_from_number(x, modifiers_table) + local modifiers = {} + local idx = 1 + while x > 0 do + if _G.bit then + if _G.bit.band(x, 1) == 1 then + modifiers[#modifiers + 1] = modifiers_table[idx] + end + x = _G.bit.rshift(x, 1) + else + --TODO(jdrouhard): remove this branch once `bit` module is available for non-LuaJIT (#21222) + if x % 2 == 1 then + modifiers[#modifiers + 1] = modifiers_table[idx] + end + x = math.floor(x / 2) + end + idx = idx + 1 + end + + return modifiers +end + +--- Converts a raw token list to a list of highlight ranges used by the on_win callback +--- +---@private +---@return STTokenRange[] +local function tokens_to_ranges(data, bufnr, client) + local legend = client.server_capabilities.semanticTokensProvider.legend + local token_types = legend.tokenTypes + local token_modifiers = legend.tokenModifiers + local ranges = {} + + local line + local start_char = 0 + for i = 1, #data, 5 do + local delta_line = data[i] + line = line and line + delta_line or delta_line + local delta_start = data[i + 1] + start_char = delta_line == 0 and start_char + delta_start or delta_start + + -- data[i+3] +1 because Lua tables are 1-indexed + local token_type = token_types[data[i + 3] + 1] + local modifiers = modifiers_from_number(data[i + 4], token_modifiers) + + ---@private + local function _get_byte_pos(char_pos) + return util._get_line_byte_from_position(bufnr, { + line = line, + character = char_pos, + }, client.offset_encoding) + end + + local start_col = _get_byte_pos(start_char) + local end_col = _get_byte_pos(start_char + data[i + 2]) + + if token_type then + ranges[#ranges + 1] = { + line = line, + start_col = start_col, + end_col = end_col, + type = token_type, + modifiers = modifiers, + extmark_added = false, + } + end + end + + return ranges +end + +--- Construct a new STHighlighter for the buffer +--- +---@private +---@param bufnr number +function STHighlighter.new(bufnr) + local self = setmetatable({}, { __index = STHighlighter }) + + self.bufnr = bufnr + self.augroup = api.nvim_create_augroup('vim_lsp_semantic_tokens:' .. bufnr, { clear = true }) + self.client_state = {} + + STHighlighter.active[bufnr] = self + + api.nvim_buf_attach(bufnr, false, { + on_lines = function(_, buf) + local highlighter = STHighlighter.active[buf] + if not highlighter then + return true + end + highlighter:on_change() + end, + on_reload = function(_, buf) + local highlighter = STHighlighter.active[buf] + if highlighter then + highlighter:reset() + highlighter:send_request() + end + end, + on_detach = function(_, buf) + local highlighter = STHighlighter.active[buf] + if highlighter then + highlighter:destroy() + end + end, + }) + + api.nvim_create_autocmd({ 'BufWinEnter', 'InsertLeave' }, { + buffer = self.bufnr, + group = self.augroup, + callback = function() + self:send_request() + end, + }) + + api.nvim_create_autocmd('LspDetach', { + buffer = self.bufnr, + group = self.augroup, + callback = function(args) + self:detach(args.data.client_id) + if vim.tbl_isempty(self.client_state) then + self:destroy() + end + end, + }) + + return self +end + +---@private +function STHighlighter:destroy() + for client_id, _ in pairs(self.client_state) do + self:detach(client_id) + end + + api.nvim_del_augroup_by_id(self.augroup) + STHighlighter.active[self.bufnr] = nil +end + +---@private +function STHighlighter:attach(client_id) + local state = self.client_state[client_id] + if not state then + state = { + namespace = api.nvim_create_namespace('vim_lsp_semantic_tokens:' .. client_id), + active_request = {}, + current_result = {}, + } + self.client_state[client_id] = state + end +end + +---@private +function STHighlighter:detach(client_id) + local state = self.client_state[client_id] + if state then + --TODO: delete namespace if/when that becomes possible + api.nvim_buf_clear_namespace(self.bufnr, state.namespace, 0, -1) + self.client_state[client_id] = nil + end +end + +--- This is the entry point for getting all the tokens in a buffer. +--- +--- For the given clients (or all attached, if not provided), this sends a request +--- to ask for semantic tokens. If the server supports delta requests, that will +--- be prioritized if we have a previous requestId and token array. +--- +--- This function will skip servers where there is an already an active request in +--- flight for the same version. If there is a stale request in flight, that is +--- cancelled prior to sending a new one. +--- +--- Finally, if the request was successful, the requestId and document version +--- are saved to facilitate document synchronization in the response. +--- +---@private +function STHighlighter:send_request() + local version = util.buf_versions[self.bufnr] + + self:reset_timer() + + for client_id, state in pairs(self.client_state) do + local client = vim.lsp.get_client_by_id(client_id) + + local current_result = state.current_result + local active_request = state.active_request + + -- Only send a request for this client if the current result is out of date and + -- there isn't a current a request in flight for this version + if client and current_result.version ~= version and active_request.version ~= version then + -- cancel stale in-flight request + if active_request.request_id then + client.cancel_request(active_request.request_id) + active_request = {} + state.active_request = active_request + end + + local spec = client.server_capabilities.semanticTokensProvider.full + local hasEditProvider = type(spec) == 'table' and spec.delta + + local params = { textDocument = util.make_text_document_params(self.bufnr) } + local method = 'textDocument/semanticTokens/full' + + if hasEditProvider and current_result.result_id then + method = method .. '/delta' + params.previousResultId = current_result.result_id + end + local success, request_id = client.request(method, params, function(err, response, ctx) + -- look client up again using ctx.client_id instead of using a captured + -- client object + local c = vim.lsp.get_client_by_id(ctx.client_id) + local highlighter = STHighlighter.active[ctx.bufnr] + if not err and c and highlighter then + highlighter:process_response(response, c, version) + end + end, self.bufnr) + + if success then + active_request.request_id = request_id + active_request.version = version + end + end + end +end + +--- This function will parse the semantic token responses and set up the cache +--- (current_result). It also performs document synchronization by checking the +--- version of the document associated with the resulting request_id and only +--- performing work if the response is not out-of-date. +--- +--- Delta edits are applied if necessary, and new highlight ranges are calculated +--- and stored in the buffer state. +--- +--- Finally, a redraw command is issued to force nvim to redraw the screen to +--- pick up changed highlight tokens. +--- +---@private +function STHighlighter:process_response(response, client, version) + local state = self.client_state[client.id] + if not state then + return + end + + -- ignore stale responses + if state.active_request.version and version ~= state.active_request.version then + return + end + + -- reset active request + state.active_request = {} + + -- skip nil responses + if response == nil then + return + end + + -- if we have a response to a delta request, update the state of our tokens + -- appropriately. if it's a full response, just use that + local tokens + local token_edits = response.edits + if token_edits then + table.sort(token_edits, function(a, b) + return a.start < b.start + end) + + tokens = {} + local old_tokens = state.current_result.tokens + local idx = 1 + for _, token_edit in ipairs(token_edits) do + vim.list_extend(tokens, old_tokens, idx, token_edit.start) + if token_edit.data then + vim.list_extend(tokens, token_edit.data) + end + idx = token_edit.start + token_edit.deleteCount + 1 + end + vim.list_extend(tokens, old_tokens, idx) + else + tokens = response.data + end + + -- Update the state with the new results + local current_result = state.current_result + current_result.version = version + current_result.result_id = response.resultId + current_result.tokens = tokens + current_result.highlights = tokens_to_ranges(tokens, self.bufnr, client) + current_result.namespace_cleared = false + + api.nvim_command('redraw!') +end + +--- on_win handler for the decoration provider (see |nvim_set_decoration_provider|) +--- +--- If there is a current result for the buffer and the version matches the +--- current document version, then the tokens are valid and can be applied. As +--- the buffer is drawn, this function will add extmark highlights for every +--- token in the range of visible lines. Once a highlight has been added, it +--- sticks around until the document changes and there's a new set of matching +--- highlight tokens available. +--- +--- If this is the first time a buffer is being drawn with a new set of +--- highlights for the current document version, the namespace is cleared to +--- remove extmarks from the last version. It's done here instead of the response +--- handler to avoid the "blink" that occurs due to the timing between the +--- response handler and the actual redraw. +--- +---@private +function STHighlighter:on_win(topline, botline) + for _, state in pairs(self.client_state) do + local current_result = state.current_result + if current_result.version and current_result.version == util.buf_versions[self.bufnr] then + if not current_result.namespace_cleared then + api.nvim_buf_clear_namespace(self.bufnr, state.namespace, 0, -1) + current_result.namespace_cleared = true + end + + -- We can't use ephemeral extmarks because the buffer updates are not in + -- sync with the list of semantic tokens. There's a delay between the + -- buffer changing and when the LSP server can respond with updated + -- tokens, and we don't want to "blink" the token highlights while + -- updates are in flight, and we don't want to use stale tokens because + -- they likely won't line up right with the actual buffer. + -- + -- Instead, we have to use normal extmarks that can attach to locations + -- in the buffer and are persisted between redraws. + local highlights = current_result.highlights + local idx = binary_search(highlights, topline) + + for i = idx, #highlights do + local token = highlights[i] + + if token.line > botline then + break + end + + if not token.extmark_added then + -- `strict = false` is necessary here for the 1% of cases where the + -- current result doesn't actually match the buffer contents. Some + -- LSP servers can respond with stale tokens on requests if they are + -- still processing changes from a didChange notification. + -- + -- LSP servers that do this _should_ follow up known stale responses + -- with a refresh notification once they've finished processing the + -- didChange notification, which would re-synchronize the tokens from + -- our end. + -- + -- The server I know of that does this is clangd when the preamble of + -- a file changes and the token request is processed with a stale + -- preamble while the new one is still being built. Once the preamble + -- finishes, clangd sends a refresh request which lets the client + -- re-synchronize the tokens. + api.nvim_buf_set_extmark(self.bufnr, state.namespace, token.line, token.start_col, { + hl_group = '@' .. token.type, + end_col = token.end_col, + priority = vim.highlight.priorities.semantic_tokens, + strict = false, + }) + + -- TODO(bfredl) use single extmark when hl_group supports table + if #token.modifiers > 0 then + for _, modifier in pairs(token.modifiers) do + api.nvim_buf_set_extmark(self.bufnr, state.namespace, token.line, token.start_col, { + hl_group = '@' .. modifier, + end_col = token.end_col, + priority = vim.highlight.priorities.semantic_tokens + 1, + strict = false, + }) + end + end + + token.extmark_added = true + end + end + end + end +end + +--- Reset the buffer's highlighting state and clears the extmark highlights. +--- +---@private +function STHighlighter:reset() + for client_id, state in pairs(self.client_state) do + api.nvim_buf_clear_namespace(self.bufnr, state.namespace, 0, -1) + state.current_result = {} + if state.active_request.request_id then + local client = vim.lsp.get_client_by_id(client_id) + assert(client) + client.cancel_request(state.active_request.request_id) + state.active_request = {} + end + end +end + +--- Mark a client's results as dirty. This method will cancel any active +--- requests to the server and pause new highlights from being added +--- in the on_win callback. The rest of the current results are saved +--- in case the server supports delta requests. +--- +---@private +---@param client_id number +function STHighlighter:mark_dirty(client_id) + local state = self.client_state[client_id] + assert(state) + + -- if we clear the version from current_result, it'll cause the + -- next request to be sent and will also pause new highlights + -- from being added in on_win until a new result comes from + -- the server + if state.current_result then + state.current_result.version = nil + end + + if state.active_request.request_id then + local client = vim.lsp.get_client_by_id(client_id) + assert(client) + client.cancel_request(state.active_request.request_id) + state.active_request = {} + end +end + +---@private +function STHighlighter:on_change() + self:reset_timer() + if self.debounce > 0 then + self.timer = vim.defer_fn(function() + self:send_request() + end, self.debounce) + else + self:send_request() + end +end + +---@private +function STHighlighter:reset_timer() + local timer = self.timer + if timer then + self.timer = nil + if not timer:is_closing() then + timer:stop() + timer:close() + end + end +end + +local M = {} + +--- Start the semantic token highlighting engine for the given buffer with the +--- given client. The client must already be attached to the buffer. +--- +--- NOTE: This is currently called automatically by |vim.lsp.buf_attach_client()|. To +--- opt-out of semantic highlighting with a server that supports it, you can +--- delete the semanticTokensProvider table from the {server_capabilities} of +--- your client in your |LspAttach| callback or your configuration's +--- `on_attach` callback: +--- <pre>lua +--- client.server_capabilities.semanticTokensProvider = nil +--- </pre> +--- +---@param bufnr number +---@param client_id number +---@param opts (nil|table) Optional keyword arguments +--- - debounce (number, default: 200): Debounce token requests +--- to the server by the given number in milliseconds +function M.start(bufnr, client_id, opts) + vim.validate({ + bufnr = { bufnr, 'n', false }, + client_id = { client_id, 'n', false }, + }) + + opts = opts or {} + assert( + (not opts.debounce or type(opts.debounce) == 'number'), + 'opts.debounce must be a number with the debounce time in milliseconds' + ) + + local client = vim.lsp.get_client_by_id(client_id) + if not client then + vim.notify('[LSP] No client with id ' .. client_id, vim.log.levels.ERROR) + return + end + + if not vim.lsp.buf_is_attached(bufnr, client_id) then + vim.notify( + '[LSP] Client with id ' .. client_id .. ' not attached to buffer ' .. bufnr, + vim.log.levels.WARN + ) + return + end + + if not vim.tbl_get(client.server_capabilities, 'semanticTokensProvider', 'full') then + vim.notify('[LSP] Server does not support semantic tokens', vim.log.levels.WARN) + return + end + + local highlighter = STHighlighter.active[bufnr] + + if not highlighter then + highlighter = STHighlighter.new(bufnr) + highlighter.debounce = opts.debounce or 200 + else + highlighter.debounce = math.max(highlighter.debounce, opts.debounce or 200) + end + + highlighter:attach(client_id) + highlighter:send_request() +end + +--- Stop the semantic token highlighting engine for the given buffer with the +--- given client. +--- +--- NOTE: This is automatically called by a |LspDetach| autocmd that is set up as part +--- of `start()`, so you should only need this function to manually disengage the semantic +--- token engine without fully detaching the LSP client from the buffer. +--- +---@param bufnr number +---@param client_id number +function M.stop(bufnr, client_id) + vim.validate({ + bufnr = { bufnr, 'n', false }, + client_id = { client_id, 'n', false }, + }) + + local highlighter = STHighlighter.active[bufnr] + if not highlighter then + return + end + + highlighter:detach(client_id) + + if vim.tbl_isempty(highlighter.client_state) then + highlighter:destroy() + end +end + +--- Return the semantic token(s) at the given position. +--- If called without arguments, returns the token under the cursor. +--- +---@param bufnr number|nil Buffer number (0 for current buffer, default) +---@param row number|nil Position row (default cursor position) +---@param col number|nil Position column (default cursor position) +--- +---@return table|nil (table|nil) List of tokens at position +function M.get_at_pos(bufnr, row, col) + if bufnr == nil or bufnr == 0 then + bufnr = api.nvim_get_current_buf() + end + + local highlighter = STHighlighter.active[bufnr] + if not highlighter then + return + end + + if row == nil or col == nil then + local cursor = api.nvim_win_get_cursor(0) + row, col = cursor[1] - 1, cursor[2] + end + + local tokens = {} + for client_id, client in pairs(highlighter.client_state) do + local highlights = client.current_result.highlights + if highlights then + local idx = binary_search(highlights, row) + for i = idx, #highlights do + local token = highlights[i] + + if token.line > row then + break + end + + if token.start_col <= col and token.end_col > col then + token.client_id = client_id + tokens[#tokens + 1] = token + end + end + end + end + return tokens +end + +--- Force a refresh of all semantic tokens +--- +--- Only has an effect if the buffer is currently active for semantic token +--- highlighting (|vim.lsp.semantic_tokens.start()| has been called for it) +--- +---@param bufnr (nil|number) default: current buffer +function M.force_refresh(bufnr) + vim.validate({ + bufnr = { bufnr, 'n', true }, + }) + + if bufnr == nil or bufnr == 0 then + bufnr = api.nvim_get_current_buf() + end + + local highlighter = STHighlighter.active[bufnr] + if not highlighter then + return + end + + highlighter:reset() + highlighter:send_request() +end + +--- |lsp-handler| for the method `workspace/semanticTokens/refresh` +--- +--- Refresh requests are sent by the server to indicate a project-wide change +--- that requires all tokens to be re-requested by the client. This handler will +--- invalidate the current results of all buffers and automatically kick off a +--- new request for buffers that are displayed in a window. For those that aren't, a +--- the BufWinEnter event should take care of it next time it's displayed. +--- +---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#semanticTokens_refreshRequest +handlers['workspace/semanticTokens/refresh'] = function(err, _, ctx) + if err then + return vim.NIL + end + + for _, bufnr in ipairs(vim.lsp.get_buffers_by_client_id(ctx.client_id)) do + local highlighter = STHighlighter.active[bufnr] + if highlighter and highlighter.client_state[ctx.client_id] then + highlighter:mark_dirty(ctx.client_id) + + if not vim.tbl_isempty(vim.fn.win_findbuf(bufnr)) then + highlighter:send_request() + end + end + end + + return vim.NIL +end + +local namespace = api.nvim_create_namespace('vim_lsp_semantic_tokens') +api.nvim_set_decoration_provider(namespace, { + on_win = function(_, _, bufnr, topline, botline) + local highlighter = STHighlighter.active[bufnr] + if highlighter then + highlighter:on_win(topline, botline) + end + end, +}) + +--- for testing only! there is no guarantee of API stability with this! +--- +---@private +M.__STHighlighter = STHighlighter + +return M diff --git a/runtime/lua/vim/lsp/sync.lua b/runtime/lua/vim/lsp/sync.lua index 0d65e86b55..826352f036 100644 --- a/runtime/lua/vim/lsp/sync.lua +++ b/runtime/lua/vim/lsp/sync.lua @@ -392,7 +392,7 @@ end ---@param lastline number line to begin search in old_lines for last difference ---@param new_lastline number line to begin search in new_lines for last difference ---@param offset_encoding string encoding requested by language server ----@returns table TextDocumentContentChangeEvent see https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#textDocumentContentChangeEvent +---@returns table TextDocumentContentChangeEvent see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentContentChangeEvent function M.compute_diff( prev_lines, curr_lines, diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index b0f9c1660e..38051e6410 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -856,7 +856,7 @@ end --- `textDocument/signatureHelp`, and potentially others. --- ---@param input (`MarkedString` | `MarkedString[]` | `MarkupContent`) ----@param contents (table, optional, default `{}`) List of strings to extend with converted lines +---@param contents (table|nil) List of strings to extend with converted lines. Defaults to {}. ---@returns {contents}, extended with lines of converted markdown. ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover function M.convert_input_to_markdown_lines(input, contents) @@ -1015,6 +1015,7 @@ end --- - border (string or table) override `border` --- - focusable (string or table) override `focusable` --- - zindex (string or table) override `zindex`, defaults to 50 +--- - relative ("mouse"|"cursor") defaults to "cursor" ---@returns (table) Options function M.make_floating_popup_options(width, height, opts) validate({ @@ -1029,7 +1030,8 @@ function M.make_floating_popup_options(width, height, opts) local anchor = '' local row, col - local lines_above = vim.fn.winline() - 1 + local lines_above = opts.relative == 'mouse' and vim.fn.getmousepos().line - 1 + or vim.fn.winline() - 1 local lines_below = vim.fn.winheight(0) - lines_above if lines_above < lines_below then @@ -1042,7 +1044,9 @@ function M.make_floating_popup_options(width, height, opts) row = 0 end - if vim.fn.wincol() + width + (opts.offset_x or 0) <= api.nvim_get_option('columns') then + local wincol = opts.relative == 'mouse' and vim.fn.getmousepos().column or vim.fn.wincol() + + if wincol + width + (opts.offset_x or 0) <= api.nvim_get_option('columns') then anchor = anchor .. 'W' col = 0 else @@ -1050,17 +1054,26 @@ function M.make_floating_popup_options(width, height, opts) col = 1 end + local title = (opts.border and opts.title) and opts.title or nil + local title_pos + + if title then + title_pos = opts.title_pos or 'center' + end + return { anchor = anchor, col = col + (opts.offset_x or 0), height = height, focusable = opts.focusable, - relative = 'cursor', + relative = opts.relative == 'mouse' and 'mouse' or 'cursor', row = row + (opts.offset_y or 0), style = 'minimal', width = width, border = opts.border or default_border, zindex = opts.zindex or 50, + title = title, + title_pos = title_pos, } end @@ -1068,7 +1081,7 @@ end --- ---@param location table (`Location`|`LocationLink`) ---@param offset_encoding "utf-8" | "utf-16" | "utf-32" ----@param opts table options +---@param opts table|nil options --- - reuse_win (boolean) Jump to existing window if buffer is already open. --- - focus (boolean) Whether to focus/jump to location if possible. Defaults to true. ---@return boolean `true` if succeeded @@ -1125,7 +1138,7 @@ end --- ---@param location table (`Location`|`LocationLink`) ---@param offset_encoding "utf-8" | "utf-16" | "utf-32" ----@param reuse_win boolean Jump to existing window if buffer is already open. +---@param reuse_win boolean|nil Jump to existing window if buffer is already open. ---@return boolean `true` if the jump succeeded function M.jump_to_location(location, offset_encoding, reuse_win) if offset_encoding == nil then @@ -1252,7 +1265,7 @@ function M.stylize_markdown(bufnr, contents, opts) -- when ft is nil, we get the ft from the regex match local matchers = { block = { nil, '```+([a-zA-Z0-9_]*)', '```+' }, - pre = { '', '<pre>', '</pre>' }, + pre = { nil, '<pre>([a-z0-9]*)', '</pre>' }, code = { '', '<code>', '</code>' }, text = { 'text', '<text>', '</text>' }, } @@ -1277,8 +1290,6 @@ function M.stylize_markdown(bufnr, contents, opts) -- Clean up contents = M._trim(contents, opts) - -- Insert blank line separator after code block? - local add_sep = opts.separator == nil and true or opts.separator local stripped = {} local highlights = {} -- keep track of lnums that contain markdown @@ -1306,7 +1317,7 @@ function M.stylize_markdown(bufnr, contents, opts) finish = #stripped, }) -- add a separator, but not on the last line - if add_sep and i < #contents then + if opts.separator and i < #contents then table.insert(stripped, '---') markdown_lines[#stripped] = true end @@ -1670,7 +1681,7 @@ do --[[ References ]] ---@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 + ---@see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentContentChangeEvent function M.buf_highlight_references(bufnr, references, offset_encoding) validate({ bufnr = { bufnr, 'n', true }, @@ -1901,7 +1912,7 @@ end --- Creates a `TextDocumentPositionParams` object for the current buffer and cursor position. --- ---@param window number|nil: 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` +---@param offset_encoding string|nil 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(window, offset_encoding) @@ -1924,7 +1935,7 @@ function M._get_offset_encoding(bufnr) local offset_encoding - for _, client in pairs(vim.lsp.buf_get_clients(bufnr)) do + for _, client in pairs(vim.lsp.get_active_clients({ bufnr = bufnr })) do if client.offset_encoding == nil then vim.notify_once( string.format( diff --git a/runtime/lua/vim/secure.lua b/runtime/lua/vim/secure.lua new file mode 100644 index 0000000000..443b152273 --- /dev/null +++ b/runtime/lua/vim/secure.lua @@ -0,0 +1,188 @@ +local M = {} + +---@private +--- Reads trust database from $XDG_STATE_HOME/nvim/trust. +--- +---@return (table) Contents of trust database, if it exists. Empty table otherwise. +local function read_trust() + local trust = {} + local f = io.open(vim.fn.stdpath('state') .. '/trust', 'r') + if f then + local contents = f:read('*a') + if contents then + for line in vim.gsplit(contents, '\n') do + local hash, file = string.match(line, '^(%S+) (.+)$') + if hash and file then + trust[file] = hash + end + end + end + f:close() + end + return trust +end + +---@private +--- Writes provided {trust} table to trust database at +--- $XDG_STATE_HOME/nvim/trust. +--- +---@param trust (table) Trust table to write +local function write_trust(trust) + vim.validate({ trust = { trust, 't' } }) + local f = assert(io.open(vim.fn.stdpath('state') .. '/trust', 'w')) + + local t = {} + for p, h in pairs(trust) do + t[#t + 1] = string.format('%s %s\n', h, p) + end + f:write(table.concat(t)) + f:close() +end + +--- Attempt to read the file at {path} prompting the user if the file should be +--- trusted. The user's choice is persisted in a trust database at +--- $XDG_STATE_HOME/nvim/trust. +--- +---@see |:trust| +--- +---@param path (string) Path to a file to read. +--- +---@return (string|nil) The contents of the given file if it exists and is +--- trusted, or nil otherwise. +function M.read(path) + vim.validate({ path = { path, 's' } }) + local fullpath = vim.loop.fs_realpath(vim.fs.normalize(path)) + if not fullpath then + return nil + end + + local trust = read_trust() + + if trust[fullpath] == '!' then + -- File is denied + return nil + end + + local contents + do + local f = io.open(fullpath, 'r') + if not f then + return nil + end + contents = f:read('*a') + f:close() + end + + local hash = vim.fn.sha256(contents) + if trust[fullpath] == hash then + -- File already exists in trust database + return contents + end + + -- File either does not exist in trust database or the hash does not match + local ok, result = pcall( + vim.fn.confirm, + string.format('%s is not trusted.', fullpath), + '&ignore\n&view\n&deny\n&allow', + 1 + ) + + if not ok and result ~= 'Keyboard interrupt' then + error(result) + elseif not ok or result == 0 or result == 1 then + -- Cancelled or ignored + return nil + elseif result == 2 then + -- View + vim.cmd('sview ' .. fullpath) + return nil + elseif result == 3 then + -- Deny + trust[fullpath] = '!' + contents = nil + elseif result == 4 then + -- Allow + trust[fullpath] = hash + end + + write_trust(trust) + + return contents +end + +--- Manage the trust database. +--- +--- The trust database is located at |$XDG_STATE_HOME|/nvim/trust. +--- +---@param opts (table): +--- - action (string): "allow" to add a file to the trust database and trust it, +--- "deny" to add a file to the trust database and deny it, +--- "remove" to remove file from the trust database +--- - path (string|nil): Path to a file to update. Mutually exclusive with {bufnr}. +--- Cannot be used when {action} is "allow". +--- - bufnr (number|nil): Buffer number to update. Mutually exclusive with {path}. +---@return (boolean, string) success, msg: +--- - true and full path of target file if operation was successful +--- - false and error message on failure +function M.trust(opts) + vim.validate({ + path = { opts.path, 's', true }, + bufnr = { opts.bufnr, 'n', true }, + action = { + opts.action, + function(m) + return m == 'allow' or m == 'deny' or m == 'remove' + end, + [["allow" or "deny" or "remove"]], + }, + }) + + local path = opts.path + local bufnr = opts.bufnr + local action = opts.action + + assert(not path or not bufnr, '"path" and "bufnr" are mutually exclusive') + + if action == 'allow' then + assert(not path, '"path" is not valid when action is "allow"') + end + + local fullpath + if path then + fullpath = vim.loop.fs_realpath(vim.fs.normalize(path)) + elseif bufnr then + local bufname = vim.api.nvim_buf_get_name(bufnr) + if bufname == '' then + return false, 'buffer is not associated with a file' + end + fullpath = vim.loop.fs_realpath(vim.fs.normalize(bufname)) + else + error('one of "path" or "bufnr" is required') + end + + if not fullpath then + return false, string.format('invalid path: %s', path) + end + + local trust = read_trust() + + if action == 'allow' then + local newline = vim.bo[bufnr].fileformat == 'unix' and '\n' or '\r\n' + local contents = table.concat(vim.api.nvim_buf_get_lines(bufnr, 0, -1, false), newline) + if vim.bo[bufnr].endofline then + contents = contents .. newline + end + local hash = vim.fn.sha256(contents) + + trust[fullpath] = hash + elseif action == 'deny' then + trust[fullpath] = '!' + elseif action == 'remove' then + trust[fullpath] = nil + end + + write_trust(trust) + return true, fullpath +end + +return M diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index f03d608e56..cc48e3f193 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -49,6 +49,9 @@ vim.deepcopy = (function() if f then return f(orig, cache or {}) else + if type(orig) == 'userdata' and orig == vim.NIL then + return vim.NIL + end error('Cannot deepcopy object of type ' .. type(orig)) end end @@ -57,12 +60,13 @@ end)() --- Splits a string at each instance of a separator. --- ---@see |vim.split()| +---@see |luaref-patterns| ---@see https://www.lua.org/pil/20.2.html ---@see http://lua-users.org/wiki/StringLibraryTutorial --- ---@param s string String to split ---@param sep string Separator or pattern ----@param plain boolean If `true` use `sep` literally (passed to string.find) +---@param plain (boolean|nil) If `true` use `sep` literally (passed to string.find) ---@return fun():string (function) Iterator over the split components function vim.gsplit(s, sep, plain) vim.validate({ s = { s, 's' }, sep = { sep, 's' }, plain = { plain, 'b', true } }) @@ -99,20 +103,18 @@ end --- Splits a string at each instance of a separator. --- --- Examples: ---- <pre> ---- split(":aa::b:", ":") => {'','aa','','b',''} ---- split("axaby", "ab?") => {'','x','y'} ---- split("x*yz*o", "*", {plain=true}) => {'x','yz','o'} ---- split("|x|y|z|", "|", {trimempty=true}) => {'x', 'y', 'z'} +--- <pre>lua +--- split(":aa::b:", ":") --> {'','aa','','b',''} +--- split("axaby", "ab?") --> {'','x','y'} +--- split("x*yz*o", "*", {plain=true}) --> {'x','yz','o'} +--- split("|x|y|z|", "|", {trimempty=true}) --> {'x', 'y', 'z'} --- </pre> --- ---@see |vim.gsplit()| --- ----@alias split_kwargs {plain: boolean, trimempty: boolean} | boolean | nil ---- ---@param s string String to split ---@param sep string Separator or pattern ----@param kwargs? {plain: boolean, trimempty: boolean} (table|nil) Keyword arguments: +---@param kwargs (table|nil) Keyword arguments: --- - plain: (boolean) If `true` use `sep` literally (passed to string.find) --- - trimempty: (boolean) If `true` remove empty items from the front --- and back of the list @@ -159,8 +161,8 @@ end --- ---@see From https://github.com/premake/premake-core/blob/master/src/base/table.lua --- ----@param t table<T, any> (table) Table ---@generic T: table +---@param t table<T, any> (table) Table ---@return T[] (list) List of keys function vim.tbl_keys(t) assert(type(t) == 'table', string.format('Expected table, got %s', type(t))) @@ -382,7 +384,7 @@ end --- Return `nil` if the key does not exist. --- --- Examples: ---- <pre> +--- <pre>lua --- vim.tbl_get({ key = { nested_key = true }}, 'key', 'nested_key') == true --- vim.tbl_get({ key = {}}, 'key', 'nested_key') == nil --- </pre> @@ -394,15 +396,14 @@ end function vim.tbl_get(o, ...) local keys = { ... } if #keys == 0 then - return + return nil end for i, k in ipairs(keys) do - if type(o[k]) ~= 'table' and next(keys, i) then - return nil - end o = o[k] if o == nil then - return + return nil + elseif type(o) ~= 'table' and next(keys, i) then + return nil end end return o @@ -417,8 +418,8 @@ end ---@generic T: table ---@param dst T List which will be modified and appended to ---@param src table List from which values will be inserted ----@param start? number Start index on src. Defaults to 1 ----@param finish? number Final index on src. Defaults to `#src` +---@param start (number|nil) Start index on src. Defaults to 1 +---@param finish (number|nil) Final index on src. Defaults to `#src` ---@return T dst function vim.list_extend(dst, src, start, finish) vim.validate({ @@ -457,6 +458,33 @@ function vim.tbl_flatten(t) return result end +--- Enumerate a table sorted by its keys. +--- +---@see Based on https://github.com/premake/premake-core/blob/master/src/base/table.lua +--- +---@param t table List-like table +---@return iterator over sorted keys and their values +function vim.spairs(t) + assert(type(t) == 'table', string.format('Expected table, got %s', type(t))) + + -- collect the keys + local keys = {} + for k in pairs(t) do + table.insert(keys, k) + end + table.sort(keys) + + -- Return the iterator function. + -- TODO(justinmk): Return "iterator function, table {t}, and nil", like pairs()? + local i = 0 + return function() + i = i + 1 + if keys[i] then + return keys[i], t[keys[i]] + end + end +end + --- Tests if a Lua table can be treated as an array. --- --- Empty table `{}` is assumed to be an array, unless it was created by @@ -486,7 +514,7 @@ function vim.tbl_islist(t) -- TODO(bfredl): in the future, we will always be inside nvim -- then this check can be deleted. if vim._empty_dict_mt == nil then - return nil + return false end return getmetatable(t) ~= vim._empty_dict_mt end @@ -494,9 +522,9 @@ end --- Counts the number of non-nil values in table `t`. --- ---- <pre> ---- vim.tbl_count({ a=1, b=2 }) => 2 ---- vim.tbl_count({ 1, 2 }) => 2 +--- <pre>lua +--- vim.tbl_count({ a=1, b=2 }) --> 2 +--- vim.tbl_count({ 1, 2 }) --> 2 --- </pre> --- ---@see https://github.com/Tieske/Penlight/blob/master/lua/pl/tablex.lua @@ -516,8 +544,8 @@ end --- ---@generic T ---@param list T[] (list) Table ----@param start number Start range of slice ----@param finish number End range of slice +---@param start number|nil Start range of slice +---@param finish number|nil End range of slice ---@return T[] (list) Copy of table sliced from start to finish (inclusive) function vim.list_slice(list, start, finish) local new_list = {} @@ -529,6 +557,7 @@ end --- Trim whitespace (Lua pattern "%s") from both sides of a string. --- +---@see |luaref-patterns| ---@see https://www.lua.org/pil/20.2.html ---@param s string String to trim ---@return string String with whitespace removed from its beginning and end @@ -544,7 +573,7 @@ end ---@return string %-escaped pattern string function vim.pesc(s) vim.validate({ s = { s, 's' } }) - return s:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1') + return (s:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1')) end --- Tests if `s` starts with `prefix`. @@ -570,7 +599,7 @@ end --- Validates a parameter specification (types and values). --- --- Usage example: ---- <pre> +--- <pre>lua --- function user.new(name, age, hobbies) --- vim.validate{ --- name={name, 'string'}, @@ -582,24 +611,24 @@ end --- </pre> --- --- Examples with explicit argument values (can be run directly): ---- <pre> +--- <pre>lua --- vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}} ---- => NOP (success) +--- --> NOP (success) --- --- vim.validate{arg1={1, 'table'}} ---- => error('arg1: expected table, got number') +--- --> 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') +--- --> error('arg1: expected even number, got 3') --- </pre> --- --- If multiple types are valid they can be given as a list. ---- <pre> +--- <pre>lua --- vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}} ---- => NOP (success) +--- --> NOP (success) --- --- vim.validate{arg1={1, {'string', table'}}} ---- => error('arg1: expected string|table, got number') +--- --> error('arg1: expected string|table, got number') --- --- </pre> --- @@ -734,7 +763,7 @@ end --- If {create} is `nil`, this will create a defaulttable whose constructor function is --- this function, effectively allowing to create nested tables on the fly: --- ---- <pre> +--- <pre>lua --- local a = vim.defaulttable() --- a.b.c = 1 --- </pre> diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 6cd00516bf..582922ecb6 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -240,7 +240,7 @@ function M.get_captures_at_pos(bufnr, row, col) if M.is_in_node_range(node, row, col) then local c = q._query.captures[capture] -- name of the capture in the query if c ~= nil then - table.insert(matches, { capture = c, metadata = metadata }) + table.insert(matches, { capture = c, metadata = metadata, lang = tree:lang() }) end end end @@ -275,16 +275,17 @@ end ---@param row number Position row ---@param col number Position column ---@param opts table Optional keyword arguments: +--- - lang string|nil Parser language --- - ignore_injections boolean Ignore injected languages (default true) --- ----@return userdata |tsnode| under the cursor +---@return userdata|nil |tsnode| under the cursor function M.get_node_at_pos(bufnr, row, col, opts) if bufnr == 0 then bufnr = a.nvim_get_current_buf() end local ts_range = { row, col, row, col } - local root_lang_tree = M.get_parser(bufnr) + local root_lang_tree = M.get_parser(bufnr, opts.lang) if not root_lang_tree then return end @@ -313,7 +314,7 @@ end --- In this case, add ``vim.bo.syntax = 'on'`` after the call to `start`. --- --- Example: ---- <pre> +--- <pre>lua --- vim.api.nvim_create_autocmd( 'FileType', { pattern = 'tex', --- callback = function(args) --- vim.treesitter.start(args.buf, 'latex') @@ -326,12 +327,8 @@ end ---@param lang (string|nil) Language of the parser (default: buffer filetype) function M.start(bufnr, lang) bufnr = bufnr or a.nvim_get_current_buf() - local parser = M.get_parser(bufnr, lang) - M.highlighter.new(parser) - - vim.b[bufnr].ts_highlight = true end --- Stops treesitter highlighting for a buffer @@ -343,8 +340,203 @@ function M.stop(bufnr) if M.highlighter.active[bufnr] then M.highlighter.active[bufnr]:destroy() end +end + +--- Open a window that displays a textual representation of the nodes in the language tree. +--- +--- While in the window, press "a" to toggle display of anonymous nodes, "I" to toggle the +--- display of the source language of each node, and press <Enter> to jump to the node under the +--- cursor in the source buffer. +--- +---@param opts table|nil Optional options table with the following possible keys: +--- - lang (string|nil): The language of the source buffer. If omitted, the +--- filetype of the source buffer is used. +--- - bufnr (number|nil): Buffer to draw the tree into. If omitted, a new +--- buffer is created. +--- - winid (number|nil): Window id to display the tree buffer in. If omitted, +--- a new window is created with {command}. +--- - command (string|nil): Vimscript command to create the window. Default +--- value is "topleft 60vnew". Only used when {winid} is nil. +--- - title (string|fun(bufnr:number):string|nil): Title of the window. If a +--- function, it accepts the buffer number of the source buffer as its only +--- argument and should return a string. +function M.show_tree(opts) + vim.validate({ + opts = { opts, 't', true }, + }) + + opts = opts or {} + + local Playground = require('vim.treesitter.playground') + local buf = a.nvim_get_current_buf() + local win = a.nvim_get_current_win() + local pg = assert(Playground:new(buf, opts.lang)) + + -- Close any existing playground window + if vim.b[buf].playground then + local w = vim.b[buf].playground + if a.nvim_win_is_valid(w) then + a.nvim_win_close(w, true) + end + end + + local w = opts.winid + if not w then + vim.cmd(opts.command or 'topleft 60vnew') + w = a.nvim_get_current_win() + end + + local b = opts.bufnr + if b then + a.nvim_win_set_buf(w, b) + else + b = a.nvim_win_get_buf(w) + end + + vim.b[buf].playground = w + + vim.wo[w].scrolloff = 5 + vim.wo[w].wrap = false + vim.bo[b].buflisted = false + vim.bo[b].buftype = 'nofile' + vim.bo[b].bufhidden = 'wipe' + + local title = opts.title + if not title then + local bufname = a.nvim_buf_get_name(buf) + title = string.format('Syntax tree for %s', vim.fn.fnamemodify(bufname, ':.')) + elseif type(title) == 'function' then + title = title(buf) + end + + assert(type(title) == 'string', 'Window title must be a string') + a.nvim_buf_set_name(b, title) + + pg:draw(b) + + vim.fn.matchadd('Comment', '\\[[0-9:-]\\+\\]') + vim.fn.matchadd('String', '".*"') - vim.bo[bufnr].syntax = 'on' + a.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) + a.nvim_buf_set_keymap(b, 'n', '<CR>', '', { + desc = 'Jump to the node under the cursor in the source buffer', + callback = function() + local row = a.nvim_win_get_cursor(w)[1] + local pos = pg:get(row) + a.nvim_set_current_win(win) + a.nvim_win_set_cursor(win, { pos.lnum + 1, pos.col }) + end, + }) + a.nvim_buf_set_keymap(b, 'n', 'a', '', { + desc = 'Toggle anonymous nodes', + callback = function() + pg.opts.anon = not pg.opts.anon + pg:draw(b) + end, + }) + a.nvim_buf_set_keymap(b, 'n', 'I', '', { + desc = 'Toggle language display', + callback = function() + pg.opts.lang = not pg.opts.lang + pg:draw(b) + end, + }) + + local group = a.nvim_create_augroup('treesitter/playground', {}) + + a.nvim_create_autocmd('CursorMoved', { + group = group, + buffer = b, + callback = function() + a.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) + local row = a.nvim_win_get_cursor(w)[1] + local pos = pg:get(row) + a.nvim_buf_set_extmark(buf, pg.ns, pos.lnum, pos.col, { + end_row = pos.end_lnum, + end_col = math.max(0, pos.end_col), + hl_group = 'Visual', + }) + end, + }) + + a.nvim_create_autocmd('CursorMoved', { + group = group, + buffer = buf, + callback = function() + if not a.nvim_buf_is_loaded(b) then + return true + end + + a.nvim_buf_clear_namespace(b, pg.ns, 0, -1) + + local cursor = a.nvim_win_get_cursor(win) + local cursor_node = M.get_node_at_pos(buf, cursor[1] - 1, cursor[2], { + lang = opts.lang, + ignore_injections = false, + }) + if not cursor_node then + return + end + + local cursor_node_id = cursor_node:id() + for i, v in pg:iter() do + if v.id == cursor_node_id then + local start = v.depth + local end_col = start + #v.text + a.nvim_buf_set_extmark(b, pg.ns, i - 1, start, { + end_col = end_col, + hl_group = 'Visual', + }) + a.nvim_win_set_cursor(w, { i, 0 }) + break + end + end + end, + }) + + a.nvim_create_autocmd({ 'TextChanged', 'InsertLeave' }, { + group = group, + buffer = buf, + callback = function() + if not a.nvim_buf_is_loaded(b) then + return true + end + + pg = assert(Playground:new(buf, opts.lang)) + pg:draw(b) + end, + }) + + a.nvim_create_autocmd('BufLeave', { + group = group, + buffer = b, + callback = function() + a.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) + end, + }) + + a.nvim_create_autocmd('BufLeave', { + group = group, + buffer = buf, + callback = function() + if not a.nvim_buf_is_loaded(b) then + return true + end + + a.nvim_buf_clear_namespace(b, pg.ns, 0, -1) + end, + }) + + a.nvim_create_autocmd('BufHidden', { + group = group, + buffer = buf, + once = true, + callback = function() + if a.nvim_win_is_valid(w) then + a.nvim_win_close(w, true) + end + end, + }) end return M diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua index 4995c80a02..c0a1eca0ce 100644 --- a/runtime/lua/vim/treesitter/health.lua +++ b/runtime/lua/vim/treesitter/health.lua @@ -1,5 +1,6 @@ local M = {} local ts = vim.treesitter +local health = require('vim.health') --- Lists the parsers currently installed --- @@ -10,27 +11,23 @@ end --- Performs a healthcheck for treesitter integration function M.check() - local report_info = vim.fn['health#report_info'] - local report_ok = vim.fn['health#report_ok'] - local report_error = vim.fn['health#report_error'] local parsers = M.list_parsers() - report_info(string.format('Runtime ABI version : %d', ts.language_version)) + health.report_info(string.format('Nvim runtime ABI version: %d', ts.language_version)) for _, parser in pairs(parsers) do local parsername = vim.fn.fnamemodify(parser, ':t:r') - local is_loadable, ret = pcall(ts.language.require_language, parsername) - if not is_loadable then - report_error(string.format('Impossible to load parser for %s: %s', parsername, ret)) + if not is_loadable or not ret then + health.report_error( + string.format('Parser "%s" failed to load (path: %s): %s', parsername, parser, ret or '?') + ) elseif ret then local lang = ts.language.inspect_language(parsername) - report_ok( - string.format('Loaded parser for %s: ABI version %d', parsername, lang._abi_version) + health.report_ok( + string.format('Parser: %-10s ABI: %d, path: %s', parsername, lang._abi_version, parser) ) - else - report_error(string.format('Unable to load parser for %s', parsername)) end end end diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 83a26aff13..d77a0d0d03 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -88,7 +88,10 @@ function TSHighlighter.new(tree, opts) end end + self.orig_spelloptions = vim.bo[self.bufnr].spelloptions + vim.bo[self.bufnr].syntax = '' + vim.b[self.bufnr].ts_highlight = true TSHighlighter.active[self.bufnr] = self @@ -114,6 +117,14 @@ function TSHighlighter:destroy() if TSHighlighter.active[self.bufnr] then TSHighlighter.active[self.bufnr] = nil end + + if vim.api.nvim_buf_is_loaded(self.bufnr) then + vim.bo[self.bufnr].spelloptions = self.orig_spelloptions + vim.b[self.bufnr].ts_highlight = nil + if vim.g.syntax_on == 1 then + a.nvim_exec_autocmds('FileType', { group = 'syntaxset', buffer = self.bufnr }) + end + end end ---@private @@ -164,7 +175,7 @@ function TSHighlighter:get_query(lang) end ---@private -local function on_line_impl(self, buf, line, spell) +local function on_line_impl(self, buf, line, is_spell_nav) self.tree:for_each_tree(function(tstree, tree) if not tstree then return @@ -201,17 +212,26 @@ local function on_line_impl(self, buf, line, spell) local start_row, start_col, end_row, end_col = node:range() local hl = highlighter_query.hl_cache[capture] - local is_spell = highlighter_query:query().captures[capture] == 'spell' + local capture_name = highlighter_query:query().captures[capture] + local spell = nil + if capture_name == 'spell' then + spell = true + elseif capture_name == 'nospell' then + spell = false + end + + -- Give nospell a higher priority so it always overrides spell captures. + local spell_pri_offset = capture_name == 'nospell' and 1 or 0 - if hl and end_row >= line and (not spell or is_spell) then + if hl and end_row >= line and (not is_spell_nav or spell ~= nil) then a.nvim_buf_set_extmark(buf, ns, start_row, start_col, { 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) + spell_pri_offset, -- Low but leaves room below conceal = metadata.conceal, - spell = is_spell, + spell = spell, }) end if start_row > line then diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index e9d70c4204..a1e96f8ef2 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -608,7 +608,9 @@ end ---@return userdata|nil Found |tsnode| function LanguageTree:named_node_for_range(range, opts) local tree = self:tree_for_range(range, opts) - return tree:root():named_descendant_for_range(unpack(range)) + if tree then + return tree:root():named_descendant_for_range(unpack(range)) + end end --- Gets the appropriate language that contains {range}. diff --git a/runtime/lua/vim/treesitter/playground.lua b/runtime/lua/vim/treesitter/playground.lua new file mode 100644 index 0000000000..bb073290c6 --- /dev/null +++ b/runtime/lua/vim/treesitter/playground.lua @@ -0,0 +1,186 @@ +local api = vim.api + +local M = {} + +---@class Playground +---@field ns number API namespace +---@field opts table Options table with the following keys: +--- - anon (boolean): If true, display anonymous nodes +--- - lang (boolean): If true, display the language alongside each node +--- +---@class Node +---@field id number Node id +---@field text string Node text +---@field named boolean True if this is a named (non-anonymous) node +---@field depth number Depth of the node within the tree +---@field lnum number Beginning line number of this node in the source buffer +---@field col number Beginning column number of this node in the source buffer +---@field end_lnum number Final line number of this node in the source buffer +---@field end_col number Final column number of this node in the source buffer +---@field lang string Source language of this node + +--- Traverse all child nodes starting at {node}. +--- +--- This is a recursive function. The {depth} parameter indicates the current recursion level. +--- {lang} is a string indicating the language of the tree currently being traversed. Each traversed +--- node is added to {tree}. When recursion completes, {tree} is an array of all nodes in the order +--- they were visited. +--- +--- {injections} is a table mapping node ids from the primary tree to language tree injections. Each +--- injected language has a series of trees nested within the primary language's tree, and the root +--- node of each of these trees is contained within a node in the primary tree. The {injections} +--- table maps nodes in the primary tree to root nodes of injected trees. +--- +---@param node userdata Starting node to begin traversal |tsnode| +---@param depth number Current recursion depth +---@param lang string Language of the tree currently being traversed +---@param injections table Mapping of node ids to root nodes of injected language trees (see +--- explanation above) +---@param tree Node[] Output table containing a list of tables each representing a node in the tree +---@private +local function traverse(node, depth, lang, injections, tree) + local injection = injections[node:id()] + if injection then + traverse(injection.root, depth, injection.lang, injections, tree) + end + + for child, field in node:iter_children() do + local type = child:type() + local lnum, col, end_lnum, end_col = child:range() + local named = child:named() + local text + if named then + if field then + text = string.format('%s: (%s)', field, type) + else + text = string.format('(%s)', type) + end + else + text = string.format('"%s"', type:gsub('\n', '\\n')) + end + + table.insert(tree, { + id = child:id(), + text = text, + named = named, + depth = depth, + lnum = lnum, + col = col, + end_lnum = end_lnum, + end_col = end_col, + lang = lang, + }) + + traverse(child, depth + 1, lang, injections, tree) + end + + return tree +end + +--- Create a new Playground object. +--- +---@param bufnr number Source buffer number +---@param lang string|nil Language of source buffer +--- +---@return Playground|nil +---@return string|nil Error message, if any +--- +---@private +function M.new(self, bufnr, lang) + local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0, lang) + if not ok then + return nil, 'No parser available for the given buffer' + end + + -- For each child tree (injected language), find the root of the tree and locate the node within + -- the primary tree that contains that root. Add a mapping from the node in the primary tree to + -- the root in the child tree to the {injections} table. + local root = parser:parse()[1]:root() + local injections = {} + parser:for_each_child(function(child, lang_) + child:for_each_tree(function(tree) + local r = tree:root() + local node = root:named_descendant_for_range(r:range()) + if node then + injections[node:id()] = { + lang = lang_, + root = r, + } + end + end) + end) + + local nodes = traverse(root, 0, parser:lang(), injections, {}) + + local named = {} + for _, v in ipairs(nodes) do + if v.named then + named[#named + 1] = v + end + end + + local t = { + ns = api.nvim_create_namespace(''), + nodes = nodes, + named = named, + opts = { + anon = false, + lang = false, + }, + } + + setmetatable(t, self) + self.__index = self + return t +end + +--- Write the contents of this Playground into {bufnr}. +--- +---@param bufnr number Buffer number to write into. +---@private +function M.draw(self, bufnr) + vim.bo[bufnr].modifiable = true + local lines = {} + for _, item in self:iter() do + lines[#lines + 1] = table.concat({ + string.rep(' ', item.depth), + item.text, + item.lnum == item.end_lnum + and string.format(' [%d:%d-%d]', item.lnum + 1, item.col + 1, item.end_col) + or string.format( + ' [%d:%d-%d:%d]', + item.lnum + 1, + item.col + 1, + item.end_lnum + 1, + item.end_col + ), + self.opts.lang and string.format(' %s', item.lang) or '', + }) + end + api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + vim.bo[bufnr].modifiable = false +end + +--- Get node {i} from this Playground object. +--- +--- The node number is dependent on whether or not anonymous nodes are displayed. +--- +---@param i number Node number to get +---@return Node +---@private +function M.get(self, i) + local t = self.opts.anon and self.nodes or self.named + return t[i] +end + +--- Iterate over all of the nodes in this Playground object. +--- +---@return function Iterator over all nodes in this Playground +---@return table +---@return number +---@private +function M.iter(self) + return ipairs(self.opts.anon and self.nodes or self.named) +end + +return M diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 7ca7384a88..dbf134573d 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -419,7 +419,8 @@ local directive_handlers = { --- Adds a new predicate to be used in queries --- ---@param name string Name of the predicate, without leading # ----@param handler function(match:string, pattern:string, bufnr:number, predicate:function) +---@param handler function(match:table, pattern:string, bufnr:number, predicate:string[]) +--- - see |vim.treesitter.query.add_directive()| for argument meanings function M.add_predicate(name, handler, force) if predicate_handlers[name] and not force then error(string.format('Overriding %s', name)) @@ -436,7 +437,12 @@ end --- metadata table `metadata[capture_id].key = value` --- ---@param name string Name of the directive, without leading # ----@param handler function(match:string, pattern:string, bufnr:number, predicate:function, metadata:table) +---@param handler function(match:table, pattern:string, bufnr:number, predicate:string[], metadata:table) +--- - match: see |treesitter-query| +--- - node-level data are accessible via `match[capture_id]` +--- - pattern: see |treesitter-query| +--- - predicate: list of strings containing the full directive being called, e.g. +--- `(node (#set! conceal "-"))` would get the predicate `{ "#set!", "conceal", "-" }` function M.add_directive(name, handler, force) if directive_handlers[name] and not force then error(string.format('Overriding %s', name)) @@ -549,7 +555,7 @@ end --- The iterator returns three values: a numeric id identifying the capture, --- the captured node, and metadata from any directives processing the match. --- The following example shows how to get captures by name: ---- <pre> +--- <pre>lua --- for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do --- local name = query.captures[id] -- name of the capture in the query --- -- typically useful info about the node: @@ -603,7 +609,7 @@ end --- 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 is an example iterating over all captures in every match: ---- <pre> +--- <pre>lua --- 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] diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua index 6f1ce3089d..8f5be15221 100644 --- a/runtime/lua/vim/ui.lua +++ b/runtime/lua/vim/ui.lua @@ -21,7 +21,7 @@ local M = {} --- --- --- Example: ---- <pre> +--- <pre>lua --- vim.ui.select({ 'tabs', 'spaces' }, { --- prompt = 'Select tabs or spaces:', --- format_item = function(item) @@ -73,11 +73,12 @@ end --- user inputs. ---@param on_confirm function ((input|nil) -> ()) --- Called once the user confirms or abort the input. ---- `input` is what the user typed. +--- `input` is what the user typed (it might be +--- an empty string if nothing was entered), or --- `nil` if the user aborted the dialog. --- --- Example: ---- <pre> +--- <pre>lua --- vim.ui.input({ prompt = 'Enter value for shiftwidth: ' }, function(input) --- vim.o.shiftwidth = tonumber(input) --- end) @@ -88,11 +89,17 @@ function M.input(opts, on_confirm) }) opts = (opts and not vim.tbl_isempty(opts)) and opts or vim.empty_dict() - local input = vim.fn.input(opts) - if #input > 0 then - on_confirm(input) - else + + -- Note that vim.fn.input({}) returns an empty string when cancelled. + -- vim.ui.input() should distinguish aborting from entering an empty string. + local _canceled = vim.NIL + opts = vim.tbl_extend('keep', opts, { cancelreturn = _canceled }) + + local ok, input = pcall(vim.fn.input, opts) + if not ok or input == _canceled then on_confirm(nil) + else + on_confirm(input) end end diff --git a/runtime/menu.vim b/runtime/menu.vim index 0a5ac36095..2671bb51cb 100644 --- a/runtime/menu.vim +++ b/runtime/menu.vim @@ -2,7 +2,7 @@ " You can also use this as a start for your own set of menus. " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2020 Mar 29 +" Last Change: 2022 Nov 27 " Note that ":an" (short for ":anoremenu") is often used to make a menu work " in all modes and avoid side effects from mappings defined by the user. @@ -89,6 +89,21 @@ an 9999.75 &Help.-sep2- <Nop> an 9999.80 &Help.&Version :version<CR> an 9999.90 &Help.&About :intro<CR> +if exists(':tlmenu') + tlnoremenu 9999.10 &Help.&Overview<Tab><F1> <C-W>:help<CR> + tlnoremenu 9999.20 &Help.&User\ Manual <C-W>:help usr_toc<CR> + tlnoremenu 9999.30 &Help.&How-To\ Links <C-W>:help how-to<CR> + tlnoremenu <silent> 9999.40 &Help.&Find\.\.\. <C-W>:call <SID>Helpfind()<CR> + tlnoremenu 9999.45 &Help.-sep1- <Nop> + tlnoremenu 9999.50 &Help.&Credits <C-W>:help credits<CR> + tlnoremenu 9999.60 &Help.Co&pying <C-W>:help copying<CR> + tlnoremenu 9999.70 &Help.&Sponsor/Register <C-W>:help sponsor<CR> + tlnoremenu 9999.70 &Help.O&rphans <C-W>:help kcc<CR> + tlnoremenu 9999.75 &Help.-sep2- <Nop> + tlnoremenu 9999.80 &Help.&Version <C-W>:version<CR> + tlnoremenu 9999.90 &Help.&About <C-W>:intro<CR> +endif + fun! s:Helpfind() if !exists("g:menutrans_help_dialog") let g:menutrans_help_dialog = "Enter a command or word to find help on:\n\nPrepend i_ for Input mode commands (e.g.: i_CTRL-X)\nPrepend c_ for command-line editing commands (e.g.: c_<Del>)\nPrepend ' for an option name (e.g.: 'shiftwidth')" @@ -124,12 +139,7 @@ if has("diff") an 10.420 &File.Split\ Patched\ &By\.\.\. :browse vert diffpatch<CR> endif -if has("printer") - an 10.500 &File.-SEP3- <Nop> - an 10.510 &File.&Print :hardcopy<CR> - vunmenu &File.&Print - vnoremenu &File.&Print :hardcopy<CR> -elseif has("unix") +if has("unix") an 10.500 &File.-SEP3- <Nop> an 10.510 &File.&Print :w !lpr<CR> vunmenu &File.&Print @@ -702,6 +712,11 @@ func s:BMCanAdd(name, num) return 0 endif + " no name with control characters + if a:name =~ '[\x01-\x1f]' + return 0 + endif + " no special buffer, such as terminal or popup let buftype = getbufvar(a:num, '&buftype') if buftype != '' && buftype != 'nofile' && buftype != 'nowrite' @@ -1049,11 +1064,7 @@ if has("toolbar") an <silent> 1.20 ToolBar.Save :if expand("%") == ""<Bar>browse confirm w<Bar>else<Bar>confirm w<Bar>endif<CR> an 1.30 ToolBar.SaveAll :browse confirm wa<CR> - if has("printer") - an 1.40 ToolBar.Print :hardcopy<CR> - vunmenu ToolBar.Print - vnoremenu ToolBar.Print :hardcopy<CR> - elseif has("unix") + if has("unix") an 1.40 ToolBar.Print :w !lpr<CR> vunmenu ToolBar.Print vnoremenu ToolBar.Print :w !lpr<CR> diff --git a/cmake.packaging/neovim.ico b/runtime/neovim.ico Binary files differindex e0c151c966..e0c151c966 100644 --- a/cmake.packaging/neovim.ico +++ b/runtime/neovim.ico diff --git a/runtime/nvim.appdata.xml b/runtime/nvim.appdata.xml index e8d392ec7d..7411a7190a 100644 --- a/runtime/nvim.appdata.xml +++ b/runtime/nvim.appdata.xml @@ -26,6 +26,8 @@ </screenshots> <releases> + <release date="2022-12-29" version="0.8.2"/> + <release date="2022-11-14" version="0.8.1"/> <release date="2022-09-30" version="0.8.0"/> <release date="2022-04-15" version="0.7.0"/> <release date="2021-12-31" version="0.6.1"/> diff --git a/runtime/optwin.vim b/runtime/optwin.vim index 5f0bee6be4..200254321e 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -1,7 +1,7 @@ " These commands create the option window. " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2022 Oct 02 +" Last Change: 2022 Dec 16 " If there already is an option window, jump to that one. let buf = bufnr('option-window') @@ -20,7 +20,7 @@ let s:cpo_save = &cpo set cpo&vim " function to be called when <CR> is hit in the option-window -fun! <SID>CR() +func <SID>CR() " If on a continued comment line, go back to the first comment line let lnum = search("^[^\t]", 'bWcn') @@ -47,10 +47,10 @@ fun! <SID>CR() elseif match(line, '^ \=[0-9]') >= 0 exe "norm! /" . line . "\<CR>zt" endif -endfun +endfunc " function to be called when <Space> is hit in the option-window -fun! <SID>Space() +func <SID>Space() let lnum = line(".") let line = getline(lnum) @@ -67,16 +67,17 @@ fun! <SID>Space() endif endif -endfun +endfunc -let s:local_to_window = '(local to window)' -let s:local_to_buffer = '(local to buffer)' -let s:global_or_local = '(global or local to buffer)' +let s:local_to_window = gettext('(local to window)') +let s:local_to_buffer = gettext('(local to buffer)') +let s:global_or_local = gettext('(global or local to buffer)') " find the window in which the option applies " returns 0 for global option, 1 for local option, -1 for error -fun! <SID>Find(lnum) - if getline(a:lnum - 1) =~ "(local to" +func <SID>Find(lnum) + let line = getline(a:lnum - 1) + if line =~ s:local_to_window || line =~ s:local_to_buffer let local = 1 let thiswin = winnr() wincmd p @@ -95,10 +96,10 @@ fun! <SID>Find(lnum) let local = -1 endif return local -endfun +endfunc " Update a "set" line in the option window -fun! <SID>Update(lnum, line, local, thiswin) +func <SID>Update(lnum, line, local, thiswin) " get the new value of the option and update the option window line if match(a:line, "=") >= 0 let name = substitute(a:line, '^ \tset \([^=]*\)=.*', '\1', "") @@ -123,7 +124,7 @@ fun! <SID>Update(lnum, line, local, thiswin) endif endif set nomodified -endfun +endfunc " Reset 'title' and 'icon' to make it work faster. " Reset 'undolevels' to avoid undo'ing until the buffer is empty. @@ -149,13 +150,13 @@ exe $OPTWIN_CMD . ' new option-window' setlocal ts=15 tw=0 noro buftype=nofile " Insert help and a "set" command for each option. -call append(0, '" Each "set" line shows the current value of an option (on the left).') -call append(1, '" Hit <CR> on a "set" line to execute it.') -call append(2, '" A boolean option will be toggled.') -call append(3, '" For other options you can edit the value before hitting <CR>.') -call append(4, '" Hit <CR> on a help line to open a help window on this option.') -call append(5, '" Hit <CR> on an index line to jump there.') -call append(6, '" Hit <Space> on a "set" line to refresh it.') +call append(0, gettext('" Each "set" line shows the current value of an option (on the left).')) +call append(1, gettext('" Hit <Enter> on a "set" line to execute it.')) +call append(2, gettext('" A boolean option will be toggled.')) +call append(3, gettext('" For other options you can edit the value before hitting <Enter>.')) +call append(4, gettext('" Hit <Enter> on a help line to open a help window on this option.')) +call append(5, gettext('" Hit <Enter> on an index line to jump there.')) +call append(6, gettext('" Hit <Space> on a "set" line to refresh it.')) " These functions are called often below. Keep them fast! @@ -170,34 +171,34 @@ func <SID>AddOption(name, text) endfunc " Init a local binary option -fun! <SID>BinOptionL(name) +func <SID>BinOptionL(name) let val = getwinvar(winnr('#'), '&' . a:name) call append("$", substitute(substitute(" \tset " . val . a:name . "\t" . \!val . a:name, "0", "no", ""), "1", "", "")) -endfun +endfunc " Init a global binary option -fun! <SID>BinOptionG(name, val) +func <SID>BinOptionG(name, val) call append("$", substitute(substitute(" \tset " . a:val . a:name . "\t" . \!a:val . a:name, "0", "no", ""), "1", "", "")) -endfun +endfunc " Init a local string option -fun! <SID>OptionL(name) +func <SID>OptionL(name) let val = escape(getwinvar(winnr('#'), '&' . a:name), " \t\\\"|") call append("$", " \tset " . a:name . "=" . val) -endfun +endfunc " Init a global string option -fun! <SID>OptionG(name, val) +func <SID>OptionG(name, val) call append("$", " \tset " . a:name . "=" . escape(a:val, " \t\\\"|")) -endfun +endfunc let s:idx = 1 let s:lnum = line("$") call append("$", "") -fun! <SID>Header(text) +func <SID>Header(text) let line = s:idx . " " . a:text if s:idx < 10 let line = " " . line @@ -208,15 +209,15 @@ fun! <SID>Header(text) call append(s:lnum, line) let s:idx = s:idx + 1 let s:lnum = s:lnum + 1 -endfun +endfunc " Get the value of 'pastetoggle'. It could be a special key. -fun! <SID>PTvalue() +func <SID>PTvalue() redir @a silent set pt redir END return substitute(@a, '[^=]*=\(.*\)', '\1', "") -endfun +endfunc " Restore the previous value of 'cpoptions' here, it's used below. let &cpo = s:cpo_save @@ -224,1056 +225,1016 @@ let &cpo = s:cpo_save " List of all options, organized by function. " The text should be sufficient to know what the option is used for. -call <SID>Header("important") -call append("$", "compatible\tbehave very Vi compatible (not advisable)") +call <SID>Header(gettext("important")) +call <SID>AddOption("compatible", gettext("behave very Vi compatible (not advisable)")) call <SID>BinOptionG("cp", &cp) -call append("$", "cpoptions\tlist of flags to specify Vi compatibility") +call <SID>AddOption("cpoptions", gettext("list of flags to specify Vi compatibility")) call <SID>OptionG("cpo", &cpo) -call append("$", "paste\tpaste mode, insert typed text literally") +call <SID>AddOption("paste", gettext("paste mode, insert typed text literally")) call <SID>BinOptionG("paste", &paste) -call append("$", "pastetoggle\tkey sequence to toggle paste mode") +call <SID>AddOption("pastetoggle", gettext("key sequence to toggle paste mode")) if &pt =~ "\x80" call append("$", " \tset pt=" . <SID>PTvalue()) else call <SID>OptionG("pt", &pt) endif -call append("$", "runtimepath\tlist of directories used for runtime files and plugins") +call <SID>AddOption("runtimepath", gettext("list of directories used for runtime files and plugins")) call <SID>OptionG("rtp", &rtp) -call append("$", "packpath\tlist of directories used for plugin packages") +call <SID>AddOption("packpath", gettext("list of directories used for plugin packages")) call <SID>OptionG("pp", &pp) -call append("$", "helpfile\tname of the main help file") +call <SID>AddOption("helpfile", gettext("name of the main help file")) call <SID>OptionG("hf", &hf) -call <SID>Header("moving around, searching and patterns") -call append("$", "whichwrap\tlist of flags specifying which commands wrap to another line") -call append("$", "\t(local to window)") -call <SID>OptionL("ww") -call append("$", "startofline\tmany jump commands move the cursor to the first non-blank") -call append("$", "\tcharacter of a line") +call <SID>Header(gettext("moving around, searching and patterns")) +call <SID>AddOption("whichwrap", gettext("list of flags specifying which commands wrap to another line")) +call <SID>OptionG("ww", &ww) +call <SID>AddOption("startofline", gettext("many jump commands move the cursor to the first non-blank\ncharacter of a line")) call <SID>BinOptionG("sol", &sol) -call append("$", "paragraphs\tnroff macro names that separate paragraphs") +call <SID>AddOption("paragraphs", gettext("nroff macro names that separate paragraphs")) call <SID>OptionG("para", ¶) -call append("$", "sections\tnroff macro names that separate sections") +call <SID>AddOption("sections", gettext("nroff macro names that separate sections")) call <SID>OptionG("sect", §) -call append("$", "path\tlist of directory names used for file searching") -call append("$", "\t(global or local to buffer)") +call <SID>AddOption("path", gettext("list of directory names used for file searching")) +call append("$", "\t" .. s:global_or_local) call <SID>OptionG("pa", &pa) -call <SID>AddOption("cdhome", ":cd without argument goes to the home directory") +call <SID>AddOption("cdhome", gettext(":cd without argument goes to the home directory")) call <SID>BinOptionG("cdh", &cdh) -call append("$", "cdpath\tlist of directory names used for :cd") +call <SID>AddOption("cdpath", gettext("list of directory names used for :cd")) call <SID>OptionG("cd", &cd) if exists("+autochdir") - call append("$", "autochdir\tchange to directory of file in buffer") + call <SID>AddOption("autochdir", gettext("change to directory of file in buffer")) call <SID>BinOptionG("acd", &acd) endif -call append("$", "wrapscan\tsearch commands wrap around the end of the buffer") +call <SID>AddOption("wrapscan", gettext("search commands wrap around the end of the buffer")) call <SID>BinOptionG("ws", &ws) -call append("$", "incsearch\tshow match for partly typed search command") +call <SID>AddOption("incsearch", gettext("show match for partly typed search command")) call <SID>BinOptionG("is", &is) -call append("$", "magic\tchange the way backslashes are used in search patterns") +call <SID>AddOption("magic", gettext("change the way backslashes are used in search patterns")) call <SID>BinOptionG("magic", &magic) -call append("$", "regexpengine\tselect the default regexp engine used") +call <SID>AddOption("regexpengine", gettext("select the default regexp engine used")) call <SID>OptionG("re", &re) -call append("$", "ignorecase\tignore case when using a search pattern") +call <SID>AddOption("ignorecase", gettext("ignore case when using a search pattern")) call <SID>BinOptionG("ic", &ic) -call append("$", "smartcase\toverride 'ignorecase' when pattern has upper case characters") +call <SID>AddOption("smartcase", gettext("override 'ignorecase' when pattern has upper case characters")) call <SID>BinOptionG("scs", &scs) -call append("$", "casemap\twhat method to use for changing case of letters") +call <SID>AddOption("casemap", gettext("what method to use for changing case of letters")) call <SID>OptionG("cmp", &cmp) -call append("$", "maxmempattern\tmaximum amount of memory in Kbyte used for pattern matching") +call <SID>AddOption("maxmempattern", gettext("maximum amount of memory in Kbyte used for pattern matching")) call append("$", " \tset mmp=" . &mmp) -call append("$", "define\tpattern for a macro definition line") -call append("$", "\t(global or local to buffer)") +call <SID>AddOption("define", gettext("pattern for a macro definition line")) +call append("$", "\t" .. s:global_or_local) call <SID>OptionG("def", &def) if has("find_in_path") - call append("$", "include\tpattern for an include-file line") - call append("$", "\t(local to buffer)") + call <SID>AddOption("include", gettext("pattern for an include-file line")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("inc") - call append("$", "includeexpr\texpression used to transform an include line to a file name") - call append("$", "\t(local to buffer)") + call <SID>AddOption("includeexpr", gettext("expression used to transform an include line to a file name")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("inex") endif -call <SID>Header("tags") -call append("$", "tagbsearch\tuse binary searching in tags files") +call <SID>Header(gettext("tags")) +call <SID>AddOption("tagbsearch", gettext("use binary searching in tags files")) call <SID>BinOptionG("tbs", &tbs) -call append("$", "taglength\tnumber of significant characters in a tag name or zero") +call <SID>AddOption("taglength", gettext("number of significant characters in a tag name or zero")) call append("$", " \tset tl=" . &tl) -call append("$", "tags\tlist of file names to search for tags") -call append("$", "\t(global or local to buffer)") +call <SID>AddOption("tags", gettext("list of file names to search for tags")) +call append("$", "\t" .. s:global_or_local) call <SID>OptionG("tag", &tag) -call append("$", "tagcase\thow to handle case when searching in tags files:") -call append("$", "\t\"followic\" to follow 'ignorecase', \"ignore\" or \"match\"") -call append("$", "\t(global or local to buffer)") +call <SID>AddOption("tagcase", gettext("how to handle case when searching in tags files:\n\"followic\" to follow 'ignorecase', \"ignore\" or \"match\"")) +call append("$", "\t" .. s:global_or_local) call <SID>OptionG("tc", &tc) -call append("$", "tagrelative\tfile names in a tags file are relative to the tags file") +call <SID>AddOption("tagrelative", gettext("file names in a tags file are relative to the tags file")) call <SID>BinOptionG("tr", &tr) -call append("$", "tagstack\ta :tag command will use the tagstack") +call <SID>AddOption("tagstack", gettext("a :tag command will use the tagstack")) call <SID>BinOptionG("tgst", &tgst) -call append("$", "showfulltag\twhen completing tags in Insert mode show more info") +call <SID>AddOption("showfulltag", gettext("when completing tags in Insert mode show more info")) call <SID>BinOptionG("sft", &sft) if has("eval") - call append("$", "tagfunc\ta function to be used to perform tag searches") - call append("$", "\t(local to buffer)") + call <SID>AddOption("tagfunc", gettext("a function to be used to perform tag searches")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("tfu") endif -if has("cscope") - call append("$", "cscopeprg\tcommand for executing cscope") - call <SID>OptionG("csprg", &csprg) - call append("$", "cscopetag\tuse cscope for tag commands") - call <SID>BinOptionG("cst", &cst) - call append("$", "cscopetagorder\t0 or 1; the order in which \":cstag\" performs a search") - call append("$", " \tset csto=" . &csto) - call append("$", "cscopeverbose\tgive messages when adding a cscope database") - call <SID>BinOptionG("csverb", &csverb) - call append("$", "cscopepathcomp\thow many components of the path to show") - call append("$", " \tset cspc=" . &cspc) - call append("$", "cscopequickfix\twhen to open a quickfix window for cscope") - call <SID>OptionG("csqf", &csqf) - call append("$", "cscoperelative\tfile names in a cscope file are relative to that file") - call <SID>BinOptionG("csre", &csre) -endif -call <SID>Header("displaying text") -call append("$", "scroll\tnumber of lines to scroll for CTRL-U and CTRL-D") -call append("$", "\t(local to window)") +call <SID>Header(gettext("displaying text")) +call <SID>AddOption("scroll", gettext("number of lines to scroll for CTRL-U and CTRL-D")) +call append("$", "\t" .. s:local_to_window) call <SID>OptionL("scr") -call append("$", "scrolloff\tnumber of screen lines to show around the cursor") +call <SID>AddOption("scrolloff", gettext("number of screen lines to show around the cursor")) call append("$", " \tset so=" . &so) -call append("$", "wrap\tlong lines wrap") -call append("$", "\t(local to window)") +call <SID>AddOption("wrap", gettext("long lines wrap")) +call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("wrap") -call append("$", "linebreak\twrap long lines at a character in 'breakat'") -call append("$", "\t(local to window)") +call <SID>AddOption("linebreak", gettext("wrap long lines at a character in 'breakat'")) +call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("lbr") -call append("$", "breakindent\tpreserve indentation in wrapped text") -call append("$", "\t(local to window)") +call <SID>AddOption("breakindent", gettext("preserve indentation in wrapped text")) +call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("bri") -call append("$", "breakindentopt\tadjust breakindent behaviour") -call append("$", "\t(local to window)") +call <SID>AddOption("breakindentopt", gettext("adjust breakindent behaviour")) +call append("$", "\t" .. s:local_to_window) call <SID>OptionL("briopt") -call append("$", "breakat\twhich characters might cause a line break") +call <SID>AddOption("breakat", gettext("which characters might cause a line break")) call <SID>OptionG("brk", &brk) -call append("$", "showbreak\tstring to put before wrapped screen lines") +call <SID>AddOption("showbreak", gettext("string to put before wrapped screen lines")) call <SID>OptionG("sbr", &sbr) -call append("$", "sidescroll\tminimal number of columns to scroll horizontally") +call <SID>AddOption("sidescroll", gettext("minimal number of columns to scroll horizontally")) call append("$", " \tset ss=" . &ss) -call append("$", "sidescrolloff\tminimal number of columns to keep left and right of the cursor") +call <SID>AddOption("sidescrolloff", gettext("minimal number of columns to keep left and right of the cursor")) call append("$", " \tset siso=" . &siso) -call append("$", "display\tinclude \"lastline\" to show the last line even if it doesn't fit") -call append("$", "\tinclude \"uhex\" to show unprintable characters as a hex number") +call <SID>AddOption("display", gettext("include \"lastline\" to show the last line even if it doesn't fit\ninclude \"uhex\" to show unprintable characters as a hex number")) call <SID>OptionG("dy", &dy) -call append("$", "fillchars\tcharacters to use for the status line, folds and filler lines") +call <SID>AddOption("fillchars", gettext("characters to use for the status line, folds and filler lines")) call <SID>OptionG("fcs", &fcs) -call append("$", "cmdheight\tnumber of lines used for the command-line") +call <SID>AddOption("cmdheight", gettext("number of lines used for the command-line")) call append("$", " \tset ch=" . &ch) -call append("$", "columns\twidth of the display") +call <SID>AddOption("columns", gettext("width of the display")) call append("$", " \tset co=" . &co) -call append("$", "lines\tnumber of lines in the display") +call <SID>AddOption("lines", gettext("number of lines in the display")) call append("$", " \tset lines=" . &lines) -call append("$", "window\tnumber of lines to scroll for CTRL-F and CTRL-B") +call <SID>AddOption("window", gettext("number of lines to scroll for CTRL-F and CTRL-B")) call append("$", " \tset window=" . &window) -call append("$", "lazyredraw\tdon't redraw while executing macros") +call <SID>AddOption("lazyredraw", gettext("don't redraw while executing macros")) call <SID>BinOptionG("lz", &lz) if has("reltime") - call append("$", "redrawtime\ttimeout for 'hlsearch' and :match highlighting in msec") + call <SID>AddOption("redrawtime", gettext("timeout for 'hlsearch' and :match highlighting in msec")) call append("$", " \tset rdt=" . &rdt) endif -call append("$", "writedelay\tdelay in msec for each char written to the display") -call append("$", "\t(for debugging)") +call <SID>AddOption("writedelay", gettext("delay in msec for each char written to the display\n(for debugging)")) call append("$", " \tset wd=" . &wd) -call append("$", "list\tshow <Tab> as ^I and end-of-line as $") -call append("$", "\t(local to window)") +call <SID>AddOption("list", gettext("show <Tab> as ^I and end-of-line as $")) +call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("list") -call append("$", "listchars\tlist of strings used for list mode") +call <SID>AddOption("listchars", gettext("list of strings used for list mode")) call <SID>OptionG("lcs", &lcs) -call append("$", "number\tshow the line number for each line") -call append("$", "\t(local to window)") +call <SID>AddOption("number", gettext("show the line number for each line")) +call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("nu") -call append("$", "relativenumber\tshow the relative line number for each line") -call append("$", "\t(local to window)") +call <SID>AddOption("relativenumber", gettext("show the relative line number for each line")) +call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("rnu") if has("linebreak") - call append("$", "numberwidth\tnumber of columns to use for the line number") - call append("$", "\t(local to window)") + call <SID>AddOption("numberwidth", gettext("number of columns to use for the line number")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("nuw") endif if has("conceal") - call append("$", "conceallevel\tcontrols whether concealable text is hidden") - call append("$", "\t(local to window)") + call <SID>AddOption("conceallevel", gettext("controls whether concealable text is hidden")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("cole") - call append("$", "concealcursor\tmodes in which text in the cursor line can be concealed") - call append("$", "\t(local to window)") + call <SID>AddOption("concealcursor", gettext("modes in which text in the cursor line can be concealed")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("cocu") endif -call <SID>Header("syntax, highlighting and spelling") -call append("$", "background\t\"dark\" or \"light\"; the background color brightness") +call <SID>Header(gettext("syntax, highlighting and spelling")) +call <SID>AddOption("background", gettext("\"dark\" or \"light\"; the background color brightness")) call <SID>OptionG("bg", &bg) -call append("$", "filetype\ttype of file; triggers the FileType event when set") -call append("$", "\t(local to buffer)") +call <SID>AddOption("filetype", gettext("type of file; triggers the FileType event when set")) +call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("ft") if has("syntax") - call append("$", "syntax\tname of syntax highlighting used") - call append("$", "\t(local to buffer)") + call <SID>AddOption("syntax", gettext("name of syntax highlighting used")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("syn") - call append("$", "synmaxcol\tmaximum column to look for syntax items") - call append("$", "\t(local to buffer)") + call <SID>AddOption("synmaxcol", gettext("maximum column to look for syntax items")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("smc") endif -call append("$", "highlight\twhich highlighting to use for various occasions") +call <SID>AddOption("highlight", gettext("which highlighting to use for various occasions")) call <SID>OptionG("hl", &hl) -call append("$", "hlsearch\thighlight all matches for the last used search pattern") +call <SID>AddOption("hlsearch", gettext("highlight all matches for the last used search pattern")) call <SID>BinOptionG("hls", &hls) if has("termguicolors") - call append("$", "termguicolors\tuse GUI colors for the terminal") + call <SID>AddOption("termguicolors", gettext("use GUI colors for the terminal")) call <SID>BinOptionG("tgc", &tgc) endif if has("syntax") - call append("$", "cursorcolumn\thighlight the screen column of the cursor") - call append("$", "\t(local to window)") + call <SID>AddOption("cursorcolumn", gettext("highlight the screen column of the cursor")) + call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("cuc") - call append("$", "cursorline\thighlight the screen line of the cursor") - call append("$", "\t(local to window)") + call <SID>AddOption("cursorline", gettext("highlight the screen line of the cursor")) + call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("cul") - call append("$", "cursorlineopt\tspecifies which area 'cursorline' highlights") - call append("$", "\t(local to window)") + call <SID>AddOption("cursorlineopt", gettext("specifies which area 'cursorline' highlights")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("culopt") - call append("$", "colorcolumn\tcolumns to highlight") - call append("$", "\t(local to window)") + call <SID>AddOption("colorcolumn", gettext("columns to highlight")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("cc") - call append("$", "spell\thighlight spelling mistakes") - call append("$", "\t(local to window)") + call <SID>AddOption("spell", gettext("highlight spelling mistakes")) + call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("spell") - call append("$", "spelllang\tlist of accepted languages") - call append("$", "\t(local to buffer)") + call <SID>AddOption("spelllang", gettext("list of accepted languages")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("spl") - call append("$", "spellfile\tfile that \"zg\" adds good words to") - call append("$", "\t(local to buffer)") + call <SID>AddOption("spellfile", gettext("file that \"zg\" adds good words to")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("spf") - call append("$", "spellcapcheck\tpattern to locate the end of a sentence") - call append("$", "\t(local to buffer)") + call <SID>AddOption("spellcapcheck", gettext("pattern to locate the end of a sentence")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("spc") - call append("$", "spelloptions\tflags to change how spell checking works") - call append("$", "\t(local to buffer)") + call <SID>AddOption("spelloptions", gettext("flags to change how spell checking works")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("spo") - call append("$", "spellsuggest\tmethods used to suggest corrections") + call <SID>AddOption("spellsuggest", gettext("methods used to suggest corrections")) call <SID>OptionG("sps", &sps) - call append("$", "mkspellmem\tamount of memory used by :mkspell before compressing") + call <SID>AddOption("mkspellmem", gettext("amount of memory used by :mkspell before compressing")) call <SID>OptionG("msm", &msm) endif -call <SID>Header("multiple windows") -call append("$", "laststatus\t0, 1, 2 or 3; when to use a status line for the last window") +call <SID>Header(gettext("multiple windows")) +call <SID>AddOption("laststatus", gettext("0, 1, 2 or 3; when to use a status line for the last window")) call append("$", " \tset ls=" . &ls) if has("statusline") - call append("$", "statusline\talternate format to be used for a status line") + call <SID>AddOption("statuscolumn", gettext("custom format for the status column")) + call append("$", "\t" .. s:local_to_window) + call <SID>OptionG("stc", &stc) + call <SID>AddOption("statusline", gettext("alternate format to be used for a status line")) call <SID>OptionG("stl", &stl) endif -call append("$", "equalalways\tmake all windows the same size when adding/removing windows") +call <SID>AddOption("equalalways", gettext("make all windows the same size when adding/removing windows")) call <SID>BinOptionG("ea", &ea) -call append("$", "eadirection\tin which direction 'equalalways' works: \"ver\", \"hor\" or \"both\"") +call <SID>AddOption("eadirection", gettext("in which direction 'equalalways' works: \"ver\", \"hor\" or \"both\"")) call <SID>OptionG("ead", &ead) -call append("$", "winheight\tminimal number of lines used for the current window") +call <SID>AddOption("winheight", gettext("minimal number of lines used for the current window")) call append("$", " \tset wh=" . &wh) -call append("$", "winminheight\tminimal number of lines used for any window") +call <SID>AddOption("winminheight", gettext("minimal number of lines used for any window")) call append("$", " \tset wmh=" . &wmh) -call append("$", "winfixheight\tkeep the height of the window") -call append("$", "\t(local to window)") +call <SID>AddOption("winfixheight", gettext("keep the height of the window")) +call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("wfh") -call append("$", "winfixwidth\tkeep the width of the window") -call append("$", "\t(local to window)") +call <SID>AddOption("winfixwidth", gettext("keep the width of the window")) +call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("wfw") -call append("$", "winwidth\tminimal number of columns used for the current window") +call <SID>AddOption("winwidth", gettext("minimal number of columns used for the current window")) call append("$", " \tset wiw=" . &wiw) -call append("$", "winminwidth\tminimal number of columns used for any window") +call <SID>AddOption("winminwidth", gettext("minimal number of columns used for any window")) call append("$", " \tset wmw=" . &wmw) -call append("$", "helpheight\tinitial height of the help window") +call <SID>AddOption("helpheight", gettext("initial height of the help window")) call append("$", " \tset hh=" . &hh) if has("quickfix") - " call append("$", "previewpopup\tuse a popup window for preview") + " call <SID>AddOption("previewpopup", gettext("use a popup window for preview")) " call append("$", " \tset pvp=" . &pvp) - call append("$", "previewheight\tdefault height for the preview window") + call <SID>AddOption("previewheight", gettext("default height for the preview window")) call append("$", " \tset pvh=" . &pvh) - call append("$", "previewwindow\tidentifies the preview window") - call append("$", "\t(local to window)") + call <SID>AddOption("previewwindow", gettext("identifies the preview window")) + call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("pvw") endif -call append("$", "hidden\tdon't unload a buffer when no longer shown in a window") +call <SID>AddOption("hidden", gettext("don't unload a buffer when no longer shown in a window")) call <SID>BinOptionG("hid", &hid) -call append("$", "switchbuf\t\"useopen\" and/or \"split\"; which window to use when jumping") -call append("$", "\tto a buffer") +call <SID>AddOption("switchbuf", gettext("\"useopen\" and/or \"split\"; which window to use when jumping\nto a buffer")) call <SID>OptionG("swb", &swb) -call append("$", "splitbelow\ta new window is put below the current one") +call <SID>AddOption("splitbelow", gettext("a new window is put below the current one")) call <SID>BinOptionG("sb", &sb) -call append("$", "splitkeep\ta determines scroll behavior for split windows") +call <SID>AddOption("splitkeep", gettext("determines scroll behavior for split windows")) call <SID>BinOptionG("spk", &spk) -call append("$", "splitright\ta new window is put right of the current one") +call <SID>AddOption("splitright", gettext("a new window is put right of the current one")) call <SID>BinOptionG("spr", &spr) -call append("$", "scrollbind\tthis window scrolls together with other bound windows") -call append("$", "\t(local to window)") +call <SID>AddOption("scrollbind", gettext("this window scrolls together with other bound windows")) +call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("scb") -call append("$", "scrollopt\t\"ver\", \"hor\" and/or \"jump\"; list of options for 'scrollbind'") +call <SID>AddOption("scrollopt", gettext("\"ver\", \"hor\" and/or \"jump\"; list of options for 'scrollbind'")) call <SID>OptionG("sbo", &sbo) -call append("$", "cursorbind\tthis window's cursor moves together with other bound windows") -call append("$", "\t(local to window)") +call <SID>AddOption("cursorbind", gettext("this window's cursor moves together with other bound windows")) +call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("crb") if has("terminal") - call append("$", "termsize\tsize of a terminal window") - call append("$", "\t(local to window)") + call <SID>AddOption("termsize", gettext("size of a terminal window")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("tms") - call append("$", "termkey\tkey that precedes Vim commands in a terminal window") - call append("$", "\t(local to window)") + call <SID>AddOption("termkey", gettext("key that precedes Vim commands in a terminal window")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("tk") endif -call <SID>Header("multiple tab pages") -call append("$", "showtabline\t0, 1 or 2; when to use a tab pages line") +call <SID>Header(gettext("multiple tab pages")) +call <SID>AddOption("showtabline", gettext("0, 1 or 2; when to use a tab pages line")) call append("$", " \tset stal=" . &stal) -call append("$", "tabpagemax\tmaximum number of tab pages to open for -p and \"tab all\"") +call <SID>AddOption("tabpagemax", gettext("maximum number of tab pages to open for -p and \"tab all\"")) call append("$", " \tset tpm=" . &tpm) -call append("$", "tabline\tcustom tab pages line") +call <SID>AddOption("tabline", gettext("custom tab pages line")) call <SID>OptionG("tal", &tal) if has("gui") - call append("$", "guitablabel\tcustom tab page label for the GUI") + call <SID>AddOption("guitablabel", gettext("custom tab page label for the GUI")) call <SID>OptionG("gtl", >l) - call append("$", "guitabtooltip\tcustom tab page tooltip for the GUI") + call <SID>AddOption("guitabtooltip", gettext("custom tab page tooltip for the GUI")) call <SID>OptionG("gtt", >t) endif -call <SID>Header("terminal") -call append("$", "scrolljump\tminimal number of lines to scroll at a time") +call <SID>Header(gettext("terminal")) +call <SID>AddOption("scrolljump", gettext("minimal number of lines to scroll at a time")) call append("$", " \tset sj=" . &sj) if has("gui") || has("msdos") || has("win32") - call append("$", "guicursor\tspecifies what the cursor looks like in different modes") + call <SID>AddOption("guicursor", gettext("specifies what the cursor looks like in different modes")) call <SID>OptionG("gcr", &gcr) endif if has("title") let &title = s:old_title - call append("$", "title\tshow info in the window title") + call <SID>AddOption("title", gettext("show info in the window title")) call <SID>BinOptionG("title", &title) set notitle - call append("$", "titlelen\tpercentage of 'columns' used for the window title") + call <SID>AddOption("titlelen", gettext("percentage of 'columns' used for the window title")) call append("$", " \tset titlelen=" . &titlelen) - call append("$", "titlestring\twhen not empty, string to be used for the window title") + call <SID>AddOption("titlestring", gettext("when not empty, string to be used for the window title")) call <SID>OptionG("titlestring", &titlestring) - call append("$", "titleold\tstring to restore the title to when exiting Vim") + call <SID>AddOption("titleold", gettext("string to restore the title to when exiting Vim")) call <SID>OptionG("titleold", &titleold) let &icon = s:old_icon - call append("$", "icon\tset the text of the icon for this window") + call <SID>AddOption("icon", gettext("set the text of the icon for this window")) call <SID>BinOptionG("icon", &icon) set noicon - call append("$", "iconstring\twhen not empty, text for the icon of this window") + call <SID>AddOption("iconstring", gettext("when not empty, text for the icon of this window")) call <SID>OptionG("iconstring", &iconstring) endif -call <SID>Header("using the mouse") -call append("$", "mouse\tlist of flags for using the mouse") +call <SID>Header(gettext("using the mouse")) +call <SID>AddOption("mouse", gettext("list of flags for using the mouse")) call <SID>OptionG("mouse", &mouse) if has("gui") - call append("$", "mousefocus\tthe window with the mouse pointer becomes the current one") + call <SID>AddOption("mousefocus", gettext("the window with the mouse pointer becomes the current one")) call <SID>BinOptionG("mousef", &mousef) - call append("$", "mousehide\thide the mouse pointer while typing") + call <SID>AddOption("mousehide", gettext("hide the mouse pointer while typing")) call <SID>BinOptionG("mh", &mh) endif -call append("$", "mousemodel\t\"extend\", \"popup\" or \"popup_setpos\"; what the right") -call append("$", "\tmouse button is used for") +call <SID>AddOption("mousemodel", gettext("\"extend\", \"popup\" or \"popup_setpos\"; what the right\nmouse button is used for")) call <SID>OptionG("mousem", &mousem) -call append("$", "mousetime\tmaximum time in msec to recognize a double-click") +call <SID>AddOption("mousetime", gettext("maximum time in msec to recognize a double-click")) call append("$", " \tset mouset=" . &mouset) if has("mouseshape") - call append("$", "mouseshape\twhat the mouse pointer looks like in different modes") + call <SID>AddOption("mouseshape", gettext("what the mouse pointer looks like in different modes")) call <SID>OptionG("mouses", &mouses) endif if has("gui") - call <SID>Header("GUI") - call append("$", "guifont\tlist of font names to be used in the GUI") + call <SID>Header(gettext("GUI")) + call <SID>AddOption("guifont", gettext("list of font names to be used in the GUI")) call <SID>OptionG("gfn", &gfn) if has("xfontset") - call append("$", "guifontset\tpair of fonts to be used, for multibyte editing") + call <SID>AddOption("guifontset", gettext("pair of fonts to be used, for multibyte editing")) call <SID>OptionG("gfs", &gfs) endif - call append("$", "guifontwide\tlist of font names to be used for double-wide characters") + call <SID>AddOption("guifontwide", gettext("list of font names to be used for double-wide characters")) call <SID>OptionG("gfw", &gfw) - call append("$", "guioptions\tlist of flags that specify how the GUI works") + call <SID>AddOption("guioptions", gettext("list of flags that specify how the GUI works")) call <SID>OptionG("go", &go) if has("gui_gtk") - call append("$", "toolbar\t\"icons\", \"text\" and/or \"tooltips\"; how to show the toolbar") + call <SID>AddOption("toolbar", gettext("\"icons\", \"text\" and/or \"tooltips\"; how to show the toolbar")) call <SID>OptionG("tb", &tb) if has("gui_gtk2") - call append("$", "toolbariconsize\tsize of toolbar icons") + call <SID>AddOption("toolbariconsize", gettext("size of toolbar icons")) call <SID>OptionG("tbis", &tbis) endif endif if has("browse") - call append("$", "browsedir\t\"last\", \"buffer\" or \"current\": which directory used for the file browser") + call <SID>AddOption("browsedir", gettext("\"last\", \"buffer\" or \"current\": which directory used for the file browser")) call <SID>OptionG("bsdir", &bsdir) endif if has("multi_lang") - call append("$", "langmenu\tlanguage to be used for the menus") + call <SID>AddOption("langmenu", gettext("language to be used for the menus")) call <SID>OptionG("langmenu", &lm) endif - call append("$", "menuitems\tmaximum number of items in one menu") + call <SID>AddOption("menuitems", gettext("maximum number of items in one menu")) call append("$", " \tset mis=" . &mis) if has("winaltkeys") - call append("$", "winaltkeys\t\"no\", \"yes\" or \"menu\"; how to use the ALT key") + call <SID>AddOption("winaltkeys", gettext("\"no\", \"yes\" or \"menu\"; how to use the ALT key")) call <SID>OptionG("wak", &wak) endif - call append("$", "linespace\tnumber of pixel lines to use between characters") + call <SID>AddOption("linespace", gettext("number of pixel lines to use between characters")) call append("$", " \tset lsp=" . &lsp) if has("balloon_eval") || has("balloon_eval_term") - call append("$", "balloondelay\tdelay in milliseconds before a balloon may pop up") + call <SID>AddOption("balloondelay", gettext("delay in milliseconds before a balloon may pop up")) call append("$", " \tset bdlay=" . &bdlay) if has("balloon_eval") - call append("$", "ballooneval\tuse balloon evaluation in the GUI") + call <SID>AddOption("ballooneval", gettext("use balloon evaluation in the GUI")) call <SID>BinOptionG("beval", &beval) endif if has("balloon_eval_term") - call append("$", "balloonevalterm\tuse balloon evaluation in the terminal") + call <SID>AddOption("balloonevalterm", gettext("use balloon evaluation in the terminal")) call <SID>BinOptionG("bevalterm", &beval) endif if has("eval") - call append("$", "balloonexpr\texpression to show in balloon eval") + call <SID>AddOption("balloonexpr", gettext("expression to show in balloon eval")) call append("$", " \tset bexpr=" . &bexpr) endif endif endif -if has("printer") - call <SID>Header("printing") - call append("$", "printoptions\tlist of items that control the format of :hardcopy output") - call <SID>OptionG("popt", &popt) - call append("$", "printdevice\tname of the printer to be used for :hardcopy") - call <SID>OptionG("pdev", &pdev) - if has("postscript") - call append("$", "printexpr\texpression used to print the PostScript file for :hardcopy") - call <SID>OptionG("pexpr", &pexpr) - endif - call append("$", "printfont\tname of the font to be used for :hardcopy") - call <SID>OptionG("pfn", &pfn) - call append("$", "printheader\tformat of the header used for :hardcopy") - call <SID>OptionG("pheader", &pheader) - if has("postscript") - call append("$", "printencoding\tencoding used to print the PostScript file for :hardcopy") - call <SID>OptionG("penc", &penc) - endif - call append("$", "printmbcharset\tthe CJK character set to be used for CJK output from :hardcopy") - call <SID>OptionG("pmbcs", &pmbcs) - call append("$", "printmbfont\tlist of font names to be used for CJK output from :hardcopy") - call <SID>OptionG("pmbfn", &pmbfn) -endif - -call <SID>Header("messages and info") -call append("$", "terse\tadd 's' flag in 'shortmess' (don't show search message)") +call <SID>Header(gettext("messages and info")) +call <SID>AddOption("terse", gettext("add 's' flag in 'shortmess' (don't show search message)")) call <SID>BinOptionG("terse", &terse) -call append("$", "shortmess\tlist of flags to make messages shorter") +call <SID>AddOption("shortmess", gettext("list of flags to make messages shorter")) call <SID>OptionG("shm", &shm) -call append("$", "showcmd\tshow (partial) command keys in the status line") +call <SID>AddOption("showcmd", gettext("show (partial) command keys in location given by 'showcmdloc'")) let &sc = s:old_sc call <SID>BinOptionG("sc", &sc) set nosc -call append("$", "showmode\tdisplay the current mode in the status line") +call <SID>AddOption("showcmdloc", gettext("location where to show the (partial) command keys for 'showcmd'")) + call <SID>OptionG("sloc", &sloc) +call <SID>AddOption("showmode", gettext("display the current mode in the status line")) call <SID>BinOptionG("smd", &smd) -call append("$", "ruler\tshow cursor position below each window") +call <SID>AddOption("ruler", gettext("show cursor position below each window")) let &ru = s:old_ru call <SID>BinOptionG("ru", &ru) set noru if has("statusline") - call append("$", "rulerformat\talternate format to be used for the ruler") + call <SID>AddOption("rulerformat", gettext("alternate format to be used for the ruler")) call <SID>OptionG("ruf", &ruf) endif -call append("$", "report\tthreshold for reporting number of changed lines") +call <SID>AddOption("report", gettext("threshold for reporting number of changed lines")) call append("$", " \tset report=" . &report) -call append("$", "verbose\tthe higher the more messages are given") +call <SID>AddOption("verbose", gettext("the higher the more messages are given")) call append("$", " \tset vbs=" . &vbs) -call append("$", "verbosefile\tfile to write messages in") +call <SID>AddOption("verbosefile", gettext("file to write messages in")) call <SID>OptionG("vfile", &vfile) -call append("$", "more\tpause listings when the screen is full") +call <SID>AddOption("more", gettext("pause listings when the screen is full")) call <SID>BinOptionG("more", &more) if has("dialog_con") || has("dialog_gui") - call append("$", "confirm\tstart a dialog when a command fails") + call <SID>AddOption("confirm", gettext("start a dialog when a command fails")) call <SID>BinOptionG("cf", &cf) endif -call append("$", "errorbells\tring the bell for error messages") +call <SID>AddOption("errorbells", gettext("ring the bell for error messages")) call <SID>BinOptionG("eb", &eb) -call append("$", "visualbell\tuse a visual bell instead of beeping") +call <SID>AddOption("visualbell", gettext("use a visual bell instead of beeping")) call <SID>BinOptionG("vb", &vb) -call append("$", "belloff\tdo not ring the bell for these reasons") +call <SID>AddOption("belloff", gettext("do not ring the bell for these reasons")) call <SID>OptionG("belloff", &belloff) if has("multi_lang") - call append("$", "helplang\tlist of preferred languages for finding help") + call <SID>AddOption("helplang", gettext("list of preferred languages for finding help")) call <SID>OptionG("hlg", &hlg) endif -call <SID>Header("selecting text") -call append("$", "selection\t\"old\", \"inclusive\" or \"exclusive\"; how selecting text behaves") +call <SID>Header(gettext("selecting text")) +call <SID>AddOption("selection", gettext("\"old\", \"inclusive\" or \"exclusive\"; how selecting text behaves")) call <SID>OptionG("sel", &sel) -call append("$", "selectmode\t\"mouse\", \"key\" and/or \"cmd\"; when to start Select mode") -call append("$", "\tinstead of Visual mode") +call <SID>AddOption("selectmode", gettext("\"mouse\", \"key\" and/or \"cmd\"; when to start Select mode\ninstead of Visual mode")) call <SID>OptionG("slm", &slm) if has("clipboard") - call append("$", "clipboard\t\"unnamed\" to use the * register like unnamed register") - call append("$", "\t\"autoselect\" to always put selected text on the clipboard") + call <SID>AddOption("clipboard", gettext("\"unnamed\" to use the * register like unnamed register\n\"autoselect\" to always put selected text on the clipboard")) call <SID>OptionG("cb", &cb) endif -call append("$", "keymodel\t\"startsel\" and/or \"stopsel\"; what special keys can do") +call <SID>AddOption("keymodel", gettext("\"startsel\" and/or \"stopsel\"; what special keys can do")) call <SID>OptionG("km", &km) -call <SID>Header("editing text") -call append("$", "undolevels\tmaximum number of changes that can be undone") -call append("$", "\t(global or local to buffer)") +call <SID>Header(gettext("editing text")) +call <SID>AddOption("undolevels", gettext("maximum number of changes that can be undone")) +call append("$", "\t" .. s:global_or_local) call append("$", " \tset ul=" . s:old_ul) -call append("$", "undofile\tautomatically save and restore undo history") +call <SID>AddOption("undofile", gettext("automatically save and restore undo history")) call <SID>BinOptionG("udf", &udf) -call append("$", "undodir\tlist of directories for undo files") +call <SID>AddOption("undodir", gettext("list of directories for undo files")) call <SID>OptionG("udir", &udir) -call append("$", "undoreload\tmaximum number lines to save for undo on a buffer reload") +call <SID>AddOption("undoreload", gettext("maximum number lines to save for undo on a buffer reload")) call append("$", " \tset ur=" . &ur) -call append("$", "modified\tchanges have been made and not written to a file") +call <SID>AddOption("modified", gettext("changes have been made and not written to a file")) call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("mod") -call append("$", "readonly\tbuffer is not to be written") +call <SID>AddOption("readonly", gettext("buffer is not to be written")) call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("ro") -call <SID>AddOption("modifiable", "changes to the text are possible") +call <SID>AddOption("modifiable", gettext("changes to the text are possible")) call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("ma") -call append("$", "textwidth\tline length above which to break a line") +call <SID>AddOption("textwidth", gettext("line length above which to break a line")) call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("tw") -call append("$", "wrapmargin\tmargin from the right in which to break a line") +call <SID>AddOption("wrapmargin", gettext("margin from the right in which to break a line")) call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("wm") -call append("$", "backspace\tspecifies what <BS>, CTRL-W, etc. can do in Insert mode") +call <SID>AddOption("backspace", gettext("specifies what <BS>, CTRL-W, etc. can do in Insert mode")) call append("$", " \tset bs=" . &bs) -call append("$", "comments\tdefinition of what comment lines look like") -call append("$", "\t(local to buffer)") +call <SID>AddOption("comments", gettext("definition of what comment lines look like")) +call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("com") -call append("$", "formatoptions\tlist of flags that tell how automatic formatting works") -call append("$", "\t(local to buffer)") +call <SID>AddOption("formatoptions", gettext("list of flags that tell how automatic formatting works")) +call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("fo") -call append("$", "formatlistpat\tpattern to recognize a numbered list") -call append("$", "\t(local to buffer)") +call <SID>AddOption("formatlistpat", gettext("pattern to recognize a numbered list")) +call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("flp") if has("eval") - call append("$", "formatexpr\texpression used for \"gq\" to format lines") - call append("$", "\t(local to buffer)") + call <SID>AddOption("formatexpr", gettext("expression used for \"gq\" to format lines")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("fex") endif if has("insert_expand") - call append("$", "complete\tspecifies how Insert mode completion works for CTRL-N and CTRL-P") - call append("$", "\t(local to buffer)") + call <SID>AddOption("complete", gettext("specifies how Insert mode completion works for CTRL-N and CTRL-P")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("cpt") - call append("$", "completeopt\twhether to use a popup menu for Insert mode completion") + call <SID>AddOption("completeopt", gettext("whether to use a popup menu for Insert mode completion")) call <SID>OptionG("cot", &cot) - call append("$", "pumheight\tmaximum height of the popup menu") + call <SID>AddOption("pumheight", gettext("maximum height of the popup menu")) call <SID>OptionG("ph", &ph) - if exists("&pw") - call append("$", "pumwidth\tminimum width of the popup menu") - call <SID>OptionG("pw", &pw) - endif - call append("$", "completefunc\tuser defined function for Insert mode completion") - call append("$", "\t(local to buffer)") + call <SID>AddOption("pumwidth", gettext("minimum width of the popup menu")) + call <SID>OptionG("pw", &pw) + call <SID>AddOption("completefunc", gettext("user defined function for Insert mode completion")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("cfu") - call append("$", "omnifunc\tfunction for filetype-specific Insert mode completion") - call append("$", "\t(local to buffer)") + call <SID>AddOption("omnifunc", gettext("function for filetype-specific Insert mode completion")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("ofu") - call append("$", "dictionary\tlist of dictionary files for keyword completion") - call append("$", "\t(global or local to buffer)") + call <SID>AddOption("dictionary", gettext("list of dictionary files for keyword completion")) + call append("$", "\t" .. s:global_or_local) call <SID>OptionG("dict", &dict) - call append("$", "thesaurus\tlist of thesaurus files for keyword completion") - call append("$", "\t(global or local to buffer)") + call <SID>AddOption("thesaurus", gettext("list of thesaurus files for keyword completion")) + call append("$", "\t" .. s:global_or_local) call <SID>OptionG("tsr", &tsr) + call <SID>AddOption("thesaurusfunc", gettext("function used for thesaurus completion")) + call append("$", "\t" .. s:global_or_local) + call <SID>OptionG("tsrfu", &tsrfu) endif -call append("$", "infercase\tadjust case of a keyword completion match") -call append("$", "\t(local to buffer)") +call <SID>AddOption("infercase", gettext("adjust case of a keyword completion match")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("inf") if has("digraphs") - call append("$", "digraph\tenable entering digraphs with c1 <BS> c2") + call <SID>AddOption("digraph", gettext("enable entering digraphs with c1 <BS> c2")) call <SID>BinOptionG("dg", &dg) endif -call append("$", "tildeop\tthe \"~\" command behaves like an operator") +call <SID>AddOption("tildeop", gettext("the \"~\" command behaves like an operator")) call <SID>BinOptionG("top", &top) -call <SID>AddOption("operatorfunc", "function called for the \"g@\" operator") +call <SID>AddOption("operatorfunc", gettext("function called for the \"g@\" operator")) call <SID>OptionG("opfunc", &opfunc) -call append("$", "showmatch\twhen inserting a bracket, briefly jump to its match") +call <SID>AddOption("showmatch", gettext("when inserting a bracket, briefly jump to its match")) call <SID>BinOptionG("sm", &sm) -call append("$", "matchtime\ttenth of a second to show a match for 'showmatch'") +call <SID>AddOption("matchtime", gettext("tenth of a second to show a match for 'showmatch'")) call append("$", " \tset mat=" . &mat) -call append("$", "matchpairs\tlist of pairs that match for the \"%\" command") +call <SID>AddOption("matchpairs", gettext("list of pairs that match for the \"%\" command")) call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("mps") -call append("$", "joinspaces\tuse two spaces after '.' when joining a line") +call <SID>AddOption("joinspaces", gettext("use two spaces after '.' when joining a line")) call <SID>BinOptionG("js", &js) -call <SID>AddOption("nrformats", "\"alpha\", \"octal\", \"hex\", \"bin\" and/or \"unsigned\"; number formats\nrecognized for CTRL-A and CTRL-X commands") +call <SID>AddOption("nrformats", gettext("\"alpha\", \"octal\", \"hex\", \"bin\" and/or \"unsigned\"; number formats\nrecognized for CTRL-A and CTRL-X commands")) call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("nf") -call <SID>Header("tabs and indenting") -call append("$", "tabstop\tnumber of spaces a <Tab> in the text stands for") -call append("$", "\t(local to buffer)") +call <SID>Header(gettext("tabs and indenting")) +call <SID>AddOption("tabstop", gettext("number of spaces a <Tab> in the text stands for")) +call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("ts") -call append("$", "shiftwidth\tnumber of spaces used for each step of (auto)indent") -call append("$", "\t(local to buffer)") +call <SID>AddOption("shiftwidth", gettext("number of spaces used for each step of (auto)indent")) +call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("sw") if has("vartabs") - call append("$", "vartabstop\tlist of number of spaces a tab counts for") - call append("$", "\t(local to buffer)") + call <SID>AddOption("vartabstop", gettext("list of number of spaces a tab counts for")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("vts") - call append("$", "varsofttabstop\tlist of number of spaces a soft tabsstop counts for") - call append("$", "\t(local to buffer)") + call <SID>AddOption("varsofttabstop", gettext("list of number of spaces a soft tabsstop counts for")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("vsts") endif -call append("$", "smarttab\ta <Tab> in an indent inserts 'shiftwidth' spaces") +call <SID>AddOption("smarttab", gettext("a <Tab> in an indent inserts 'shiftwidth' spaces")) call <SID>BinOptionG("sta", &sta) -call append("$", "softtabstop\tif non-zero, number of spaces to insert for a <Tab>") -call append("$", "\t(local to buffer)") +call <SID>AddOption("softtabstop", gettext("if non-zero, number of spaces to insert for a <Tab>")) +call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("sts") -call append("$", "shiftround\tround to 'shiftwidth' for \"<<\" and \">>\"") +call <SID>AddOption("shiftround", gettext("round to 'shiftwidth' for \"<<\" and \">>\"")) call <SID>BinOptionG("sr", &sr) -call append("$", "expandtab\texpand <Tab> to spaces in Insert mode") -call append("$", "\t(local to buffer)") +call <SID>AddOption("expandtab", gettext("expand <Tab> to spaces in Insert mode")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("et") -call append("$", "autoindent\tautomatically set the indent of a new line") -call append("$", "\t(local to buffer)") +call <SID>AddOption("autoindent", gettext("automatically set the indent of a new line")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("ai") if has("smartindent") - call append("$", "smartindent\tdo clever autoindenting") - call append("$", "\t(local to buffer)") + call <SID>AddOption("smartindent", gettext("do clever autoindenting")) + call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("si") endif if has("cindent") - call append("$", "cindent\tenable specific indenting for C code") - call append("$", "\t(local to buffer)") + call <SID>AddOption("cindent", gettext("enable specific indenting for C code")) + call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("cin") - call append("$", "cinoptions\toptions for C-indenting") - call append("$", "\t(local to buffer)") + call <SID>AddOption("cinoptions", gettext("options for C-indenting")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("cino") - call append("$", "cinkeys\tkeys that trigger C-indenting in Insert mode") - call append("$", "\t(local to buffer)") + call <SID>AddOption("cinkeys", gettext("keys that trigger C-indenting in Insert mode")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("cink") - call append("$", "cinwords\tlist of words that cause more C-indent") - call append("$", "\t(local to buffer)") + call <SID>AddOption("cinwords", gettext("list of words that cause more C-indent")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("cinw") - call append("$", "cinscopedecls\tlist of scope declaration names used by cino-g") - call append("$", "\t(local to buffer)") + call <SID>AddOption("cinscopedecls", gettext("list of scope declaration names used by cino-g")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("cinsd") - call append("$", "indentexpr\texpression used to obtain the indent of a line") - call append("$", "\t(local to buffer)") + call <SID>AddOption("indentexpr", gettext("expression used to obtain the indent of a line")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("inde") - call append("$", "indentkeys\tkeys that trigger indenting with 'indentexpr' in Insert mode") - call append("$", "\t(local to buffer)") + call <SID>AddOption("indentkeys", gettext("keys that trigger indenting with 'indentexpr' in Insert mode")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("indk") endif -call append("$", "copyindent\tcopy whitespace for indenting from previous line") -call append("$", "\t(local to buffer)") +call <SID>AddOption("copyindent", gettext("copy whitespace for indenting from previous line")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("ci") -call append("$", "preserveindent\tpreserve kind of whitespace when changing indent") -call append("$", "\t(local to buffer)") +call <SID>AddOption("preserveindent", gettext("preserve kind of whitespace when changing indent")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("pi") if has("lispindent") - call append("$", "lisp\tenable lisp mode") - call append("$", "\t(local to buffer)") + call <SID>AddOption("lisp", gettext("enable lisp mode")) + call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("lisp") - call append("$", "lispwords\twords that change how lisp indenting works") + call <SID>AddOption("lispwords", gettext("words that change how lisp indenting works")) call <SID>OptionL("lw") + call <SID>AddOption("lispoptions", gettext("options for Lisp indenting")) + call <SID>OptionL("lop") endif if has("folding") - call <SID>Header("folding") - call <SID>AddOption("foldenable", "unset to display all folds open") + call <SID>Header(gettext("folding")) + call <SID>AddOption("foldenable", gettext("unset to display all folds open")) call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("fen") - call append("$", "foldlevel\tfolds with a level higher than this number will be closed") + call <SID>AddOption("foldlevel", gettext("folds with a level higher than this number will be closed")) call append("$", "\t" .. s:local_to_window) call <SID>OptionL("fdl") - call append("$", "foldlevelstart\tvalue for 'foldlevel' when starting to edit a file") + call <SID>AddOption("foldlevelstart", gettext("value for 'foldlevel' when starting to edit a file")) call append("$", " \tset fdls=" . &fdls) - call append("$", "foldcolumn\twidth of the column used to indicate folds") - call append("$", "\t(local to window)") + call <SID>AddOption("foldcolumn", gettext("width of the column used to indicate folds")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("fdc") - call append("$", "foldtext\texpression used to display the text of a closed fold") - call append("$", "\t(local to window)") + call <SID>AddOption("foldtext", gettext("expression used to display the text of a closed fold")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("fdt") - call append("$", "foldclose\tset to \"all\" to close a fold when the cursor leaves it") + call <SID>AddOption("foldclose", gettext("set to \"all\" to close a fold when the cursor leaves it")) call <SID>OptionG("fcl", &fcl) - call append("$", "foldopen\tspecifies for which commands a fold will be opened") + call <SID>AddOption("foldopen", gettext("specifies for which commands a fold will be opened")) call <SID>OptionG("fdo", &fdo) - call append("$", "foldminlines\tminimum number of screen lines for a fold to be closed") - call append("$", "\t(local to window)") + call <SID>AddOption("foldminlines", gettext("minimum number of screen lines for a fold to be closed")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("fml") - call append("$", "commentstring\ttemplate for comments; used to put the marker in") + call <SID>AddOption("commentstring", gettext("template for comments; used to put the marker in")) call <SID>OptionL("cms") - call <SID>AddOption("foldmethod", "folding type: \"manual\", \"indent\", \"expr\", \"marker\",\n\"syntax\" or \"diff\"") + call <SID>AddOption("foldmethod", gettext("folding type: \"manual\", \"indent\", \"expr\", \"marker\",\n\"syntax\" or \"diff\"")) call append("$", "\t" .. s:local_to_window) call <SID>OptionL("fdm") - call append("$", "foldexpr\texpression used when 'foldmethod' is \"expr\"") + call <SID>AddOption("foldexpr", gettext("expression used when 'foldmethod' is \"expr\"")) call append("$", "\t" .. s:local_to_window) call <SID>OptionL("fde") - call append("$", "foldignore\tused to ignore lines when 'foldmethod' is \"indent\"") + call <SID>AddOption("foldignore", gettext("used to ignore lines when 'foldmethod' is \"indent\"")) call append("$", "\t" .. s:local_to_window) call <SID>OptionL("fdi") - call append("$", "foldmarker\tmarkers used when 'foldmethod' is \"marker\"") + call <SID>AddOption("foldmarker", gettext("markers used when 'foldmethod' is \"marker\"")) call append("$", "\t" .. s:local_to_window) call <SID>OptionL("fmr") - call append("$", "foldnestmax\tmaximum fold depth for when 'foldmethod' is \"indent\" or \"syntax\"") + call <SID>AddOption("foldnestmax", gettext("maximum fold depth for when 'foldmethod' is \"indent\" or \"syntax\"")) call append("$", "\t" .. s:local_to_window) call <SID>OptionL("fdn") endif if has("diff") - call <SID>Header("diff mode") - call append("$", "diff\tuse diff mode for the current window") - call append("$", "\t(local to window)") + call <SID>Header(gettext("diff mode")) + call <SID>AddOption("diff", gettext("use diff mode for the current window")) + call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("diff") - call append("$", "diffopt\toptions for using diff mode") + call <SID>AddOption("diffopt", gettext("options for using diff mode")) call <SID>OptionG("dip", &dip) - call append("$", "diffexpr\texpression used to obtain a diff file") + call <SID>AddOption("diffexpr", gettext("expression used to obtain a diff file")) call <SID>OptionG("dex", &dex) - call append("$", "patchexpr\texpression used to patch a file") + call <SID>AddOption("patchexpr", gettext("expression used to patch a file")) call <SID>OptionG("pex", &pex) endif -call <SID>Header("mapping") -call append("$", "maxmapdepth\tmaximum depth of mapping") +call <SID>Header(gettext("mapping")) +call <SID>AddOption("maxmapdepth", gettext("maximum depth of mapping")) call append("$", " \tset mmd=" . &mmd) -call append("$", "remap\trecognize mappings in mapped keys") -call append("$", "timeout\tallow timing out halfway into a mapping") +call <SID>AddOption("timeout", gettext("allow timing out halfway into a mapping")) call <SID>BinOptionG("to", &to) -call append("$", "ttimeout\tallow timing out halfway into a key code") +call <SID>AddOption("ttimeout", gettext("allow timing out halfway into a key code")) call <SID>BinOptionG("ttimeout", &ttimeout) -call append("$", "timeoutlen\ttime in msec for 'timeout'") +call <SID>AddOption("timeoutlen", gettext("time in msec for 'timeout'")) call append("$", " \tset tm=" . &tm) -call append("$", "ttimeoutlen\ttime in msec for 'ttimeout'") +call <SID>AddOption("ttimeoutlen", gettext("time in msec for 'ttimeout'")) call append("$", " \tset ttm=" . &ttm) -call <SID>Header("reading and writing files") -call append("$", "modeline\tenable using settings from modelines when reading a file") -call append("$", "\t(local to buffer)") +call <SID>Header(gettext("reading and writing files")) +call <SID>AddOption("modeline", gettext("enable using settings from modelines when reading a file")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("ml") -call append("$", "modelineexpr\tallow setting expression options from a modeline") +call <SID>AddOption("modelineexpr", gettext("allow setting expression options from a modeline")) call <SID>BinOptionG("mle", &mle) -call append("$", "modelines\tnumber of lines to check for modelines") +call <SID>AddOption("modelines", gettext("number of lines to check for modelines")) call append("$", " \tset mls=" . &mls) -call append("$", "binary\tbinary file editing") -call append("$", "\t(local to buffer)") +call <SID>AddOption("binary", gettext("binary file editing")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("bin") -call append("$", "endofline\tlast line in the file has an end-of-line") -call append("$", "\t(local to buffer)") +call <SID>AddOption("endofline", gettext("last line in the file has an end-of-line")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("eol") -call append("$", "fixendofline\tfixes missing end-of-line at end of text file") -call append("$", "\t(local to buffer)") +call <SID>AddOption("endoffile", gettext("last line in the file followed by CTRL-Z")) +call append("$", "\t" .. s:local_to_buffer) +call <SID>BinOptionL("eof") +call <SID>AddOption("fixendofline", gettext("fixes missing end-of-line at end of text file")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("fixeol") -call append("$", "bomb\tprepend a Byte Order Mark to the file") -call append("$", "\t(local to buffer)") +call <SID>AddOption("bomb", gettext("prepend a Byte Order Mark to the file")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("bomb") -call append("$", "fileformat\tend-of-line format: \"dos\", \"unix\" or \"mac\"") -call append("$", "\t(local to buffer)") +call <SID>AddOption("fileformat", gettext("end-of-line format: \"dos\", \"unix\" or \"mac\"")) +call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("ff") -call append("$", "fileformats\tlist of file formats to look for when editing a file") +call <SID>AddOption("fileformats", gettext("list of file formats to look for when editing a file")) call <SID>OptionG("ffs", &ffs) -call append("$", "\t(local to buffer)") -call append("$", "write\twriting files is allowed") +call <SID>AddOption("write", gettext("writing files is allowed")) call <SID>BinOptionG("write", &write) -call append("$", "writebackup\twrite a backup file before overwriting a file") +call <SID>AddOption("writebackup", gettext("write a backup file before overwriting a file")) call <SID>BinOptionG("wb", &wb) -call append("$", "backup\tkeep a backup after overwriting a file") +call <SID>AddOption("backup", gettext("keep a backup after overwriting a file")) call <SID>BinOptionG("bk", &bk) -call append("$", "backupskip\tpatterns that specify for which files a backup is not made") +call <SID>AddOption("backupskip", gettext("patterns that specify for which files a backup is not made")) call append("$", " \tset bsk=" . &bsk) -call append("$", "backupcopy\twhether to make the backup as a copy or rename the existing file") -call append("$", "\t(global or local to buffer)") +call <SID>AddOption("backupcopy", gettext("whether to make the backup as a copy or rename the existing file")) +call append("$", "\t" .. s:global_or_local) call append("$", " \tset bkc=" . &bkc) -call append("$", "backupdir\tlist of directories to put backup files in") +call <SID>AddOption("backupdir", gettext("list of directories to put backup files in")) call <SID>OptionG("bdir", &bdir) -call append("$", "backupext\tfile name extension for the backup file") +call <SID>AddOption("backupext", gettext("file name extension for the backup file")) call <SID>OptionG("bex", &bex) -call append("$", "autowrite\tautomatically write a file when leaving a modified buffer") +call <SID>AddOption("autowrite", gettext("automatically write a file when leaving a modified buffer")) call <SID>BinOptionG("aw", &aw) -call append("$", "autowriteall\tas 'autowrite', but works with more commands") +call <SID>AddOption("autowriteall", gettext("as 'autowrite', but works with more commands")) call <SID>BinOptionG("awa", &awa) -call append("$", "writeany\talways write without asking for confirmation") +call <SID>AddOption("writeany", gettext("always write without asking for confirmation")) call <SID>BinOptionG("wa", &wa) -call append("$", "autoread\tautomatically read a file when it was modified outside of Vim") -call append("$", "\t(global or local to buffer)") +call <SID>AddOption("autoread", gettext("automatically read a file when it was modified outside of Vim")) +call append("$", "\t" .. s:global_or_local) call <SID>BinOptionG("ar", &ar) -call append("$", "patchmode\tkeep oldest version of a file; specifies file name extension") +call <SID>AddOption("patchmode", gettext("keep oldest version of a file; specifies file name extension")) call <SID>OptionG("pm", &pm) -call append("$", "fsync\tforcibly sync the file to disk after writing it") +call <SID>AddOption("fsync", gettext("forcibly sync the file to disk after writing it")) call <SID>BinOptionG("fs", &fs) -call <SID>Header("the swap file") -call append("$", "directory\tlist of directories for the swap file") +call <SID>Header(gettext("the swap file")) +call <SID>AddOption("directory", gettext("list of directories for the swap file")) call <SID>OptionG("dir", &dir) -call append("$", "swapfile\tuse a swap file for this buffer") -call append("$", "\t(local to buffer)") +call <SID>AddOption("swapfile", gettext("use a swap file for this buffer")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("swf") -call append("$", "updatecount\tnumber of characters typed to cause a swap file update") +call <SID>AddOption("updatecount", gettext("number of characters typed to cause a swap file update")) call append("$", " \tset uc=" . &uc) -call append("$", "updatetime\ttime in msec after which the swap file will be updated") +call <SID>AddOption("updatetime", gettext("time in msec after which the swap file will be updated")) call append("$", " \tset ut=" . &ut) -call <SID>Header("command line editing") -call <SID>AddOption("history", "how many command lines are remembered") +call <SID>Header(gettext("command line editing")) +call <SID>AddOption("history", gettext("how many command lines are remembered")) call append("$", " \tset hi=" . &hi) -call append("$", "wildchar\tkey that triggers command-line expansion") +call <SID>AddOption("wildchar", gettext("key that triggers command-line expansion")) call append("$", " \tset wc=" . &wc) -call append("$", "wildcharm\tlike 'wildchar' but can also be used in a mapping") +call <SID>AddOption("wildcharm", gettext("like 'wildchar' but can also be used in a mapping")) call append("$", " \tset wcm=" . &wcm) -call append("$", "wildmode\tspecifies how command line completion works") +call <SID>AddOption("wildmode", gettext("specifies how command line completion works")) call <SID>OptionG("wim", &wim) if has("wildoptions") - call append("$", "wildoptions\tempty or \"tagfile\" to list file name of matching tags") + call <SID>AddOption("wildoptions", gettext("empty or \"tagfile\" to list file name of matching tags")) call <SID>OptionG("wop", &wop) endif -call append("$", "suffixes\tlist of file name extensions that have a lower priority") +call <SID>AddOption("suffixes", gettext("list of file name extensions that have a lower priority")) call <SID>OptionG("su", &su) if has("file_in_path") - call append("$", "suffixesadd\tlist of file name extensions added when searching for a file") - call append("$", "\t(local to buffer)") + call <SID>AddOption("suffixesadd", gettext("list of file name extensions added when searching for a file")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("sua") endif if has("wildignore") - call append("$", "wildignore\tlist of patterns to ignore files for file name completion") + call <SID>AddOption("wildignore", gettext("list of patterns to ignore files for file name completion")) call <SID>OptionG("wig", &wig) endif -call append("$", "fileignorecase\tignore case when using file names") +call <SID>AddOption("fileignorecase", gettext("ignore case when using file names")) call <SID>BinOptionG("fic", &fic) -call append("$", "wildignorecase\tignore case when completing file names") +call <SID>AddOption("wildignorecase", gettext("ignore case when completing file names")) call <SID>BinOptionG("wic", &wic) if has("wildmenu") - call append("$", "wildmenu\tcommand-line completion shows a list of matches") + call <SID>AddOption("wildmenu", gettext("command-line completion shows a list of matches")) call <SID>BinOptionG("wmnu", &wmnu) endif -call append("$", "cedit\tkey used to open the command-line window") +call <SID>AddOption("cedit", gettext("key used to open the command-line window")) call <SID>OptionG("cedit", &cedit) -call append("$", "cmdwinheight\theight of the command-line window") +call <SID>AddOption("cmdwinheight", gettext("height of the command-line window")) call <SID>OptionG("cwh", &cwh) -call <SID>Header("executing external commands") -call append("$", "shell\tname of the shell program used for external commands") +call <SID>Header(gettext("executing external commands")) +call <SID>AddOption("shell", gettext("name of the shell program used for external commands")) call <SID>OptionG("sh", &sh) -call append("$", "shellquote\tcharacter(s) to enclose a shell command in") +call <SID>AddOption("shellquote", gettext("character(s) to enclose a shell command in")) call <SID>OptionG("shq", &shq) -call append("$", "shellxquote\tlike 'shellquote' but include the redirection") +call <SID>AddOption("shellxquote", gettext("like 'shellquote' but include the redirection")) call <SID>OptionG("sxq", &sxq) -call append("$", "shellxescape\tcharacters to escape when 'shellxquote' is (") +call <SID>AddOption("shellxescape", gettext("characters to escape when 'shellxquote' is (")) call <SID>OptionG("sxe", &sxe) -call append("$", "shellcmdflag\targument for 'shell' to execute a command") +call <SID>AddOption("shellcmdflag", gettext("argument for 'shell' to execute a command")) call <SID>OptionG("shcf", &shcf) -call append("$", "shellredir\tused to redirect command output to a file") +call <SID>AddOption("shellredir", gettext("used to redirect command output to a file")) call <SID>OptionG("srr", &srr) -call append("$", "shelltemp\tuse a temp file for shell commands instead of using a pipe") +call <SID>AddOption("shelltemp", gettext("use a temp file for shell commands instead of using a pipe")) call <SID>BinOptionG("stmp", &stmp) -call append("$", "equalprg\tprogram used for \"=\" command") -call append("$", "\t(global or local to buffer)") +call <SID>AddOption("equalprg", gettext("program used for \"=\" command")) +call append("$", "\t" .. s:global_or_local) call <SID>OptionG("ep", &ep) -call append("$", "formatprg\tprogram used to format lines with \"gq\" command") +call <SID>AddOption("formatprg", gettext("program used to format lines with \"gq\" command")) call <SID>OptionG("fp", &fp) -call append("$", "keywordprg\tprogram used for the \"K\" command") +call <SID>AddOption("keywordprg", gettext("program used for the \"K\" command")) call <SID>OptionG("kp", &kp) -call append("$", "warn\twarn when using a shell command and a buffer has changes") +call <SID>AddOption("warn", gettext("warn when using a shell command and a buffer has changes")) call <SID>BinOptionG("warn", &warn) if has("quickfix") - call <SID>Header("running make and jumping to errors (quickfix)") - call append("$", "errorfile\tname of the file that contains error messages") + call <SID>Header(gettext("running make and jumping to errors (quickfix)")) + call <SID>AddOption("errorfile", gettext("name of the file that contains error messages")) call <SID>OptionG("ef", &ef) - call append("$", "errorformat\tlist of formats for error messages") - call append("$", "\t(global or local to buffer)") + call <SID>AddOption("errorformat", gettext("list of formats for error messages")) + call append("$", "\t" .. s:global_or_local) call <SID>OptionG("efm", &efm) - call append("$", "makeprg\tprogram used for the \":make\" command") - call append("$", "\t(global or local to buffer)") + call <SID>AddOption("makeprg", gettext("program used for the \":make\" command")) + call append("$", "\t" .. s:global_or_local) call <SID>OptionG("mp", &mp) - call append("$", "shellpipe\tstring used to put the output of \":make\" in the error file") + call <SID>AddOption("shellpipe", gettext("string used to put the output of \":make\" in the error file")) call <SID>OptionG("sp", &sp) - call append("$", "makeef\tname of the errorfile for the 'makeprg' command") + call <SID>AddOption("makeef", gettext("name of the errorfile for the 'makeprg' command")) call <SID>OptionG("mef", &mef) - call append("$", "grepprg\tprogram used for the \":grep\" command") - call append("$", "\t(global or local to buffer)") + call <SID>AddOption("grepprg", gettext("program used for the \":grep\" command")) + call append("$", "\t" .. s:global_or_local) call <SID>OptionG("gp", &gp) - call append("$", "grepformat\tlist of formats for output of 'grepprg'") + call <SID>AddOption("grepformat", gettext("list of formats for output of 'grepprg'")) call <SID>OptionG("gfm", &gfm) - call append("$", "makeencoding\tencoding of the \":make\" and \":grep\" output") - call append("$", "\t(global or local to buffer)") + call <SID>AddOption("makeencoding", gettext("encoding of the \":make\" and \":grep\" output")) + call append("$", "\t" .. s:global_or_local) call <SID>OptionG("menc", &menc) endif if has("win32") - call <SID>Header("system specific") - call <SID>AddOption("shellslash", "use forward slashes in file names; for Unix-like shells") + call <SID>Header(gettext("system specific")) + call <SID>AddOption("shellslash", gettext("use forward slashes in file names; for Unix-like shells")) call <SID>BinOptionG("ssl", &ssl) - call <SID>AddOption("completeslash", "specifies slash/backslash used for completion") + call <SID>AddOption("completeslash", gettext("specifies slash/backslash used for completion")) call <SID>OptionG("csl", &csl) endif -call <SID>Header("language specific") -call append("$", "isfname\tspecifies the characters in a file name") +call <SID>Header(gettext("language specific")) +call <SID>AddOption("isfname", gettext("specifies the characters in a file name")) call <SID>OptionG("isf", &isf) -call append("$", "isident\tspecifies the characters in an identifier") +call <SID>AddOption("isident", gettext("specifies the characters in an identifier")) call <SID>OptionG("isi", &isi) -call append("$", "iskeyword\tspecifies the characters in a keyword") -call append("$", "\t(local to buffer)") +call <SID>AddOption("iskeyword", gettext("specifies the characters in a keyword")) +call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("isk") -call append("$", "isprint\tspecifies printable characters") +call <SID>AddOption("isprint", gettext("specifies printable characters")) call <SID>OptionG("isp", &isp) if has("textobjects") - call append("$", "quoteescape\tspecifies escape characters in a string") - call append("$", "\t(local to buffer)") + call <SID>AddOption("quoteescape", gettext("specifies escape characters in a string")) + call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("qe") endif if has("rightleft") - call append("$", "rightleft\tdisplay the buffer right-to-left") - call append("$", "\t(local to window)") + call <SID>AddOption("rightleft", gettext("display the buffer right-to-left")) + call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("rl") - call append("$", "rightleftcmd\twhen to edit the command-line right-to-left") - call append("$", "\t(local to window)") + call <SID>AddOption("rightleftcmd", gettext("when to edit the command-line right-to-left")) + call append("$", "\t" .. s:local_to_window) call <SID>OptionL("rlc") - call append("$", "revins\tinsert characters backwards") + call <SID>AddOption("revins", gettext("insert characters backwards")) call <SID>BinOptionG("ri", &ri) - call append("$", "allowrevins\tallow CTRL-_ in Insert and Command-line mode to toggle 'revins'") + call <SID>AddOption("allowrevins", gettext("allow CTRL-_ in Insert and Command-line mode to toggle 'revins'")) call <SID>BinOptionG("ari", &ari) - call append("$", "aleph\tthe ASCII code for the first letter of the Hebrew alphabet") + call <SID>AddOption("aleph", gettext("the ASCII code for the first letter of the Hebrew alphabet")) call append("$", " \tset al=" . &al) - call append("$", "hkmap\tuse Hebrew keyboard mapping") + call <SID>AddOption("hkmap", gettext("use Hebrew keyboard mapping")) call <SID>BinOptionG("hk", &hk) - call append("$", "hkmapp\tuse phonetic Hebrew keyboard mapping") + call <SID>AddOption("hkmapp", gettext("use phonetic Hebrew keyboard mapping")) call <SID>BinOptionG("hkp", &hkp) endif if has("arabic") - call append("$", "arabic\tprepare for editing Arabic text") - call append("$", "\t(local to window)") + call <SID>AddOption("arabic", gettext("prepare for editing Arabic text")) + call append("$", "\t" .. s:local_to_window) call <SID>BinOptionL("arab") - call append("$", "arabicshape\tperform shaping of Arabic characters") + call <SID>AddOption("arabicshape", gettext("perform shaping of Arabic characters")) call <SID>BinOptionG("arshape", &arshape) - call append("$", "termbidi\tterminal will perform bidi handling") + call <SID>AddOption("termbidi", gettext("terminal will perform bidi handling")) call <SID>BinOptionG("tbidi", &tbidi) endif if has("keymap") - call append("$", "keymap\tname of a keyboard mapping") + call <SID>AddOption("keymap", gettext("name of a keyboard mapping")) call <SID>OptionL("kmp") endif if has("langmap") - call append("$", "langmap\tlist of characters that are translated in Normal mode") + call <SID>AddOption("langmap", gettext("list of characters that are translated in Normal mode")) call <SID>OptionG("lmap", &lmap) - call append("$", "langremap\tapply 'langmap' to mapped characters") + call <SID>AddOption("langremap", gettext("apply 'langmap' to mapped characters")) call <SID>BinOptionG("lrm", &lrm) endif if has("xim") - call append("$", "imdisable\twhen set never use IM; overrules following IM options") + call <SID>AddOption("imdisable", gettext("when set never use IM; overrules following IM options")) call <SID>BinOptionG("imd", &imd) endif -call append("$", "iminsert\tin Insert mode: 1: use :lmap; 2: use IM; 0: neither") -call append("$", "\t(local to window)") +call <SID>AddOption("iminsert", gettext("in Insert mode: 1: use :lmap; 2: use IM; 0: neither")) +call append("$", "\t" .. s:local_to_window) call <SID>OptionL("imi") -call append("$", "imsearch\tentering a search pattern: 1: use :lmap; 2: use IM; 0: neither") -call append("$", "\t(local to window)") +call <SID>AddOption("imsearch", gettext("entering a search pattern: 1: use :lmap; 2: use IM; 0: neither")) +call append("$", "\t" .. s:local_to_window) call <SID>OptionL("ims") if has("xim") - call append("$", "imcmdline\twhen set always use IM when starting to edit a command line") + call <SID>AddOption("imcmdline", gettext("when set always use IM when starting to edit a command line")) call <SID>BinOptionG("imc", &imc) - call append("$", "imstatusfunc\tfunction to obtain IME status") + call <SID>AddOption("imstatusfunc", gettext("function to obtain IME status")) call <SID>OptionG("imsf", &imsf) - call append("$", "imactivatefunc\tfunction to enable/disable IME") + call <SID>AddOption("imactivatefunc", gettext("function to enable/disable IME")) call <SID>OptionG("imaf", &imaf) endif -call <SID>Header("multi-byte characters") -call <SID>AddOption("encoding", "character encoding used in Nvim: \"utf-8\"") +call <SID>Header(gettext("multi-byte characters")) +call <SID>AddOption("encoding", gettext("character encoding used in Nvim: \"utf-8\"")) call <SID>OptionG("enc", &enc) -call append("$", "fileencoding\tcharacter encoding for the current file") +call <SID>AddOption("fileencoding", gettext("character encoding for the current file")) call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("fenc") -call append("$", "fileencodings\tautomatically detected character encodings") +call <SID>AddOption("fileencodings", gettext("automatically detected character encodings")) call <SID>OptionG("fencs", &fencs) -call append("$", "charconvert\texpression used for character encoding conversion") +call <SID>AddOption("charconvert", gettext("expression used for character encoding conversion")) call <SID>OptionG("ccv", &ccv) -call append("$", "delcombine\tdelete combining (composing) characters on their own") +call <SID>AddOption("delcombine", gettext("delete combining (composing) characters on their own")) call <SID>BinOptionG("deco", &deco) -call append("$", "maxcombine\tmaximum number of combining (composing) characters displayed") +call <SID>AddOption("maxcombine", gettext("maximum number of combining (composing) characters displayed")) call <SID>OptionG("mco", &mco) if has("xim") && has("gui_gtk") - call append("$", "imactivatekey\tkey that activates the X input method") + call <SID>AddOption("imactivatekey", gettext("key that activates the X input method")) call <SID>OptionG("imak", &imak) endif -call append("$", "ambiwidth\twidth of ambiguous width characters") +call <SID>AddOption("ambiwidth", gettext("width of ambiguous width characters")) call <SID>OptionG("ambw", &ambw) -call append("$", "emoji\temoji characters are full width") +call <SID>AddOption("emoji", gettext("emoji characters are full width")) call <SID>BinOptionG("emo", &emo) -call <SID>Header("various") -call <SID>AddOption("virtualedit", "when to use virtual editing: \"block\", \"insert\", \"all\"\nand/or \"onemore\"") +call <SID>Header(gettext("various")) +call <SID>AddOption("virtualedit", gettext("when to use virtual editing: \"block\", \"insert\", \"all\"\nand/or \"onemore\"")) call <SID>OptionG("ve", &ve) -call append("$", "eventignore\tlist of autocommand events which are to be ignored") +call <SID>AddOption("eventignore", gettext("list of autocommand events which are to be ignored")) call <SID>OptionG("ei", &ei) -call append("$", "loadplugins\tload plugin scripts when starting up") +call <SID>AddOption("loadplugins", gettext("load plugin scripts when starting up")) call <SID>BinOptionG("lpl", &lpl) -call append("$", "exrc\tenable reading .vimrc/.exrc/.gvimrc in the current directory") +call <SID>AddOption("exrc", gettext("enable reading .vimrc/.exrc/.gvimrc in the current directory")) call <SID>BinOptionG("ex", &ex) -call append("$", "secure\tsafer working with script files in the current directory") +call <SID>AddOption("secure", gettext("safer working with script files in the current directory")) call <SID>BinOptionG("secure", &secure) -call append("$", "gdefault\tuse the 'g' flag for \":substitute\"") +call <SID>AddOption("gdefault", gettext("use the 'g' flag for \":substitute\"")) call <SID>BinOptionG("gd", &gd) if exists("+opendevice") - call append("$", "opendevice\tallow reading/writing devices") + call <SID>AddOption("opendevice", gettext("allow reading/writing devices")) call <SID>BinOptionG("odev", &odev) endif if exists("+maxfuncdepth") - call append("$", "maxfuncdepth\tmaximum depth of function calls") + call <SID>AddOption("maxfuncdepth", gettext("maximum depth of function calls")) call append("$", " \tset mfd=" . &mfd) endif if has("mksession") - call append("$", "sessionoptions\tlist of words that specifies what to put in a session file") + call <SID>AddOption("sessionoptions", gettext("list of words that specifies what to put in a session file")) call <SID>OptionG("ssop", &ssop) - call append("$", "viewoptions\tlist of words that specifies what to save for :mkview") + call <SID>AddOption("viewoptions", gettext("list of words that specifies what to save for :mkview")) call <SID>OptionG("vop", &vop) - call append("$", "viewdir\tdirectory where to store files with :mkview") + call <SID>AddOption("viewdir", gettext("directory where to store files with :mkview")) call <SID>OptionG("vdir", &vdir) endif if has("shada") - call append("$", "viminfo\tlist that specifies what to write in the ShaDa file") + call <SID>AddOption("viminfo", gettext("list that specifies what to write in the ShaDa file")) call <SID>OptionG("vi", &vi) endif if has("quickfix") - call append("$", "bufhidden\twhat happens with a buffer when it's no longer in a window") + call <SID>AddOption("bufhidden", gettext("what happens with a buffer when it's no longer in a window")) call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("bh") - call <SID>AddOption("buftype", "empty, \"nofile\", \"nowrite\", \"quickfix\", etc.: type of buffer") + call <SID>AddOption("buftype", gettext("empty, \"nofile\", \"nowrite\", \"quickfix\", etc.: type of buffer")) call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("bt") endif -call append("$", "buflisted\twhether the buffer shows up in the buffer list") -call append("$", "\t(local to buffer)") +call <SID>AddOption("buflisted", gettext("whether the buffer shows up in the buffer list")) +call append("$", "\t" .. s:local_to_buffer) call <SID>BinOptionL("bl") -call append("$", "debug\tset to \"msg\" to see all error messages") +call <SID>AddOption("debug", gettext("set to \"msg\" to see all error messages")) call append("$", " \tset debug=" . &debug) -call append("$", "signcolumn\twhether to show the signcolumn") -call append("$", "\t(local to window)") +call <SID>AddOption("signcolumn", gettext("whether to show the signcolumn")) +call append("$", "\t" .. s:local_to_window) call <SID>OptionL("scl") set cpo&vim @@ -1299,13 +1260,13 @@ if has("syntax") endif endif if exists("&mzschemedll") - call append("$", "mzschemedll\tname of the Tcl dynamic library") + call <SID>AddOption("mzschemedll", gettext("name of the MzScheme dynamic library")) call <SID>OptionG("mzschemedll", &mzschemedll) - call append("$", "mzschemegcdll\tname of the Tcl GC dynamic library") + call <SID>AddOption("mzschemegcdll", gettext("name of the MzScheme GC dynamic library")) call <SID>OptionG("mzschemegcdll", &mzschemegcdll) endif if has('pythonx') - call append("$", "pyxversion\twhether to use Python 2 or 3") + call <SID>AddOption("pyxversion", gettext("whether to use Python 2 or 3")) call append("$", " \tset pyx=" . &wd) endif @@ -1322,7 +1283,7 @@ augroup optwin \ call <SID>unload() | delfun <SID>unload augroup END -fun! <SID>unload() +func <SID>unload() delfun <SID>CR delfun <SID>Space delfun <SID>Find @@ -1333,7 +1294,7 @@ fun! <SID>unload() delfun <SID>BinOptionG delfun <SID>Header au! optwin -endfun +endfunc " Restore the previous value of 'title' and 'icon'. let &title = s:old_title diff --git a/runtime/pack/dist/opt/cfilter/plugin/cfilter.lua b/runtime/pack/dist/opt/cfilter/plugin/cfilter.lua new file mode 100644 index 0000000000..20c158da65 --- /dev/null +++ b/runtime/pack/dist/opt/cfilter/plugin/cfilter.lua @@ -0,0 +1,114 @@ +---------------------------------------- +-- This file is generated via github.com/tjdevries/vim9jit +-- For any bugs, please first consider reporting there. +---------------------------------------- + +-- Ignore "value assigned to a local variable is unused" because +-- we can't guarantee that local variables will be used by plugins +-- luacheck: ignore 311 + +local vim9 = require('_vim9script') +local M = {} +local Qf_filter = nil +-- vim9script + +-- # cfilter.vim: Plugin to filter entries from a quickfix/location list +-- # Last Change: Jun 30, 2022 +-- # Maintainer: Yegappan Lakshmanan (yegappan AT yahoo DOT com) +-- # Version: 2.0 +-- # +-- # Commands to filter the quickfix list: +-- # :Cfilter[!] /{pat}/ +-- # Create a new quickfix list from entries matching {pat} in the current +-- # quickfix list. Both the file name and the text of the entries are +-- # matched against {pat}. If ! is supplied, then entries not matching +-- # {pat} are used. The pattern can be optionally enclosed using one of +-- # the following characters: ', ", /. If the pattern is empty, then the +-- # last used search pattern is used. +-- # :Lfilter[!] /{pat}/ +-- # Same as :Cfilter but operates on the current location list. +-- # + +Qf_filter = function(qf, searchpat, bang) + qf = vim9.bool(qf) + local Xgetlist = function() end + local Xsetlist = function() end + local cmd = '' + local firstchar = '' + local lastchar = '' + local pat = '' + local title = '' + local Cond = function() end + local items = {} + + if vim9.bool(qf) then + Xgetlist = function(...) + return vim.fn['getqflist'](...) + end + Xsetlist = function(...) + return vim.fn['setqflist'](...) + end + cmd = ':Cfilter' .. bang + else + Xgetlist = function(...) + return vim9.fn_ref(M, 'getloclist', vim.deepcopy({ 0 }), ...) + end + + Xsetlist = function(...) + return vim9.fn_ref(M, 'setloclist', vim.deepcopy({ 0 }), ...) + end + + cmd = ':Lfilter' .. bang + end + + firstchar = vim9.index(searchpat, 0) + lastchar = vim9.slice(searchpat, -1, nil) + if firstchar == lastchar and (firstchar == '/' or firstchar == '"' or firstchar == "'") then + pat = vim9.slice(searchpat, 1, -2) + if pat == '' then + -- # Use the last search pattern + pat = vim.fn.getreg('/') + end + else + pat = searchpat + end + + if pat == '' then + return + end + + if bang == '!' then + Cond = function(_, val) + return vim9.ops.NotRegexpMatches(val.text, pat) + and vim9.ops.NotRegexpMatches(vim9.fn.bufname(val.bufnr), pat) + end + else + Cond = function(_, val) + return vim9.ops.RegexpMatches(val.text, pat) + or vim9.ops.RegexpMatches(vim9.fn.bufname(val.bufnr), pat) + end + end + + items = vim9.fn_mut('filter', { Xgetlist(), Cond }, { replace = 0 }) + title = cmd .. ' /' .. pat .. '/' + Xsetlist({}, ' ', { ['title'] = title, ['items'] = items }) +end + +vim.api.nvim_create_user_command('Cfilter', function(__vim9_arg_1) + Qf_filter(true, __vim9_arg_1.args, (__vim9_arg_1.bang and '!' or '')) +end, { + bang = true, + nargs = '+', + complete = nil, +}) + +vim.api.nvim_create_user_command('Lfilter', function(__vim9_arg_1) + Qf_filter(false, __vim9_arg_1.args, (__vim9_arg_1.bang and '!' or '')) +end, { + bang = true, + nargs = '+', + complete = nil, +}) + +-- # vim: shiftwidth=2 sts=2 expandtab +return M diff --git a/runtime/pack/dist/opt/cfilter/plugin/cfilter.vim b/runtime/pack/dist/opt/cfilter/plugin/cfilter.vim deleted file mode 100644 index fe4455fe2e..0000000000 --- a/runtime/pack/dist/opt/cfilter/plugin/cfilter.vim +++ /dev/null @@ -1,62 +0,0 @@ -" cfilter.vim: Plugin to filter entries from a quickfix/location list -" Last Change: Aug 23, 2018 -" Maintainer: Yegappan Lakshmanan (yegappan AT yahoo DOT com) -" Version: 1.1 -" -" Commands to filter the quickfix list: -" :Cfilter[!] /{pat}/ -" Create a new quickfix list from entries matching {pat} in the current -" quickfix list. Both the file name and the text of the entries are -" matched against {pat}. If ! is supplied, then entries not matching -" {pat} are used. The pattern can be optionally enclosed using one of -" the following characters: ', ", /. If the pattern is empty, then the -" last used search pattern is used. -" :Lfilter[!] /{pat}/ -" Same as :Cfilter but operates on the current location list. -" -if exists("loaded_cfilter") - finish -endif -let loaded_cfilter = 1 - -func s:Qf_filter(qf, searchpat, bang) - if a:qf - let Xgetlist = function('getqflist') - let Xsetlist = function('setqflist') - let cmd = ':Cfilter' . a:bang - else - let Xgetlist = function('getloclist', [0]) - let Xsetlist = function('setloclist', [0]) - let cmd = ':Lfilter' . a:bang - endif - - let firstchar = a:searchpat[0] - let lastchar = a:searchpat[-1:] - if firstchar == lastchar && - \ (firstchar == '/' || firstchar == '"' || firstchar == "'") - let pat = a:searchpat[1:-2] - if pat == '' - " Use the last search pattern - let pat = @/ - endif - else - let pat = a:searchpat - endif - - if pat == '' - return - endif - - if a:bang == '!' - let cond = 'v:val.text !~# pat && bufname(v:val.bufnr) !~# pat' - else - let cond = 'v:val.text =~# pat || bufname(v:val.bufnr) =~# pat' - endif - - let items = filter(Xgetlist(), cond) - let title = cmd . ' /' . pat . '/' - call Xsetlist([], ' ', {'title' : title, 'items' : items}) -endfunc - -com! -nargs=+ -bang Cfilter call s:Qf_filter(1, <q-args>, <q-bang>) -com! -nargs=+ -bang Lfilter call s:Qf_filter(0, <q-args>, <q-bang>) diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim index bfece6aa72..99fd7dba42 100644 --- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim +++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim @@ -2,7 +2,7 @@ " " Author: Bram Moolenaar " Copyright: Vim license applies, see ":help license" -" Last Change: 2022 Jun 24 +" Last Change: 2022 Nov 10 " " WORK IN PROGRESS - The basics works stable, more to come " Note: In general you need at least GDB 7.12 because this provides the @@ -154,10 +154,16 @@ func s:StartDebug_internal(dict) let s:save_columns = 0 let s:allleft = 0 - if exists('g:termdebug_wide') - if &columns < g:termdebug_wide + let wide = 0 + if exists('g:termdebug_config') + let wide = get(g:termdebug_config, 'wide', 0) + elseif exists('g:termdebug_wide') + let wide = g:termdebug_wide + endif + if wide > 0 + if &columns < wide let s:save_columns = &columns - let &columns = g:termdebug_wide + let &columns = wide " If we make the Vim window wider, use the whole left half for the debug " windows. let s:allleft = 1 @@ -168,7 +174,12 @@ func s:StartDebug_internal(dict) endif " Override using a terminal window by setting g:termdebug_use_prompt to 1. - let use_prompt = exists('g:termdebug_use_prompt') && g:termdebug_use_prompt + let use_prompt = 0 + if exists('g:termdebug_config') + let use_prompt = get(g:termdebug_config, 'use_prompt', 0) + elseif exists('g:termdebug_use_prompt') + let use_prompt = g:termdebug_use_prompt + endif if !has('win32') && !use_prompt let s:way = 'terminal' else @@ -903,7 +914,14 @@ func s:InstallCommands() endif if has('menu') && &mouse != '' - call s:InstallWinbar() + " install the window toolbar by default, can be disabled in the config + let winbar = 1 + if exists('g:termdebug_config') + let winbar = get(g:termdebug_config, 'winbar', 1) + endif + if winbar + call s:InstallWinbar() + endif let popup = 1 if exists('g:termdebug_config') diff --git a/runtime/pack/dist/opt/vimball/doc/vimball.txt b/runtime/pack/dist/opt/vimball/doc/vimball.txt index 9965a216e4..602fe85954 100644 --- a/runtime/pack/dist/opt/vimball/doc/vimball.txt +++ b/runtime/pack/dist/opt/vimball/doc/vimball.txt @@ -88,7 +88,7 @@ MAKING A VIMBALL *:MkVimball* If you wish to force slashes into the filename, that can also be done by using the exclamation mark (ie. :MkVimball! path/filename). - The tip at http://vim.wikia.com/wiki/Using_VimBall_with_%27Make%27 + The tip at https://vim.wikia.com/wiki/Using_VimBall_with_%27Make%27 has a good idea on how to automate the production of vimballs using make. @@ -171,12 +171,12 @@ WINDOWS *vimball-windows* > Item Tool/Suite Free Website ---- ---------- ---- ------- - 7zip tool y http://www.7-zip.org/ - Winzip tool n http://www.winzip.com/downwz.htm - unxutils suite y http://unxutils.sourceforge.net/ - cygwin suite y http://www.cygwin.com/ - GnuWin32 suite y http://gnuwin32.sourceforge.net/ - MinGW suite y http://www.mingw.org/ + 7zip tool y https://www.7-zip.org/ + Winzip tool n https://www.winzip.com/downwz.htm + unxutils suite y https://unxutils.sourceforge.net/ + cygwin suite y https://www.cygwin.com/ + GnuWin32 suite y https://gnuwin32.sourceforge.net/ + MinGW suite y https://www.mingw.org/ < ============================================================================== diff --git a/runtime/plugin/editorconfig.lua b/runtime/plugin/editorconfig.lua new file mode 100644 index 0000000000..54cd0e828e --- /dev/null +++ b/runtime/plugin/editorconfig.lua @@ -0,0 +1,13 @@ +local group = vim.api.nvim_create_augroup('editorconfig', {}) +vim.api.nvim_create_autocmd({ 'BufNewFile', 'BufRead', 'BufFilePost' }, { + group = group, + callback = function(args) + -- Buffer-local enable has higher priority + local enable = vim.F.if_nil(vim.b.editorconfig, vim.F.if_nil(vim.g.editorconfig, true)) + if not enable then + return + end + + require('editorconfig').config(args.buf) + end, +}) diff --git a/runtime/plugin/matchparen.vim b/runtime/plugin/matchparen.vim index ce2225c5f8..3982489b92 100644 --- a/runtime/plugin/matchparen.vim +++ b/runtime/plugin/matchparen.vim @@ -1,6 +1,6 @@ " Vim plugin for showing matching parens " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2021 Apr 08 +" Last Change: 2022 Dec 01 " Exit quickly when: " - this plugin was already loaded (or disabled) @@ -19,8 +19,8 @@ endif augroup matchparen " Replace all matchparen autocommands - autocmd! CursorMoved,CursorMovedI,WinEnter,WinScrolled * call s:Highlight_Matching_Pair() - autocmd! WinLeave * call s:Remove_Matches() + autocmd! CursorMoved,CursorMovedI,WinEnter,BufWinEnter,WinScrolled * call s:Highlight_Matching_Pair() + autocmd! WinLeave,BufLeave * call s:Remove_Matches() if exists('##TextChanged') autocmd! TextChanged,TextChangedI * call s:Highlight_Matching_Pair() endif diff --git a/runtime/plugin/nvim.lua b/runtime/plugin/nvim.lua new file mode 100644 index 0000000000..815886f896 --- /dev/null +++ b/runtime/plugin/nvim.lua @@ -0,0 +1,7 @@ +vim.api.nvim_create_user_command('Inspect', function(cmd) + if cmd.bang then + vim.pretty_print(vim.inspect_pos()) + else + vim.show_pos() + end +end, { desc = 'Inspect highlights and extmarks at the cursor', bang = true }) diff --git a/runtime/print/ascii.ps b/runtime/print/ascii.ps deleted file mode 100644 index 5fcffb655f..0000000000 --- a/runtime/print/ascii.ps +++ /dev/null @@ -1,22 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-ascii -%%Version: 1.0 0 -%%EndComments -/VIM-ascii[ -32{/.notdef}repeat -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -128{/.notdef}repeat] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/cidfont.ps b/runtime/print/cidfont.ps deleted file mode 100644 index a06ebc8c4c..0000000000 --- a/runtime/print/cidfont.ps +++ /dev/null @@ -1,26 +0,0 @@ -%!PS-Adobe-3.0 Resource-ProcSet -%%Title: VIM-CIDFont -%%Version: 1.0 0 -%%EndComments -% Editing of this file is NOT RECOMMENDED. You run a very good risk of causing -% all PostScript printing from VIM failing if you do. PostScript is not called -% a write-only language for nothing! -/CP currentpacking d T setpacking -/SB 256 string d -/CIDN? systemdict/composefont known d /GS? systemdict/.makeoperator known d -CIDN?{ -GS?{/vim_findresource{2 copy resourcestatus not{1 index SB cvs runlibfile}{ -pop pop}ifelse findresource}bd/vim_composefont{0 get/CIDFont vim_findresource -exch/CMap vim_findresource exch[exch]composefont pop}bd}{/vim_findresource -/findresource ld/vim_composefont{composefont pop}bd}ifelse -}{ -/vim_fontname{0 get SB cvs length dup SB exch(-)putinterval 1 add dup SB exch -dup 256 exch sub getinterval 3 -1 roll exch cvs length add SB exch 0 exch -getinterval cvn}bd/vim_composefont{vim_fontname findfont d}bd -} ifelse -/cfs{exch scalefont d}bd -/sffs{findfont 3 1 roll 1 index mul exch 2 index/FontMatrix get matrix copy -scale makefont d}bd -CP setpacking -% vim:ff=unix: -%%EOF diff --git a/runtime/print/cns_roman.ps b/runtime/print/cns_roman.ps deleted file mode 100644 index dba385cae0..0000000000 --- a/runtime/print/cns_roman.ps +++ /dev/null @@ -1,23 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-cns_roman -%%Version: 1.0 0 -%%EndComments -% Different to ASCII at code point 126 -/VIM-cns_roman[ -32{/.notdef}repeat -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /overline /.notdef -128{/.notdef}repeat] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/cp1250.ps b/runtime/print/cp1250.ps deleted file mode 100644 index 9e733cc760..0000000000 --- a/runtime/print/cp1250.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-cp1250 -%%Version: 1.0 0 -%%EndComments -/VIM-cp1250[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /tilde /.notdef -/Euro /.notdef /quotesinglbase /.notdef /quotedblbase /ellipsis /dagger /daggerdbl -/.notdef /perthousand /Scaron /guilsinglleft /Sacute /Tcaron /Zcaron /Zacute -/.notdef /quoteleft /quoteright /quotedblleft /quotedblright /bullet /endash /emdash -/.notdef /trademark /scaron /guilsinglright /sacute /tcaron /zcaron /zacute -/space /caron /breve /Lslash /currency /Aogonek /brokenbar /section -/dieresis /copyright /Scedilla /guillemotleft /logicalnot /hyphen /registered /Zdotaccent -/degree /plusminus /ogonek /lslash /acute /mu /paragraph /periodcentered -/cedilla /aogonek /scedilla /guillemotright /Lcaron /hungarumlaut /lcaron /zdotaccent -/Racute /Aacute /Acircumflex /Abreve /Adieresis /Lacute /Cacute /Ccedilla -/Ccaron /Eacute /Eogonek /Edieresis /Ecaron /Iacute /Icircumflex /Dcaron -/Dcroat /Nacute /Ncaron /Oacute /Ocircumflex /Ohungarumlaut /Odieresis /multiply -/Rcaron /Uring /Uacute /Uhungarumlaut /Udieresis /Yacute /Tcedilla /germandbls -/racute /aacute /acircumflex /abreve /adieresis /lacute /cacute /ccedilla -/ccaron /eacute /eogonek /edieresis /ecaron /iacute /icircumflex /dcaron -/dcroat /nacute /ncaron /oacute /ocircumflex /ohungarumlaut /odieresis /divide -/rcaron /uring /uacute /uhungarumlaut /udieresis /yacute /tcedilla /dotaccent] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/cp1251.ps b/runtime/print/cp1251.ps deleted file mode 100644 index 7137504e7a..0000000000 --- a/runtime/print/cp1251.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-cp1251 -%%Version: 1.0 0 -%%EndComments -/VIM-cp1251[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/afii10051 /afii10052 /quotesinglbase /afii10100 /quotedblbase /ellipsis /dagger /daggerdbl -/Euro /perthousand /afii10058 /guilsinglleft /afii10059 /afii10061 /afii10060 /afii10145 -/afii10099 /quoteleft /quoteright /quotedblleft /quotedblright /bullet /endash /emdash -/.notdef /trademark /afii10106 /guilsinglright /afii10107 /afii10109 /afii10108 /afii10193 -/space /afii10062 /afii10110 /afii10057 /currency /afii10050 /brokenbar /section -/afii10023 /copyright /afii10053 /guillemotleft /logicalnot /hyphen /registered /afii10056 -/degree /plusminus /afii10055 /afii10103 /afii10098 /mu /paragraph /periodcentered -/afii10071 /afii61352 /afii10101 /guillemotright /afii10105 /afii10054 /afii10102 /afii10104 -/afii10017 /afii10018 /afii10019 /afii10020 /afii10021 /afii10022 /afii10024 /afii10025 -/afii10026 /afii10027 /afii10028 /afii10029 /afii10030 /afii10031 /afii10032 /afii10033 -/afii10034 /afii10035 /afii10036 /afii10037 /afii10038 /afii10039 /afii10040 /afii10041 -/afii10042 /afii10043 /afii10044 /afii10045 /afii10046 /afii10047 /afii10048 /afii10049 -/afii10065 /afii10066 /afii10067 /afii10068 /afii10069 /afii10070 /afii10072 /afii10073 -/afii10074 /afii10075 /afii10076 /afii10077 /afii10078 /afii10079 /afii10080 /afii10081 -/afii10082 /afii10083 /afii10084 /afii10085 /afii10086 /afii10087 /afii10088 /afii10089 -/afii10090 /afii10091 /afii10092 /afii10093 /afii10094 /afii10095 /afii10096 /afii10097] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/cp1252.ps b/runtime/print/cp1252.ps deleted file mode 100644 index a4dd7e675f..0000000000 --- a/runtime/print/cp1252.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-cp1252 -%%Version: 1.0 0 -%%EndComments -/VIM-cp1252[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/Euro /.notdef /quotesinglbase /florin /quotedblbase /ellipsis /dagger /daggerdbl -/circumflex /perthousand /Scaron /guilsinglleft /OE /.notdef /Zcaron /.notdef -/.notdef /quoteleft /quoteright /quotedblleft /quotedblright /bullet /endash /emdash -/tilde /trademark /scaron /guilsinglright /oe /.notdef /zcaron /Ydieresis -/space /exclamdown /cent /sterling /currency /yen /brokenbar /section -/dieresis /copyright /ordfeminine /guillemotleft /logicalnot /hyphen /registered /macron -/degree /plusminus /twosuperior /threesuperior /acute /mu /paragraph /periodcentered -/cedilla /onesuperior /ordmasculine /guillemotright /onequarter /onehalf /threequarters /questiondown -/Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla -/Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis -/Eth /Ntilde /Ograve /Oacute /Ocircumflex /Otilde /Odieresis /multiply -/Oslash /Ugrave /Uacute /Ucircumflex /Udieresis /Yacute /Thorn /germandbls -/agrave /aacute /acircumflex /atilde /adieresis /aring /ae /ccedilla -/egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis -/eth /ntilde /ograve /oacute /ocircumflex /otilde /odieresis /divide -/oslash /ugrave /uacute /ucircumflex /udieresis /yacute /thorn /ydieresis] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/cp1253.ps b/runtime/print/cp1253.ps deleted file mode 100644 index 0482232af1..0000000000 --- a/runtime/print/cp1253.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-cp1253 -%%Version: 1.0 0 -%%EndComments -/VIM-cp1253[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/.notdef /.notdef /quotesinglbase /florin /quotedblbase /ellipsis /dagger /daggerdbl -/.notdef /perthousand /.notdef /guilsinglleft /.notdef /.notdef /.notdef /.notdef -/.notdef /quoteleft /quoteright /quotedblleft /quotedblright /bullet /endash /emdash -/.notdef /trademark /.notdef /guilsinglright /.notdef /.notdef /.notdef /.notdef -/space /dieresistonos /Alphatonos /sterling /currency /yen /brokenbar /section -/dieresis /copyright /ordfeminine /guillemotleft /logicalnot /hyphen /registered /emdash -/degree /plusminus /twosuperior /threesuperior /tonos /mu /paragraph /periodcentered -/Epsilontonos /Etatonos /Iotatonos /guillemotright /Omicrontonos /onehalf /Upsilontonos /Omegatonos -/iotadieresistonos /Alpha /Beta /Gamma /Delta /Epsilon /Zeta /Eta -/Theta /Iota /Kappa /Lambda /Mu /Nu /Xi /Omicron -/Pi /Rho /.notdef /Sigma /Tau /Upsilon /Phi /Chi -/Psi /Omega /Iotadieresis /Upsilondieresis /alphatonos /epsilontonos /etatonos /iotatonos -/upsilondieresistonos /alpha /beta /gamma /delta /epsilon /zeta /eta -/theta /iota /kappa /lambda /mu /nu /xi /omicron -/pi /rho /sigma1 /sigma /tau /upsilon /phi /chi -/psi /omega /iotadieresis /upsilondieresis /omicrontonos /upsilontonos /omegatonos /.notdef] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/cp1254.ps b/runtime/print/cp1254.ps deleted file mode 100644 index 9fe7e47710..0000000000 --- a/runtime/print/cp1254.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-cp1254 -%%Version: 1.0 0 -%%EndComments -/VIM-cp1254[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/Euro /.notdef /quotesinglbase /florin /quotedblbase /ellipsis /dagger /daggerdbl -/circumflex /perthousand /Scaron /guilsinglleft /OE /.notdef /Zcaron /.notdef -/.notdef /quoteleft /quoteright /quotedblleft /quotedblright /bullet /endash /emdash -/tilde /trademark /scaron /guilsinglright /oe /.notdef /zcaron /Ydieresis -/space /exclamdown /cent /sterling /currency /yen /brokenbar /section -/dieresis /copyright /ordfeminine /guillemotleft /logicalnot /hyphen /registered /macron -/degree /plusminus /twosuperior /threesuperior /acute /mu /paragraph /periodcentered -/cedilla /onesuperior /ordmasculine /guillemotright /onequarter /onehalf /threequarters /questiondown -/Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla -/Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis -/Gbreve /Ntilde /Ograve /Oacute /Ocircumflex /Otilde /Odieresis /multiply -/Oslash /Ugrave /Uacute /Ucircumflex /Udieresis /Idotaccent /Scedilla /germandbls -/agrave /aacute /acircumflex /atilde /adieresis /aring /ae /ccedilla -/egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis -/gbreve /ntilde /ograve /oacute /ocircumflex /otilde /odieresis /divide -/oslash /ugrave /uacute /ucircumflex /udieresis /dotlessi /scedilla /ydieresis] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/cp1255.ps b/runtime/print/cp1255.ps deleted file mode 100644 index cd82f46a0e..0000000000 --- a/runtime/print/cp1255.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-cp1255 -%%Version: 1.0 0 -%%EndComments -/VIM-cp1255[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/.notdef /.notdef /quotesinglbase /florin /quotedblbase /ellipsis /dagger /daggerdbl -/circumflex /perthousand /.notdef /guilsinglleft /.notdef /.notdef /.notdef /.notdef -/.notdef /quoteleft /quoteright /quotedblleft /quotedblright /bullet /endash /emdash -/tilde /trademark /.notdef /guilsinglright /.notdef /.notdef /.notdef /.notdef -/space /.notdef /cent /sterling /newsheqelsign /yen /brokenbar /section -/dieresis /copyright /.notdef /guillemotleft /logicalnot /hyphen /registered /macron -/degree /plusminus /twosuperior /threesuperior /acute /mu /paragraph /periodcentered -/.notdef /onesuperior /.notdef /guillemotright /onequarter /onehalf /threequarters /.notdef -/sheva /hatafsegol /hatafpatah /hatafqamats /hiriq /tsere /segol /patah -/qamats /holam /.notdef /qubuts /dagesh /meteg /maqaf /rafe -/paseq /shindot /sindot /sofpasuq /doublevav /vavyod /doubleyod /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/alef /bet /gimel /dalet /he /vav /zayin /het -/tet /yod /finalkaf /kaf /lamed /finalmem /mem /finalnun -/nun /samekh /ayin /finalpe /pe /finaltsadi /tsadi /qof -/resh /shin /tav /.notdef /.notdef /.notdef /.notdef /.notdef] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/cp1257.ps b/runtime/print/cp1257.ps deleted file mode 100644 index 2e5d7a7b2d..0000000000 --- a/runtime/print/cp1257.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-cp1257 -%%Version: 1.0 0 -%%EndComments -/VIM-cp1257[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/.notdef /.notdef /quotesinglbase /.notdef /quotedblbase /ellipsis /dagger /daggerdbl -/.notdef /perthousand /.notdef /guilsinglleft /.notdef /.notdef /.notdef /.notdef -/.notdef /quoteleft /quoteright /quotedblleft /quotedblright /bullet /endash /emdash -/.notdef /trademark /.notdef /guilsinglright /.notdef /.notdef /.notdef /.notdef -/space /caron /breve /sterling /currency /.notdef /brokenbar /section -/dieresis /copyright /Rcedilla /guillemotleft /logicalnot /hyphen /registered /AE -/degree /plusminus /ogonek /threesuperior /acute /mu /paragraph /periodcentered -/cedilla /onesuperior /rcedilla /guillemotright /onequarter /onehalf /threequarters /ae -/Aogonek /Iogonek /Amacron /Cacute /Adieresis /Aring /Eogonek /Emacron -/Ccaron /Eacute /Zacute /Edot /Gcedilla /Kcedilla /Imacron /Lcedilla -/Scaron /Nacute /Ncedilla /Oacute /Omacron /Otilde /Odieresis /multiply -/Uogonek /Lslash /Sacute /Umacron /Udieresis /Zdotaccent /Zcaron /germandbls -/aogonek /iogonek /amacron /cacute /adieresis /aring /eogonek /emacron -/ccaron /eacute /zacute /edot /gcedilla /kcedilla /imacron /lcedilla -/scaron /nacute /ncedilla /oacute /omacron /otilde /odieresis /divide -/uogonek /lslash /sacute /umacron /udieresis /zdotaccent /zcaron /dotaccent] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/gb_roman.ps b/runtime/print/gb_roman.ps deleted file mode 100644 index fa78dbf5d6..0000000000 --- a/runtime/print/gb_roman.ps +++ /dev/null @@ -1,23 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-gb_roman -%%Version: 1.0 0 -%%EndComments -% Different to ASCII at code points 36 and 126 -/VIM-gb_roman[ -32{/.notdef}repeat -/space /exclam /quotedbl /numbersign /yuan /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /overline /.notdef -128{/.notdef}repeat] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/hp-roman8.ps b/runtime/print/hp-roman8.ps deleted file mode 100644 index d71b876db1..0000000000 --- a/runtime/print/hp-roman8.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-hp-roman8 -%%Version: 1.0 0 -%%EndComments -/VIM-hp-roman8[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /Agrave /Acircumflex /Egrave /Ecircumflex /Edieresis /Icircumflex /Idieresis -/acute /grave /circumflex /dieresis /tilde /Ugrave /Ucircumflex /lira -/macron /Yacute /yacute /degree /Ccedilla /ccedilla /Ntilde /ntilde -/exclamdown /questiondown /currency /sterling /yen /section /florin /cent -/acircumflex /ecircumflex /ocircumflex /ucircumflex /aacute /eacute /oacute /uacute -/agrave /egrave /ograve /ugrave /adieresis /edieresis /odieresis /udieresis -/Aring /icircumflex /Oslash /AE /aring /iacute /oslash /ae -/Adieresis /igrave /Odieresis /Udieresis /Eacute /idieresis /germandbls /Ocircumflex -/Aacute /Atilde /atilde /Eth /eth /Iacute /Igrave /Oacute -/Ograve /Otilde /otilde /Scaron /scaron /Uacute /Ydieresis /ydieresis -/Thorn /thorn /periodcentered /mu /paragraph /threequarters /hyphen /onequarter -/onehalf /ordfeminine /ordmasculine /guillemotleft /filledbox /guillemotright /plusminus /.notdef] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/iso-8859-10.ps b/runtime/print/iso-8859-10.ps deleted file mode 100644 index 7d8e2a0f96..0000000000 --- a/runtime/print/iso-8859-10.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-iso-8859-10 -%%Version: 1.0 0 -%%EndComments -/VIM-iso-8859-10[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /hyphen /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /Aogonek /Emacron /Gcedilla /Imacron /Itilde /Kcedilla /section -/Lcedilla /Dcroat /Scaron /Tbar /Zcaron /endash /Umacron /Eng -/degree /aogonek /emacron /gcedilla /imacron /itilde /kcedilla /periodcentered -/lcedilla /dcroat /scaron /tbar /zcaron /emdash /umacron /eng -/Amacron /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Iogonek -/Ccaron /Eacute /Eogonek /Edieresis /Edot /Iacute /Icircumflex /Idieresis -/Eth /Ncedilla /Omacron /Oacute /Ocircumflex /Otilde /Odieresis /Utilde -/Oslash /Uogonek /Uacute /Ucircumflex /Udieresis /Yacute /Thorn /germandbls -/amacron /aacute /acircumflex /atilde /adieresis /aring /ae /iogonek -/ccaron /eacute /eogonek /edieresis /edot /iacute /icircumflex /idieresis -/eth /ncedilla /omacron /oacute /ocircumflex /otilde /odieresis /utilde -/oslash /uogonek /uacute /ucircumflex /udieresis /yacute /thorn /kgreenlandic] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/iso-8859-11.ps b/runtime/print/iso-8859-11.ps deleted file mode 100644 index 78f775befc..0000000000 --- a/runtime/print/iso-8859-11.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-iso-8859-11 -%%Version: 1.0 0 -%%EndComments -/VIM-iso-8859-11[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /uni0E01 /uni0E02 /uni0E03 /uni0E04 /uni0E05 /uni0E06 /uni0E07 -/uni0E08 /uni0E09 /uni0E0A /uni0E0B /uni0E0C /uni0E0D /uni0E0E /uni0E0F -/uni0E10 /uni0E11 /uni0E12 /uni0E13 /uni0E14 /uni0E15 /uni0E16 /uni0E17 -/uni0E18 /uni0E19 /uni0E1A /uni0E1B /uni0E1C /uni0E1D /uni0E1E /uni0E1F -/uni0E20 /uni0E21 /uni0E22 /uni0E23 /uni0E24 /uni0E25 /uni0E26 /uni0E27 -/uni0E28 /uni0E29 /uni0E2A /uni0E2B /uni0E2C /uni0E2D /uni0E2E /uni0E2F -/uni0E30 /uni0E31 /uni0E32 /uni0E33 /uni0E34 /uni0E35 /uni0E36 /uni0E37 -/uni0E38 /uni0E39 /uni0E3A /.notdef /space /.notdef /.notdef /uni0E3F -/uni0E40 /uni0E41 /uni0E42 /uni0E43 /uni0E44 /uni0E45 /uni0E46 /uni0E47 -/uni0E48 /uni0E49 /uni0E4A /uni0E4B /uni0E4C /uni0E4D /uni0E4E /uni0E4F -/uni0E50 /uni0E51 /uni0E52 /uni0E53 /uni0E54 /uni0E55 /uni0E56 /uni0E57 - /uni0E58 /uni0E59 /uni0E5A /.notdef /.notdef /.notdef /.notdef /.notdef] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/iso-8859-13.ps b/runtime/print/iso-8859-13.ps deleted file mode 100644 index b4348f6619..0000000000 --- a/runtime/print/iso-8859-13.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-iso-8859-13 -%%Version: 1.0 0 -%%EndComments -/VIM-iso-8859-13[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /quotedblright /cent /sterling /currency /quotedblbase /brokenbar /section -/Oslash /copyright /Rcedilla /guillemotleft /logicalnot /hyphen /registered /AE -/degree /plusminus /twosuperior /threesuperior /quotedblleft /mu /paragraph /periodcentered -/oslash /onesuperior /rcedilla /guillemotright /onequarter /onehalf /threequarters /ae -/Aogonek /Iogonek /Amacron /Cacute /Adieresis /Aring /Eogonek /Emacron -/Ccaron /Eacute /Zacute /Edot /Gcedilla /Kcedilla /Imacron /Lcedilla -/Scaron /Nacute /Ncedilla /Oacute /Omacron /Otilde /Odieresis /multiply -/Uogonek /Lslash /Sacute /Umacron /Udieresis /Zdotaccent /Zcaron /germandbls -/aogonek /iogonek /amacron /cacute /adieresis /aring /eogonek /emacron -/ccaron /eacute /zacute /edot /gcedilla /kcedilla /imacron /lcedilla -/scaron /nacute /ncedilla /oacute /omacron /otilde /odieresis /divide -/uogonek /lslash /sacute /umacron /udieresis /zdotaccent /zcaron /quoteright] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/iso-8859-14.ps b/runtime/print/iso-8859-14.ps deleted file mode 100644 index cdfe04268a..0000000000 --- a/runtime/print/iso-8859-14.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-iso-8859-14 -%%Version: 1.0 0 -%%EndComments -/VIM-iso-8859-14[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /uni1E02 /uni1E03 /sterling /Cdotaccent /cdotaccent /uni1E0A /section -/Wgrave /copyright /Wacute /uni1E0B /Ygrave /hyphen /registered /Ydieresis -/uni1E1E /uni1E1F /Gdotaccent /gdotaccent /uni1E40 /uni1E41 /paragraph /uni1E56 -/wgrave /uni1E57 /wacute /uni1E60 /ygrave /Wdieresis /wdieresis /uni1E61 -/Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla -/Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis -/Wcircumflex /Ntilde /Ograve /Oacute /Ocircumflex /Otilde /Odieresis /uni1E6A -/Oslash /Ugrave /Uacute /Ucircumflex /Udieresis /Yacute /Ycircumflex /germandbls -/agrave /aacute /acircumflex /atilde /adieresis /aring /ae /ccedilla -/egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis -/wcircumflex /ntilde /ograve /oacute /ocircumflex /otilde /odieresis /uni1E6B -/oslash /ugrave /uacute /ucircumflex /udieresis /yacute /ycircumflex /ydieresis] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/iso-8859-15.ps b/runtime/print/iso-8859-15.ps deleted file mode 100644 index 46ea691ff1..0000000000 --- a/runtime/print/iso-8859-15.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-iso-8859-15 -%%Version: 1.0 0 -%%EndComments -/VIM-iso-8859-15[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclamdown /cent /sterling /Euro /yen /Scaron /section -/scaron /copyright /ordfeminine /guillemotleft /logicalnot /hyphen /registered /macron -/degree /plusminus /twosuperior /threesuperior /Zcaron /mu /paragraph /periodcentered -/zcaron /onesuperior /ordmasculine /guillemotright /OE /oe /Ydieresis /questiondown -/Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla -/Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis -/Eth /Ntilde /Ograve /Oacute /Ocircumflex /Otilde /Odieresis /multiply -/Oslash /Ugrave /Uacute /Ucircumflex /Udieresis /Yacute /Thorn /germandbls -/agrave /aacute /acircumflex /atilde /adieresis /aring /ae /ccedilla -/egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis -/eth /ntilde /ograve /oacute /ocircumflex /otilde /odieresis /divide -/oslash /ugrave /uacute /ucircumflex /udieresis /yacute /thorn /ydieresis] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/iso-8859-2.ps b/runtime/print/iso-8859-2.ps deleted file mode 100644 index f6e1933be7..0000000000 --- a/runtime/print/iso-8859-2.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-iso-8859-2 -%%Version: 1.0 0 -%%EndComments -/VIM-iso-8859-2[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /Aogonek /breve /Lslash /currency /Lcaron /Sacute /section -/dieresis /Scaron /Scedilla /Tcaron /Zacute /hyphen /Zcaron /Zdotaccent -/degree /aogonek /ogonek /lslash /acute /lcaron /sacute /caron -/cedilla /scaron /scedilla /tcaron /zacute /hungarumlaut /zcaron /zdotaccent -/Racute /Aacute /Acircumflex /Abreve /Adieresis /Lacute /Cacute /Ccedilla -/Ccaron /Eacute /Eogonek /Edieresis /Ecaron /Iacute /Icircumflex /Dcaron -/Dcroat /Nacute /Ncaron /Oacute /Ocircumflex /Ohungarumlaut /Odieresis /multiply -/Rcaron /Uring /Uacute /Uhungarumlaut /Udieresis /Yacute /Tcedilla /germandbls -/racute /aacute /acircumflex /abreve /adieresis /lacute /cacute /ccedilla -/ccaron /eacute /eogonek /edieresis /ecaron /iacute /icircumflex /dcaron -/dcroat /nacute /ncaron /oacute /ocircumflex /ohungarumlaut /odieresis /divide -/rcaron /uring /uacute /uhungarumlaut /udieresis /yacute /tcedilla /dotaccent] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/iso-8859-3.ps b/runtime/print/iso-8859-3.ps deleted file mode 100644 index b5a3474fb3..0000000000 --- a/runtime/print/iso-8859-3.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-iso-8859-3 -%%Version: 1.0 0 -%%EndComments -/VIM-iso-8859-3[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /Hbar /breve /sterling /currency /.notdef /Hcircumflex /section -/dieresis /Idot /Scedilla /Gbreve /Jcircumflex /hyphen /.notdef /Zdotaccent -/degree /hbar /twosuperior /threesuperior /acute /mu /hcircumflex /periodcentered -/cedilla /dotlessi /scedilla /gbreve /jcircumflex /onehalf /.notdef /zdotaccent -/Agrave /Aacute /Acircumflex /.notdef /Adieresis /Cdotaccent /Ccircumflex /Ccedilla -/Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis -/.notdef /Ntilde /Ograve /Oacute /Ocircumflex /Gdotaccent /Odieresis /multiply -/Gcircumflex /Ugrave /Uacute /Ucircumflex /Udieresis /Ubreve /Scircumflex /germandbls -/agrave /aacute /acircumflex /.notdef /adieresis /cdotaccent /ccircumflex /ccedilla -/egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis -/.notdef /ntilde /ograve /oacute /ocircumflex /gdotaccent /odieresis /divide -/gcircumflex /ugrave /uacute /ucircumflex /udieresis /ubreve /scircumflex /dotaccent] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/iso-8859-4.ps b/runtime/print/iso-8859-4.ps deleted file mode 100644 index c917d1ff37..0000000000 --- a/runtime/print/iso-8859-4.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-iso-8859-4 -%%Version: 1.0 0 -%%EndComments -/VIM-iso-8859-4[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /Aogonek /kgreenlandic /Rcedilla /currency /Itilde /Lcedilla /section -/dieresis /Scaron /Emacron /Gcedilla /Tbar /.notdef /Zcaron /macron -/degree /aogonek /ogonek /rcedilla /acute /itilde /lcedilla /caron -/cedilla /scaron /emacron /gcedilla /tbar /Eng /zcaron /eng -/Amacron /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Iogonek -/Ccaron /Eacute /Eogonek /Edieresis /Edot /Iacute /Icircumflex /Imacron -/Dcroat /Ncedilla /Omacron /Kcedilla /Ocircumflex /Otilde /Odieresis /multiply -/Oslash /Uogonek /Uacute /Ucircumflex /Udieresis /Utilde /Umacron /germandbls -/amacron /aacute /acircumflex /atilde /adieresis /aring /ae /iogonek -/ccaron /eacute /eogonek /edieresis /edot /iacute /icircumflex /imacron -/dcroat /ncedilla /omacron /kcedilla /ocircumflex /otilde /odieresis /divide -/oslash /uogonek /uacute /ucircumflex /udieresis /utilde /umacron /dotaccent] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/iso-8859-5.ps b/runtime/print/iso-8859-5.ps deleted file mode 100644 index dbe9628900..0000000000 --- a/runtime/print/iso-8859-5.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-iso-8859-5 -%%Version: 1.0 0 -%%EndComments -/VIM-iso-8859-5[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /afii10023 /afii10051 /afii10052 /afii10053 /afii10054 /afii10055 /afii10056 -/afii10057 /afii10058 /afii10059 /afii10060 /afii10061 /.notdef /afii10062 /afii10145 -/afii10017 /afii10018 /afii10019 /afii10020 /afii10021 /afii10022 /afii10024 /afii10025 -/afii10026 /afii10027 /afii10028 /afii10029 /afii10030 /afii10031 /afii10032 /afii10033 -/afii10034 /afii10035 /afii10036 /afii10037 /afii10038 /afii10039 /afii10040 /afii10041 -/afii10042 /afii10043 /afii10044 /afii10045 /afii10046 /afii10047 /afii10048 /afii10049 -/afii10065 /afii10066 /afii10067 /afii10068 /afii10069 /afii10070 /afii10072 /afii10073 -/afii10074 /afii10075 /afii10076 /afii10077 /afii10078 /afii10079 /afii10080 /afii10081 -/afii10082 /afii10083 /afii10084 /afii10085 /afii10086 /afii10087 /afii10088 /afii10089 -/afii10090 /afii10091 /afii10092 /afii10093 /afii10094 /afii10095 /afii10096 /afii10097 -/afii61352 /afii10071 /afii10099 /afii10100 /afii10101 /afii10102 /afii10103 /afii10104 -/afii10105 /afii10106 /afii10107 /afii10108 /afii10109 /section /afii10110 /afii10193] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/iso-8859-7.ps b/runtime/print/iso-8859-7.ps deleted file mode 100644 index fc16bf1a61..0000000000 --- a/runtime/print/iso-8859-7.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-iso-8859-7 -%%Version: 1.0 0 -%%EndComments -/VIM-iso-8859-7[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /quotereversed /quoteright /sterling /.notdef /.notdef /brokenbar /section -/dieresis /copyright /.notdef /guillemotleft /logicalnot /.notdef /.notdef /emdash -/degree /plusminus /twosuperior /threesuperior /tonos /dieresistonos /Alphatonos /periodcentered -/Epsilontonos /Etatonos /Iotatonos /guillemotright /Omicrontonos /onehalf /Upsilontonos /Omegatonos -/iotadieresistonos /Alpha /Beta /Gamma /Delta /Epsilon /Zeta /Eta -/Theta /Iota /Kappa /Lambda /Mu /Nu /Xi /Omicron -/Pi /Rho /.notdef /Sigma /Tau /Upsilon /Phi /Chi -/Psi /Omega /Iotadieresis /Upsilondieresis /alphatonos /epsilontonos /etatonos /iotatonos -/upsilondieresistonos /alpha /beta /gamma /delta /epsilon /zeta /eta -/theta /iota /kappa /lambda /mu /nu /xi /omicron -/pi /rho /sigma1 /sigma /tau /upsilon /phi /chi -/psi /omega /iotadieresis /upsilondieresis /omicrontonos /upsilontonos /omegatonos /.notdef] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/iso-8859-8.ps b/runtime/print/iso-8859-8.ps deleted file mode 100644 index 15193cc8ea..0000000000 --- a/runtime/print/iso-8859-8.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-iso-8859-8 -%%Version: 1.0 0 -%%EndComments -/VIM-iso-8859-8[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /.notdef /cent /sterling /currency /yen /brokenbar /section -/dieresis /copyright /multiply /guillemotleft /logicalnot /hyphen /registered /macron -/degree /plusminus /twosuperior /threesuperior /acute /mu /paragraph /periodcentered -/cedilla /onesuperior /divide /guillemotright /onequarter /onehalf /threequarters /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /underscoredbl -/alef /bet /gimel /dalet /he /vav /zayin /het -/tet /yod /finalkaf /kaf /lamed /finalmem /mem /finalnun -/nun /samekh /ayin /finalpe /pe /finaltsadi /tsadi /qof -/resh /shin /tav /.notdef /.notdef /.notdef /.notdef /.notdef] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/iso-8859-9.ps b/runtime/print/iso-8859-9.ps deleted file mode 100644 index d40f6e9864..0000000000 --- a/runtime/print/iso-8859-9.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-iso-8859-9 -%%Version: 1.0 0 -%%EndComments -/VIM-iso-8859-9[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclamdown /cent /sterling /currency /yen /brokenbar /section -/dieresis /copyright /ordfeminine /guillemotleft /logicalnot /hyphen /registered /macron -/degree /plusminus /twosuperior /threesuperior /acute /mu /paragraph /periodcentered -/cedilla /onesuperior /ordmasculine /guillemotright /onequarter /onehalf /threequarters /questiondown -/Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla -/Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis -/Gbreve /Ntilde /Ograve /Oacute /Ocircumflex /Otilde /Odieresis /multiply -/Oslash /Ugrave /Uacute /Ucircumflex /Udieresis /Idotaccent /Scedilla /germandbls -/agrave /aacute /acircumflex /atilde /adieresis /aring /ae /ccedilla -/egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis -/gbreve /ntilde /ograve /oacute /ocircumflex /otilde /odieresis /divide -/oslash /ugrave /uacute /ucircumflex /udieresis /dotlessi /scedilla /ydieresis] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/jis_roman.ps b/runtime/print/jis_roman.ps deleted file mode 100644 index f24a8069a3..0000000000 --- a/runtime/print/jis_roman.ps +++ /dev/null @@ -1,23 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-jis_roman -%%Version: 1.0 0 -%%EndComments -% Different to ASCII at code points 92 and 126 -/VIM-jis_roman[ -32{/.notdef}repeat -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /yen /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /overline /.notdef -128{/.notdef}repeat] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/koi8-r.ps b/runtime/print/koi8-r.ps deleted file mode 100644 index d42daabb49..0000000000 --- a/runtime/print/koi8-r.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-koi8-r -%%Version: 1.0 0 -%%EndComments -/VIM-koi8-r[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/SF100000 /SF110000 /SF010000 /SF030000 /SF020000 /SF040000 /SF080000 /SF090000 -/SF060000 /SF070000 /SF050000 /upblock /dnblock /block /lfblock /rtblock -/ltshade /shade /dkshade /integraltp /filledbox /bullet /radical /approxequal -/lessequal /greaterequal /space /integralbt /degree /twosuperior /periodcentered /divide -/SF430000 /SF240000 /SF510000 /afii10071 /SF520000 /SF390000 /SF220000 /SF210000 -/SF250000 /SF500000 /SF490000 /SF380000 /SF280000 /SF270000 /SF260000 /SF360000 -/SF370000 /SF420000 /SF190000 /afii10023 /SF200000 /SF230000 /SF470000 /SF480000 -/SF410000 /SF450000 /SF460000 /SF400000 /SF540000 /SF530000 /SF440000 /copyright -/afii10096 /afii10065 /afii10066 /afii10088 /afii10069 /afii10070 /afii10086 /afii10068 -/afii10087 /afii10074 /afii10075 /afii10076 /afii10077 /afii10078 /afii10079 /afii10080 -/afii10081 /afii10097 /afii10082 /afii10083 /afii10084 /afii10085 /afii10072 /afii10067 -/afii10094 /afii10093 /afii10073 /afii10090 /afii10095 /afii10091 /afii10089 /afii10092 -/afii10048 /afii10017 /afii10018 /afii10040 /afii10021 /afii10022 /afii10038 /afii10020 -/afii10039 /afii10026 /afii10027 /afii10028 /afii10029 /afii10030 /afii10031 /afii10032 -/afii10033 /afii10049 /afii10034 /afii10035 /afii10036 /afii10037 /afii10024 /afii10019 -/afii10046 /afii10045 /afii10025 /afii10042 /afii10047 /afii10043 /afii10041 /afii10044] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/koi8-u.ps b/runtime/print/koi8-u.ps deleted file mode 100644 index 53631049fe..0000000000 --- a/runtime/print/koi8-u.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-koi8-u -%%Version: 1.0 0 -%%EndComments -/VIM-koi8-u[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/SF100000 /SF110000 /SF010000 /SF030000 /SF020000 /SF040000 /SF080000 /SF090000 -/SF060000 /SF070000 /SF050000 /upblock /dnblock /block /lfblock /rtblock -/ltshade /shade /dkshade /integraltp /filledbox /bullet /radical /approxequal -/lessequal /greaterequal /space /integralbt /degree /twosuperior /periodcentered /divide -/SF430000 /SF240000 /SF510000 /afii10071 /afii10101 /SF390000 /afii10103 /afii10104 -/SF250000 /SF500000 /SF490000 /SF380000 /SF280000 /afii10098 /SF260000 /SF360000 -/SF370000 /SF420000 /SF190000 /afii10023 /afii10053 /SF230000 /afii10055 /afii10056 -/SF410000 /SF450000 /SF460000 /SF400000 /SF540000 /afii10050 /SF440000 /copyright -/afii10096 /afii10065 /afii10066 /afii10088 /afii10069 /afii10070 /afii10086 /afii10068 -/afii10087 /afii10074 /afii10075 /afii10076 /afii10077 /afii10078 /afii10079 /afii10080 -/afii10081 /afii10097 /afii10082 /afii10083 /afii10084 /afii10085 /afii10072 /afii10067 -/afii10094 /afii10093 /afii10073 /afii10090 /afii10095 /afii10091 /afii10089 /afii10092 -/afii10048 /afii10017 /afii10018 /afii10040 /afii10021 /afii10022 /afii10038 /afii10020 -/afii10039 /afii10026 /afii10027 /afii10028 /afii10029 /afii10030 /afii10031 /afii10032 -/afii10033 /afii10049 /afii10034 /afii10035 /afii10036 /afii10037 /afii10024 /afii10019 -/afii10046 /afii10045 /afii10025 /afii10042 /afii10047 /afii10043 /afii10041 /afii10044] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/ks_roman.ps b/runtime/print/ks_roman.ps deleted file mode 100644 index b688550a65..0000000000 --- a/runtime/print/ks_roman.ps +++ /dev/null @@ -1,23 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-ks_roman -%%Version: 1.0 0 -%%EndComments -% Different to ASCII at code points 96 and 126 -/VIM-ks_roman[ -32{/.notdef}repeat -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /won /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /overline /.notdef -128{/.notdef}repeat] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/latin1.ps b/runtime/print/latin1.ps deleted file mode 100644 index 569db9bfe0..0000000000 --- a/runtime/print/latin1.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-latin1 -%%Version: 1.0 0 -%%EndComments -/VIM-latin1[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclamdown /cent /sterling /currency /yen /brokenbar /section -/dieresis /copyright /ordfeminine /guillemotleft /logicalnot /hyphen /registered /macron -/degree /plusminus /twosuperior /threesuperior /acute /mu /paragraph /periodcentered -/cedilla /onesuperior /ordmasculine /guillemotright /onequarter /onehalf /threequarters /questiondown -/Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla -/Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis -/Eth /Ntilde /Ograve /Oacute /Ocircumflex /Otilde /Odieresis /multiply -/Oslash /Ugrave /Uacute /Ucircumflex /Udieresis /Yacute /Thorn /germandbls -/agrave /aacute /acircumflex /atilde /adieresis /aring /ae /ccedilla -/egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis -/eth /ntilde /ograve /oacute /ocircumflex /otilde /odieresis /divide -/oslash /ugrave /uacute /ucircumflex /udieresis /yacute /thorn /ydieresis] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/mac-roman.ps b/runtime/print/mac-roman.ps deleted file mode 100644 index b0941be650..0000000000 --- a/runtime/print/mac-roman.ps +++ /dev/null @@ -1,40 +0,0 @@ -%!PS-Adobe-3.0 Resource-Encoding -%%Title: VIM-mac-roman -%%Version: 1.0 0 -%%EndComments -/VIM-mac-roman[ -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef -/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle -/parenleft /parenright /asterisk /plus /comma /minus /period /slash -/zero /one /two /three /four /five /six /seven -/eight /nine /colon /semicolon /less /equal /greater /question -/at /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 /bracketleft /backslash /bracketright /asciicircum /underscore -/grave /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 /braceleft /bar /braceright /asciitilde /.notdef -/Adieresis /Aring /Ccedilla /Eacute /Ntilde /Odieresis /Udieresis /aacute -/agrave /acircumflex /adieresis /atilde /aring /ccedilla /eacute /egrave -/ecircumflex /edieresis /iacute /igrave /icircumflex /idieresis /ntilde /oacute -/ograve /ocircumflex /odieresis /otilde /uacute /ugrave /ucircumflex /udieresis -/dagger /degree /cent /sterling /section /bullet /paragraph /germandbls -/registered /copyright /trademark /acute /dieresis /notequal /AE /Oslash -/infinity /plusminus /lessequal /greaterequal /yen /mu /partialdiff /summation -/Pi /pi /integral /ordfeminine /ordmasculine /Omega /ae /oslash -/questiondown /exclamdown /logicalnot /radical /florin /approxequal /delta /guillemotleft -/guillemotright /ellipsis /space /Agrave /Atilde /Otilde /OE /oe -/endash /emdash /quotedblleft /quotedblright /quoteleft /quoteright /divide /lozenge -/ydieresis /Ydieresis /fraction /currency /guilsinglleft /guilsinglright /fi /fl -/daggerdbl /periodcentered /quotesinglbase /quotedblbase /perthousand /Acircumflex /Ecircumflex /Aacute -/Edieresis /Egrave /Iacute /Icircumflex /Idieresis /Igrave /Oacute /Ocircumflex -/heart /Ograve /Uacute /Ucircumflex /Ugrave /dotlessi /circumflex /tilde -/macron /breve /dotaccent /ring /cedilla /hungarumlaut /ogonek /caron] -/Encoding defineresource pop -% vim:ff=unix: -%%EOF diff --git a/runtime/print/prolog.ps b/runtime/print/prolog.ps deleted file mode 100644 index 620856999f..0000000000 --- a/runtime/print/prolog.ps +++ /dev/null @@ -1,44 +0,0 @@ -%!PS-Adobe-3.0 Resource-ProcSet -%%Title: VIM-Prolog -%%Version: 1.4 1 -%%EndComments -% Editing of this file is NOT RECOMMENDED. You run a very good risk of causing -% all PostScript printing from VIM failing if you do. PostScript is not called -% a write-only language for nothing! -/packedarray where not{userdict begin/setpacking/pop load def/currentpacking -false def end}{pop}ifelse/CP currentpacking def true setpacking -/bd{bind def}bind def/ld{load def}bd/ed{exch def}bd/d/def ld -/db{dict begin}bd/cde{currentdict end}bd -/T true d/F false d -/SO null d/sv{/SO save d}bd/re{SO restore}bd -/L2 systemdict/languagelevel 2 copy known{get exec}{pop pop 1}ifelse 2 ge d -/m/moveto ld/s/show ld /ms{m s}bd /g/setgray ld/r/setrgbcolor ld/sp{showpage}bd -/gs/gsave ld/gr/grestore ld/cp/currentpoint ld -/ul{gs UW setlinewidth cp UO add 2 copy newpath m 3 1 roll add exch lineto -stroke gr}bd -/bg{gs r cp BO add 4 -2 roll rectfill gr}bd -/sl{90 rotate 0 exch translate}bd -L2{ -/sspd{mark exch{setpagedevice}stopped cleartomark}bd -/nc{1 db/NumCopies ed cde sspd}bd -/sps{3 db/Orientation ed[3 1 roll]/PageSize ed/ImagingBBox null d cde sspd}bd -/dt{2 db/Tumble ed/Duplex ed cde sspd}bd -/c{1 db/Collate ed cde sspd}bd -}{ -/nc{/#copies ed}bd -/sps{statusdict/setpage get exec}bd -/dt{statusdict/settumble 2 copy known{get exec}{pop pop pop}ifelse -statusdict/setduplexmode 2 copy known{get exec}{pop pop pop}ifelse}bd -/c{pop}bd -}ifelse -/ffs{findfont exch scalefont d}bd/sf{setfont}bd -/ref{1 db findfont dup maxlength dict/NFD ed{exch dup/FID ne{exch NFD 3 1 roll -put}{pop pop}ifelse}forall/Encoding findresource dup length 256 eq{NFD/Encoding -3 -1 roll put}{pop}ifelse NFD dup/FontType get 3 ne{/CharStrings}{/CharProcs} -ifelse 2 copy known{2 copy get dup maxlength dict copy[/questiondown/space]{2 -copy known{2 copy get 2 index/.notdef 3 -1 roll put pop exit}if pop}forall put -}{pop pop}ifelse dup NFD/FontName 3 -1 roll put NFD definefont pop end}bd -CP setpacking -(\004)cvn{}bd -% vim:ff=unix: -%%EOF diff --git a/runtime/queries/help/highlights.scm b/runtime/queries/help/highlights.scm index 6be4e49c81..c0d88301bc 100644 --- a/runtime/queries/help/highlights.scm +++ b/runtime/queries/help/highlights.scm @@ -2,6 +2,8 @@ (h2) @text.title (h3) @text.title (column_heading) @text.title +(column_heading + "~" @conceal (#set! conceal "")) (tag "*" @conceal (#set! conceal "") text: (_) @label) @@ -9,8 +11,15 @@ "|" @conceal (#set! conceal "") text: (_) @text.reference) (optionlink - text: (_) @text.literal) + text: (_) @text.reference) (codespan "`" @conceal (#set! conceal "") - text: (_) @string) + text: (_) @text.literal) +(codeblock) @text.literal +(codeblock + [">" (language)] @conceal (#set! conceal "")) +(block + "<" @conceal (#set! conceal "")) (argument) @parameter +(keycode) @string.special +(url) @text.uri diff --git a/runtime/queries/help/injections.scm b/runtime/queries/help/injections.scm new file mode 100644 index 0000000000..09bbe44e84 --- /dev/null +++ b/runtime/queries/help/injections.scm @@ -0,0 +1,3 @@ +(codeblock + (language) @language + (code) @content) diff --git a/runtime/queries/lua/highlights.scm b/runtime/queries/lua/highlights.scm index 054d787932..2c0dc5447a 100644 --- a/runtime/queries/lua/highlights.scm +++ b/runtime/queries/lua/highlights.scm @@ -131,6 +131,11 @@ ((identifier) @variable.builtin (#eq? @variable.builtin "self")) +(variable_list + attribute: (attribute + (["<" ">"] @punctuation.bracket + (identifier) @attribute))) + ;; Constants ((identifier) @constant diff --git a/runtime/queries/vim/highlights.scm b/runtime/queries/vim/highlights.scm index 3d1729b2cd..239b0a0b37 100644 --- a/runtime/queries/vim/highlights.scm +++ b/runtime/queries/vim/highlights.scm @@ -36,7 +36,8 @@ ;; Function related (function_declaration name: (_) @function) -(call_expression function: (identifier) @function) +(call_expression function: (identifier) @function.call) +(call_expression function: (scoped_identifier (identifier) @function.call)) (parameters (identifier) @parameter) (default_parameter (identifier) @parameter) @@ -59,14 +60,20 @@ "execute" "normal" "set" + "setfiletype" "setlocal" "silent" "echo" + "echon" + "echohl" "echomsg" + "echoerr" "autocmd" "augroup" "return" "syntax" + "filetype" + "source" "lua" "ruby" "perl" @@ -98,10 +105,21 @@ "ex" "visual" "view" + "eval" ] @keyword (map_statement cmd: _ @keyword) (command_name) @function.macro +;; Filetype command + +(filetype_statement [ + "detect" + "plugin" + "indent" + "on" + "off" +] @keyword) + ;; Syntax command (syntax_statement (keyword) @string) @@ -118,6 +136,8 @@ "match" "cluster" "region" + "clear" + "include" ] @keyword) (syntax_argument name: _ @keyword) @@ -175,15 +195,18 @@ ;; Literals -(string_literal) @string @spell +(string_literal) @string (integer_literal) @number (float_literal) @float (comment) @comment @spell +(line_continuation_comment) @comment @spell (pattern) @string.special (pattern_multi) @string.regex (filename) @string (heredoc (body) @string) -((heredoc (parameter) @keyword)) +(heredoc (parameter) @keyword) +[ (marker_definition) (endmarker) ] @label +(literal_dictionary (literal_key) @label) ((scoped_identifier (scope) @_scope . (identifier) @boolean) (#eq? @_scope "v:") @@ -219,12 +242,16 @@ "%=" ".=" "..=" + "<<" + "=<<" + (match_case) ] @operator ; Some characters have different meanings based on the context (unary_operation "!" @operator) (binary_operation "." @operator) + ;; Punctuation [ @@ -234,6 +261,7 @@ "}" "[" "]" + "#{" ] @punctuation.bracket (field_expression "." @punctuation.delimiter) @@ -249,6 +277,9 @@ ((set_value) @number (#match? @number "^[0-9]+(\.[0-9]+)?$")) +(inv_option "!" @operator) +(set_item "?" @operator) + ((set_item option: (option_name) @_option value: (set_value) @function) diff --git a/runtime/queries/vim/injections.scm b/runtime/queries/vim/injections.scm index e2dea8fe75..fdd025bfd9 100644 --- a/runtime/queries/vim/injections.scm +++ b/runtime/queries/vim/injections.scm @@ -1,13 +1,14 @@ (lua_statement (script (body) @lua)) (lua_statement (chunk) @lua) -; (ruby_statement (script (body) @ruby)) -; (ruby_statement (chunk) @ruby) -; (python_statement (script (body) @python)) -; (python_statement (chunk) @python) +(ruby_statement (script (body) @ruby)) +(ruby_statement (chunk) @ruby) +(python_statement (script (body) @python)) +(python_statement (chunk) @python) +;; If we support perl at some point... ;; (perl_statement (script (body) @perl)) ;; (perl_statement (chunk) @perl) -; (autocmd_statement (pattern) @regex) +(autocmd_statement (pattern) @regex) ((set_item option: (option_name) @_option @@ -23,4 +24,5 @@ "patchexpr" "pex" "charconvert" "ccv")) -; (comment) @comment +(comment) @comment +(line_continuation_comment) @comment diff --git a/runtime/scripts.vim b/runtime/scripts.vim deleted file mode 100644 index 2d8bfdcb05..0000000000 --- a/runtime/scripts.vim +++ /dev/null @@ -1,459 +0,0 @@ -" Vim support file to detect file types in scripts -" -" Maintainer: Bram Moolenaar <Bram@vim.org> -" Last change: 2021 Jan 22 - -" This file is called by an autocommand for every file that has just been -" loaded into a buffer. It checks if the type of file can be recognized by -" the file contents. The autocommand is in $VIMRUNTIME/filetype.vim. -" -" Note that the pattern matches are done with =~# to avoid the value of the -" 'ignorecase' option making a difference. Where case is to be ignored use -" =~? instead. Do not use =~ anywhere. - -" Only run when using legacy filetype -if !exists('g:do_legacy_filetype') - finish -endif - -" Only do the rest when the FileType autocommand has not been triggered yet. -if did_filetype() - finish -endif - -" Load the user defined scripts file first -" Only do this when the FileType autocommand has not been triggered yet -if exists("myscriptsfile") && filereadable(expand(myscriptsfile)) - execute "source " . myscriptsfile - if did_filetype() - finish - endif -endif - -" Line continuation is used here, remove 'C' from 'cpoptions' -let s:cpo_save = &cpo -set cpo&vim - -let s:line1 = getline(1) - -if s:line1 =~# "^#!" - " A script that starts with "#!". - - " Check for a line like "#!/usr/bin/env {options} bash". Turn it into - " "#!/usr/bin/bash" to make matching easier. - " Recognize only a few {options} that are commonly used. - if s:line1 =~# '^#!\s*\S*\<env\s' - let s:line1 = substitute(s:line1, '\S\+=\S\+', '', 'g') - let s:line1 = substitute(s:line1, '\(-[iS]\|--ignore-environment\|--split-string\)', '', '') - let s:line1 = substitute(s:line1, '\<env\s\+', '', '') - endif - - " Get the program name. - " Only accept spaces in PC style paths: "#!c:/program files/perl [args]". - " If the word env is used, use the first word after the space: - " "#!/usr/bin/env perl [path/args]" - " If there is no path use the first word: "#!perl [path/args]". - " Otherwise get the last word after a slash: "#!/usr/bin/perl [path/args]". - if s:line1 =~# '^#!\s*\a:[/\\]' - let s:name = substitute(s:line1, '^#!.*[/\\]\(\i\+\).*', '\1', '') - elseif s:line1 =~# '^#!.*\<env\>' - let s:name = substitute(s:line1, '^#!.*\<env\>\s\+\(\i\+\).*', '\1', '') - elseif s:line1 =~# '^#!\s*[^/\\ ]*\>\([^/\\]\|$\)' - let s:name = substitute(s:line1, '^#!\s*\([^/\\ ]*\>\).*', '\1', '') - else - let s:name = substitute(s:line1, '^#!\s*\S*[/\\]\(\i\+\).*', '\1', '') - endif - - " tcl scripts may have #!/bin/sh in the first line and "exec wish" in the - " third line. Suggested by Steven Atkinson. - if getline(3) =~# '^exec wish' - let s:name = 'wish' - endif - - " Bourne-like shell scripts: bash bash2 ksh ksh93 sh - if s:name =~# '^\(bash\d*\|\|ksh\d*\|sh\)\>' - call dist#ft#SetFileTypeSH(s:line1) " defined in filetype.vim - - " csh scripts - elseif s:name =~# '^csh\>' - if exists("g:filetype_csh") - call dist#ft#SetFileTypeShell(g:filetype_csh) - else - call dist#ft#SetFileTypeShell("csh") - endif - - " tcsh scripts - elseif s:name =~# '^tcsh\>' - call dist#ft#SetFileTypeShell("tcsh") - - " Z shell scripts - elseif s:name =~# '^zsh\>' - set ft=zsh - - " TCL scripts - elseif s:name =~# '^\(tclsh\|wish\|expectk\|itclsh\|itkwish\)\>' - set ft=tcl - - " Expect scripts - elseif s:name =~# '^expect\>' - set ft=expect - - " Gnuplot scripts - elseif s:name =~# '^gnuplot\>' - set ft=gnuplot - - " Makefiles - elseif s:name =~# 'make\>' - set ft=make - - " Pike - elseif s:name =~# '^pike\%(\>\|[0-9]\)' - set ft=pike - - " Lua - elseif s:name =~# 'lua' - set ft=lua - - " Perl - elseif s:name =~# 'perl' - set ft=perl - - " PHP - elseif s:name =~# 'php' - set ft=php - - " Python - elseif s:name =~# 'python' - set ft=python - - " Groovy - elseif s:name =~# '^groovy\>' - set ft=groovy - - " Raku - elseif s:name =~# 'raku' - set ft=raku - - " Ruby - elseif s:name =~# 'ruby' - set ft=ruby - - " JavaScript - elseif s:name =~# 'node\(js\)\=\>\|js\>' || s:name =~# 'rhino\>' - set ft=javascript - - " BC calculator - elseif s:name =~# '^bc\>' - set ft=bc - - " sed - elseif s:name =~# 'sed\>' - set ft=sed - - " OCaml-scripts - elseif s:name =~# 'ocaml' - set ft=ocaml - - " Awk scripts; also finds "gawk" - elseif s:name =~# 'awk\>' - set ft=awk - - " Website MetaLanguage - elseif s:name =~# 'wml' - set ft=wml - - " Scheme scripts - elseif s:name =~# 'scheme' - set ft=scheme - - " CFEngine scripts - elseif s:name =~# 'cfengine' - set ft=cfengine - - " Erlang scripts - elseif s:name =~# 'escript' - set ft=erlang - - " Haskell - elseif s:name =~# 'haskell' - set ft=haskell - - " Scala - elseif s:name =~# 'scala\>' - set ft=scala - - " Clojure - elseif s:name =~# 'clojure' - set ft=clojure - - " Free Pascal - elseif s:name =~# 'instantfpc\>' - set ft=pascal - - " Fennel - elseif s:name =~# 'fennel\>' - set ft=fennel - - " MikroTik RouterOS script - elseif s:name =~# 'rsc\>' - set ft=routeros - - " Fish shell - elseif s:name =~# 'fish\>' - set ft=fish - - " Gforth - elseif s:name =~# 'gforth\>' - set ft=forth - - " Icon - elseif s:name =~# 'icon\>' - set ft=icon - - " Guile - elseif s:name =~# 'guile' - set ft=scheme - - endif - unlet s:name - -else - " File does not start with "#!". - - let s:line2 = getline(2) - let s:line3 = getline(3) - let s:line4 = getline(4) - let s:line5 = getline(5) - - " Bourne-like shell scripts: sh ksh bash bash2 - if s:line1 =~# '^:$' - call dist#ft#SetFileTypeSH(s:line1) " defined in filetype.vim - - " Z shell scripts - elseif s:line1 =~# '^#compdef\>' || s:line1 =~# '^#autoload\>' || - \ "\n".s:line1."\n".s:line2."\n".s:line3."\n".s:line4."\n".s:line5 =~# '\n\s*emulate\s\+\%(-[LR]\s\+\)\=[ckz]\=sh\>' - set ft=zsh - - " ELM Mail files - elseif s:line1 =~# '^From \([a-zA-Z][a-zA-Z_0-9\.=-]*\(@[^ ]*\)\=\|-\) .* \(19\|20\)\d\d$' - set ft=mail - - " Mason - elseif s:line1 =~# '^<[%&].*>' - set ft=mason - - " Vim scripts (must have '" vim' as the first line to trigger this) - elseif s:line1 =~# '^" *[vV]im$' - set ft=vim - - " libcxx and libstdc++ standard library headers like "iostream" do not have - " an extension, recognize the Emacs file mode. - elseif s:line1 =~? '-\*-.*C++.*-\*-' - set ft=cpp - - " MOO - elseif s:line1 =~# '^\*\* LambdaMOO Database, Format Version \%([1-3]\>\)\@!\d\+ \*\*$' - set ft=moo - - " Diff file: - " - "diff" in first line (context diff) - " - "Only in " in first line - " - "--- " in first line and "+++ " in second line (unified diff). - " - "*** " in first line and "--- " in second line (context diff). - " - "# It was generated by makepatch " in the second line (makepatch diff). - " - "Index: <filename>" in the first line (CVS file) - " - "=== ", line of "=", "---", "+++ " (SVK diff) - " - "=== ", "--- ", "+++ " (bzr diff, common case) - " - "=== (removed|added|renamed|modified)" (bzr diff, alternative) - " - "# HG changeset patch" in first line (Mercurial export format) - elseif s:line1 =~# '^\(diff\>\|Only in \|\d\+\(,\d\+\)\=[cda]\d\+\>\|# It was generated by makepatch \|Index:\s\+\f\+\r\=$\|===== \f\+ \d\+\.\d\+ vs edited\|==== //\f\+#\d\+\|# HG changeset patch\)' - \ || (s:line1 =~# '^--- ' && s:line2 =~# '^+++ ') - \ || (s:line1 =~# '^\* looking for ' && s:line2 =~# '^\* comparing to ') - \ || (s:line1 =~# '^\*\*\* ' && s:line2 =~# '^--- ') - \ || (s:line1 =~# '^=== ' && ((s:line2 =~# '^=\{66\}' && s:line3 =~# '^--- ' && s:line4 =~# '^+++') || (s:line2 =~# '^--- ' && s:line3 =~# '^+++ '))) - \ || (s:line1 =~# '^=== \(removed\|added\|renamed\|modified\)') - set ft=diff - - " PostScript Files (must have %!PS as the first line, like a2ps output) - elseif s:line1 =~# '^%![ \t]*PS' - set ft=postscr - - " M4 scripts: Guess there is a line that starts with "dnl". - elseif s:line1 =~# '^\s*dnl\>' - \ || s:line2 =~# '^\s*dnl\>' - \ || s:line3 =~# '^\s*dnl\>' - \ || s:line4 =~# '^\s*dnl\>' - \ || s:line5 =~# '^\s*dnl\>' - set ft=m4 - - " AmigaDos scripts - elseif $TERM == "amiga" - \ && (s:line1 =~# "^;" || s:line1 =~? '^\.bra') - set ft=amiga - - " SiCAD scripts (must have procn or procd as the first line to trigger this) - elseif s:line1 =~? '^ *proc[nd] *$' - set ft=sicad - - " Purify log files start with "**** Purify" - elseif s:line1 =~# '^\*\*\*\* Purify' - set ft=purifylog - - " XML - elseif s:line1 =~# '<?\s*xml.*?>' - set ft=xml - - " XHTML (e.g.: PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN") - elseif s:line1 =~# '\<DTD\s\+XHTML\s' - set ft=xhtml - - " HTML (e.g.: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN") - " Avoid "doctype html", used by slim. - elseif s:line1 =~? '<!DOCTYPE\s\+html\>' - set ft=html - - " PDF - elseif s:line1 =~# '^%PDF-' - set ft=pdf - - " XXD output - elseif s:line1 =~# '^\x\{7}: \x\{2} \=\x\{2} \=\x\{2} \=\x\{2} ' - set ft=xxd - - " RCS/CVS log output - elseif s:line1 =~# '^RCS file:' || s:line2 =~# '^RCS file:' - set ft=rcslog - - " CVS commit - elseif s:line2 =~# '^CVS:' || getline("$") =~# '^CVS: ' - set ft=cvs - - " Prescribe - elseif s:line1 =~# '^!R!' - set ft=prescribe - - " Send-pr - elseif s:line1 =~# '^SEND-PR:' - set ft=sendpr - - " SNNS files - elseif s:line1 =~# '^SNNS network definition file' - set ft=snnsnet - elseif s:line1 =~# '^SNNS pattern definition file' - set ft=snnspat - elseif s:line1 =~# '^SNNS result file' - set ft=snnsres - - " Virata - elseif s:line1 =~# '^%.\{-}[Vv]irata' - \ || s:line2 =~# '^%.\{-}[Vv]irata' - \ || s:line3 =~# '^%.\{-}[Vv]irata' - \ || s:line4 =~# '^%.\{-}[Vv]irata' - \ || s:line5 =~# '^%.\{-}[Vv]irata' - set ft=virata - - " Strace - elseif s:line1 =~# '[0-9:.]* *execve(' || s:line1 =~# '^__libc_start_main' - set ft=strace - - " VSE JCL - elseif s:line1 =~# '^\* $$ JOB\>' || s:line1 =~# '^// *JOB\>' - set ft=vsejcl - - " TAK and SINDA - elseif s:line4 =~# 'K & K Associates' || s:line2 =~# 'TAK 2000' - set ft=takout - elseif s:line3 =~# 'S Y S T E M S I M P R O V E D ' - set ft=sindaout - elseif getline(6) =~# 'Run Date: ' - set ft=takcmp - elseif getline(9) =~# 'Node File 1' - set ft=sindacmp - - " DNS zone files - elseif s:line1.s:line2.s:line3.s:line4 =~# '^; <<>> DiG [0-9.]\+.* <<>>\|$ORIGIN\|$TTL\|IN\s\+SOA' - set ft=bindzone - - " BAAN - elseif s:line1 =~# '|\*\{1,80}' && s:line2 =~# 'VRC ' - \ || s:line2 =~# '|\*\{1,80}' && s:line3 =~# 'VRC ' - set ft=baan - - " Valgrind - elseif s:line1 =~# '^==\d\+== valgrind' || s:line3 =~# '^==\d\+== Using valgrind' - set ft=valgrind - - " Go docs - elseif s:line1 =~# '^PACKAGE DOCUMENTATION$' - set ft=godoc - - " Renderman Interface Bytestream - elseif s:line1 =~# '^##RenderMan' - set ft=rib - - " Scheme scripts - elseif s:line1 =~# 'exec\s\+\S*scheme' || s:line2 =~# 'exec\s\+\S*scheme' - set ft=scheme - - " Git output - elseif s:line1 =~# '^\(commit\|tree\|object\) \x\{40,\}\>\|^tag \S\+$' - set ft=git - - " Gprof (gnu profiler) - elseif s:line1 == 'Flat profile:' - \ && s:line2 == '' - \ && s:line3 =~# '^Each sample counts as .* seconds.$' - set ft=gprof - - " Erlang terms - " (See also: http://www.gnu.org/software/emacs/manual/html_node/emacs/Choosing-Modes.html#Choosing-Modes) - elseif s:line1 =~? '-\*-.*erlang.*-\*-' - set ft=erlang - - " YAML - elseif s:line1 =~# '^%YAML' - set ft=yaml - - " MikroTik RouterOS script - 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 - while getline(s:lnum) =~# "^? " && s:lnum < line("$") - let s:lnum += 1 - endwhile - if getline(s:lnum) =~# '^Index:\s\+\f\+$' - set ft=diff - - " locale input files: Formal Definitions of Cultural Conventions - " filename must be like en_US, fr_FR@euro or en_US.UTF-8 - elseif expand("%") =~# '\a\a_\a\a\($\|[.@]\)\|i18n$\|POSIX$\|translit_' - let s:lnum = 1 - while s:lnum < 100 && s:lnum < line("$") - if getline(s:lnum) =~# '^LC_\(IDENTIFICATION\|CTYPE\|COLLATE\|MONETARY\|NUMERIC\|TIME\|MESSAGES\|PAPER\|TELEPHONE\|MEASUREMENT\|NAME\|ADDRESS\)$' - setf fdcc - break - endif - let s:lnum += 1 - endwhile - endif - unlet s:lnum - - endif - - unlet s:line2 s:line3 s:line4 s:line5 - -endif - -" Restore 'cpoptions' -let &cpo = s:cpo_save - -unlet s:cpo_save s:line1 diff --git a/runtime/synmenu.vim b/runtime/synmenu.vim index ba9e6a198d..a664e7689d 100644 --- a/runtime/synmenu.vim +++ b/runtime/synmenu.vim @@ -2,7 +2,7 @@ " This file is normally sourced from menu.vim. " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2017 Oct 28 +" Last Change: 2022 Oct 04 " Define the SetSyn function, used for the Syntax menu entries. " Set 'filetype' and also 'syntax' if it is manually selected. diff --git a/runtime/syntax/2html.vim b/runtime/syntax/2html.vim index 8adbd76950..ce6797deaa 100644 --- a/runtime/syntax/2html.vim +++ b/runtime/syntax/2html.vim @@ -1,6 +1,6 @@ " Vim syntax support file " Maintainer: Ben Fritz <fritzophrenic@gmail.com> -" Last Change: 2020 Jan 05 +" Last Change: 2022 Dec 26 " " Additional contributors: " @@ -815,202 +815,204 @@ endif " HTML header, with the title and generator ;-). Left free space for the CSS, " to be filled at the end. -call extend(s:lines, [ - \ "<html>", - \ "<head>"]) -" include encoding as close to the top as possible, but only if not already -" contained in XML information (to avoid haggling over content type) -if s:settings.encoding != "" && !s:settings.use_xhtml - if s:html5 - call add(s:lines, '<meta charset="' . s:settings.encoding . '"' . s:tag_close) - else - call add(s:lines, "<meta http-equiv=\"content-type\" content=\"text/html; charset=" . s:settings.encoding . '"' . s:tag_close) - endif -endif -call extend(s:lines, [ - \ ("<title>".expand("%:p:~")."</title>"), - \ ("<meta name=\"Generator\" content=\"Vim/".v:version/100.".".v:version%100.'"'.s:tag_close), - \ ("<meta name=\"plugin-version\" content=\"".s:pluginversion.'"'.s:tag_close) - \ ]) -call add(s:lines, '<meta name="syntax" content="'.s:current_syntax.'"'.s:tag_close) -call add(s:lines, '<meta name="settings" content="'. - \ join(filter(keys(s:settings),'s:settings[v:val]'),','). - \ ',prevent_copy='.s:settings.prevent_copy. - \ ',use_input_for_pc='.s:settings.use_input_for_pc. - \ '"'.s:tag_close) -call add(s:lines, '<meta name="colorscheme" content="'. - \ (exists('g:colors_name') - \ ? g:colors_name - \ : 'none'). '"'.s:tag_close) - -if s:settings.use_css +if !s:settings.no_doc call extend(s:lines, [ - \ "<style" . (s:html5 ? "" : " type=\"text/css\"") . ">", - \ s:settings.use_xhtml ? "" : "<!--"]) - let s:ieonly = [] - if s:settings.dynamic_folds - if s:settings.hover_unfold - " if we are doing hover_unfold, use css 2 with css 1 fallback for IE6 - call extend(s:lines, [ - \ ".FoldColumn { text-decoration: none; white-space: pre; }", - \ "", - \ "body * { margin: 0; padding: 0; }", "", - \ ".open-fold > span.Folded { display: none; }", - \ ".open-fold > .fulltext { display: inline; }", - \ ".closed-fold > .fulltext { display: none; }", - \ ".closed-fold > span.Folded { display: inline; }", - \ "", - \ ".open-fold > .toggle-open { display: none; }", - \ ".open-fold > .toggle-closed { display: inline; }", - \ ".closed-fold > .toggle-open { display: inline; }", - \ ".closed-fold > .toggle-closed { display: none; }", - \ "", "", - \ '/* opening a fold while hovering won''t be supported by IE6 and other', - \ "similar browsers, but it should fail gracefully. */", - \ ".closed-fold:hover > .fulltext { display: inline; }", - \ ".closed-fold:hover > .toggle-filler { display: none; }", - \ ".closed-fold:hover > .Folded { display: none; }"]) - " TODO: IE6 is REALLY old and I can't even test it anymore. Maybe we - " should remove this? Leave it in for now, it was working at one point, - " and doesn't affect any modern browsers. Even newer IE versions should - " support the above code and ignore the following. - let s:ieonly = [ - \ "<!--[if lt IE 7]><style type=\"text/css\">", - \ ".open-fold .fulltext { display: inline; }", - \ ".open-fold span.Folded { display: none; }", - \ ".open-fold .toggle-open { display: none; }", - \ ".open-fold .toggle-closed { display: inline; }", - \ "", - \ ".closed-fold .fulltext { display: none; }", - \ ".closed-fold span.Folded { display: inline; }", - \ ".closed-fold .toggle-open { display: inline; }", - \ ".closed-fold .toggle-closed { display: none; }", - \ "</style>", - \ "<![endif]-->", - \] + \ "<html>", + \ "<head>"]) + " include encoding as close to the top as possible, but only if not already + " contained in XML information (to avoid haggling over content type) + if s:settings.encoding != "" && !s:settings.use_xhtml + if s:html5 + call add(s:lines, '<meta charset="' . s:settings.encoding . '"' . s:tag_close) else - " if we aren't doing hover_unfold, use CSS 1 only - call extend(s:lines, [ - \ ".FoldColumn { text-decoration: none; white-space: pre; }", - \ ".open-fold .fulltext { display: inline; }", - \ ".open-fold span.Folded { display: none; }", - \ ".open-fold .toggle-open { display: none; }", - \ ".open-fold .toggle-closed { display: inline; }", - \ "", - \ ".closed-fold .fulltext { display: none; }", - \ ".closed-fold span.Folded { display: inline; }", - \ ".closed-fold .toggle-open { display: inline; }", - \ ".closed-fold .toggle-closed { display: none; }", - \]) + call add(s:lines, "<meta http-equiv=\"content-type\" content=\"text/html; charset=" . s:settings.encoding . '"' . s:tag_close) endif endif - " else we aren't doing any dynamic folding, no need for any special rules - call extend(s:lines, [ - \ s:settings.use_xhtml ? "" : '-->', - \ "</style>", - \]) - call extend(s:lines, s:ieonly) - unlet s:ieonly -endif + \ ("<title>".expand("%:p:~")."</title>"), + \ ("<meta name=\"Generator\" content=\"Vim/".v:version/100.".".v:version%100.'"'.s:tag_close), + \ ("<meta name=\"plugin-version\" content=\"".s:pluginversion.'"'.s:tag_close) + \ ]) + call add(s:lines, '<meta name="syntax" content="'.s:current_syntax.'"'.s:tag_close) + call add(s:lines, '<meta name="settings" content="'. + \ join(filter(keys(s:settings),'s:settings[v:val]'),','). + \ ',prevent_copy='.s:settings.prevent_copy. + \ ',use_input_for_pc='.s:settings.use_input_for_pc. + \ '"'.s:tag_close) + call add(s:lines, '<meta name="colorscheme" content="'. + \ (exists('g:colors_name') + \ ? g:colors_name + \ : 'none'). '"'.s:tag_close) -let s:uses_script = s:settings.dynamic_folds || s:settings.line_ids + if s:settings.use_css + call extend(s:lines, [ + \ "<style" . (s:html5 ? "" : " type=\"text/css\"") . ">", + \ s:settings.use_xhtml ? "" : "<!--"]) + let s:ieonly = [] + if s:settings.dynamic_folds + if s:settings.hover_unfold + " if we are doing hover_unfold, use css 2 with css 1 fallback for IE6 + call extend(s:lines, [ + \ ".FoldColumn { text-decoration: none; white-space: pre; }", + \ "", + \ "body * { margin: 0; padding: 0; }", "", + \ ".open-fold > span.Folded { display: none; }", + \ ".open-fold > .fulltext { display: inline; }", + \ ".closed-fold > .fulltext { display: none; }", + \ ".closed-fold > span.Folded { display: inline; }", + \ "", + \ ".open-fold > .toggle-open { display: none; }", + \ ".open-fold > .toggle-closed { display: inline; }", + \ ".closed-fold > .toggle-open { display: inline; }", + \ ".closed-fold > .toggle-closed { display: none; }", + \ "", "", + \ '/* opening a fold while hovering won''t be supported by IE6 and other', + \ "similar browsers, but it should fail gracefully. */", + \ ".closed-fold:hover > .fulltext { display: inline; }", + \ ".closed-fold:hover > .toggle-filler { display: none; }", + \ ".closed-fold:hover > .Folded { display: none; }"]) + " TODO: IE6 is REALLY old and I can't even test it anymore. Maybe we + " should remove this? Leave it in for now, it was working at one point, + " and doesn't affect any modern browsers. Even newer IE versions should + " support the above code and ignore the following. + let s:ieonly = [ + \ "<!--[if lt IE 7]><style type=\"text/css\">", + \ ".open-fold .fulltext { display: inline; }", + \ ".open-fold span.Folded { display: none; }", + \ ".open-fold .toggle-open { display: none; }", + \ ".open-fold .toggle-closed { display: inline; }", + \ "", + \ ".closed-fold .fulltext { display: none; }", + \ ".closed-fold span.Folded { display: inline; }", + \ ".closed-fold .toggle-open { display: inline; }", + \ ".closed-fold .toggle-closed { display: none; }", + \ "</style>", + \ "<![endif]-->", + \] + else + " if we aren't doing hover_unfold, use CSS 1 only + call extend(s:lines, [ + \ ".FoldColumn { text-decoration: none; white-space: pre; }", + \ ".open-fold .fulltext { display: inline; }", + \ ".open-fold span.Folded { display: none; }", + \ ".open-fold .toggle-open { display: none; }", + \ ".open-fold .toggle-closed { display: inline; }", + \ "", + \ ".closed-fold .fulltext { display: none; }", + \ ".closed-fold span.Folded { display: inline; }", + \ ".closed-fold .toggle-open { display: inline; }", + \ ".closed-fold .toggle-closed { display: none; }", + \]) + endif + endif + " else we aren't doing any dynamic folding, no need for any special rules -" insert script tag if needed -if s:uses_script - call extend(s:lines, [ - \ "", - \ "<script" . (s:html5 ? "" : " type='text/javascript'") . ">", - \ s:settings.use_xhtml ? '//<![CDATA[' : "<!--"]) -endif + call extend(s:lines, [ + \ s:settings.use_xhtml ? "" : '-->', + \ "</style>", + \]) + call extend(s:lines, s:ieonly) + unlet s:ieonly + endif -" insert javascript to toggle folds open and closed -if s:settings.dynamic_folds - call extend(s:lines, [ - \ "", - \ "function toggleFold(objID)", - \ "{", - \ " var fold;", - \ " fold = document.getElementById(objID);", - \ " if (fold.className == 'closed-fold')", - \ " {", - \ " fold.className = 'open-fold';", - \ " }", - \ " else if (fold.className == 'open-fold')", - \ " {", - \ " fold.className = 'closed-fold';", - \ " }", - \ "}" - \ ]) -endif + let s:uses_script = s:settings.dynamic_folds || s:settings.line_ids -if s:settings.line_ids - " insert javascript to get IDs from line numbers, and to open a fold before - " jumping to any lines contained therein - call extend(s:lines, [ - \ "", - \ "/* function to open any folds containing a jumped-to line before jumping to it */", - \ "function JumpToLine()", - \ "{", - \ " var lineNum;", - \ " lineNum = window.location.hash;", - \ " lineNum = lineNum.substr(1); /* strip off '#' */", - \ "", - \ " if (lineNum.indexOf('L') == -1) {", - \ " lineNum = 'L'+lineNum;", - \ " }", - \ " var lineElem = document.getElementById(lineNum);" - \ ]) + " insert script tag if needed + if s:uses_script + call extend(s:lines, [ + \ "", + \ "<script" . (s:html5 ? "" : " type='text/javascript'") . ">", + \ s:settings.use_xhtml ? '//<![CDATA[' : "<!--"]) + endif + " insert javascript to toggle folds open and closed if s:settings.dynamic_folds call extend(s:lines, [ \ "", - \ " /* navigate upwards in the DOM tree to open all folds containing the line */", - \ " var node = lineElem;", - \ " while (node && node.id != 'vimCodeElement".s:settings.id_suffix."')", + \ "function toggleFold(objID)", + \ "{", + \ " var fold;", + \ " fold = document.getElementById(objID);", + \ " if (fold.className == 'closed-fold')", + \ " {", + \ " fold.className = 'open-fold';", + \ " }", + \ " else if (fold.className == 'open-fold')", \ " {", - \ " if (node.className == 'closed-fold')", - \ " {", - \ " node.className = 'open-fold';", - \ " }", - \ " node = node.parentNode;", + \ " fold.className = 'closed-fold';", \ " }", + \ "}" \ ]) endif - call extend(s:lines, [ - \ " /* Always jump to new location even if the line was hidden inside a fold, or", - \ " * we corrected the raw number to a line ID.", - \ " */", - \ " if (lineElem) {", - \ " lineElem.scrollIntoView(true);", - \ " }", - \ " return true;", - \ "}", - \ "if ('onhashchange' in window) {", - \ " window.onhashchange = JumpToLine;", - \ "}" - \ ]) -endif -" insert script closing tag if needed -if s:uses_script - call extend(s:lines, [ - \ '', - \ s:settings.use_xhtml ? '//]]>' : '-->', - \ "</script>" - \ ]) -endif + if s:settings.line_ids + " insert javascript to get IDs from line numbers, and to open a fold before + " jumping to any lines contained therein + call extend(s:lines, [ + \ "", + \ "/* function to open any folds containing a jumped-to line before jumping to it */", + \ "function JumpToLine()", + \ "{", + \ " var lineNum;", + \ " lineNum = window.location.hash;", + \ " lineNum = lineNum.substr(1); /* strip off '#' */", + \ "", + \ " if (lineNum.indexOf('L') == -1) {", + \ " lineNum = 'L'+lineNum;", + \ " }", + \ " var lineElem = document.getElementById(lineNum);" + \ ]) + + if s:settings.dynamic_folds + call extend(s:lines, [ + \ "", + \ " /* navigate upwards in the DOM tree to open all folds containing the line */", + \ " var node = lineElem;", + \ " while (node && node.id != 'vimCodeElement".s:settings.id_suffix."')", + \ " {", + \ " if (node.className == 'closed-fold')", + \ " {", + \ " node.className = 'open-fold';", + \ " }", + \ " node = node.parentNode;", + \ " }", + \ ]) + endif + call extend(s:lines, [ + \ " /* Always jump to new location even if the line was hidden inside a fold, or", + \ " * we corrected the raw number to a line ID.", + \ " */", + \ " if (lineElem) {", + \ " lineElem.scrollIntoView(true);", + \ " }", + \ " return true;", + \ "}", + \ "if ('onhashchange' in window) {", + \ " window.onhashchange = JumpToLine;", + \ "}" + \ ]) + endif -call extend(s:lines, ["</head>", - \ "<body".(s:settings.line_ids ? " onload='JumpToLine();'" : "").">"]) + " insert script closing tag if needed + if s:uses_script + call extend(s:lines, [ + \ '', + \ s:settings.use_xhtml ? '//]]>' : '-->', + \ "</script>" + \ ]) + endif + + call extend(s:lines, ["</head>", + \ "<body".(s:settings.line_ids ? " onload='JumpToLine();'" : "").">"]) +endif if s:settings.no_pre " if we're not using CSS we use a font tag which can't have a div inside if s:settings.use_css - call extend(s:lines, ["<div id='vimCodeElement".s:settings.id_suffix."'>"]) + call extend(s:lines, ["<div id='vimCodeElement" .. s:settings.id_suffix .. "'>"]) endif else - call extend(s:lines, ["<pre id='vimCodeElement".s:settings.id_suffix."'>"]) + call extend(s:lines, ["<pre id='vimCodeElement" .. s:settings.id_suffix .. "'>"]) endif exe s:orgwin . "wincmd w" @@ -1721,12 +1723,15 @@ endif if s:settings.no_pre if !s:settings.use_css " Close off the font tag that encapsulates the whole <body> - call extend(s:lines, ["</font>", "</body>", "</html>"]) + call extend(s:lines, ["</font>"]) else - call extend(s:lines, ["</div>", "</body>", "</html>"]) + call extend(s:lines, ["</div>"]) endif else - call extend(s:lines, ["</pre>", "</body>", "</html>"]) + call extend(s:lines, ["</pre>"]) +endif +if !s:settings.no_doc + call extend(s:lines, ["</body>", "</html>"]) endif exe s:newwin . "wincmd w" @@ -1742,15 +1747,15 @@ unlet s:lines " The generated HTML is admittedly ugly and takes a LONG time to fold. " Make sure the user doesn't do syntax folding when loading a generated file, " using a modeline. -call append(line('$'), "<!-- vim: set foldmethod=manual : -->") +if !s:settings.no_modeline + call append(line('$'), "<!-- vim: set foldmethod=manual : -->") +endif " Now, when we finally know which, we define the colors and styles -if s:settings.use_css +if s:settings.use_css && !s:settings.no_doc 1;/<style\>/+1 -endif -" Normal/global attributes -if s:settings.use_css + " Normal/global attributes if s:settings.no_pre call append('.', "body { color: " . s:fgc . "; background-color: " . s:bgc . "; font-family: ". s:htmlfont ."; }") + @@ -1874,7 +1879,9 @@ if s:settings.use_css endif endif endif -else +endif + +if !s:settings.use_css && !s:settings.no_doc " For Netscape 4, set <body> attributes too, though, strictly speaking, it's " incorrect. execute '%s:<body\([^>]*\):<body bgcolor="' . s:bgc . '" text="' . s:fgc . '"\1>\r<font face="'. s:htmlfont .'"' @@ -1882,7 +1889,7 @@ endif " Gather attributes for all other classes. Do diff first so that normal " highlight groups are inserted before it. -if s:settings.use_css +if s:settings.use_css && !s:settings.no_doc if s:diff_mode call append('.', filter(map(keys(s:diffstylelist), "s:diffstylelist[v:val]"), 'v:val != ""')) endif @@ -1892,20 +1899,22 @@ if s:settings.use_css endif " Add hyperlinks -" TODO: add option to not do this? Maybe just make the color the same as the -" text highlight group normally is? -%s+\(https\=://\S\{-}\)\(\([.,;:}]\=\(\s\|$\)\)\|[\\"'<>]\|>\|<\|"\)+<a href="\1">\1</a>\2+ge +if !s:settings.no_links + %s+\(https\=://\S\{-}\)\(\([.,;:}]\=\(\s\|$\)\)\|[\\"'<>]\|>\|<\|"\)+<a href="\1">\1</a>\2+ge +endif " The DTD -if s:settings.use_xhtml - exe "normal! gg$a\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" -elseif s:html5 - exe "normal! gg0i<!DOCTYPE html>\n" -else - exe "normal! gg0i<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n" +if !s:settings.no_doc + if s:settings.use_xhtml + exe "normal! gg$a\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" + elseif s:html5 + exe "normal! gg0i<!DOCTYPE html>\n" + else + exe "normal! gg0i<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n" + endif endif -if s:settings.use_xhtml +if s:settings.use_xhtml && !s:settings.no_doc exe "normal! gg/<html/e\na xmlns=\"http://www.w3.org/1999/xhtml\"\e" endif diff --git a/runtime/syntax/c.vim b/runtime/syntax/c.vim index 890e9ae1a7..50878a78ea 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: 2022 Apr 24 +" Last Change: 2022 Oct 05 " Quit when a (custom) syntax file was already loaded if exists("b:current_syntax") diff --git a/runtime/syntax/cabal.vim b/runtime/syntax/cabal.vim index 92e6b8331e..74cda51266 100644 --- a/runtime/syntax/cabal.vim +++ b/runtime/syntax/cabal.vim @@ -4,7 +4,9 @@ " Maintainer: Marcin Szamotulski <profunctor@pm.me> " Previous Maintainer: Vincent Berthoux <twinside@gmail.com> " File Types: .cabal -" Last Change: 21 Nov 2020 +" Last Change: 22 Oct 2022 +" v1.6: Added support for foreign-libraries +" Added highlighting for various fields " v1.5: Incorporated changes from " https://github.com/sdiehl/haskell-vim-proto/blob/master/vim/syntax/cabal.vim " Use `syn keyword` instead of `syn match`. @@ -61,13 +63,14 @@ syn keyword cabalCategory contained \ test-suite \ source-repository \ flag + \ foreign-library \ custom-setup \ common syn match cabalCategoryTitle contained /[^{]*\ze{\?/ syn match cabalCategoryRegion \ contains=cabalCategory,cabalCategoryTitle \ nextgroup=cabalCategory skipwhite - \ /^\c\s*\(contained\|executable\|library\|benchmark\|test-suite\|source-repository\|flag\|custom-setup\|common\)\+\s*\%(.*$\|$\)/ + \ /^\c\s*\(contained\|executable\|library\|benchmark\|test-suite\|source-repository\|flag\|foreign-library\|custom-setup\|common\)\+\s*\%(.*$\|$\)/ syn keyword cabalTruth true false " cabalStatementRegion which limits the scope of cabalStatement keywords, this @@ -77,6 +80,7 @@ syn keyword cabalStatement contained containedin=cabalStatementRegion \ default-language \ default-extensions \ author + \ autogen-includes \ autogen-modules \ asm-sources \ asm-options @@ -84,7 +88,7 @@ syn keyword cabalStatement contained containedin=cabalStatementRegion \ bug-reports \ build-depends \ build-tools - \ build-tools-depends + \ build-tool-depends \ build-type \ buildable \ c-sources @@ -95,6 +99,7 @@ syn keyword cabalStatement contained containedin=cabalStatementRegion \ cmm-sources \ cmm-options \ cpp-options + \ cxx-options \ cxx-sources \ data-dir \ data-files @@ -111,7 +116,9 @@ syn keyword cabalStatement contained containedin=cabalStatementRegion \ extra-framework-dirs \ extra-ghci-libraries \ extra-lib-dirs + \ extra-lib-dirs-static \ extra-libraries + \ extra-libraries-static \ extra-library-flavours \ extra-source-files \ extra-tmp-files @@ -133,6 +140,8 @@ syn keyword cabalStatement contained containedin=cabalStatementRegion \ install-includes \ js-sources \ ld-options + \ lib-version-info + \ lib-version-linux \ license \ license-file \ location @@ -141,20 +150,26 @@ syn keyword cabalStatement contained containedin=cabalStatementRegion \ manual \ mixins \ module + \ mod-def-file \ name \ nhc98-options + \ options \ other-extensions \ other-language \ other-languages \ other-modules \ package-url \ pkgconfig-depends + \ scope \ setup-depends + \ signatures \ stability \ subdir \ synopsis + \ reexported-modules \ tag \ tested-with + \ test-module \ type \ version \ virtual-modules diff --git a/runtime/syntax/checkhealth.vim b/runtime/syntax/checkhealth.vim index 37f1822740..4b0ce75a54 100644 --- a/runtime/syntax/checkhealth.vim +++ b/runtime/syntax/checkhealth.vim @@ -1,26 +1,21 @@ " Vim syntax file -" Language: Neovim checkhealth buffer -" Last Change: 2021 Dec 15 +" Language: Nvim :checkhealth buffer +" Last Change: 2022 Nov 10 if exists("b:current_syntax") finish endif -runtime! syntax/markdown.vim +runtime! syntax/help.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 +syn keyword healthError ERROR[:] +syn keyword healthWarning WARNING[:] +syn keyword healthSuccess OK[:] +syn match helpSectionDelim "^======*\n.*$" +syn match healthHeadingChar "=" conceal cchar=─ contained containedin=helpSectionDelim hi def link healthError Error hi def link healthWarning WarningMsg diff --git a/runtime/syntax/cs.vim b/runtime/syntax/cs.vim index 722ddbedf6..104470ac4b 100644 --- a/runtime/syntax/cs.vim +++ b/runtime/syntax/cs.vim @@ -3,7 +3,7 @@ " Maintainer: Nick Jensen <nickspoon@gmail.com> " Former Maintainers: Anduin Withers <awithers@anduin.com> " Johannes Zellner <johannes@zellner.org> -" Last Change: 2022-03-01 +" Last Change: 2022-11-16 " Filenames: *.cs " License: Vim (see :h license) " Repository: https://github.com/nickspoons/vim-cs @@ -25,6 +25,9 @@ syn keyword csType bool byte char decimal double float int long object sbyte sho syn keyword csType nint nuint " contextual syn keyword csStorage enum interface namespace struct +syn match csStorage "\<record\ze\_s\+@\=\h\w*\_s*[<(:{;]" +syn match csStorage "\%(\<\%(partial\|new\|public\|protected\|internal\|private\|abstract\|sealed\|static\|unsafe\|readonly\)\)\@9<=\_s\+record\>" +syn match csStorage "\<record\ze\_s\+\%(class\|struct\)" syn match csStorage "\<delegate\>" syn keyword csRepeat break continue do for foreach goto return while syn keyword csConditional else if switch @@ -44,6 +47,9 @@ syn keyword csManagedModifier managed unmanaged contained " Modifiers syn match csUsingModifier "\<global\ze\_s\+using\>" syn keyword csAccessModifier internal private protected public +syn keyword csModifier operator nextgroup=csCheckedModifier skipwhite skipempty +syn keyword csCheckedModifier checked contained + " TODO: in new out syn keyword csModifier abstract const event override readonly sealed static virtual volatile syn match csModifier "\<\%(extern\|fixed\|unsafe\)\>" @@ -76,7 +82,7 @@ syn match csAccess "\<this\>" " Extension method parameter modifier syn match csModifier "\<this\ze\_s\+@\=\h" -syn keyword csUnspecifiedStatement as in is nameof operator out params ref sizeof stackalloc using +syn keyword csUnspecifiedStatement as in is nameof out params ref sizeof stackalloc using syn keyword csUnsupportedStatement value syn keyword csUnspecifiedKeyword explicit implicit @@ -183,7 +189,7 @@ syn match csUnicodeNumber +\\u\x\{4}+ contained contains=csUnicodeSpecifier disp syn match csUnicodeNumber +\\U00\x\{6}+ contained contains=csUnicodeSpecifier display syn match csUnicodeSpecifier +\\[uUx]+ contained display -syn region csString matchgroup=csQuote start=+"+ end=+"+ end=+$+ extend contains=csSpecialChar,csSpecialError,csUnicodeNumber,@Spell +syn region csString matchgroup=csQuote start=+"+ end=+"\%(u8\)\=+ end=+$+ extend contains=csSpecialChar,csSpecialError,csUnicodeNumber,@Spell syn match csCharacter "'[^']*'" contains=csSpecialChar,csSpecialCharError,csUnicodeNumber display syn match csCharacter "'\\''" contains=csSpecialChar display syn match csCharacter "'[^\\]'" display @@ -200,7 +206,7 @@ syn match csReal "\<\d\+\%(_\+\d\+\)*[fdm]\>" display syn case match syn cluster csNumber contains=csInteger,csReal -syn region csInterpolatedString matchgroup=csQuote start=+\$"+ end=+"+ extend contains=csInterpolation,csEscapedInterpolation,csSpecialChar,csSpecialError,csUnicodeNumber,@Spell +syn region csInterpolatedString matchgroup=csQuote start=+\$"+ end=+"\%(u8\)\=+ extend contains=csInterpolation,csEscapedInterpolation,csSpecialChar,csSpecialError,csUnicodeNumber,@Spell syn region csInterpolation matchgroup=csInterpolationDelimiter start=+{+ end=+}+ keepend contained contains=@csAll,csBraced,csBracketed,csInterpolationAlign,csInterpolationFormat syn match csEscapedInterpolation "{{" transparent contains=NONE display @@ -210,10 +216,10 @@ syn match csInterpolationFormat +:[^}]\+}+ contained contains=csInterpolationFor syn match csInterpolationAlignDel +,+ contained display syn match csInterpolationFormatDel +:+ contained display -syn region csVerbatimString matchgroup=csQuote start=+@"+ end=+"+ skip=+""+ extend contains=csVerbatimQuote,@Spell +syn region csVerbatimString matchgroup=csQuote start=+@"+ end=+"\%(u8\)\=+ skip=+""+ extend contains=csVerbatimQuote,@Spell syn match csVerbatimQuote +""+ contained -syn region csInterVerbString matchgroup=csQuote start=+$@"+ start=+@$"+ end=+"+ skip=+""+ extend contains=csInterpolation,csEscapedInterpolation,csSpecialChar,csSpecialError,csUnicodeNumber,csVerbatimQuote,@Spell +syn region csInterVerbString matchgroup=csQuote start=+$@"+ start=+@$"+ end=+"\%(u8\)\=+ skip=+""+ extend contains=csInterpolation,csEscapedInterpolation,csSpecialChar,csSpecialError,csUnicodeNumber,csVerbatimQuote,@Spell syn cluster csString contains=csString,csInterpolatedString,csVerbatimString,csInterVerbString @@ -256,6 +262,7 @@ hi def link csException Exception hi def link csModifier StorageClass hi def link csAccessModifier csModifier hi def link csAsyncModifier csModifier +hi def link csCheckedModifier csModifier hi def link csManagedModifier csModifier hi def link csUsingModifier csModifier diff --git a/runtime/syntax/debchangelog.vim b/runtime/syntax/debchangelog.vim index 9bd836801e..691c3778c1 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: 2022 Jul 25 +" Last Change: 2022 Oct 29 " URL: https://salsa.debian.org/vim-team/vim-debian/blob/master/syntax/debchangelog.vim " Standard syntax initialization @@ -21,9 +21,9 @@ let s:cpo = &cpo set cpo-=C let s:supported = [ \ 'oldstable', 'stable', 'testing', 'unstable', 'experimental', 'sid', 'rc-buggy', - \ 'buster', 'bullseye', 'bookworm', 'trixie', + \ 'buster', 'bullseye', 'bookworm', 'trixie', 'forky', \ - \ 'trusty', 'xenial', 'bionic', 'focal', 'jammy', 'kinetic', + \ 'trusty', 'xenial', 'bionic', 'focal', 'jammy', 'kinetic', 'lunar', \ 'devel' \ ] let s:unsupported = [ diff --git a/runtime/syntax/debsources.vim b/runtime/syntax/debsources.vim index ea9e59ea8e..9b75797b54 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: 2022 Jul 25 +" Last Change: 2022 Oct 29 " URL: https://salsa.debian.org/vim-team/vim-debian/blob/master/syntax/debsources.vim " Standard syntax initialization @@ -23,9 +23,9 @@ let s:cpo = &cpo set cpo-=C let s:supported = [ \ 'oldstable', 'stable', 'testing', 'unstable', 'experimental', 'sid', 'rc-buggy', - \ 'buster', 'bullseye', 'bookworm', 'trixie', + \ 'buster', 'bullseye', 'bookworm', 'trixie', 'forky', \ - \ 'trusty', 'xenial', 'bionic', 'focal', 'jammy', 'kinetic', + \ 'trusty', 'xenial', 'bionic', 'focal', 'jammy', 'kinetic', 'lunar', \ 'devel' \ ] let s:unsupported = [ diff --git a/runtime/syntax/editorconfig.vim b/runtime/syntax/editorconfig.vim new file mode 100644 index 0000000000..4392d2b2d5 --- /dev/null +++ b/runtime/syntax/editorconfig.vim @@ -0,0 +1,17 @@ +runtime! syntax/dosini.vim +unlet! b:current_syntax + +syntax match editorconfigUnknownProperty "^\s*\zs\w\+\ze\s*=" +syntax keyword editorconfigProperty root + +lua<< +local props = {} +for k in pairs(require('editorconfig').properties) do + props[#props + 1] = k +end +vim.cmd(string.format('syntax keyword editorconfigProperty %s', table.concat(props, ' '))) +. + +hi def link editorconfigProperty dosiniLabel + +let b:current_syntax = 'editorconfig' diff --git a/runtime/syntax/fstab.vim b/runtime/syntax/fstab.vim index 318488713b..7e18c267f7 100644 --- a/runtime/syntax/fstab.vim +++ b/runtime/syntax/fstab.vim @@ -2,8 +2,8 @@ " Language: fstab file " Maintainer: Radu Dineiu <radu.dineiu@gmail.com> " URL: https://raw.github.com/rid9/vim-fstab/master/syntax/fstab.vim -" Last Change: 2020 Dec 30 -" Version: 1.4 +" Last Change: 2022 Dec 11 +" Version: 1.6.2 " " Credits: " David Necas (Yeti) <yeti@physics.muni.cz> @@ -56,71 +56,124 @@ syn keyword fsMountPointKeyword contained none swap " Type syn cluster fsTypeCluster contains=fsTypeKeyword,fsTypeUnknown syn match fsTypeUnknown /\s\+\zs\w\+/ contained -syn keyword fsTypeKeyword contained adfs ados affs anon_inodefs atfs audiofs auto autofs bdev befs bfs btrfs binfmt_misc cd9660 cfs cgroup cifs coda configfs cpuset cramfs devfs devpts devtmpfs e2compr efs ext2 ext2fs ext3 ext4 fdesc ffs filecore fuse fuseblk fusectl hfs hpfs hugetlbfs iso9660 jffs jffs2 jfs kernfs lfs linprocfs mfs minix mqueue msdos ncpfs nfs nfsd nilfs2 none ntfs null nwfs overlay ovlfs pipefs portal proc procfs pstore ptyfs qnx4 reiserfs ramfs romfs securityfs shm smbfs squashfs sockfs sshfs std subfs swap sysfs sysv tcfs tmpfs udf ufs umap umsdos union usbfs userfs vfat vs3fs vxfs wrapfs wvfs xenfs xfs zisofs +syn keyword fsTypeKeyword contained adfs ados affs anon_inodefs atfs audiofs auto autofs bdev befs bfs btrfs binfmt_misc cd9660 ceph cfs cgroup cifs coda coherent configfs cpuset cramfs debugfs devfs devpts devtmpfs dlmfs e2compr ecryptfs efivarfs efs erofs exfat ext2 ext2fs ext3 ext4 f2fs fdesc ffs filecore fuse fuseblk fusectl gfs2 hfs hfsplus hpfs hugetlbfs iso9660 jffs jffs2 jfs kernfs lfs linprocfs mfs minix mqueue msdos ncpfs nfs nfs4 nfsd nilfs2 none ntfs ntfs3 null nwfs ocfs2 omfs overlay ovlfs pipefs portal proc procfs pstore ptyfs pvfs2 qnx4 qnx6 reiserfs ramfs romfs rpc_pipefs securityfs shm smbfs spufs squashfs sockfs sshfs std subfs swap sysfs sysv tcfs tmpfs ubifs udf ufs umap umsdos union usbfs userfs v9fs vfat virtiofs vs3fs vxfs wrapfs wvfs xenfs xenix xfs zisofs zonefs " Options " ------- " Options: General syn cluster fsOptionsCluster contains=fsOperator,fsOptionsGeneral,fsOptionsKeywords,fsTypeUnknown syn match fsOptionsNumber /\d\+/ +syn match fsOptionsNumberSigned /[-+]\?\d\+/ syn match fsOptionsNumberOctal /[0-8]\+/ syn match fsOptionsString /[a-zA-Z0-9_-]\+/ +syn keyword fsOptionsTrueFalse true false syn keyword fsOptionsYesNo yes no +syn keyword fsOptionsYN y n +syn keyword fsOptions01 0 1 syn cluster fsOptionsCheckCluster contains=fsOptionsExt2Check,fsOptionsFatCheck syn keyword fsOptionsSize 512 1024 2048 -syn keyword fsOptionsGeneral async atime auto bind current defaults dev devgid devmode devmtime devuid dirsync exec force fstab kudzu loop mand move noatime noauto noclusterr noclusterw nodev nodevmtime nodiratime noexec nomand norelatime nosuid nosymfollow nouser owner rbind rdonly relatime remount ro rq rw suid suiddir supermount sw sync union update user users wxallowed xx nofail failok +syn keyword fsOptionsGeneral async atime auto bind current defaults dev devgid devmode devmtime devuid dirsync exec force fstab kudzu loop managed mand move noatime noauto noclusterr noclusterw nodev nodevmtime nodiratime noexec nomand norelatime nosuid nosymfollow nouser owner pamconsole rbind rdonly relatime remount ro rq rw suid suiddir supermount sw sync union update user users wxallowed xx nofail failok lazytime syn match fsOptionsGeneral /_netdev/ +syn match fsOptionsKeywords contained /\<x-systemd\.\%(requires\|before\|after\|wanted-by\|required-by\|requires-mounts-for\|idle-timeout\|device-timeout\|mount-timeout\)=/ nextgroup=fsOptionsString +syn match fsOptionsKeywords contained /\<x-systemd\.\%(device-bound\|automount\|makefs\|growfs\|rw-only\)/ +syn match fsOptionsKeywords contained /\<x-initrd\.mount/ + +syn match fsOptionsKeywords contained /\<cache=/ nextgroup=fsOptionsCache +syn keyword fsOptionsCache yes no none strict loose fscache mmap + +syn match fsOptionsKeywords contained /\<dax=/ nextgroup=fsOptionsDax +syn keyword fsOptionsDax inode never always + +syn match fsOptionsKeywords contained /\<errors=/ nextgroup=fsOptionsErrors +syn keyword fsOptionsErrors contained continue panic withdraw remount-ro recover zone-ro zone-offline repair + +syn match fsOptionsKeywords contained /\<\%(sec\)=/ nextgroup=fsOptionsSecurityMode +syn keyword fsOptionsSecurityMode contained none krb5 krb5i ntlm ntlmi ntlmv2 ntlmv2i ntlmssp ntlmsspi sys lkey lkeyi lkeyp spkm spkmi spkmp + " Options: adfs syn match fsOptionsKeywords contained /\<\%([ug]id\|o\%(wn\|th\)mask\)=/ nextgroup=fsOptionsNumber +syn match fsOptionsKeywords contained /\<ftsuffix=/ nextgroup=fsOptions01 " Options: affs -syn match fsOptionsKeywords contained /\<\%(set[ug]id\|mode\|reserved\)=/ nextgroup=fsOptionsNumber +syn match fsOptionsKeywords contained /\<mode=/ nextgroup=fsOptionsString +syn match fsOptionsKeywords contained /\<\%(set[ug]id\|reserved\)=/ nextgroup=fsOptionsNumber syn match fsOptionsKeywords contained /\<\%(prefix\|volume\|root\)=/ nextgroup=fsOptionsString syn match fsOptionsKeywords contained /\<bs=/ nextgroup=fsOptionsSize -syn keyword fsOptionsKeywords contained protect usemp verbose +syn keyword fsOptionsKeywords contained protect usemp verbose nofilenametruncate mufs " Options: btrfs -syn match fsOptionsKeywords contained /\<\%(subvol\|subvolid\|subvolrootid\|device\|compress\|compress-force\|fatal_errors\)=/ nextgroup=fsOptionsString +syn match fsOptionsKeywords contained /\<\%(subvol\|subvolid\|subvolrootid\|device\|compress\|compress-force\|check_int_print_mask\|space_cache\)=/ nextgroup=fsOptionsString syn match fsOptionsKeywords contained /\<\%(max_inline\|alloc_start\|thread_pool\|metadata_ratio\|check_int_print_mask\)=/ nextgroup=fsOptionsNumber -syn keyword fsOptionsKeywords contained degraded nodatasum nodatacow nobarrier ssd ssd_spread noacl notreelog flushoncommit space_cache nospace_cache clear_cache user_subvol_rm_allowed autodefrag inode_cache enospc_debug recovery check_int check_int_data skip_balance discard +syn match fsOptionsKeywords contained /\<discard=/ nextgroup=fsOptionsBtrfsDiscard +syn keyword fsOptionsBtrfsDiscard sync async +syn match fsOptionsKeywords contained /\<fatal_errors=/ nextgroup=fsOptionsBtrfsFatalErrors +syn keyword fsOptionsBtrfsFatalErrors bug panic +syn match fsOptionsKeywords contained /\<fragment=/ nextgroup=fsOptionsBtrfsFragment +syn keyword fsOptionsBtrfsFragment data metadata all +syn keyword fsOptionsKeywords contained degraded datasum nodatasum datacow nodatacow barrier nobarrier ssd ssd_spread nossd nossd_spread noacl treelog notreelog flushoncommit noflushoncommit space_cache nospace_cache clear_cache user_subvol_rm_allowed autodefrag noautodefrag inode_cache noinode_cache enospc_debug noenospc_debug recovery check_int check_int_data skip_balance discard nodiscard compress compress-force nologreplay rescan_uuid_tree rescue usebackuproot " Options: cd9660 syn keyword fsOptionsKeywords contained extatt gens norrip nostrictjoilet +" Options: ceph +syn match fsOptionsKeywords contained /\<\%(mon_addr\|fsid\|rasize\|mount_timeout\|caps_max\)=/ nextgroup=fsOptionsString +syn keyword fsOptionsKeywords contained rbytes norbytes nocrc dcache nodcache noasyncreaddir noquotadf nocopyfrom +syn match fsOptionsKeywords contained /\<recover_session=/ nextgroup=fsOptionsCephRecoverSession +syn keyword fsOptionsCephRecoverSession contained no clean + +" Options: cifs +syn match fsOptionsKeywords contained /\<\%(user\|password\|credentials\|servernetbiosname\|servern\|netbiosname\|file_mode\|dir_mode\|ip\|domain\|prefixpath\)=/ nextgroup=fsOptionsString +syn match fsOptionsKeywords contained /\<\%(cruid\|backupuid\|backupgid\)=/ nextgroup=fsOptionsNumber +syn keyword fsOptionsKeywords contained forceuid forcegid guest setuids nosetuids perm noperm dynperm strictcache rwpidforward mapchars nomapchars cifsacl nocase ignorecase nobrl sfu serverino noserverino nounix fsc multiuser posixpaths noposixpaths + " Options: devpts " -- everything already defined +" Options: ecryptfs +syn match fsOptionsKeywords contained /\<\%(ecryptfs_\%(sig\|fnek_sig\|cipher\|key_bytes\)\|key\)=/ nextgroup=fsOptionsString +syn keyword fsOptionsKeywords contained ecryptfs_passthrough no_sig_cache ecryptfs_encrypted_view ecryptfs_xattr +syn match fsOptionsKeywords contained /\<ecryptfs_enable_filename_crypto=/ nextgroup=fsOptionsYN +syn match fsOptionsKeywords contained /\<verbosity=/ nextgroup=fsOptions01 + +" Options: erofs +syn match fsOptionsKeywords contained /\<cache_strategy=/ nextgroup=fsOptionsEroCacheStrategy +syn keyword fsOptionsEroCacheStrategy contained disabled readahead readaround + " Options: ext2 syn match fsOptionsKeywords contained /\<check=*/ nextgroup=@fsOptionsCheckCluster -syn match fsOptionsKeywords contained /\<errors=/ nextgroup=fsOptionsExt2Errors syn match fsOptionsKeywords contained /\<\%(res[gu]id\|sb\)=/ nextgroup=fsOptionsNumber syn keyword fsOptionsExt2Check contained none normal strict -syn keyword fsOptionsExt2Errors contained continue panic -syn match fsOptionsExt2Errors contained /\<remount-ro\>/ +syn match fsOptionsErrors contained /\<remount-ro\>/ syn keyword fsOptionsKeywords contained acl bsddf minixdf debug grpid bsdgroups minixdf nocheck nogrpid oldalloc orlov sysvgroups nouid32 nobh user_xattr nouser_xattr " Options: ext3 syn match fsOptionsKeywords contained /\<journal=/ nextgroup=fsOptionsExt3Journal syn match fsOptionsKeywords contained /\<data=/ nextgroup=fsOptionsExt3Data +syn match fsOptionsKeywords contained /\<data_err=/ nextgroup=fsOptionsExt3DataErr syn match fsOptionsKeywords contained /\<commit=/ nextgroup=fsOptionsNumber +syn match fsOptionsKeywords contained /\<jqfmt=/ nextgroup=fsOptionsExt3Jqfmt +syn match fsOptionsKeywords contained /\<\%(usrjquota\|grpjquota\)=/ nextgroup=fsOptionsString syn keyword fsOptionsExt3Journal contained update inum syn keyword fsOptionsExt3Data contained journal ordered writeback +syn keyword fsOptionsExt3DataErr contained ignore abort +syn keyword fsOptionsExt3Jqfmt contained vfsold vfsv0 vfsv1 syn keyword fsOptionsKeywords contained noload user_xattr nouser_xattr acl " Options: ext4 syn match fsOptionsKeywords contained /\<journal=/ nextgroup=fsOptionsExt4Journal syn match fsOptionsKeywords contained /\<data=/ nextgroup=fsOptionsExt4Data -syn match fsOptionsKeywords contained /\<barrier=/ nextgroup=fsOptionsExt4Barrier +syn match fsOptionsKeywords contained /\<barrier=/ nextgroup=fsOptions01 syn match fsOptionsKeywords contained /\<journal_dev=/ nextgroup=fsOptionsNumber syn match fsOptionsKeywords contained /\<resuid=/ nextgroup=fsOptionsNumber syn match fsOptionsKeywords contained /\<resgid=/ nextgroup=fsOptionsNumber syn match fsOptionsKeywords contained /\<sb=/ nextgroup=fsOptionsNumber -syn match fsOptionsKeywords contained /\<commit=/ nextgroup=fsOptionsNumber +syn match fsOptionsKeywords contained /\<\%(commit\|inode_readahead_blks\|stripe\|max_batch_time\|min_batch_time\|init_itable\|max_dir_size_kb\)=/ nextgroup=fsOptionsNumber +syn match fsOptionsKeywords contained /\<journal_ioprio=/ nextgroup=fsOptionsExt4JournalIoprio syn keyword fsOptionsExt4Journal contained update inum syn keyword fsOptionsExt4Data contained journal ordered writeback -syn match fsOptionsExt4Barrier /[0-1]/ -syn keyword fsOptionsKeywords contained noload extents orlov oldalloc user_xattr nouser_xattr acl noacl reservation noreservation bsddf minixdf check=none nocheck debug grpid nogroupid sysvgroups bsdgroups quota noquota grpquota usrquota bh nobh +syn keyword fsOptionsExt4JournalIoprio contained 0 1 2 3 4 5 6 7 +syn keyword fsOptionsKeywords contained noload extents orlov oldalloc user_xattr nouser_xattr acl noacl reservation noreservation bsddf minixdf check=none nocheck debug grpid nogroupid sysvgroups bsdgroups quota noquota grpquota usrquota bh nobh journal_checksum nojournal_checksum journal_async_commit delalloc nodelalloc auto_da_alloc noauto_da_alloc noinit_itable block_validity noblock_validity dioread_lock dioread_nolock i_version nombcache prjquota " Options: fat syn match fsOptionsKeywords contained /\<blocksize=/ nextgroup=fsOptionsSize @@ -135,39 +188,124 @@ syn keyword fsOptionsConv contained b t a binary text auto syn keyword fsOptionsFatType contained 12 16 32 syn keyword fsOptionsKeywords contained quiet sys_immutable showexec dots nodots +" Options: fuse +syn match fsOptionsKeywords contained /\<\%(fd\|user_id\|group_id\|blksize\)=/ nextgroup=fsOptionsNumber +syn match fsOptionsKeywords contained /\<\%(rootmode\)=/ nextgroup=fsOptionsString + " Options: hfs -syn match fsOptionsKeywords contained /\<\%(creator|type\)=/ nextgroup=fsOptionsString +syn match fsOptionsKeywords contained /\<\%(creator\|type\)=/ nextgroup=fsOptionsString syn match fsOptionsKeywords contained /\<\%(dir\|file\|\)_umask=/ nextgroup=fsOptionsNumberOctal syn match fsOptionsKeywords contained /\<\%(session\|part\)=/ nextgroup=fsOptionsNumber +" Options: hfsplus +syn match fsOptionsKeywords contained /\<nls=/ nextgroup=fsOptionsString +syn keyword fsOptionsKeywords contained decompose nodecompose + +" Options: f2fs +syn match fsOptionsKeywords contained /\<background_gc=/ nextgroup=fsOptionsF2fsBackgroundGc +syn keyword fsOptionsF2fsBackgroundGc contained on off sync +syn match fsOptionsKeywords contained /\<active_logs=/ nextgroup=fsOptionsF2fsActiveLogs +syn keyword fsOptionsF2fsActiveLogs contained 2 4 6 +syn match fsOptionsKeywords contained /\<alloc_mode=/ nextgroup=fsOptionsF2fsAllocMode +syn keyword fsOptionsF2fsAllocMode contained reuse default +syn match fsOptionsKeywords contained /\<fsync_mode=/ nextgroup=fsOptionsF2fsFsyncMode +syn keyword fsOptionsF2fsFsyncMode contained posix strict nobarrier +syn match fsOptionsKeywords contained /\<compress_mode=/ nextgroup=fsOptionsF2fsCompressMode +syn keyword fsOptionsF2fsCompressMode contained fs user +syn match fsOptionsKeywords contained /\<discard_unit=/ nextgroup=fsOptionsF2fsDiscardUnit +syn keyword fsOptionsF2fsDiscardUnit contained block segment section +syn match fsOptionsKeywords contained /\<memory=/ nextgroup=fsOptionsF2fsMemory +syn keyword fsOptionsF2fsMemory contained normal low +syn match fsOptionsKeywords contained /\<\%(inline_xattr_size\|reserve_root\|fault_injection\|fault_type\|io_bits\|compress_log_size\)=/ nextgroup=fsOptionsNumber +syn match fsOptionsKeywords contained /\<\%(prjjquota\|test_dummy_encryption\|checkpoint\|compress_algorithm\|compress_extension\|nocompress_extension\)=/ nextgroup=fsOptionsString +syn keyword fsOptionsKeyWords contained gc_merge nogc_merge disable_roll_forward no_heap disable_ext_identify inline_xattr noinline_xattr inline_data noinline_data inline_dentry noinline_dentry flush_merge fastboot extent_cache noextent_cache data_flush offusrjquota offgrpjquota offprjjquota test_dummy_encryption checkpoint_merge nocheckpoint_merge compress_chksum compress_cache inlinecrypt atgc + " Options: ffs syn keyword fsOptionsKeyWords contained noperm softdep +" Options: gfs2 +syn match fsOptionsKeywords contained /\<\%(lockproto\|locktable\)=/ nextgroup=fsOptionsString +syn match fsOptionsKeywords contained /\<\%(quota_quantum\|statfs_quantum\|statfs_percent\)=/ nextgroup=fsOptionsNumber +syn match fsOptionsKeywords contained /\<quota=/ nextgroup=fsOptionsGfs2Quota +syn keyword fsOptionsGfs2Quota contained off account on +syn keyword fsOptionsKeywords contained localcaching localflocks ignore_local_fs upgrade spectator meta + " Options: hpfs syn match fsOptionsKeywords contained /\<case=/ nextgroup=fsOptionsHpfsCase syn keyword fsOptionsHpfsCase contained lower asis +syn match fsOptionsKeywords contained /\<chkdsk=/ nextgroup=fsOptionsHpfsChkdsk +syn keyword fsOptionsHpfsChkdsk contained no errors always +syn match fsOptionsKeywords contained /\<eas=/ nextgroup=fsOptionsHpfsEas +syn keyword fsOptionsHpfsEas contained no ro rw +syn match fsOptionsKeywords contained /\<timeshift=/ nextgroup=fsOptionsNumberSigned " Options: iso9660 syn match fsOptionsKeywords contained /\<map=/ nextgroup=fsOptionsIsoMap syn match fsOptionsKeywords contained /\<block=/ nextgroup=fsOptionsSize -syn match fsOptionsKeywords contained /\<\%(session\|sbsector\)=/ nextgroup=fsOptionsNumber +syn match fsOptionsKeywords contained /\<\%(session\|sbsector\|dmode\)=/ nextgroup=fsOptionsNumber syn keyword fsOptionsIsoMap contained n o a normal off acorn -syn keyword fsOptionsKeywords contained norock nojoilet unhide cruft +syn keyword fsOptionsKeywords contained norock nojoliet hide unhide cruft overriderockperm showassoc syn keyword fsOptionsConv contained m mtext " Options: jfs syn keyword fsOptionsKeywords nointegrity integrity " Options: nfs -syn match fsOptionsKeywords contained /\<\%(rsize\|wsize\|timeo\|retrans\|acregmin\|acregmax\|acdirmin\|acdirmax\|actimeo\|retry\|port\|mountport\|mounthost\|mountprog\|mountvers\|nfsprog\|nfsvers\|namelen\)=/ nextgroup=fsOptionsString -syn keyword fsOptionsKeywords contained bg fg soft hard intr cto ac tcp udp lock nobg nofg nosoft nohard nointr noposix nocto noac notcp noudp nolock +syn match fsOptionsKeywords contained /\<lookupcache=/ nextgroup=fsOptionsNfsLookupCache +syn keyword fsOptionsNfsLookupCache contained all none pos positive +syn match fsOptionsKeywords contained /\<local_lock=/ nextgroup=fsOptionsNfsLocalLock +syn keyword fsOptionsNfsLocalLock contained all flock posix none +syn match fsOptionsKeywords contained /\<\%(mounthost\|mountprog\|nfsprog\|namelen\|proto\|mountproto\|clientaddr\)=/ nextgroup=fsOptionsString +syn match fsOptionsKeywords contained /\<\%(timeo\|retrans\|[rw]size\|acregmin\|acregmax\|acdirmin\|acdirmax\|actimeo\|retry\|port\|mountport\|mountvers\|namlen\|nfsvers\|vers\|minorversion\)=/ nextgroup=fsOptionsNumber +syn keyword fsOptionsKeywords contained bg fg soft hard intr cto ac tcp udp lock nobg nofg nosoft nohard nointr noposix nocto noac notcp noudp nolock sharecache nosharecache resvport noresvport rdirplus nordirplus + +" Options: nilfs2 +syn match fsOptionsKeywords contained /\<order=/ nextgroup=fsOptionsNilfs2Order +syn keyword fsOptionsNilfs2Order contained relaxed strict +syn match fsOptionsKeywords contained /\<\%([cp]p\)=/ nextgroup=fsOptionsNumber +syn keyword fsOptionsKeywords contained nogc " Options: ntfs +syn match fsOptionsKeywords contained /\<mft_zone_multiplier=/ nextgroup=fsOptionsNtfsMftZoneMultiplier +syn keyword fsOptionsNtfsMftZoneMultiplier contained 1 2 3 4 syn match fsOptionsKeywords contained /\<\%(posix=*\|uni_xlate=\)/ nextgroup=fsOptionsNumber +syn match fsOptionsKeywords contained /\<\%(sloppy\|show_sys_files\|case_sensitive\|disable_sparse\)=/ nextgroup=fsOptionsTrueFalse syn keyword fsOptionsKeywords contained utf8 +" Options: ntfs3 +syn keyword fsOptionsKeywords contained noacsrules nohidden sparse showmeta prealloc + +" Options: ntfs-3g +syn match fsOptionsKeywords contained /\<\%(usermapping\|locale\|streams_interface\)=/ nextgroup=fsOptionsString +syn keyword fsOptionsKeywords contained permissions inherit recover norecover ignore_case remove_hiberfile hide_hid_files hide_dot_files windows_names silent no_def_opts efs_raw compression nocompression no_detach + +" Options: ocfs2 +syn match fsOptionsKeywords contained /\<\%(resv_level\|dir_resv_level\)=/ nextgroup=fsOptionsOcfs2ResvLevel +syn keyword fsOptionsOcfs2ResvLevel contained 0 1 2 3 4 5 6 7 8 +syn match fsOptionsKeywords contained /\<coherency=/ nextgroup=fsOptionsOcfs2Coherency +syn keyword fsOptionsOcfs2Coherency contained full buffered +syn match fsOptionsKeywords contained /\<\%(atime_quantum\|preferred_slot\|localalloc\)=/ nextgroup=fsOptionsNumber +syn keyword fsOptionsKeywords contained strictatime inode64 + +" Options: overlay +syn match fsOptionsKeywords contained /\<redirect_dir=/ nextgroup=fsOptionsOverlayRedirectDir +syn keyword fsOptionsOverlayRedirectDir contained on follow off nofollow + " Options: proc -" -- everything already defined +syn match fsOptionsKeywords contained /\<\%(hidepid\|subset\)=/ nextgroup=fsOptionsString + +" Options: qnx4 +syn match fsOptionsKeywords contained /\<bitmap=/ nextgroup=fsOptionsQnx4Bitmap +syn keyword fsOptionsQnx4Bitmap contained always lazy nonrmv +syn keyword fsOptionsKeywords contained grown noembed overalloc unbusy + +" Options: qnx6 +syn match fsOptionsKeywords contained /\<hold=/ nextgroup=fsOptionsQnx6Hold +syn keyword fsOptionsQnx6Hold contained allow root deny +syn match fsOptionsKeywords contained /\<sync=/ nextgroup=fsOptionsQnx6Sync +syn keyword fsOptionsQnx6Sync contained mandatory optional none +syn match fsOptionsKeywords contained /\<snapshot=/ nextgroup=fsOptionsNumber +syn keyword fsOptionsKeywords contained alignio " Options: reiserfs syn match fsOptionsKeywords contained /\<hash=/ nextgroup=fsOptionsReiserHash @@ -176,7 +314,7 @@ syn keyword fsOptionsReiserHash contained rupasov tea r5 detect syn keyword fsOptionsKeywords contained hashed_relocation noborder nolog notail no_unhashed_relocation replayonly " Options: sshfs -syn match fsOptionsKeywords contained /\<\%(BatchMode\|ChallengeResponseAuthentication\|CheckHostIP\|ClearAllForwardings\|Compression\|EnableSSHKeysign\|ForwardAgent\|ForwardX11\|ForwardX11Trusted\|GatewayPorts\|GSSAPIAuthentication\|GSSAPIDelegateCredentials\|HashKnownHosts\|HostbasedAuthentication\|IdentitiesOnly\|NoHostAuthenticationForLocalhost\|PasswordAuthentication\|PubkeyAuthentication\|RhostsRSAAuthentication\|RSAAuthentication\|TCPKeepAlive\|UsePrivilegedPort\|cache\)=/ nextgroup=fsOptionsYesNo +syn match fsOptionsKeywords contained /\<\%(BatchMode\|ChallengeResponseAuthentication\|CheckHostIP\|ClearAllForwardings\|Compression\|EnableSSHKeysign\|ForwardAgent\|ForwardX11\|ForwardX11Trusted\|GatewayPorts\|GSSAPIAuthentication\|GSSAPIDelegateCredentials\|HashKnownHosts\|HostbasedAuthentication\|IdentitiesOnly\|NoHostAuthenticationForLocalhost\|PasswordAuthentication\|PubkeyAuthentication\|RhostsRSAAuthentication\|RSAAuthentication\|TCPKeepAlive\|UsePrivilegedPort\)=/ nextgroup=fsOptionsYesNo syn match fsOptionsKeywords contained /\<\%(ControlMaster\|StrictHostKeyChecking\|VerifyHostKeyDNS\)=/ nextgroup=fsOptionsSshYesNoAsk syn match fsOptionsKeywords contained /\<\%(AddressFamily\|BindAddress\|Cipher\|Ciphers\|ControlPath\|DynamicForward\|EscapeChar\|GlobalKnownHostsFile\|HostKeyAlgorithms\|HostKeyAlias\|HostName\|IdentityFile\|KbdInteractiveDevices\|LocalForward\|LogLevel\|MACs\|PreferredAuthentications\|Protocol\|ProxyCommand\|RemoteForward\|RhostsAuthentication\|SendEnv\|SmartcardDevice\|User\|UserKnownHostsFile\|XAuthLocation\|comment\|workaround\|idmap\|ssh_command\|sftp_server\|fsname\)=/ nextgroup=fsOptionsString syn match fsOptionsKeywords contained /\<\%(CompressionLevel\|ConnectionAttempts\|ConnectTimeout\|NumberOfPasswordPrompts\|Port\|ServerAliveCountMax\|ServerAliveInterval\|cache_timeout\|cache_X_timeout\|ssh_protocol\|directport\|max_read\|umask\|uid\|gid\|entry_timeout\|negative_timeout\|attr_timeout\)=/ nextgroup=fsOptionsNumber @@ -190,12 +328,19 @@ syn keyword fsOptionsKeywords contained procuid " Options: swap syn match fsOptionsKeywords contained /\<pri=/ nextgroup=fsOptionsNumber +" Options: ubifs +syn match fsOptionsKeywords contained /\<\%(compr\|auth_key\|auth_hash_name\)=/ nextgroup=fsOptionsString +syn keyword fsOptionsKeywords contained bulk_read no_bulk_read chk_data_crc no_chk_data_crc + " Options: tmpfs +syn match fsOptionsKeywords contained /\<huge=/ nextgroup=fsOptionsTmpfsHuge +syn keyword fsOptionsTmpfsHuge contained never always within_size advise deny force +syn match fsOptionsKeywords contained /\<\%(size\|mpol\)=/ nextgroup=fsOptionsString syn match fsOptionsKeywords contained /\<nr_\%(blocks\|inodes\)=/ nextgroup=fsOptionsNumber " Options: udf syn match fsOptionsKeywords contained /\<\%(anchor\|partition\|lastblock\|fileset\|rootdir\)=/ nextgroup=fsOptionsString -syn keyword fsOptionsKeywords contained unhide undelete strict novrs +syn keyword fsOptionsKeywords contained unhide undelete strict nostrict novrs adinicb noadinicb shortad longad " Options: ufs syn match fsOptionsKeywords contained /\<ufstype=/ nextgroup=fsOptionsUfsType @@ -208,14 +353,32 @@ syn keyword fsOptionsUfsError contained panic lock umount repair syn match fsOptionsKeywords contained /\<\%(dev\|bus\|list\)\%(id\|gid\)=/ nextgroup=fsOptionsNumber syn match fsOptionsKeywords contained /\<\%(dev\|bus\|list\)mode=/ nextgroup=fsOptionsNumberOctal +" Options: v9fs +syn match fsOptionsKeywords contained /\<\%(trans\)=/ nextgroup=fsOptionsV9Trans +syn keyword fsOptionsV9Trans unix tcp fd virtio rdma +syn match fsOptionsKeywords contained /\<debug=/ nextgroup=fsOptionsV9Debug +syn keyword fsOptionsV9Debug 0x01 0x02 0x04 0x08 0x10 0x20 0x40 0x80 0x100 0x200 0x400 0x800 +syn match fsOptionsKeywords contained /\<version=/ nextgroup=fsOptionsV9Version +syn keyword fsOptionsV9Version 9p2000 9p2000.u 9p2000.L +syn match fsOptionsKeywords contained /\<\%([ua]name\|[rw]fdno\|access\)=/ nextgroup=fsOptionsString +syn match fsOptionsKeywords contained /\<msize=/ nextgroup=fsOptionsNumber +syn keyword fsOptionsKeywords contained noextend dfltuid dfltgid afid nodevmap cachetag + " Options: vfat -syn keyword fsOptionsKeywords contained nonumtail posix utf8 -syn match fsOptionsKeywords contained /shortname=/ nextgroup=fsOptionsVfatShortname +syn match fsOptionsKeywords contained /\<shortname=/ nextgroup=fsOptionsVfatShortname syn keyword fsOptionsVfatShortname contained lower win95 winnt mixed +syn match fsOptionsKeywords contained /\<nfs=/ nextgroup=fsOptionsVfatNfs +syn keyword fsOptionsVfatNfs contained stale_rw nostale_ro +syn match fsOptionsKeywords contained /\<\%(tz\|dos1xfloppy\)=/ nextgroup=fsOptionsString +syn match fsOptionsKeywords contained /\<\%(allow_utime\|codepage\)=/ nextgroup=fsOptionsNumber +syn match fsOptionsKeywords contained /\<time_offset=/ nextgroup=fsOptionsNumberSigned +syn keyword fsOptionsKeywords contained nonumtail posix utf8 usefree flush rodir " Options: xfs -syn match fsOptionsKeywords contained /\%(biosize\|logbufs\|logbsize\|logdev\|rtdev\|sunit\|swidth\)=/ nextgroup=fsOptionsString -syn keyword fsOptionsKeywords contained dmapi xdsm noalign noatime noquota norecovery osyncisdsync quota usrquota uqnoenforce grpquota gqnoenforce +syn match fsOptionsKeywords contained /\<logbufs=/ nextgroup=fsOptionsXfsLogBufs +syn keyword fsOptionsXfsLogBufs contained 2 3 4 5 6 7 8 +syn match fsOptionsKeywords contained /\%(allocsize\|biosize\|logbsize\|logdev\|rtdev\|sunit\|swidth\)=/ nextgroup=fsOptionsString +syn keyword fsOptionsKeywords contained dmapi xdsm noalign noatime noquota norecovery osyncisdsync quota usrquota uqnoenforce grpquota gqnoenforce attr2 noattr2 filestreams ikeep noikeep inode32 inode64 largeio nolargeio nouuid uquota qnoenforce gquota pquota pqnoenforce swalloc wsync " Frequency / Pass No. syn cluster fsFreqPassCluster contains=fsFreqPassNumber,fsFreqPassError @@ -257,31 +420,71 @@ hi def link fsMountPointError Error hi def link fsMountPointKeyword Keyword hi def link fsFreqPassError Error -hi def link fsOptionsGeneral Type -hi def link fsOptionsKeywords Keyword -hi def link fsOptionsNumber Number -hi def link fsOptionsNumberOctal Number -hi def link fsOptionsString String -hi def link fsOptionsSize Number +hi def link fsOptionsBtrfsDiscard String +hi def link fsOptionsBtrfsFatalErrors String +hi def link fsOptionsBtrfsFragment String +hi def link fsOptionsCache String +hi def link fsOptionsCephRecoverSession String +hi def link fsOptionsConv String +hi def link fsOptionsDax String +hi def link fsOptionsEroCacheStrategy String +hi def link fsOptionsErrors String hi def link fsOptionsExt2Check String -hi def link fsOptionsExt2Errors String -hi def link fsOptionsExt3Journal String hi def link fsOptionsExt3Data String -hi def link fsOptionsExt4Journal String +hi def link fsOptionsExt3DataErr String +hi def link fsOptionsExt3Journal String +hi def link fsOptionsExt3Jqfmt String hi def link fsOptionsExt4Data String -hi def link fsOptionsExt4Barrier Number +hi def link fsOptionsExt4Journal String +hi def link fsOptionsExt4JournalIoprio Number +hi def link fsOptionsF2fsActiveLogs Number +hi def link fsOptionsF2fsAllocMode String +hi def link fsOptionsF2fsBackgroundGc String +hi def link fsOptionsF2fsCompressMode String +hi def link fsOptionsF2fsDiscardUnit String +hi def link fsOptionsF2fsFsyncMode String +hi def link fsOptionsF2fsMemory String hi def link fsOptionsFatCheck String -hi def link fsOptionsConv String hi def link fsOptionsFatType Number -hi def link fsOptionsYesNo String +hi def link fsOptionsGeneral Type +hi def link fsOptionsGfs2Quota String hi def link fsOptionsHpfsCase String +hi def link fsOptionsHpfsChkdsk String +hi def link fsOptionsHpfsEas String hi def link fsOptionsIsoMap String +hi def link fsOptionsKeywords Keyword +hi def link fsOptionsNfsLocalLock String +hi def link fsOptionsNfsLookupCache String +hi def link fsOptionsNilfs2Order String +hi def link fsOptionsNtfsMftZoneMultiplier Number +hi def link fsOptionsNumber Number +hi def link fsOptionsNumberOctal Number +hi def link fsOptionsNumberSigned Number +hi def link fsOptionsOcfs2Coherency String +hi def link fsOptionsOcfs2ResvLevel Number +hi def link fsOptionsOverlayRedirectDir String +hi def link fsOptionsQnx4Bitmap String +hi def link fsOptionsQnx6Hold String +hi def link fsOptionsQnx6Sync String hi def link fsOptionsReiserHash String +hi def link fsOptionsSecurityMode String +hi def link fsOptionsSize Number hi def link fsOptionsSshYesNoAsk String -hi def link fsOptionsUfsType String +hi def link fsOptionsString String +hi def link fsOptionsTmpfsHuge String hi def link fsOptionsUfsError String - +hi def link fsOptionsUfsType String +hi def link fsOptionsV9Debug String +hi def link fsOptionsV9Trans String +hi def link fsOptionsV9Version String +hi def link fsOptionsVfatNfs String hi def link fsOptionsVfatShortname String +hi def link fsOptionsXfsLogBufs Number + +hi def link fsOptionsTrueFalse Boolean +hi def link fsOptionsYesNo String +hi def link fsOptionsYN String +hi def link fsOptions01 Number let b:current_syntax = "fstab" diff --git a/runtime/syntax/go.vim b/runtime/syntax/go.vim index 0c326254b8..904c8ad7f2 100644 --- a/runtime/syntax/go.vim +++ b/runtime/syntax/go.vim @@ -5,7 +5,7 @@ " go.vim: Vim syntax file for Go. " Language: Go " Maintainer: Billie Cleek <bhcleek@gmail.com> -" Latest Revision: 2021-09-18 +" Latest Revision: 2022-11-17 " License: BSD-style. See LICENSE file in source repository. " Repository: https://github.com/fatih/vim-go @@ -117,7 +117,7 @@ hi def link goLabel Label hi def link goRepeat Repeat " Predefined types -syn keyword goType chan map bool string error +syn keyword goType chan map bool string error any comparable syn keyword goSignedInts int int8 int16 int32 int64 rune syn keyword goUnsignedInts byte uint uint8 uint16 uint32 uint64 uintptr syn keyword goFloats float32 float64 @@ -187,6 +187,8 @@ else syn region goRawString start=+`+ end=+`+ endif +syn match goImportString /^\%(\s\+\|import \)\(\h\w* \)\?\zs"[^"]\+"$/ contained containedin=goImport + if s:HighlightFormatStrings() " [n] notation is valid for specifying explicit argument indexes " 1. Match a literal % not preceded by a %. @@ -204,6 +206,7 @@ if s:HighlightFormatStrings() hi def link goFormatSpecifier goSpecialString endif +hi def link goImportString String hi def link goString String hi def link goRawString String @@ -223,9 +226,9 @@ endif " import if s:FoldEnable('import') - syn region goImport start='import (' end=')' transparent fold contains=goImport,goString,goComment + syn region goImport start='import (' end=')' transparent fold contains=goImport,goImportString,goComment else - syn region goImport start='import (' end=')' transparent contains=goImport,goString,goComment + syn region goImport start='import (' end=')' transparent contains=goImport,goImportString,goComment endif " var, const @@ -245,14 +248,10 @@ endif syn match goSingleDecl /\%(import\|var\|const\) [^(]\@=/ contains=goImport,goVar,goConst " Integers -syn match goDecimalInt "\<-\=\(0\|[1-9]_\?\(\d\|\d\+_\?\d\+\)*\)\%([Ee][-+]\=\d\+\)\=\>" -syn match goDecimalError "\<-\=\(_\(\d\+_*\)\+\|\([1-9]\d*_*\)\+__\(\d\+_*\)\+\|\([1-9]\d*_*\)\+_\+\)\%([Ee][-+]\=\d\+\)\=\>" -syn match goHexadecimalInt "\<-\=0[xX]_\?\(\x\+_\?\)\+\>" -syn match goHexadecimalError "\<-\=0[xX]_\?\(\x\+_\?\)*\(\([^ \t0-9A-Fa-f_)]\|__\)\S*\|_\)\>" -syn match goOctalInt "\<-\=0[oO]\?_\?\(\o\+_\?\)\+\>" -syn match goOctalError "\<-\=0[0-7oO_]*\(\([^ \t0-7oOxX_/)\]\}\:;]\|[oO]\{2,\}\|__\)\S*\|_\|[oOxX]\)\>" -syn match goBinaryInt "\<-\=0[bB]_\?\([01]\+_\?\)\+\>" -syn match goBinaryError "\<-\=0[bB]_\?[01_]*\([^ \t01_)]\S*\|__\S*\|_\)\>" +syn match goDecimalInt "\<-\=\%(0\|\%(\d\|\d_\d\)\+\)\>" +syn match goHexadecimalInt "\<-\=0[xX]_\?\%(\x\|\x_\x\)\+\>" +syn match goOctalInt "\<-\=0[oO]\?_\?\%(\o\|\o_\o\)\+\>" +syn match goBinaryInt "\<-\=0[bB]_\?\%([01]\|[01]_[01]\)\+\>" hi def link goDecimalInt Integer hi def link goDecimalError Error @@ -265,19 +264,55 @@ hi def link goBinaryError Error hi def link Integer Number " Floating point -syn match goFloat "\<-\=\d\+\.\d*\%([Ee][-+]\=\d\+\)\=\>" -syn match goFloat "\<-\=\.\d\+\%([Ee][-+]\=\d\+\)\=\>" +"float_lit = decimal_float_lit | hex_float_lit . +" +"decimal_float_lit = decimal_digits "." [ decimal_digits ] [ decimal_exponent ] | +" decimal_digits decimal_exponent | +" "." decimal_digits [ decimal_exponent ] . +"decimal_exponent = ( "e" | "E" ) [ "+" | "-" ] decimal_digits . +" +"hex_float_lit = "0" ( "x" | "X" ) hex_mantissa hex_exponent . +"hex_mantissa = [ "_" ] hex_digits "." [ hex_digits ] | +" [ "_" ] hex_digits | +" "." hex_digits . +"hex_exponent = ( "p" | "P" ) [ "+" | "-" ] decimal_digits . +" decimal floats with a decimal point +syn match goFloat "\<-\=\%(0\|\%(\d\|\d_\d\)\+\)\.\%(\%(\%(\d\|\d_\d\)\+\)\=\%([Ee][-+]\=\%(\d\|\d_\d\)\+\)\=\>\)\=" +syn match goFloat "\s\zs-\=\.\%(\d\|\d_\d\)\+\%(\%([Ee][-+]\=\%(\d\|\d_\d\)\+\)\>\)\=" +" decimal floats without a decimal point +syn match goFloat "\<-\=\%(0\|\%(\d\|\d_\d\)\+\)[Ee][-+]\=\%(\d\|\d_\d\)\+\>" +" hexadecimal floats with a decimal point +syn match goHexadecimalFloat "\<-\=0[xX]\%(_\x\|\x\)\+\.\%(\%(\x\|\x_\x\)\+\)\=\%([Pp][-+]\=\%(\d\|\d_\d\)\+\)\=\>" +syn match goHexadecimalFloat "\<-\=0[xX]\.\%(\x\|\x_\x\)\+\%([Pp][-+]\=\%(\d\|\d_\d\)\+\)\=\>" +" hexadecimal floats without a decimal point +syn match goHexadecimalFloat "\<-\=0[xX]\%(_\x\|\x\)\+[Pp][-+]\=\%(\d\|\d_\d\)\+\>" hi def link goFloat Float +hi def link goHexadecimalFloat Float " Imaginary literals -syn match goImaginary "\<-\=\d\+i\>" -syn match goImaginary "\<-\=\d\+[Ee][-+]\=\d\+i\>" -syn match goImaginaryFloat "\<-\=\d\+\.\d*\%([Ee][-+]\=\d\+\)\=i\>" -syn match goImaginaryFloat "\<-\=\.\d\+\%([Ee][-+]\=\d\+\)\=i\>" - -hi def link goImaginary Number -hi def link goImaginaryFloat Float +syn match goImaginaryDecimal "\<-\=\%(0\|\%(\d\|\d_\d\)\+\)i\>" +syn match goImaginaryHexadecimal "\<-\=0[xX]_\?\%(\x\|\x_\x\)\+i\>" +syn match goImaginaryOctal "\<-\=0[oO]\?_\?\%(\o\|\o_\o\)\+i\>" +syn match goImaginaryBinary "\<-\=0[bB]_\?\%([01]\|[01]_[01]\)\+i\>" + +" imaginary decimal floats with a decimal point +syn match goImaginaryFloat "\<-\=\%(0\|\%(\d\|\d_\d\)\+\)\.\%(\%(\%(\d\|\d_\d\)\+\)\=\%([Ee][-+]\=\%(\d\|\d_\d\)\+\)\=\)\=i\>" +syn match goImaginaryFloat "\s\zs-\=\.\%(\d\|\d_\d\)\+\%([Ee][-+]\=\%(\d\|\d_\d\)\+\)\=i\>" +" imaginary decimal floats without a decimal point +syn match goImaginaryFloat "\<-\=\%(0\|\%(\d\|\d_\d\)\+\)[Ee][-+]\=\%(\d\|\d_\d\)\+i\>" +" imaginary hexadecimal floats with a decimal point +syn match goImaginaryHexadecimalFloat "\<-\=0[xX]\%(_\x\|\x\)\+\.\%(\%(\x\|\x_\x\)\+\)\=\%([Pp][-+]\=\%(\d\|\d_\d\)\+\)\=i\>" +syn match goImaginaryHexadecimalFloat "\<-\=0[xX]\.\%(\x\|\x_\x\)\+\%([Pp][-+]\=\%(\d\|\d_\d\)\+\)\=i\>" +" imaginary hexadecimal floats without a decimal point +syn match goImaginaryHexadecimalFloat "\<-\=0[xX]\%(_\x\|\x\)\+[Pp][-+]\=\%(\d\|\d_\d\)\+i\>" + +hi def link goImaginaryDecimal Number +hi def link goImaginaryHexadecimal Number +hi def link goImaginaryOctal Number +hi def link goImaginaryBinary Number +hi def link goImaginaryFloat Float +hi def link goImaginaryHexadecimalFloat Float " Spaces after "[]" if s:HighlightArrayWhitespaceError() @@ -346,6 +381,8 @@ if s:HighlightOperators() syn match goOperator /\%(<<\|>>\|&^\)=\?/ " match remaining two-char operators: := && || <- ++ -- syn match goOperator /:=\|||\|<-\|++\|--/ + " match ~ + syn match goOperator /\~/ " match ... hi def link goPointerOperator goOperator @@ -353,13 +390,37 @@ if s:HighlightOperators() endif hi def link goOperator Operator +" -> type constraint opening bracket +" |-> start non-counting group +" || -> any word character +" || | -> at least one, as many as possible +" || | | -> start non-counting group +" || | | | -> match ~ +" || | | | | -> at most once +" || | | | | | -> allow a slice type +" || | | | | | | -> any word character +" || | | | | | | | -> start a non-counting group +" || | | | | | | | | -> that matches word characters and | +" || | | | | | | | | | -> close the non-counting group +" || | | | | | | | | | | -> close the non-counting group +" || | | | | | | | | | | |-> any number of matches +" || | | | | | | | | | | || -> start a non-counting group +" || | | | | | | | | | | || | -> a comma and whitespace +" || | | | | | | | | | | || | | -> at most once +" || | | | | | | | | | | || | | | -> close the non-counting group +" || | | | | | | | | | | || | | | | -> at least one of those non-counting groups, as many as possible +" || | | | | | -------- | | | | || | | | | | -> type constraint closing bracket +" || | | | | || | | | | | || | | | | | | +syn match goTypeParams /\[\%(\w\+\s\+\%(\~\?\%(\[]\)\?\w\%(\w\||\)\)*\%(,\s*\)\?\)\+\]/ nextgroup=goSimpleParams,goDeclType contained + " Functions; if s:HighlightFunctions() || s:HighlightFunctionParameters() syn match goDeclaration /\<func\>/ nextgroup=goReceiver,goFunction,goSimpleParams skipwhite skipnl + syn match goReceiverDecl /(\s*\zs\%(\%(\w\+\s\+\)\?\*\?\w\+\%(\[\%(\%(\[\]\)\?\w\+\%(,\s*\)\?\)\+\]\)\?\)\ze\s*)/ contained contains=goReceiverVar,goReceiverType,goPointerOperator syn match goReceiverVar /\w\+\ze\s\+\%(\w\|\*\)/ nextgroup=goPointerOperator,goReceiverType skipwhite skipnl contained syn match goPointerOperator /\*/ nextgroup=goReceiverType contained skipwhite skipnl - syn match goFunction /\w\+/ nextgroup=goSimpleParams contained skipwhite skipnl - syn match goReceiverType /\w\+/ contained + syn match goFunction /\w\+/ nextgroup=goSimpleParams,goTypeParams contained skipwhite skipnl + syn match goReceiverType /\w\+\%(\[\%(\%(\[\]\)\?\w\+\%(,\s*\)\?\)\+\]\)\?\ze\s*)/ contained if s:HighlightFunctionParameters() syn match goSimpleParams /(\%(\w\|\_s\|[*\.\[\],\{\}<>-]\)*)/ contained contains=goParamName,goType nextgroup=goFunctionReturn skipwhite skipnl syn match goFunctionReturn /(\%(\w\|\_s\|[*\.\[\],\{\}<>-]\)*)/ contained contains=goParamName,goType skipwhite skipnl @@ -369,7 +430,7 @@ if s:HighlightFunctions() || s:HighlightFunctionParameters() hi def link goReceiverVar goParamName hi def link goParamName Identifier endif - syn match goReceiver /(\s*\w\+\%(\s\+\*\?\s*\w\+\)\?\s*)\ze\s*\w/ contained nextgroup=goFunction contains=goReceiverVar skipwhite skipnl + syn match goReceiver /(\s*\%(\w\+\s\+\)\?\*\?\s*\w\+\%(\[\%(\%(\[\]\)\?\w\+\%(,\s*\)\?\)\+\]\)\?\s*)\ze\s*\w/ contained nextgroup=goFunction contains=goReceiverDecl skipwhite skipnl else syn keyword goDeclaration func endif @@ -377,7 +438,7 @@ hi def link goFunction Function " Function calls; if s:HighlightFunctionCalls() - syn match goFunctionCall /\w\+\ze(/ contains=goBuiltins,goDeclaration + syn match goFunctionCall /\w\+\ze\%(\[\%(\%(\[]\)\?\w\+\(,\s*\)\?\)\+\]\)\?(/ contains=goBuiltins,goDeclaration endif hi def link goFunctionCall Type @@ -404,7 +465,7 @@ hi def link goField Identifier if s:HighlightTypes() syn match goTypeConstructor /\<\w\+{\@=/ syn match goTypeDecl /\<type\>/ nextgroup=goTypeName skipwhite skipnl - syn match goTypeName /\w\+/ contained nextgroup=goDeclType skipwhite skipnl + syn match goTypeName /\w\+/ contained nextgroup=goDeclType,goTypeParams skipwhite skipnl syn match goDeclType /\<\%(interface\|struct\)\>/ skipwhite skipnl hi def link goReceiverType Type else @@ -444,7 +505,7 @@ if s:HighlightBuildConstraints() " The rs=s+2 option lets the \s*+build portion be part of the inner region " instead of the matchgroup so it will be highlighted as a goBuildKeyword. syn region goBuildComment matchgroup=goBuildCommentStart - \ start="//\s*+build\s"rs=s+2 end="$" + \ start="//\(\s*+build\s\|go:build\)"rs=s+2 end="$" \ contains=goBuildKeyword,goBuildDirectives hi def link goBuildCommentStart Comment hi def link goBuildDirectives Type diff --git a/runtime/syntax/help.vim b/runtime/syntax/help.vim index 5773e94c3e..8b469d7242 100644 --- a/runtime/syntax/help.vim +++ b/runtime/syntax/help.vim @@ -1,7 +1,7 @@ " Vim syntax file " Language: Vim help file " Maintainer: Bram Moolenaar (Bram@vim.org) -" Last Change: 2022 Sep 26 +" Last Change: 2022 Nov 13 " Quit when a (custom) syntax file was already loaded if exists("b:current_syntax") @@ -11,13 +11,14 @@ endif let s:cpo_save = &cpo set cpo&vim -syn match helpHeadline "^[-A-Z .][-A-Z0-9 .()_]*\ze\(\s\+\*\|$\)" +syn match helpHeadline "^[A-Z.][-A-Z0-9 .,()_']*?\=\ze\(\s\+\*\|$\)" syn match helpSectionDelim "^===.*===$" syn match helpSectionDelim "^---.*--$" +" Neovim: support language annotation in codeblocks if has("conceal") - syn region helpExample matchgroup=helpIgnore start=" >$" start="^>$" end="^[^ \t]"me=e-1 end="^<" concealends + syn region helpExample matchgroup=helpIgnore start=" >[a-z0-9]*$" start="^>[a-z0-9]*$" end="^[^ \t]"me=e-1 end="^<" concealends else - syn region helpExample matchgroup=helpIgnore start=" >$" start="^>$" end="^[^ \t]"me=e-1 end="^<" + syn region helpExample matchgroup=helpIgnore start=" >[a-z0-9]*$" start="^>[a-z0-9]*$" end="^[^ \t]"me=e-1 end="^<" endif syn match helpHyperTextJump "\\\@<!|[#-)!+-~]\+|" contains=helpBar syn match helpHyperTextEntry "\*[#-)!+-~]\+\*\s"he=e-1 contains=helpStar diff --git a/runtime/syntax/hgcommit.vim b/runtime/syntax/hgcommit.vim index 37fe9db8bf..e9f31bef61 100644 --- a/runtime/syntax/hgcommit.vim +++ b/runtime/syntax/hgcommit.vim @@ -1,8 +1,8 @@ " Vim syntax file -" Language: hg (Mercurial) commit file +" Language: hg/sl (Mercurial / Sapling) commit file " Maintainer: Ken Takata <kentkt at csc dot jp> -" Last Change: 2012 Aug 23 -" Filenames: hg-editor-*.txt +" Max Coplan <mchcopl@gmail.com> +" Last Change: 2022-12-08 " License: VIM License " URL: https://github.com/k-takata/hg-vim @@ -10,12 +10,15 @@ if exists("b:current_syntax") finish endif -syn match hgcommitComment "^HG:.*$" contains=@NoSpell -syn match hgcommitUser "^HG: user: \zs.*$" contains=@NoSpell contained containedin=hgcommitComment -syn match hgcommitBranch "^HG: branch \zs.*$" contains=@NoSpell contained containedin=hgcommitComment -syn match hgcommitAdded "^HG: \zsadded .*$" contains=@NoSpell contained containedin=hgcommitComment -syn match hgcommitChanged "^HG: \zschanged .*$" contains=@NoSpell contained containedin=hgcommitComment -syn match hgcommitRemoved "^HG: \zsremoved .*$" contains=@NoSpell contained containedin=hgcommitComment +syn match hgcommitComment "^\%(SL\|HG\): .*$" contains=@NoSpell +syn match hgcommitUser "^\%(SL\|HG\): user: \zs.*$" contains=@NoSpell contained containedin=hgcommitComment +syn match hgcommitBranch "^\%(SL\|HG\): branch \zs.*$" contains=@NoSpell contained containedin=hgcommitComment +syn match hgcommitAdded "^\%(SL\|HG\): \zsadded .*$" contains=@NoSpell contained containedin=hgcommitComment +syn match hgcommitChanged "^\%(SL\|HG\): \zschanged .*$" contains=@NoSpell contained containedin=hgcommitComment +syn match hgcommitRemoved "^\%(SL\|HG\): \zsremoved .*$" contains=@NoSpell contained containedin=hgcommitComment + +syn region hgcommitDiff start=/\%(^\(SL\|HG\): diff --\%(git\|cc\|combined\) \)\@=/ end=/^\%(diff --\|$\|@@\@!\|[^[:alnum:]\ +-]\S\@!\)\@=/ fold contains=@hgcommitDiff +syn include @hgcommitDiff syntax/shared/hgcommitDiff.vim hi def link hgcommitComment Comment hi def link hgcommitUser String diff --git a/runtime/syntax/hollywood.vim b/runtime/syntax/hollywood.vim index ce5ba29553..fcd03a68f0 100644 --- a/runtime/syntax/hollywood.vim +++ b/runtime/syntax/hollywood.vim @@ -1,14 +1,14 @@ " Vim syntax file -" Language: Hollywood 9.0 -" Maintainer: Tom Crecelius <holly@net-eclipse.net> -" First Author: Tom Crecelius <holly@net-eclipse.net> -" Last Change: 2021 April 13 -" Highlighting Issues: +" Language: Hollywood 9.1 +" Maintainer: Ola Sder <rolfkopman@gmail.com> +" First Author: Tom Crecelius <holly@net-eclipse.net> +" Last Change: 2022 Nov 09 +" Highlighting Issues: " Depending on your colour schema, Strings or Comments might be highlighted in " a way, you don't like. If so, try one of the following settings after " opening a hollywood script: " -" :hi link hwString MoreMsg +" :hi link hwString MoreMsg " :hi link hwString NonText " :hi link hwString String " @@ -60,10 +60,10 @@ syn region hwThenElse transparent matchgroup=hwCond start="\<Then\>" end="$" end " If .. EndIf syn region hwIfEndIf transparent matchgroup=hwCond start="\<If\>\(\(.\{-}Then.\{-}\)\@!\)" end="\<EndIf\>" contains=ALLBUT,hwTodo,hwSpecial,hwIn,hwStep,hwLineStatement skipwhite skipempty -" Else ... EndIf +" Else ... EndIf syn region hwElseEndIf contained transparent matchgroup=hwCond start="\<Else\>" end="\<EndIf\>"me=e-5 contains=ALLBUT,hwTodo,hwSpecial,hwElseIf,hwElseEndIf,hwIn,hwStep,hwFallThrough,hwLineStatement -" Then +" Then "syn keyword hwLineStatement Then contained " Forever syn keyword hwLineStatement Forever contained @@ -92,7 +92,7 @@ syn region hwLoopBlock transparent matchgroup=hwRepeat start="\<Repeat\>" end="\ " While ... Wend/Do syn region hwLoopBlock transparent matchgroup=hwRepeat start="\<While\>" end="\<Do\>" end="\<Wend\>" contains=ALLBUT,hwTodo,hwSpecial,hwElseIf,hwElse,hwIn,hwStep,hwLineStatement skipwhite skipempty -" For .. To +" For .. To syn region hwForTo transparent matchgroup=hwRepeat start="\<For\>" end="\<To\>"me=e-2 skipwhite skipempty nextgroup=hwToNext " To .. Next @@ -113,7 +113,7 @@ syn match hwPreProcessor "@\<\%(ANIM\|APPAUTHOR\|APPCOPYRIGHT\|APPDESCRIPTION\|A " predefined constants syn match hwConstant "#\<\%(ACTIVEWINDOW\|ADF_ANIM\|ADF_FX\|ADF_MOVEOBJECT\|ALL\|ALPHABETICAL\|ALPHACHANNEL\|ALPHANUMERICAL\|AMIGAICON_DEVICE\|AMIGAICON_DISK\|AMIGAICON_DRAWER\|AMIGAICON_GARBAGE\|AMIGAICON_HIDE\|AMIGAICON_KICKSTART\|AMIGAICON_NONE\|AMIGAICON_PROJECT\|AMIGAICON_SETPOSITION\|AMIGAICON_SETTITLE\|AMIGAICON_SHOW\|AMIGAICON_TOOL\|ANIM\|ANIMSTREAM\|ANIMTYPE_RASTER\|ANIMTYPE_VECTOR\|ANMFMT_GIF\|ANMFMT_IFF\|ANMFMT_MJPEG\|ANTIALIAS\|AQUA\|ARC\|ASYNCDRAW\|ASYNCOBJ\|ATTRACTIVE\|ATTRADAPTER\|ATTRALPHAINTENSITY\|ATTRBGPIC\|ATTRBITRATE\|ATTRBORDERBOTTOM\|ATTRBORDERLEFT\|ATTRBORDERLESS\|ATTRBORDERPEN\|ATTRBORDERRIGHT\|ATTRBORDERTOP\|ATTRBULLETPEN\|ATTRCANSEEK\|ATTRCLIPREGION\|ATTRCOUNT\|ATTRCURFRAME\|ATTRCURSORX\|ATTRCURSORY\|ATTRCURSUBSONG\|ATTRCYCLE\|ATTRDENSITY\|ATTRDEPTH\|ATTRDISPLAY\|ATTRDITHERMODE\|ATTRDOUBLEBUFFER\|ATTRDRIVER\|ATTRDURATION\|ATTRELAPSE\|ATTRENCODING\|ATTRFIXED\|ATTRFONTAA\|ATTRFONTASCENDER\|ATTRFONTCHARMAP\|ATTRFONTDEPTH\|ATTRFONTDESCENDER\|ATTRFONTENGINE\|ATTRFONTNAME\|ATTRFONTPALETTE\|ATTRFONTSCALABLE\|ATTRFONTSIZE\|ATTRFONTTRANSPARENTPEN\|ATTRFONTTYPE\|ATTRFORMAT\|ATTRFRAMEDELAY\|ATTRFUNCTION\|ATTRGROUP\|ATTRHARDWARE\|ATTRHASALPHA\|ATTRHASMASK\|ATTRHEIGHT\|ATTRHOSTDEPTH\|ATTRHOSTHEIGHT\|ATTRHOSTMONITORS\|ATTRHOSTSCALE\|ATTRHOSTSCALEX\|ATTRHOSTSCALEY\|ATTRHOSTTASKBAR\|ATTRHOSTTITLEBARHEIGHT\|ATTRHOSTWIDTH\|ATTRID\|ATTRIMMERSIVEMODE\|ATTRINTERPOLATE\|ATTRKEYBOARD\|ATTRLAYERID\|ATTRLAYERS\|ATTRLAYERSON\|ATTRLOADER\|ATTRMARGINLEFT\|ATTRMARGINRIGHT\|ATTRMASKMODE\|ATTRMAXHEIGHT\|ATTRMAXIMIZED\|ATTRMAXWIDTH\|ATTRMENU\|ATTRMODE\|ATTRMONITOR\|ATTRNOCLOSE\|ATTRNOHIDE\|ATTRNOMODESWITCH\|ATTRNUMENTRIES\|ATTRNUMFRAMES\|ATTRNUMSUBSONGS\|ATTRONSCREEN\|ATTRORIENTATION\|ATTROUTPUTDEVICE\|ATTRPALETTE\|ATTRPALETTEMODE\|ATTRPAUSED\|ATTRPEN\|ATTRPITCH\|ATTRPLAYING\|ATTRPOINTER\|ATTRPOSITION\|ATTRPUBSCREEN\|ATTRRAWHEIGHT\|ATTRRAWWIDTH\|ATTRRAWXPOS\|ATTRRAWYPOS\|ATTRSCALEHEIGHT\|ATTRSCALEMODE\|ATTRSCALESWITCH\|ATTRSCALEWIDTH\|ATTRSHADOWPEN\|ATTRSIZE\|ATTRSIZEABLE\|ATTRSPRITES\|ATTRSTANDARD\|ATTRSTATE\|ATTRSYSTEMBARS\|ATTRTEXT\|ATTRTITLE\|ATTRTRANSPARENTCOLOR\|ATTRTRANSPARENTPEN\|ATTRTYPE\|ATTRUSERDATA\|ATTRVISIBLE\|ATTRWIDTH\|ATTRXDPI\|ATTRXPOS\|ATTRXSERVER\|ATTRYDPI\|ATTRYPOS\|ATTRZPOS\|BARS\|BAUD_115200\|BAUD_1200\|BAUD_19200\|BAUD_2400\|BAUD_300\|BAUD_38400\|BAUD_460800\|BAUD_4800\|BAUD_57600\|BAUD_600\|BAUD_9600\|BEEPERROR\|BEEPINFORMATION\|BEEPQUESTION\|BEEPSYSTEM\|BEEPWARNING\|BGPIC\|BGPICPART\|BIGENDIAN\|BIGSINE\|BITMAP_DEFAULT\|BLACK\|BLEND\|BLUE\|BOLD\|BOOLEAN\|BORDER\|BOTTOM\|BOTTOMOUT\|BOUNCE\|BOX\|BRUSH\|BRUSH_VS_BOX\|BRUSHPART\|BULLET_ARROW\|BULLET_BOX\|BULLET_CHECKMARK\|BULLET_CIRCLE\|BULLET_CROSS\|BULLET_DASH\|BULLET_DIAMOND\|BULLET_LALPHA\|BULLET_LALPHADOUBLE\|BULLET_LALPHASINGLE\|BULLET_LROMAN\|BULLET_LROMANDOUBLE\|BULLET_LROMANSINGLE\|BULLET_NONE\|BULLET_NUMERIC\|BULLET_NUMERICDOUBLE\|BULLET_NUMERICSINGLE\|BULLET_UALPHA\|BULLET_UALPHADOUBLE\|BULLET_UALPHASINGLE\|BULLET_UROMAN\|BULLET_UROMANDOUBLE\|BULLET_UROMANSINGLE\|BYTE\|CAPBUTT\|CAPROUND\|CAPSQUARE\|CARDBOTTOM\|CARDTOP\|CENTER\|CHARMAP_ADOBECUSTOM\|CHARMAP_ADOBEEXPERT\|CHARMAP_ADOBELATIN1\|CHARMAP_ADOBESTANDARD\|CHARMAP_APPLEROMAN\|CHARMAP_BIG5\|CHARMAP_DEFAULT\|CHARMAP_JOHAB\|CHARMAP_MSSYMBOL\|CHARMAP_OLDLATIN2\|CHARMAP_SJIS\|CHARMAP_UNICODE\|CHARMAP_WANSUNG\|CHIPMEMORY\|CIRCLE\|CLIENT\|CLIPBOARD_EMPTY\|CLIPBOARD_IMAGE\|CLIPBOARD_SOUND\|CLIPBOARD_TEXT\|CLIPBOARD_UNKNOWN\|CLIPREGION\|CLOCKWIPE\|CLOSEWINDOW\|CONICAL\|COPYFILE_FAILED\|COPYFILE_OVERWRITE\|COPYFILE_STATUS\|COPYFILE_UNPROTECT\|COUNTBOTH\|COUNTDIRECTORIES\|COUNTFILES\|COUNTRY_AFGHANISTAN\|COUNTRY_ALANDISLANDS\|COUNTRY_ALBANIA\|COUNTRY_ALGERIA\|COUNTRY_AMERICANSAMOA\|COUNTRY_ANDORRA\|COUNTRY_ANGOLA\|COUNTRY_ANGUILLA\|COUNTRY_ANTARCTICA\|COUNTRY_ANTIGUAANDBARBUDA\|COUNTRY_ARGENTINA\|COUNTRY_ARMENIA\|COUNTRY_ARUBA\|COUNTRY_AUSTRALIA\|COUNTRY_AUSTRIA\|COUNTRY_AZERBAIJAN\|COUNTRY_BAHAMAS\|COUNTRY_BAHRAIN\|COUNTRY_BANGLADESH\|COUNTRY_BARBADOS\|COUNTRY_BELARUS\|COUNTRY_BELGIUM\|COUNTRY_BELIZE\|COUNTRY_BENIN\|COUNTRY_BERMUDA\|COUNTRY_BESISLANDS\|COUNTRY_BHUTAN\|COUNTRY_BOLIVIA\|COUNTRY_BOSNIAANDHERZEGOVINA\|COUNTRY_BOTSWANA\|COUNTRY_BOUVETISLAND\|COUNTRY_BRAZIL\|COUNTRY_BRUNEI\|COUNTRY_BULGARIA\|COUNTRY_BURKINAFASO\|COUNTRY_BURUNDI\|COUNTRY_CAMBODIA\|COUNTRY_CAMEROON\|COUNTRY_CANADA\|COUNTRY_CAPEVERDE\|COUNTRY_CAYMANISLANDS\|COUNTRY_CENTRALAFRICANREPUBLIC\|COUNTRY_CHAD\|COUNTRY_CHILE\|COUNTRY_CHINA\|COUNTRY_CHRISTMASISLAND\|COUNTRY_COCOSISLANDS\|COUNTRY_COLOMBIA\|COUNTRY_COMOROS\|COUNTRY_CONGO\|COUNTRY_COOKISLANDS\|COUNTRY_COSTARICA\|COUNTRY_CROATIA\|COUNTRY_CUBA\|COUNTRY_CURACAO\|COUNTRY_CYPRUS\|COUNTRY_CZECHREPUBLIC\|COUNTRY_DENMARK\|COUNTRY_DJIBOUTI\|COUNTRY_DOMINICA\|COUNTRY_DOMINICANREPUBLIC\|COUNTRY_DRCONGO\|COUNTRY_ECUADOR\|COUNTRY_EGYPT\|COUNTRY_ELSALVADOR\|COUNTRY_EQUATORIALGUINEA\|COUNTRY_ERITREA\|COUNTRY_ESTONIA\|COUNTRY_ETHIOPIA\|COUNTRY_FALKLANDISLANDS\|COUNTRY_FAROEISLANDS\|COUNTRY_FIJI\|COUNTRY_FINLAND\|COUNTRY_FRANCE\|COUNTRY_FRENCHGUIANA\|COUNTRY_FRENCHPOLYNESIA\|COUNTRY_GABON\|COUNTRY_GAMBIA\|COUNTRY_GEORGIA\|COUNTRY_GERMANY\|COUNTRY_GHANA\|COUNTRY_GIBRALTAR\|COUNTRY_GREECE\|COUNTRY_GREENLAND\|COUNTRY_GRENADA\|COUNTRY_GUADELOUPE\|COUNTRY_GUAM\|COUNTRY_GUATEMALA\|COUNTRY_GUERNSEY\|COUNTRY_GUINEA\|COUNTRY_GUINEABISSAU\|COUNTRY_GUYANA\|COUNTRY_HAITI\|COUNTRY_HOLYSEE\|COUNTRY_HONDURAS\|COUNTRY_HONGKONG\|COUNTRY_HUNGARY\|COUNTRY_ICELAND\|COUNTRY_INDIA\|COUNTRY_INDONESIA\|COUNTRY_IRAN\|COUNTRY_IRAQ\|COUNTRY_IRELAND\|COUNTRY_ISLEOFMAN\|COUNTRY_ISRAEL\|COUNTRY_ITALY\|COUNTRY_IVORYCOAST\|COUNTRY_JAMAICA\|COUNTRY_JAPAN\|COUNTRY_JERSEY\|COUNTRY_JORDAN\|COUNTRY_KAZAKHSTAN\|COUNTRY_KENYA\|COUNTRY_KIRIBATI\|COUNTRY_KUWAIT\|COUNTRY_KYRGYZSTAN\|COUNTRY_LAOS\|COUNTRY_LATVIA\|COUNTRY_LEBANON\|COUNTRY_LESOTHO\|COUNTRY_LIBERIA\|COUNTRY_LIBYA\|COUNTRY_LIECHTENSTEIN\|COUNTRY_LITHUANIA\|COUNTRY_LUXEMBOURG\|COUNTRY_MACAO\|COUNTRY_MACEDONIA\|COUNTRY_MADAGASCAR\|COUNTRY_MALAWI\|COUNTRY_MALAYSIA\|COUNTRY_MALDIVES\|COUNTRY_MALI\|COUNTRY_MALTA\|COUNTRY_MARSHALLISLANDS\|COUNTRY_MARTINIQUE\|COUNTRY_MAURITANIA\|COUNTRY_MAURITIUS\|COUNTRY_MAYOTTE\|COUNTRY_MEXICO\|COUNTRY_MICRONESIA\|COUNTRY_MOLDOVA\|COUNTRY_MONACO\|COUNTRY_MONGOLIA\|COUNTRY_MONTENEGRO\|COUNTRY_MONTSERRAT\|COUNTRY_MOROCCO\|COUNTRY_MOZAMBIQUE\|COUNTRY_MYANMAR\|COUNTRY_NAMIBIA\|COUNTRY_NAURU\|COUNTRY_NEPAL\|COUNTRY_NETHERLANDS\|COUNTRY_NEWCALEDONIA\|COUNTRY_NEWZEALAND\|COUNTRY_NICARAGUA\|COUNTRY_NIGER\|COUNTRY_NIGERIA\|COUNTRY_NIUE\|COUNTRY_NORFOLKISLAND\|COUNTRY_NORTHKOREA\|COUNTRY_NORWAY\|COUNTRY_OMAN\|COUNTRY_PAKISTAN\|COUNTRY_PALAU\|COUNTRY_PALESTINE\|COUNTRY_PANAMA\|COUNTRY_PAPUANEWGUINEA\|COUNTRY_PARAGUAY\|COUNTRY_PERU\|COUNTRY_PHILIPPINES\|COUNTRY_PITCAIRN\|COUNTRY_POLAND\|COUNTRY_PORTUGAL\|COUNTRY_PUERTORICO\|COUNTRY_QATAR\|COUNTRY_REUNION\|COUNTRY_ROMANIA\|COUNTRY_RUSSIA\|COUNTRY_RWANDA\|COUNTRY_SAINTBARTHELEMY\|COUNTRY_SAINTHELENA\|COUNTRY_SAINTKITTSANDNEVIS\|COUNTRY_SAINTLUCIA\|COUNTRY_SAINTVINCENT\|COUNTRY_SAMOA\|COUNTRY_SANMARINO\|COUNTRY_SAOTOMEANDPRINCIPE\|COUNTRY_SAUDIARABIA\|COUNTRY_SENEGAL\|COUNTRY_SERBIA\|COUNTRY_SEYCHELLES\|COUNTRY_SIERRALEONE\|COUNTRY_SINGAPORE\|COUNTRY_SLOVAKIA\|COUNTRY_SLOVENIA\|COUNTRY_SOLOMONISLANDS\|COUNTRY_SOMALIA\|COUNTRY_SOUTHAFRICA\|COUNTRY_SOUTHKOREA\|COUNTRY_SOUTHSUDAN\|COUNTRY_SPAIN\|COUNTRY_SRILANKA\|COUNTRY_SUDAN\|COUNTRY_SURINAME\|COUNTRY_SWAZILAND\|COUNTRY_SWEDEN\|COUNTRY_SWITZERLAND\|COUNTRY_SYRIA\|COUNTRY_TAIWAN\|COUNTRY_TAJIKISTAN\|COUNTRY_TANZANIA\|COUNTRY_THAILAND\|COUNTRY_TIMOR\|COUNTRY_TOGO\|COUNTRY_TONGA\|COUNTRY_TRINIDADANDTOBAGO\|COUNTRY_TUNISIA\|COUNTRY_TURKEY\|COUNTRY_TURKMENISTAN\|COUNTRY_TUVALU\|COUNTRY_UAE\|COUNTRY_UGANDA\|COUNTRY_UK\|COUNTRY_UKRAINE\|COUNTRY_UNKNOWN\|COUNTRY_URUGUAY\|COUNTRY_USA\|COUNTRY_UZBEKISTAN\|COUNTRY_VANUATU\|COUNTRY_VENEZUELA\|COUNTRY_VIETNAM\|COUNTRY_YEMEN\|COUNTRY_ZAMBIA\|COUNTSEPARATE\|CR_DEAD\|CR_RUNNING\|CR_SUSPENDED\|CROSSFADE\|CRUSHBOTTOM\|CRUSHLEFT\|CRUSHRIGHT\|CRUSHTOP\|DAMPED\|DATA_5\|DATA_6\|DATA_7\|DATA_8\|DATEDAY\|DATELOCAL\|DATELOCALNATIVE\|DATEMONTH\|DATETIME\|DATEUTC\|DATEYEAR\|DEFAULTICON\|DEFAULTSPEED\|DEINTERLACE_DEFAULT\|DEINTERLACE_DOUBLE\|DELETEFILE_FAILED\|DELETEFILE_STATUS\|DELETEFILE_UNPROTECT\|DENSITY_HIGH\|DENSITY_LOW\|DENSITY_MEDIUM\|DENSITY_NONE\|DIAGONAL\|DIRECTORY\|DIRMONITOR_ADD\|DIRMONITOR_CHANGE\|DIRMONITOR_REMOVE\|DISPLAY\|DISPMODE_ASK\|DISPMODE_FAKEFULLSCREEN\|DISPMODE_FULLSCREEN\|DISPMODE_FULLSCREENSCALE\|DISPMODE_MODEREQUESTER\|DISPMODE_MODESWITCH\|DISPMODE_SYSTEMSCALE\|DISPMODE_WINDOWED\|DISPSTATE_CLOSED\|DISPSTATE_MINIMIZED\|DISPSTATE_OPEN\|DISSOLVE\|DITHERMODE_FLOYDSTEINBERG\|DITHERMODE_NONE\|DOSTYPE_DIRECTORY\|DOSTYPE_FILE\|DOUBLE\|DOUBLEBUFFER\|DOWNLOADFILE_STATUS\|DTR_OFF\|DTR_ON\|DURATION_LONG\|DURATION_SHORT\|EDGE\|ELLIPSE\|ENCODING_AMIGA\|ENCODING_ISO8859_1\|ENCODING_RAW\|ENCODING_UTF8\|EOF\|ERR_8OR16BITONLY\|ERR_ACCELERATOR\|ERR_ADDAPPICON\|ERR_ADDAPPWIN\|ERR_ADDSYSEVENT\|ERR_ADDTASK\|ERR_ADFFREEDISP\|ERR_ADFWRONGDISP\|ERR_AFILEPROP\|ERR_AHI\|ERR_ALLOCALPHA\|ERR_ALLOCCHANNEL\|ERR_ALLOCCHUNKY\|ERR_ALLOCMASK\|ERR_ALRDYDECLRD\|ERR_ALREADYASYNC\|ERR_ALSAPCM\|ERR_AMIGAGUIDE\|ERR_ANIMDISK\|ERR_ANIMFRAME\|ERR_ANTIALIAS\|ERR_APPLET\|ERR_APPLETVERSION\|ERR_APPLICATION\|ERR_ARGS\|ERR_ARRAYDECLA\|ERR_ASSERTFAILED\|ERR_ATSUI\|ERR_AUDIOCONVERTER\|ERR_BACKFILL\|ERR_BAD8SVX\|ERR_BADBASE64\|ERR_BADBYTECODE\|ERR_BADCALLBACKRET\|ERR_BADCONSTANT\|ERR_BADDIMENSIONS\|ERR_BADENCODING\|ERR_BADINTEGER\|ERR_BADIP\|ERR_BADLAYERTYPE\|ERR_BADPLATFORM\|ERR_BADSIGNATURE\|ERR_BADUPVALUES\|ERR_BADURL\|ERR_BADWAVE\|ERR_BADYIELD\|ERR_BEGINREFRESH\|ERR_BGPICBUTTON\|ERR_BGPICPALETTE\|ERR_BGPICTYPE\|ERR_BITMAP\|ERR_BLKWOENDBLK\|ERR_BRACECLOSE\|ERR_BRACEOPEN\|ERR_BRACKETCLOSE\|ERR_BRACKETOPEN\|ERR_BRUSHLINK\|ERR_BRUSHSIZE\|ERR_BRUSHTYPE\|ERR_CACHEERROR\|ERR_CASECST\|ERR_CHANGEDIR\|ERR_CHANNELRANGE\|ERR_CHRCSTEMPTY\|ERR_CHRCSTLEN\|ERR_CLIPFORMAT\|ERR_CLIPOPEN\|ERR_CLIPREAD\|ERR_CLIPWRITE\|ERR_CLOSEDDISPLAY\|ERR_CLOSEFILE\|ERR_CMDASVAR\|ERR_CMPUNSUPPORTED\|ERR_COLORSPACE\|ERR_COMMENTSTRUCT\|ERR_COMMODITY\|ERR_COMPLEXEXPR\|ERR_COMPLEXPATTERN\|ERR_COMPLEXWHILE\|ERR_CONCAT\|ERR_CONFIG\|ERR_CONFIG2\|ERR_CONITEMS\|ERR_CONSOLEARG\|ERR_CONTEXTMENU\|ERR_COORDSRANGE\|ERR_COREFOUNDATION\|ERR_CORETEXT\|ERR_CREATEDIR\|ERR_CREATEDOCKY\|ERR_CREATEEVENT\|ERR_CREATEGC\|ERR_CREATEICON\|ERR_CREATEMENU\|ERR_CREATEPORT\|ERR_CREATESHORTCUT\|ERR_CSTDOUBLEDEF\|ERR_CTRLSTRUCT\|ERR_CYIELD\|ERR_DATATYPEALPHA\|ERR_DATATYPESAVE\|ERR_DATATYPESAVE2\|ERR_DBLENCODING\|ERR_DBPALETTE\|ERR_DBTRANSWIN\|ERR_DBVIDEOLAYER\|ERR_DDAUTOSCALE\|ERR_DDMOBILE\|ERR_DDRECVIDEO\|ERR_DEADRESUME\|ERR_DEFFONT\|ERR_DELETEFILE\|ERR_DEMO\|ERR_DEMO2\|ERR_DEMO3\|ERR_DEPTHMISMATCH\|ERR_DEPTHRANGE\|ERR_DESERIALIZE\|ERR_DIFFDEPTH\|ERR_DIFFENCODING\|ERR_DINPUT\|ERR_DIRECTSHOW\|ERR_DIRLOCK\|ERR_DISPLAYADAPTERSUPPORT\|ERR_DISPLAYDESKTOP\|ERR_DISPLAYDESKTOPPAL\|ERR_DISPLAYSIZE\|ERR_DISPMINIMIZED\|ERR_DLOPEN\|ERR_DOUBLEDECLA\|ERR_DOUBLEMENU\|ERR_DRAWPATH\|ERR_DSOUNDNOTIFY\|ERR_DSOUNDNOTIPOS\|ERR_DSOUNDPLAY\|ERR_ELSEIFAFTERELSE\|ERR_ELSETWICE\|ERR_ELSEWOIF\|ERR_EMPTYMENUTREE\|ERR_EMPTYOBJ\|ERR_EMPTYPATH\|ERR_EMPTYSCRIPT\|ERR_EMPTYTABLE\|ERR_ENDBLKWOBLK\|ERR_ENDDOUBLEBUFFER\|ERR_ENDFUNCWOFUNC\|ERR_ENDIFWOIF\|ERR_ENDSWCHWOSWCH\|ERR_ENDWITHWOWITH\|ERR_EQUALEXPECTED\|ERR_ERRORCALLED\|ERR_ESCREPLACE\|ERR_EVNTEXPCTED\|ERR_EXAMINE\|ERR_EXECUTE\|ERR_EXETYPE\|ERR_FGRABVIDSTATE\|ERR_FIELDINIT\|ERR_FILEEXIST\|ERR_FILEFORMAT\|ERR_FILENOTFOUND\|ERR_FILESIZE\|ERR_FINDACTIVITY\|ERR_FINDANIM\|ERR_FINDANIMSTREAM\|ERR_FINDAPPLICATION\|ERR_FINDARRAY\|ERR_FINDASYNCDRAW\|ERR_FINDASYNCOBJ\|ERR_FINDBGPIC\|ERR_FINDBRUSH\|ERR_FINDBUTTON\|ERR_FINDCLIENT\|ERR_FINDCLIPREGION\|ERR_FINDCST\|ERR_FINDDIR\|ERR_FINDDISPLAY\|ERR_FINDFILE\|ERR_FINDFONT\|ERR_FINDFONT2\|ERR_FINDICON\|ERR_FINDINTERVAL\|ERR_FINDLAYER\|ERR_FINDLAYERDATA\|ERR_FINDMEMBLK\|ERR_FINDMENU\|ERR_FINDMENUITEM\|ERR_FINDMONITOR\|ERR_FINDMOVE\|ERR_FINDMUSIC\|ERR_FINDOBJECTDATA\|ERR_FINDPALETTE\|ERR_FINDPATH\|ERR_FINDPLUGIN\|ERR_FINDPOINTER\|ERR_FINDPORT\|ERR_FINDSAMPLE\|ERR_FINDSELECTOR\|ERR_FINDSERIAL\|ERR_FINDSERVER\|ERR_FINDSPRITE\|ERR_FINDTEXTOBJECT\|ERR_FINDTIMEOUT\|ERR_FINDTIMER\|ERR_FINDUDPOBJECT\|ERR_FINDVIDEO\|ERR_FIRSTPREPROC\|ERR_FONTFORMAT\|ERR_FONTPATH\|ERR_FONTPATH2\|ERR_FORBIDMODAL\|ERR_FOREVERWOREPEAT\|ERR_FORWONEXT\|ERR_FRAMEGRABBER\|ERR_FREEABGPIC\|ERR_FREEADISPLAY\|ERR_FREECURPOINTER\|ERR_FT2\|ERR_FTPAUTH\|ERR_FTPERROR\|ERR_FULLSCREEN\|ERR_FUNCARGS\|ERR_FUNCDECLA\|ERR_FUNCEXPECTED\|ERR_FUNCJMP\|ERR_FUNCREMOVED\|ERR_FUNCTABLEARG\|ERR_FUNCWOENDFUNC\|ERR_GETDISKOBJ\|ERR_GETIFADDRS\|ERR_GETMONITORINFO\|ERR_GETSHORTCUT\|ERR_GRABSCREEN\|ERR_GROUPNAMEUSED\|ERR_GTK\|ERR_GUIGFX\|ERR_HEXPOINT\|ERR_HOSTNAME\|ERR_HTTPERROR\|ERR_HTTPTE\|ERR_HWBMCLOSEDISP\|ERR_HWBRUSH\|ERR_HWBRUSHFUNC\|ERR_HWDBFREEDISP\|ERR_ICONDIMS\|ERR_ICONENTRY\|ERR_ICONPARMS\|ERR_ICONSIZE\|ERR_ICONSTANDARD\|ERR_ICONVECTOR\|ERR_IFWOENDIF\|ERR_IMAGEERROR\|ERR_INCOMPATBRUSH\|ERR_INISYNTAX\|ERR_INITSERIAL\|ERR_INTERNAL\|ERR_INTERNAL1\|ERR_INTEXPECTED\|ERR_INVALIDDATE\|ERR_INVALIDUTF8\|ERR_INVALIDUTF8ARG\|ERR_INVCAPIDX\|ERR_INVINSERT\|ERR_INVNEXTKEY\|ERR_INVPATCAP\|ERR_INVREPLACE\|ERR_JAVA\|ERR_JAVAMETHOD\|ERR_JOYSTICK\|ERR_KEYFILE\|ERR_KEYNOTFOUND\|ERR_KEYWORD\|ERR_KICKSTART\|ERR_LABELDECLA\|ERR_LABELDOUBLE\|ERR_LABINFOR\|ERR_LABINFUNC\|ERR_LABINIF\|ERR_LABINWHILE\|ERR_LABMAINBLK\|ERR_LAYERRANGE\|ERR_LAYERSOFF\|ERR_LAYERSON\|ERR_LAYERSUPPORT\|ERR_LAYERSUPPORT2\|ERR_LAYERSWITCH\|ERR_LEGACYPTMOD\|ERR_LFSYNTAX\|ERR_LINKFONT\|ERR_LINKPLUGIN\|ERR_LOADFRAME\|ERR_LOADICON\|ERR_LOADPICTURE\|ERR_LOADPICTURE2\|ERR_LOADPLUGIN\|ERR_LOADSOUND\|ERR_LOADVIDEO\|ERR_LOCK\|ERR_LOCK2\|ERR_LOCKBMAP\|ERR_LOCKEDOBJ\|ERR_LOOPRANGE\|ERR_LOWFREQ\|ERR_MAGICKEY\|ERR_MALFORMPAT1\|ERR_MALFORMPAT2\|ERR_MASKNALPHA\|ERR_MAXLINES\|ERR_MAXLOCALS\|ERR_MAXPARAMS\|ERR_MAXUPVALS\|ERR_MEDIAFOUNDATION\|ERR_MEM\|ERR_MEMCODE\|ERR_MEMCST\|ERR_MEMRANGE\|ERR_MENUATTACHED\|ERR_MENUCOMPLEXITY\|ERR_MISSINGBRACKET\|ERR_MISSINGFIELD\|ERR_MISSINGOPBRACK\|ERR_MISSINGPARAMTR\|ERR_MISSINGSEPARTR\|ERR_MIXMUSMOD\|ERR_MOBILE\|ERR_MODIFYAANIM\|ERR_MODIFYABG\|ERR_MODIFYABGPIC\|ERR_MODIFYABR\|ERR_MODIFYPSMP\|ERR_MODIFYSPRITE\|ERR_MODIFYSPRITE2\|ERR_MONITORDIR\|ERR_MONITORFULLSCREEN\|ERR_MONITORRANGE\|ERR_MOVEFILE\|ERR_MSGPORT\|ERR_MULDISMOBILE\|ERR_MULTIBGPIC\|ERR_MULTIDISPLAYS\|ERR_MUSFMTSUPPORT\|ERR_MUSNOTPAUSED\|ERR_MUSNOTPLYNG\|ERR_MUSNOTPLYNG2\|ERR_MUSPAUSED\|ERR_MUSPLAYING\|ERR_NAMETOOLONG\|ERR_NAMEUSED\|ERR_NEEDAPPLICATION\|ERR_NEEDCOMPOSITE\|ERR_NEEDMORPHOS2\|ERR_NEEDOS41\|ERR_NEEDPALETTEIMAGE\|ERR_NEGCOORDS\|ERR_NEWHWPLUGIN\|ERR_NEXTWOFOR\|ERR_NOABSPATH\|ERR_NOACCESS\|ERR_NOALPHA\|ERR_NOANMLAYER\|ERR_NOAPPLET\|ERR_NOARGBVISUAL\|ERR_NOBLOCKBREAK\|ERR_NOCALLBACK\|ERR_NOCHANNEL\|ERR_NOCHAR\|ERR_NOCLIPREG\|ERR_NOCOLON\|ERR_NOCOMMA\|ERR_NOCOMPRESS\|ERR_NOCONSTANTS\|ERR_NOCONTEXTMENU\|ERR_NOCOORDCST\|ERR_NODIRPATTERN\|ERR_NODISLAYERS\|ERR_NODISPMODES\|ERR_NODOUBLEBUFFER\|ERR_NOFALLTHROUGH\|ERR_NOFILTERNAME\|ERR_NOFMBHANDLER\|ERR_NOFUNCTION\|ERR_NOHWFUNC\|ERR_NOJOYATPORT\|ERR_NOKEYWORDS\|ERR_NOLAYERS\|ERR_NOLOOP\|ERR_NOLOOPCONT\|ERR_NOMASKBRUSH\|ERR_NOMENU\|ERR_NOMIMEVIEWER\|ERR_NOMUSICCB\|ERR_NONE\|ERR_NONSUSPENDEDRESUME\|ERR_NOPALETTE\|ERR_NOPALETTEIMAGE\|ERR_NOPALETTEMODE\|ERR_NORETVAL\|ERR_NOREXX\|ERR_NOSPRITES\|ERR_NOTADIR\|ERR_NOTENOUGHPIXELS\|ERR_NOTIGER\|ERR_NOTPROTRACKER\|ERR_NOTRANSPARENCY\|ERR_NOTXTLAYER\|ERR_NUMBEREXPECTED\|ERR_NUMCALLBACK\|ERR_NUMCONCAT\|ERR_NUMEXPECTED\|ERR_NUMSTRCMP\|ERR_NUMTABLEARG\|ERR_OLDAPPLET\|ERR_OPENANIM\|ERR_OPENANIM2\|ERR_OPENAUDIO\|ERR_OPENFONT\|ERR_OPENLIB\|ERR_OPENSERIAL\|ERR_OPENSOCKET\|ERR_OPENSOUND\|ERR_OPENSOUND2\|ERR_OUTOFRANGE\|ERR_PAKFORMAT\|ERR_PALETTEFILL\|ERR_PALETTEMODE\|ERR_PALSCREEN\|ERR_PEERNAME\|ERR_PENRANGE\|ERR_PERCENTFORMAT\|ERR_PERCENTFORMATSTR\|ERR_PIPE\|ERR_PIXELFORMAT\|ERR_PIXELRANGE\|ERR_PLAYERCOMP\|ERR_PLAYVIDEO\|ERR_PLUGINARCH\|ERR_PLUGINDOUBLET\|ERR_PLUGINSUPPORT\|ERR_PLUGINSYMBOL\|ERR_PLUGINTYPE\|ERR_PLUGINVER\|ERR_POINTERFORMAT\|ERR_POINTERIMG\|ERR_PORTNOTAVAIL\|ERR_PREPROCSYM\|ERR_PROTMETATABLE\|ERR_PUBSCREEN\|ERR_QUICKTIME\|ERR_RADIOTOGGLEMENU\|ERR_RANDOMIZE\|ERR_READ\|ERR_READFILE\|ERR_READFUNC\|ERR_READONLY\|ERR_READRANGE\|ERR_READTABLE\|ERR_READVIDEOPIXELS\|ERR_RECVCLOSED\|ERR_RECVTIMEOUT\|ERR_RECVUNKNOWN\|ERR_REGCLASS\|ERR_REGISTRYREAD\|ERR_REGISTRYWRITE\|ERR_REMADLAYER\|ERR_RENAME\|ERR_RENDER\|ERR_RENDERADLAYER\|ERR_RENDERCALLBACK\|ERR_RENDERER\|ERR_REPEATWOUNTIL\|ERR_REQAUTH\|ERR_REQUIREFIELD\|ERR_REQUIREPLUGIN\|ERR_REQUIRETAGFMT\|ERR_RETWOGOSUB\|ERR_REVDWORD\|ERR_REWINDDIR\|ERR_REXXERR\|ERR_SATELLITE\|ERR_SATFREEDISP\|ERR_SAVEANIM\|ERR_SAVEICON\|ERR_SAVEIMAGE\|ERR_SAVEPNG\|ERR_SAVERALPHA\|ERR_SAVESAMPLE\|ERR_SCALEBGPIC\|ERR_SCREEN\|ERR_SCREENMODE\|ERR_SCREENSIZE\|ERR_SCRPIXFMT\|ERR_SEEK\|ERR_SEEKFILE\|ERR_SEEKFORMAT\|ERR_SEEKRANGE\|ERR_SELECTALPHACHANNEL\|ERR_SELECTANIM\|ERR_SELECTBG\|ERR_SELECTBGPIC\|ERR_SELECTBGPIC2\|ERR_SELECTBRUSH\|ERR_SELECTMASK\|ERR_SEMAPHORE\|ERR_SENDDATA\|ERR_SENDMESSAGE\|ERR_SENDTIMEOUT\|ERR_SENDUNKNOWN\|ERR_SERIALIO\|ERR_SERIALIZE\|ERR_SERIALIZETYPE\|ERR_SETADAPTER\|ERR_SETENV\|ERR_SETFILEATTR\|ERR_SETFILECOMMENT\|ERR_SETFILEDATE\|ERR_SETMENU\|ERR_SHORTIF\|ERR_SIGNAL\|ERR_SMODEALPHA\|ERR_SMPRANGE\|ERR_SOCKET\|ERR_SOCKNAME\|ERR_SOCKOPT\|ERR_SORTFUNC\|ERR_SPRITELINK\|ERR_SPRITEONSCREEN\|ERR_SPRITEONSCREEN2\|ERR_SQBRACKETCLOSE\|ERR_SQBRACKETOPEN\|ERR_STACK\|ERR_STAT\|ERR_STRCALLBACK\|ERR_STREAMASSAMPLE\|ERR_STREXPECTED\|ERR_STRINGCST\|ERR_STRINGEXPECTED\|ERR_STRORNUM\|ERR_STRTABLEARG\|ERR_STRTOOSHORT\|ERR_SURFACE\|ERR_SWCHWOENDSWCH\|ERR_SYNTAXERROR\|ERR_SYNTAXLEVELS\|ERR_SYSBUTTON\|ERR_SYSIMAGE\|ERR_SYSTOOOLD\|ERR_TABCALLBACK\|ERR_TABEXPECTED\|ERR_TABEXPECTED2\|ERR_TABEXPECTED3\|ERR_TABLEDECLA\|ERR_TABLEINDEX\|ERR_TABLEORNIL\|ERR_TABLEOVERFLOW\|ERR_TAGEXPECTED\|ERR_TASKSETUP\|ERR_TEXTARG\|ERR_TEXTCONVERT\|ERR_TEXTCONVERT2\|ERR_TEXTSYNTAX\|ERR_TEXTURE\|ERR_TFIMAGE\|ERR_TFVANIM\|ERR_TFVBGPICBRUSH\|ERR_TFVBRUSH\|ERR_TFVBRUSHBGPIC\|ERR_THREAD\|ERR_THREADEXPECTED\|ERR_TIMER\|ERR_TOKENEXPECTED\|ERR_TOOMANYARGS\|ERR_TOOMANYCAPTURES\|ERR_TOOSMALL2\|ERR_TRANSBGMOBILE\|ERR_TRANSBRUSH\|ERR_TRAYICON\|ERR_TRIALCOMPILE\|ERR_TRIALINCLUDE\|ERR_TRIALLIMIT\|ERR_TRIALSAVEVID\|ERR_UDEXPECTED\|ERR_UNBALANCEDPAT\|ERR_UNEXPECTEDEOF\|ERR_UNEXPECTEDSYM\|ERR_UNFINISHEDCAPTURE\|ERR_UNIMPLCMD\|ERR_UNKNOWN\|ERR_UNKNOWNANMOUT\|ERR_UNKNOWNATTR\|ERR_UNKNOWNCMD\|ERR_UNKNOWNCOND\|ERR_UNKNOWNFILTER\|ERR_UNKNOWNICNOUT\|ERR_UNKNOWNIMGOUT\|ERR_UNKNOWNMIMETYPE\|ERR_UNKNOWNMUSFMT\|ERR_UNKNOWNPALETTE\|ERR_UNKNOWNSEC\|ERR_UNKNOWNSEQ\|ERR_UNKNOWNSMPOUT\|ERR_UNKNOWNTAG\|ERR_UNKNUMFMT\|ERR_UNKPROTOCOL\|ERR_UNKTEXTFMT\|ERR_UNMPARENTHESES\|ERR_UNSETENV\|ERR_UNSUPPORTEDFEAT\|ERR_UNTERMINTDSTR\|ERR_UNTILWOREPEAT\|ERR_UPDATEICON\|ERR_UPLOADFORBIDDEN\|ERR_USERABORT\|ERR_VALUEEXPECTED\|ERR_VAREXPECTED\|ERR_VARLENGTH\|ERR_VARSYNTAX\|ERR_VECGFXPLUGIN\|ERR_VECTORANIM\|ERR_VECTORBRUSH\|ERR_VERSION\|ERR_VFONT\|ERR_VFONTTYPE\|ERR_VIDATTACHED\|ERR_VIDEOFRAME\|ERR_VIDEOINIT\|ERR_VIDEOLAYER\|ERR_VIDEOLAYERDRV\|ERR_VIDEOSTRATEGY\|ERR_VIDEOTRANS\|ERR_VIDLAYERFUNC\|ERR_VIDNOTPAUSED\|ERR_VIDNOTPLAYING\|ERR_VIDPAUSED\|ERR_VIDPLAYING\|ERR_VIDRECMULTI\|ERR_VIDRECTRANS\|ERR_VIDSTOPPED\|ERR_VISUALINFO\|ERR_VMMISMATCH\|ERR_WARPOS\|ERR_WENDWOWHILE\|ERR_WHILEWOWEND\|ERR_WINDOW\|ERR_WITHWOENDWITH\|ERR_WRITE\|ERR_WRITEFILE\|ERR_WRITEJPEG\|ERR_WRITEONLY\|ERR_WRONGCLIPREG\|ERR_WRONGCMDRECVIDEO\|ERR_WRONGDTYPE\|ERR_WRONGFLOAT\|ERR_WRONGHEX\|ERR_WRONGID\|ERR_WRONGOP\|ERR_WRONGOPCST\|ERR_WRONGSPRITESIZE\|ERR_WRONGUSAGE\|ERR_WRONGVSTRATEGY\|ERR_XCURSOR\|ERR_XDISPLAY\|ERR_XF86VIDMODEEXT\|ERR_XFIXES\|ERR_YIELD\|ERR_ZERODIVISION\|ERR_ZLIBDATA\|ERR_ZLIBIO\|ERR_ZLIBSTREAM\|ERR_ZLIBVERSION\|EVENTHANDLER\|FADE\|FASTMEMORY\|FASTSPEED\|FILE\|FILEATTR_ARCHIVE\|FILEATTR_DELETE_USR\|FILEATTR_EXECUTE_GRP\|FILEATTR_EXECUTE_OTH\|FILEATTR_EXECUTE_USR\|FILEATTR_HIDDEN\|FILEATTR_NORMAL\|FILEATTR_PURE\|FILEATTR_READ_GRP\|FILEATTR_READ_OTH\|FILEATTR_READ_USR\|FILEATTR_READONLY\|FILEATTR_SCRIPT\|FILEATTR_SYSTEM\|FILEATTR_WRITE_GRP\|FILEATTR_WRITE_OTH\|FILEATTR_WRITE_USR\|FILETYPE_ANIM\|FILETYPE_ICON\|FILETYPE_IMAGE\|FILETYPE_SOUND\|FILETYPE_VIDEO\|FILETYPEFLAGS_ALPHA\|FILETYPEFLAGS_FPS\|FILETYPEFLAGS_QUALITY\|FILETYPEFLAGS_SAVE\|FILLCOLOR\|FILLGRADIENT\|FILLNONE\|FILLRULEEVENODD\|FILLRULEWINDING\|FILLTEXTURE\|FLOAT\|FLOW_HARDWARE\|FLOW_OFF\|FLOW_XON_XOFF\|FONT\|FONTENGINE_INBUILT\|FONTENGINE_NATIVE\|FONTSLANT_ITALIC\|FONTSLANT_OBLIQUE\|FONTSLANT_ROMAN\|FONTTYPE_BITMAP\|FONTTYPE_COLOR\|FONTTYPE_VECTOR\|FONTWEIGHT_BLACK\|FONTWEIGHT_BOLD\|FONTWEIGHT_BOOK\|FONTWEIGHT_DEMIBOLD\|FONTWEIGHT_EXTRABLACK\|FONTWEIGHT_EXTRABOLD\|FONTWEIGHT_EXTRALIGHT\|FONTWEIGHT_HEAVY\|FONTWEIGHT_LIGHT\|FONTWEIGHT_MEDIUM\|FONTWEIGHT_NORMAL\|FONTWEIGHT_REGULAR\|FONTWEIGHT_SEMIBOLD\|FONTWEIGHT_THIN\|FONTWEIGHT_ULTRABLACK\|FONTWEIGHT_ULTRABOLD\|FONTWEIGHT_ULTRALIGHT\|FRAMEMODE_FULL\|FRAMEMODE_SINGLE\|FREESPACE\|FTPASCII\|FTPBINARY\|FUCHSIA\|FUNCTION\|GRAY\|GREEN\|HBLINDS128\|HBLINDS16\|HBLINDS32\|HBLINDS64\|HBLINDS8\|HCLOSECURTAIN\|HCLOSEGATE\|HEXNUMERICAL\|HFLIPCOIN\|HFLOWBOTTOM\|HFLOWTOP\|HIDEBRUSH\|HIDELAYER\|HKEY_CLASSES_ROOT\|HKEY_CURRENT_CONFIG\|HKEY_CURRENT_USER\|HKEY_LOCAL_MACHINE\|HKEY_USERS\|HLINES\|HLINES2\|HLOWFLIPCOIN\|HOLLYWOOD\|HOPENCURTAIN\|HOPENGATE\|HSPLIT\|HSTRANGEPUSH\|HSTRETCHCENTER\|HSTRIPES\|HSTRIPES16\|HSTRIPES2\|HSTRIPES32\|HSTRIPES4\|HSTRIPES64\|HSTRIPES8\|HW_64BIT\|HW_AMIGA\|HW_AMIGAOS3\|HW_AMIGAOS4\|HW_ANDROID\|HW_AROS\|HW_IOS\|HW_LINUX\|HW_LITTLE_ENDIAN\|HW_MACOS\|HW_MORPHOS\|HW_REVISION\|HW_VERSION\|HW_WARPOS\|HW_WINDOWS\|ICNFMT_HOLLYWOOD\|ICON\|IMAGETYPE_RASTER\|IMAGETYPE_VECTOR\|IMGFMT_BMP\|IMGFMT_GIF\|IMGFMT_ILBM\|IMGFMT_JPEG\|IMGFMT_NATIVE\|IMGFMT_PLUGIN\|IMGFMT_PNG\|IMGFMT_TIFF\|IMGFMT_UNKNOWN\|IMMERSIVE_LEANBACK\|IMMERSIVE_NONE\|IMMERSIVE_NORMAL\|IMMERSIVE_STICKY\|INACTIVEWINDOW\|INF\|INSERTBRUSH\|INTEGER\|INTERVAL\|IO_BUFFERED\|IO_FAKE64\|IO_LITTLEENDIAN\|IO_SIGNED\|IO_UNBUFFERED\|IO_UNSIGNED\|IPAUTO\|IPUNKNOWN\|IPV4\|IPV6\|ITALIC\|JOINBEVEL\|JOINMITER\|JOINROUND\|JOYDOWN\|JOYDOWNLEFT\|JOYDOWNRIGHT\|JOYLEFT\|JOYNODIR\|JOYRIGHT\|JOYUP\|JOYUPLEFT\|JOYUPRIGHT\|JUSTIFIED\|KEEPASPRAT\|KEEPPOSITION\|LANGUAGE_ABKHAZIAN\|LANGUAGE_AFAR\|LANGUAGE_AFRIKAANS\|LANGUAGE_AKAN\|LANGUAGE_ALBANIAN\|LANGUAGE_AMHARIC\|LANGUAGE_ARABIC\|LANGUAGE_ARAGONESE\|LANGUAGE_ARMENIAN\|LANGUAGE_ASSAMESE\|LANGUAGE_AVARIC\|LANGUAGE_AVESTAN\|LANGUAGE_AYMARA\|LANGUAGE_AZERBAIJANI\|LANGUAGE_BAMBARA\|LANGUAGE_BASHKIR\|LANGUAGE_BASQUE\|LANGUAGE_BELARUSIAN\|LANGUAGE_BENGALI\|LANGUAGE_BIHARI\|LANGUAGE_BISLAMA\|LANGUAGE_BOSNIAN\|LANGUAGE_BRETON\|LANGUAGE_BULGARIAN\|LANGUAGE_BURMESE\|LANGUAGE_CATALAN\|LANGUAGE_CENTRALKHMER\|LANGUAGE_CHAMORRO\|LANGUAGE_CHECHEN\|LANGUAGE_CHICHEWA\|LANGUAGE_CHINESE\|LANGUAGE_CHURCHSLAVIC\|LANGUAGE_CHUVASH\|LANGUAGE_CORNISH\|LANGUAGE_CORSICAN\|LANGUAGE_CREE\|LANGUAGE_CROATIAN\|LANGUAGE_CZECH\|LANGUAGE_DANISH\|LANGUAGE_DIVEHI\|LANGUAGE_DUTCH\|LANGUAGE_DZONGKHA\|LANGUAGE_ENGLISH\|LANGUAGE_ESPERANTO\|LANGUAGE_ESTONIAN\|LANGUAGE_EWE\|LANGUAGE_FAROESE\|LANGUAGE_FIJIAN\|LANGUAGE_FINNISH\|LANGUAGE_FRENCH\|LANGUAGE_FULAH\|LANGUAGE_GAELIC\|LANGUAGE_GALICIAN\|LANGUAGE_GANDA\|LANGUAGE_GEORGIAN\|LANGUAGE_GERMAN\|LANGUAGE_GREEK\|LANGUAGE_GREENLANDIC\|LANGUAGE_GUARANI\|LANGUAGE_GUJARATI\|LANGUAGE_HAITIAN\|LANGUAGE_HAUSA\|LANGUAGE_HEBREW\|LANGUAGE_HERERO\|LANGUAGE_HINDI\|LANGUAGE_HIRIMOTU\|LANGUAGE_HUNGARIAN\|LANGUAGE_ICELANDIC\|LANGUAGE_IDO\|LANGUAGE_IGBO\|LANGUAGE_INDONESIAN\|LANGUAGE_INTERLINGUA\|LANGUAGE_INTERLINGUE\|LANGUAGE_INUKTITUT\|LANGUAGE_INUPIAQ\|LANGUAGE_IRISH\|LANGUAGE_ITALIAN\|LANGUAGE_JAPANESE\|LANGUAGE_JAVANESE\|LANGUAGE_KANNADA\|LANGUAGE_KANURI\|LANGUAGE_KASHMIRI\|LANGUAGE_KAZAKH\|LANGUAGE_KIKUYU\|LANGUAGE_KINYARWANDA\|LANGUAGE_KIRGHIZ\|LANGUAGE_KOMI\|LANGUAGE_KONGO\|LANGUAGE_KOREAN\|LANGUAGE_KUANYAMA\|LANGUAGE_KURDISH\|LANGUAGE_LAO\|LANGUAGE_LATIN\|LANGUAGE_LATVIAN\|LANGUAGE_LIMBURGAN\|LANGUAGE_LINGALA\|LANGUAGE_LITHUANIAN\|LANGUAGE_LUBAKATANGA\|LANGUAGE_LUXEMBOURGISH\|LANGUAGE_MACEDONIAN\|LANGUAGE_MALAGASY\|LANGUAGE_MALAY\|LANGUAGE_MALAYALAM\|LANGUAGE_MALTESE\|LANGUAGE_MANX\|LANGUAGE_MAORI\|LANGUAGE_MARATHI\|LANGUAGE_MARSHALLESE\|LANGUAGE_MONGOLIAN\|LANGUAGE_NAURU\|LANGUAGE_NAVAJO\|LANGUAGE_NDONGA\|LANGUAGE_NEPALI\|LANGUAGE_NORTHERNSAMI\|LANGUAGE_NORTHNDEBELE\|LANGUAGE_NORWEGIAN\|LANGUAGE_NORWEGIANBOKMAL\|LANGUAGE_NORWEGIANNYNORSK\|LANGUAGE_OCCITAN\|LANGUAGE_OJIBWA\|LANGUAGE_ORIYA\|LANGUAGE_OROMO\|LANGUAGE_OSSETIAN\|LANGUAGE_PALI\|LANGUAGE_PANJABI\|LANGUAGE_PASHTO\|LANGUAGE_PERSIAN\|LANGUAGE_POLISH\|LANGUAGE_PORTUGUESE\|LANGUAGE_QUECHUA\|LANGUAGE_ROMANIAN\|LANGUAGE_ROMANSH\|LANGUAGE_RUNDI\|LANGUAGE_RUSSIAN\|LANGUAGE_SAMOAN\|LANGUAGE_SANGO\|LANGUAGE_SANSKRIT\|LANGUAGE_SARDINIAN\|LANGUAGE_SERBIAN\|LANGUAGE_SHONA\|LANGUAGE_SICHUANYI\|LANGUAGE_SINDHI\|LANGUAGE_SINHALA\|LANGUAGE_SLOVAK\|LANGUAGE_SLOVENIAN\|LANGUAGE_SOMALI\|LANGUAGE_SOUTHERNSOTHO\|LANGUAGE_SOUTHNDEBELE\|LANGUAGE_SPANISH\|LANGUAGE_SUNDANESE\|LANGUAGE_SWAHILI\|LANGUAGE_SWATI\|LANGUAGE_SWEDISH\|LANGUAGE_TAGALOG\|LANGUAGE_TAHITIAN\|LANGUAGE_TAJIK\|LANGUAGE_TAMIL\|LANGUAGE_TATAR\|LANGUAGE_TELUGU\|LANGUAGE_THAI\|LANGUAGE_TIBETAN\|LANGUAGE_TIGRINYA\|LANGUAGE_TONGA\|LANGUAGE_TSONGA\|LANGUAGE_TSWANA\|LANGUAGE_TURKISH\|LANGUAGE_TURKMEN\|LANGUAGE_TWI\|LANGUAGE_UIGHUR\|LANGUAGE_UKRAINIAN\|LANGUAGE_UNKNOWN\|LANGUAGE_URDU\|LANGUAGE_UZBEK\|LANGUAGE_VENDA\|LANGUAGE_VIETNAMESE\|LANGUAGE_WALLOON\|LANGUAGE_WELSH\|LANGUAGE_WESTERNFRISIAN\|LANGUAGE_WOLOF\|LANGUAGE_XHOSA\|LANGUAGE_YIDDISH\|LANGUAGE_YORUBA\|LANGUAGE_ZHUANG\|LANGUAGE_ZULU\|LAYER\|LAYER_VS_BOX\|LAYERBUTTON\|LEFT\|LEFTOUT\|LIGHTUSERDATA\|LIME\|LINE\|LINEAR\|LITTLEENDIAN\|LONG\|LOWERCURVE\|MAROON\|MASK\|MASKAND\|MASKINVISIBLE\|MASKOR\|MASKVANILLACOPY\|MASKVISIBLE\|MASKXOR\|MEMORY\|MENU\|MENUITEM_DISABLED\|MENUITEM_RADIO\|MENUITEM_SELECTED\|MENUITEM_TOGGLE\|MILLISECONDS\|MODE_READ\|MODE_READWRITE\|MODE_WRITE\|MODLALT\|MODLCOMMAND\|MODLCONTROL\|MODLSHIFT\|MODRALT\|MODRCOMMAND\|MODRCONTROL\|MODRSHIFT\|MONO16\|MONO8\|MONOSPACE\|MOVEFILE_COPY\|MOVEFILE_COPYFAILED\|MOVEFILE_DELETE\|MOVEFILE_DELETEFAILED\|MOVEFILE_UNPROTECT\|MOVELIST\|MOVEWINDOW\|MUSIC\|NAN\|NATIVE\|NATIVEENDIAN\|NAVY\|NETWORKCONNECTION\|NETWORKSERVER\|NETWORKUDP\|NEXTFRAME\|NEXTFRAME2\|NIL\|NOCOLOR\|NONE\|NOPEN\|NORMAL\|NORMALSPEED\|NOTRANSPARENCY\|NUMBER\|NUMERICAL\|OLIVE\|ONBUTTONCLICK\|ONBUTTONCLICKALL\|ONBUTTONOVER\|ONBUTTONOVERALL\|ONBUTTONRIGHTCLICK\|ONBUTTONRIGHTCLICKALL\|ONKEYDOWN\|ONKEYDOWNALL\|ORIENTATION_LANDSCAPE\|ORIENTATION_LANDSCAPEREV\|ORIENTATION_NONE\|ORIENTATION_PORTRAIT\|ORIENTATION_PORTRAITREV\|PALETTE\|PALETTE_AGA\|PALETTE_CGA\|PALETTE_DEFAULT\|PALETTE_EGA\|PALETTE_GRAY128\|PALETTE_GRAY16\|PALETTE_GRAY256\|PALETTE_GRAY32\|PALETTE_GRAY4\|PALETTE_GRAY64\|PALETTE_GRAY8\|PALETTE_MACINTOSH\|PALETTE_MONOCHROME\|PALETTE_OCS\|PALETTE_WINDOWS\|PALETTE_WORKBENCH\|PALETTEMODE_PEN\|PALETTEMODE_REMAP\|PARITY_EVEN\|PARITY_NONE\|PARITY_ODD\|PERMREQ_READEXTERNAL\|PERMREQ_WRITEEXTERNAL\|PI\|PIXELZOOM1\|PIXELZOOM2\|PLOT\|PLUGINCAPS_ANIM\|PLUGINCAPS_AUDIOADAPTER\|PLUGINCAPS_CONVERT\|PLUGINCAPS_DIRADAPTER\|PLUGINCAPS_DISPLAYADAPTER\|PLUGINCAPS_EXTENSION\|PLUGINCAPS_FILEADAPTER\|PLUGINCAPS_ICON\|PLUGINCAPS_IMAGE\|PLUGINCAPS_IPCADAPTER\|PLUGINCAPS_LIBRARY\|PLUGINCAPS_NETWORKADAPTER\|PLUGINCAPS_REQUESTERADAPTER\|PLUGINCAPS_REQUIRE\|PLUGINCAPS_SAVEANIM\|PLUGINCAPS_SAVEICON\|PLUGINCAPS_SAVEIMAGE\|PLUGINCAPS_SAVESAMPLE\|PLUGINCAPS_SERIALIZE\|PLUGINCAPS_SOUND\|PLUGINCAPS_TIMERADAPTER\|PLUGINCAPS_VECTOR\|PLUGINCAPS_VIDEO\|POINTER\|POLYGON\|PRGTYPE_APPLET\|PRGTYPE_PROGRAM\|PRGTYPE_SCRIPT\|PRINT\|PURPLE\|PUSHBOTTOM\|PUSHLEFT\|PUSHRIGHT\|PUSHTOP\|PUZZLE\|QUADRECT\|QUARTERS\|RADIAL\|RANDOMEFFECT\|RANDOMPARAMETER\|RECEIVEALL\|RECEIVEBYTES\|RECEIVEDATA_PACKET\|RECEIVELINE\|RECTBACKCENTER\|RECTBACKEAST\|RECTBACKNORTH\|RECTBACKNORTHEAST\|RECTBACKNORTHWEST\|RECTBACKSOUTH\|RECTBACKSOUTHEAST\|RECTBACKSOUTHWEST\|RECTBACKWEST\|RECTCENTER\|RECTEAST\|RECTNORTH\|RECTNORTHEAST\|RECTNORTHWEST\|RECTSOUTH\|RECTSOUTHEAST\|RECTSOUTHWEST\|RECTWEST\|RED\|REMOVELAYER\|REQ_CAMERA\|REQ_GALLERY\|REQ_HIDEICONS\|REQ_MULTISELECT\|REQ_NORMAL\|REQ_SAVEMODE\|REQICON_ERROR\|REQICON_INFORMATION\|REQICON_NONE\|REQICON_QUESTION\|REQICON_WARNING\|REVEALBOTTOM\|REVEALLEFT\|REVEALRIGHT\|REVEALTOP\|RIGHT\|RIGHTOUT\|ROLLLEFT\|ROLLTOP\|RTS_OFF\|RTS_ON\|SAMPLE\|SANS\|SCALEMODE_AUTO\|SCALEMODE_LAYER\|SCALEMODE_NONE\|SCROLLBOTTOM\|SCROLLEAST\|SCROLLLEFT\|SCROLLNORTH\|SCROLLNORTHEAST\|SCROLLNORTHWEST\|SCROLLRIGHT\|SCROLLSOUTH\|SCROLLSOUTHEAST\|SCROLLSOUTHWEST\|SCROLLTOP\|SCROLLWEST\|SECONDS\|SEEK_BEGINNING\|SEEK_CURRENT\|SEEK_END\|SELMODE_COMBO\|SELMODE_LAYERS\|SELMODE_NORMAL\|SERIAL\|SERIF\|SERVER\|SHADOW\|SHAPE\|SHDWEAST\|SHDWNORTH\|SHDWNORTHEAST\|SHDWNORTHWEST\|SHDWSOUTH\|SHDWSOUTHEAST\|SHDWSOUTHWEST\|SHDWWEST\|SHORT\|SILVER\|SIMPLEBUTTON\|SINE\|SIZEWINDOW\|SLIDEBOTTOM\|SLIDELEFT\|SLIDERIGHT\|SLIDETOP\|SLOWSPEED\|SMOOTHOUT\|SMPFMT_WAVE\|SNAPDESKTOP\|SNAPDISPLAY\|SNAPWINDOW\|SPIRAL\|SPRITE\|SPRITE_VS_BOX\|SPRITE_VS_BRUSH\|STAR\|STDERR\|STDIN\|STDOUT\|STDPTR_BUSY\|STDPTR_CUSTOM\|STDPTR_SYSTEM\|STEREO16\|STEREO8\|STOP_1\|STOP_2\|STRETCHBOTTOM\|STRETCHLEFT\|STRETCHRIGHT\|STRETCHTOP\|STRING\|STRUDEL\|SUN\|SWISS\|TABLE\|TEAL\|TEXTOBJECT\|TEXTOUT\|THREAD\|TICKS\|TIMEOUT\|TIMER\|TOP\|TOPOUT\|TRUETYPE_DEFAULT\|TURNDOWNBOTTOM\|TURNDOWNLEFT\|TURNDOWNRIGHT\|TURNDOWNTOP\|UDPCLIENT\|UDPNONE\|UDPOBJECT\|UDPSERVER\|UNDERLINED\|UNDO\|UPLOADFILE_RESPONSE\|UPLOADFILE_STATUS\|UPNDOWN\|UPPERCURVE\|USEDSPACE\|USELAYERPOSITION\|USERDATA\|VANILLACOPY\|VBLINDS128\|VBLINDS16\|VBLINDS32\|VBLINDS64\|VBLINDS8\|VCLOSECURTAIN\|VCLOSEGATE\|VECTORPATH\|VFLIPCOIN\|VFLOWLEFT\|VFLOWRIGHT\|VIDDRV_HOLLYWOOD\|VIDDRV_OS\|VIDEO\|VIEWMODE_DATE\|VIEWMODE_ICONS\|VIEWMODE_NAME\|VIEWMODE_NONE\|VIEWMODE_SIZE\|VIEWMODE_TYPE\|VLINES\|VLINES2\|VLOWFLIPCOIN\|VOID\|VOPENCURTAIN\|VOPENGATE\|VSPLIT\|VSTRANGEPUSH\|VSTRETCHCENTER\|VSTRIPES\|VSTRIPES16\|VSTRIPES2\|VSTRIPES32\|VSTRIPES4\|VSTRIPES64\|VSTRIPES8\|WALLPAPERLEFT\|WALLPAPERTOP\|WATER1\|WATER2\|WATER3\|WATER4\|WHITE\|WORD\|YELLOW\|ZOOMCENTER\|ZOOMEAST\|ZOOMIN\|ZOOMNORTH\|ZOOMNORTHEAST\|ZOOMNORTHWEST\|ZOOMOUT\|ZOOMSOUTH\|ZOOMSOUTHEAST\|ZOOMSOUTHWEST\|ZOOMWEST\)\>" " Hollywood Functions -syn keyword hwFunction ACos ARGB ASin ATan ATan2 Abs ActivateDisplay Add AddArcToPath AddBoxToPath AddCircleToPath AddEllipseToPath AddFontPath AddIconImage AddMove AddStr AddTab AddTextToPath AllocMem AllocMemFromPointer AllocMemFromVirtualFile AppendPath ApplyPatch Arc ArcDistortBrush ArrayToStr Asc Assert AsyncDrawFrame BGPicToBrush BarrelDistortBrush Base64Str Beep BeginAnimStream BeginDoubleBuffer BeginRefresh BinStr BitClear BitComplement BitSet BitTest BitXor Blue BlurBrush Box BreakEventHandler BreakWhileMouseOn BrushToBGPic BrushToGray BrushToMonochrome BrushToPenArray BrushToRGBArray ByteAsc ByteChr ByteLen ByteOffset ByteStrStr ByteVal CRC32 CRC32Str CallJavaMethod CancelAsyncDraw CancelAsyncOperation CanonizePath Cast Ceil ChangeApplicationIcon ChangeBrushTransparency ChangeDirectory ChangeDisplayMode ChangeDisplaySize ChangeInterval CharOffset CharWidth CharcoalBrush CheckEvent CheckEvents Chr Circle ClearClipboard ClearEvents ClearInterval ClearMove ClearObjectData ClearPath ClearScreen ClearSerialQueue ClearTimeout CloseAmigaGuide CloseAnim CloseAudio CloseCatalog CloseConnection CloseDirectory CloseDisplay CloseFile CloseFont CloseMusic ClosePath CloseResourceMonitor CloseSerialPort CloseServer CloseUDPObject CloseVideo Cls CollectGarbage Collision ColorRequest CompareDates CompareStr CompressFile Concat ConsolePrint ConsolePrintNR ConsolePrompt ContinueAsyncOperation ContrastBrush ContrastPalette ConvertStr ConvertToBrush CopyAnim CopyBGPic CopyBrush CopyFile CopyMem CopyObjectData CopyPalette CopyPath CopyPens CopySample CopySprite CopyTable CopyTextObject Cos CountDirectoryEntries CountJoysticks CountStr CreateAnim CreateBGPic CreateBorderBrush CreateBrush CreateButton CreateClipRegion CreateCoroutine CreateDisplay CreateGradientBGPic CreateGradientBrush CreateIcon CreateKeyDown CreateLayer CreateList CreateMenu CreateMusic CreatePalette CreatePointer CreatePort CreateRainbowBGPic CreateRexxPort CreateSample CreateServer CreateShadowBrush CreateShortcut CreateSprite CreateTextObject CreateTexturedBGPic CreateTexturedBrush CreateUDPObject CropBrush CtrlCQuit CurveTo CyclePalette DateToTimestamp DateToUTC DebugOutput DebugPrint DebugPrintNR DebugPrompt DebugStr DebugVal DecompressFile DecreasePointer DefineVirtualFile DefineVirtualFileFromString Deg DeleteAlphaChannel DeleteButton DeleteFile DeleteMask DeletePrefs DeselectMenuItem DeserializeTable DirectoryItems DisableButton DisableEvent DisableLayers DisableLineHook DisableMenuItem DisablePlugin DisableVWait DisplayAnimFrame DisplayBGPic DisplayBGPicPart DisplayBGPicPartFX DisplayBrush DisplayBrushFX DisplayBrushPart DisplaySprite DisplayTextObject DisplayTextObjectFX DisplayTransitionFX DisplayVideoFrame Div DoMove DownloadFile DrawPath DumpButtons DumpLayers DumpMem DumpVideo DumpVideoTime EdgeBrush Ellipse EmbossBrush EmptyStr EnableButton EnableEvent EnableLayers EnableLineHook EnableMenuItem EnablePlugin EnableVWait End EndDoubleBuffer EndRefresh EndSelect EndianSwap EndsWith Eof Error EscapeQuit Eval Execute Exists ExitOnError Exp ExtractPalette FileAttributes FileLength FileLines FilePart FilePos FileRequest FileSize FileToString FillMem FillMusicBuffer FindStr FinishAnimStream FinishAsyncDraw Flip FlipBrush FlipSprite FloodFill Floor FlushFile FlushMusicBuffer FlushSerialPort FontRequest ForEach ForEachI ForcePathUse ForceSound ForceVideoDriver ForceVideoMode FormatStr FrExp Frac FreeAnim FreeBGPic FreeBrush FreeClipRegion FreeDisplay FreeEventCache FreeGlyphCache FreeIcon FreeLayers FreeMem FreeMenu FreeModule FreePalette FreePath FreePointer FreeSample FreeSprite FreeTextObject FullPath GCInfo GammaBrush GammaPalette GetAnimFrame GetApplicationInfo GetApplicationList GetAsset GetAttribute GetAvailableFonts GetBaudRate GetBestPen GetBrushLink GetBrushPen GetBulletColor GetCatalogString GetChannels GetCharMaps GetClipboard GetCommandLine GetConnectionIP GetConnectionPort GetConnectionProtocol GetConstant GetCoroutineStatus GetCountryInfo GetCurrentDirectory GetCurrentPoint GetDTR GetDash GetDataBits GetDate GetDateNum GetDefaultEncoding GetDirectoryEntry GetDisplayModes GetEnv GetErrorName GetEventCode GetFPSLimit GetFileArgument GetFileAttributes GetFillRule GetFillStyle GetFlowControl GetFontColor GetFontStyle GetFormStyle GetFrontScreen GetHostName GetIconProperties GetItem GetKerningPair GetLanguageInfo GetLastError GetLayerAtPos GetLayerPen GetLayerStyle GetLineCap GetLineJoin GetLineWidth GetLocalIP GetLocalInterfaces GetLocalPort GetLocalProtocol GetMACAddress GetMemPointer GetMemString GetMemoryInfo GetMetaTable GetMiterLimit GetMonitors GetObjectData GetObjectType GetObjects GetPalettePen GetParity GetPathExtents GetPatternPosition GetPen GetPlugins GetProgramDirectory GetProgramInfo GetPubScreens GetRTS GetRandomColor GetRandomFX GetRealColor GetSampleData GetShortcutPath GetSongPosition GetStartDirectory GetStopBits GetSystemCountry GetSystemInfo GetSystemLanguage GetTempFileName GetTime GetTimeZone GetTimer GetTimestamp GetType GetVersion GetVideoFrame GetVolumeInfo GetVolumeName GetWeekday GrabDesktop Green HaveFreeChannel HaveItem HaveObject HaveObjectData HavePlugin HaveVolume HexStr HideDisplay HideKeyboard HideLayer HideLayerFX HidePointer HideScreen Hypot IIf IPairs IgnoreCase ImageRequest InKeyStr IncreasePointer InsertItem InsertLayer InsertSample InsertStr InstallEventHandler Int Intersection InvertAlphaChannel InvertBrush InvertMask InvertPalette IsAbsolutePath IsAlNum IsAlpha IsAnim IsBrushEmpty IsChannelPlaying IsCntrl IsDigit IsDirectory IsFinite IsGraph IsInf IsKeyDown IsLeftMouse IsLower IsMenuItemDisabled IsMenuItemSelected IsMidMouse IsModule IsMusic IsMusicPlaying IsNan IsNil IsOnline IsPathEmpty IsPicture IsPrint IsPunct IsRightMouse IsSample IsSamplePlaying IsSound IsSpace IsTableEmpty IsUnicode IsUpper IsVideo IsVideoPlaying IsXDigit JoyDir JoyFire LayerExists LayerToBack LayerToFront Ld LdExp LeftMouseQuit LeftStr LegacyControl Limit Line LineTo ListItems ListRequest Ln LoadAnim LoadAnimFrame LoadBGPic LoadBrush LoadIcon LoadModule LoadPalette LoadPlugin LoadPrefs LoadSample LoadSprite Locate Log LowerStr MD5 MD5Str MakeButton MakeDate MakeDirectory MakeHostPath MatchPattern Max MemToTable MidStr Min MixBrush MixRGB MixSample Mod ModifyAnimFrames ModifyButton ModifyKeyDown ModifyLayerFrames ModulateBrush ModulatePalette MonitorDirectory MouseX MouseY MoveAnim MoveBrush MoveDisplay MoveFile MoveLayer MovePointer MoveSprite MoveTextObject MoveTo Mul NPrint NextDirectoryEntry NextFrame NextItem NormalizePath OilPaintBrush OpenAmigaGuide OpenAnim OpenAudio OpenCatalog OpenConnection OpenDirectory OpenDisplay OpenFile OpenFont OpenMusic OpenResourceMonitor OpenSerialPort OpenURL OpenVideo Pack PadNum Pairs PaletteToGray ParseDate PathItems PathPart PathRequest PathToBrush PatternFindStr PatternFindStrDirect PatternFindStrShort PatternReplaceStr PauseLayer PauseModule PauseMusic PauseTimer PauseVideo Peek PeekClipboard PenArrayToBrush PerformSelector PermissionRequest PerspectiveDistortBrush Pi PixelateBrush PlayAnim PlayAnimDisk PlayLayer PlayModule PlayMusic PlaySample PlaySubsong PlayVideo Plot Poke PolarDistortBrush PollSerialQueue Polygon Pow Print QuantizeBrush RGB RGBArrayToBrush Rad RaiseOnError RasterizeBrush RawDiv RawEqual RawGet RawSet ReadBrushPixel ReadByte ReadBytes ReadChr ReadDirectory ReadFloat ReadFunction ReadInt ReadLine ReadMem ReadPen ReadPixel ReadRegistryKey ReadSerialData ReadShort ReadString ReadTable ReceiveData ReceiveUDPData Red ReduceAlphaChannel RefreshDisplay RelCurveTo RelLineTo RelMoveTo RemapBrush RemoveBrushPalette RemoveButton RemoveIconImage RemoveItem RemoveKeyDown RemoveLayer RemoveLayerFX RemoveLayers RemoveSprite RemoveSprites Rename RepeatStr ReplaceColors ReplaceStr ResetKeyStates ResetTabs ResetTimer ResolveHostName ResumeCoroutine ResumeLayer ResumeModule ResumeMusic ResumeTimer ResumeVideo ReverseFindStr ReverseStr RewindDirectory RightStr Rnd RndF RndStrong Rol Ror RotateBrush RotateLayer RotateTextObject Round Rt Run RunCallback RunRexxScript Sar SaveAnim SaveBrush SaveIcon SavePalette SavePrefs SaveSample SaveSnapshot ScaleAnim ScaleBGPic ScaleBrush ScaleLayer ScaleSprite ScaleTextObject Seek SeekLayer SeekMusic SeekVideo SelectAlphaChannel SelectAnim SelectBGPic SelectBrush SelectDisplay SelectLayer SelectMask SelectMenuItem SelectPalette SendApplicationMessage SendData SendMessage SendRexxCommand SendUDPData SepiaToneBrush SerializeTable SetAlphaIntensity SetAnimFrameDelay SetBaudRate SetBorderPen SetBrushDepth SetBrushPalette SetBrushPen SetBrushTransparency SetBrushTransparentPen SetBulletColor SetBulletPen SetChannelVolume SetClipRegion SetClipboard SetCycleTable SetDTR SetDash SetDataBits SetDefaultEncoding SetDepth SetDisplayAttributes SetDitherMode SetDrawPen SetDrawTagsDefault SetEnv SetEventTimeout SetFPSLimit SetFileAttributes SetFileEncoding SetFillRule SetFillStyle SetFlowControl SetFont SetFontColor SetFontStyle SetFormStyle SetGradientPalette SetIOMode SetIconProperties SetInterval SetLayerAnchor SetLayerBorder SetLayerDepth SetLayerFilter SetLayerLight SetLayerName SetLayerPalette SetLayerPen SetLayerShadow SetLayerStyle SetLayerTint SetLayerTransparency SetLayerTransparentPen SetLayerVolume SetLayerZPos SetLineCap SetLineJoin SetLineWidth SetListItems SetMargins SetMaskMode SetMasterVolume SetMetaTable SetMiterLimit SetMusicVolume SetNetworkProtocol SetNetworkTimeout SetObjectData SetPalette SetPaletteDepth SetPaletteMode SetPalettePen SetPaletteTransparentPen SetPanning SetParity SetPen SetPitch SetPointer SetRTS SetScreenTitle SetShadowPen SetSpriteZPos SetStandardIconImage SetStandardPalette SetStopBits SetSubtitle SetTimeout SetTimerElapse SetTitle SetTransparentPen SetTransparentThreshold SetTrayIcon SetVectorEngine SetVideoPosition SetVideoSize SetVideoVolume SetVolume SetWBIcon Sgn SharpenBrush Shl ShowDisplay ShowKeyboard ShowLayer ShowLayerFX ShowNotification ShowPointer ShowRinghioMessage ShowScreen ShowToast Shr Sin SolarizeBrush SolarizePalette Sort SplitStr Sqrt StartPath StartSubPath StartTimer StartsWith StopChannel StopLayer StopModule StopMusic StopSample StopTimer StopVideo StrLen StrStr StrToArray StringRequest StringToFile StripStr Sub SwapLayers SwirlBrush SystemRequest TableItems TableToMem Tan TextExtent TextHeight TextOut TextWidth TimerElapsed TimestampToDate TintBrush TintPalette ToHostName ToIP ToNumber ToString ToUserData TransformBrush TransformLayer TranslateLayer TranslatePath TrimBrush TrimStr UTCToDate UndefineVirtualStringFile Undo UndoFX UnleftStr UnmidStr Unpack UnrightStr UnsetEnv UploadFile UpperStr UseCarriageReturn UseFont VWait Val ValidateDate ValidateStr Vibrate Wait WaitEvent WaitKeyDown WaitLeftMouse WaitMidMouse WaitPatternPosition WaitRightMouse WaitSampleEnd WaitSongPosition WaitTimer WaterRippleBrush WhileKeyDown WhileMouseDown WhileMouseOn WhileRightMouseDown Wrap WriteAnimFrame WriteBrushPixel WriteByte WriteBytes WriteChr WriteFloat WriteFunction WriteInt WriteLine WriteMem WritePen WriteRegistryKey WriteSerialData WriteShort WriteString WriteTable YieldCoroutine +syn keyword hwFunction Abs ACos ARGB ActivateDisplay Add AddArcToPath AddBoxToPath AddCircleToPath AddEllipseToPath AddFontPath AddIconImage AddMove AddStr AddTab AddTextToPath AllocMem AllocMemFromPointer AllocMemFromVirtualFile AppendPath ApplyPatch Arc ArcDistortBrush ArrayToStr Asc ASin Assert AsyncDrawFrame ATan ATan2 BarrelDistortBrush Base64Str Beep BeginAnimStream BeginDoubleBuffer BeginRefresh BGPicToBrush BinStr BitClear BitComplement BitSet BitTest BitXor Blue BlurBrush Box BreakEventHandler BreakWhileMouseOn BrushToBGPic BrushToGray BrushToMonochrome BrushToPenArray BrushToRGBArray ByteAsc ByteChr ByteLen ByteOffset ByteStrStr ByteVal CRC32 CRC32Str CallJavaMethod CancelAsyncDraw CancelAsyncOperation CanonizePath Cast Ceil ChangeApplicationIcon ChangeBrushTransparency ChangeDirectory ChangeDisplayMode ChangeDisplaySize ChangeInterval CharcoalBrush CharOffset CharWidth CheckEvent CheckEvents Chr Circle ClearClipboard ClearEvents ClearInterval ClearMove ClearObjectData ClearPath ClearScreen ClearSerialQueue ClearTimeout CloseAmigaGuide CloseAnim CloseAudio CloseCatalog CloseConnection CloseDirectory CloseDisplay CloseFile CloseFont CloseMusic ClosePath CloseResourceMonitor CloseSerialPort CloseServer CloseUDPObject CloseVideo Cls CollectGarbage Collision ColorRequest CompareDates CompareStr CompressFile Concat ConsolePrint ConsolePrintNR ConsolePrompt ContinueAsyncOperation ContrastBrush ContrastPalette ConvertStr ConvertToBrush CopyAnim CopyBGPic CopyBrush CopyFile CopyLayer CopyMem CopyObjectData CopyPalette CopyPath CopyPens CopySample CopySprite CopyTable CopyTextObject Cos CountDirectoryEntries CountJoysticks CountStr CreateAnim CreateBGPic CreateBorderBrush CreateBrush CreateButton CreateClipRegion CreateCoroutine CreateDisplay CreateGradientBGPic CreateGradientBrush CreateIcon CreateKeyDown CreateLayer CreateList CreateMenu CreateMusic CreatePalette CreatePointer CreatePort CreateRainbowBGPic CreateRexxPort CreateSample CreateServer CreateShadowBrush CreateShortcut CreateSprite CreateTextObject CreateTexturedBGPic CreateTexturedBrush CreateUDPObject CropBrush CtrlCQuit CurveTo CyclePalette DateToTimestamp DateToUTC DebugOutput DebugPrint DebugPrintNR DebugPrompt DebugStr DebugVal DecompressFile DecreasePointer DefineVirtualFile DefineVirtualFileFromString Deg DeleteAlphaChannel DeleteButton DeleteFile DeleteMask DeletePrefs DeselectMenuItem DeserializeTable DirectoryItems DisableButton DisableEvent DisableEventHandler DisableLayers DisableLineHook DisableMenuItem DisablePlugin DisablePrecalculation DisableVWait DisplayAnimFrame DisplayBGPic DisplayBGPicPart DisplayBGPicPartFX DisplayBrush DisplayBrushFX DisplayBrushPart DisplaySprite DisplayTextObject DisplayTextObjectFX DisplayTransitionFX DisplayVideoFrame Div DoMove DownloadFile DrawPath DumpButtons DumpLayers DumpMem DumpVideo DumpVideoTime EdgeBrush Ellipse EmbossBrush EmptyStr EnableButton EnableEventHandler EnableEvent EnableLayers EnableLineHook EnableMenuItem EnablePlugin EnablePrecalculation EnableVWait End EndDoubleBuffer EndianSwap EndRefresh EndSelect EndsWith Eof Error EscapeQuit Eval Execute Exists ExitOnError Exp ExtractPalette FileAttributes FileLength FileLines FilePart FilePos FileRequest FileSize FileToString FillMem FillMusicBuffer FindStr FinishAnimStream FinishAsyncDraw Flip FlipBrush FlipSprite FloodFill Floor FlushFile FlushMusicBuffer FlushSerialPort FontRequest ForcePathUse ForceSound ForceVideoDriver ForceVideoMode ForEach ForEachI FormatStr Frac FreeAnim FreeBGPic FreeBrush FreeClipRegion FreeDisplay FreeEventCache FreeGlyphCache FreeIcon FreeLayers FreeMem FreeMenu FreeModule FreePalette FreePath FreePointer FreeSample FreeSprite FreeTextObject FrExp FullPath GammaBrush GammaPalette GCInfo GetAnimFrame GetApplicationInfo GetApplicationList GetAsset GetAttribute GetAvailableFonts GetBaudRate GetBestPen GetBrushLink GetBrushPen GetBulletColor GetCatalogString GetChannels GetCharMaps GetClipboard GetCommandLine GetConnectionIP GetConnectionPort GetConnectionProtocol GetConstant GetCoroutineStatus GetCountryInfo GetCurrentDirectory GetCurrentPoint GetDash GetDataBits GetDate GetDateNum GetDefaultEncoding GetDirectoryEntry GetDisplayModes GetDTR GetEnv GetErrorName GetEventCode GetFileArgument GetFileAttributes GetFillRule GetFillStyle GetFlowControl GetFontColor GetFontStyle GetFormStyle GetFPSLimit GetFrontScreen GetHostName GetIconProperties GetItem GetKerningPair GetLanguageInfo GetLastError GetLayerAtPos GetLayerPen GetLayerStyle GetLineCap GetLineJoin GetLineWidth GetLocalInterfaces GetLocalIP GetLocalPort GetLocalProtocol GetMACAddress GetMemoryInfo GetMemPointer GetMemString GetMetaTable GetMiterLimit GetMonitors GetObjectData GetObjects GetObjectType GetPalettePen GetParity GetPathExtents GetPatternPosition GetPen GetPlugins GetProgramDirectory GetProgramInfo GetPubScreens GetRandomColor GetRandomFX GetRealColor GetRTS GetSampleData GetShortcutPath GetSongPosition GetStartDirectory GetStopBits GetSystemCountry GetSystemInfo GetSystemLanguage GetTempFileName GetTime GetTimer GetTimestamp GetTimeZone GetType GetVersion GetVideoFrame GetVolumeInfo GetVolumeName GetWeekday Gosub Goto GrabDesktop Green HaveFreeChannel HaveItem HaveObject HaveObjectData HavePlugin HaveVolume HexStr HideDisplay HideKeyboard HideLayer HideLayerFX HidePointer HideScreen Hypot IgnoreCase IIf ImageRequest IncreasePointer InKeyStr InsertItem InsertLayer InsertSample InsertStr InstallEventHandler Intersection Int InvertAlphaChannel InvertBrush InvertMask InvertPalette IPairs IsAbsolutePath IsAlNum IsAlpha IsAnim IsAnimPlaying IsBrushEmpty IsChannelPlaying IsCntrl IsDigit IsDirectory IsFinite IsGraph IsInf IsKeyDown IsLeftMouse IsLower IsMenuItemDisabled IsMenuItemSelected IsMidMouse IsModule IsMusicPlaying IsMusic IsNan IsNil IsOnline IsPathEmpty IsPicture IsPrint IsPunct IsRightMouse IsSamplePlaying IsSample IsSound IsSpace IsTableEmpty IsUnicode IsUpper IsVideo IsVideoPlaying IsXDigit JoyDir JoyFire Label LayerExists LayerToBack LayerToFront Ld LdExp LeftMouseQuit LeftStr LegacyControl Limit Line LineTo ListItems ListRequest Ln LoadAnim LoadAnimFrame LoadBGPic LoadBrush LoadIcon LoadModule LoadPalette LoadPlugin LoadPrefs LoadSample LoadSprite Locate Log LowerStr MakeButton MakeDate MakeDirectory MakeHostPath MatchPattern Max MD5 MD5Str MemToTable MidStr Min MixBrush MixRGB MixSample ModifyAnimFrames ModifyButton ModifyKeyDown ModifyLayerFrames ModulateBrush Mod ModulatePalette MonitorDirectory MouseX MouseY MoveAnim MoveBrush MoveDisplay MoveFile MoveLayer MovePointer MoveSprite MoveTextObject MoveTo Mul NextDirectoryEntry NextFrame NextItem NormalizePath NPrint OilPaintBrush OpenAmigaGuide OpenAnim OpenAudio OpenCatalog OpenConnection OpenDirectory OpenDisplay OpenFile OpenFont OpenMusic OpenResourceMonitor OpenSerialPort OpenURL OpenVideo Pack PadNum Pairs PaletteToGray ParseDate PathItems PathPart PathRequest PathToBrush PatternFindStr PatternFindStrDirect PatternFindStrShort PatternReplaceStr PauseLayer PauseModule PauseMusic PauseTimer PauseVideo PeekClipboard Peek PenArrayToBrush PerformSelector PermissionRequest PerspectiveDistortBrush Pi PixelateBrush PlayAnim PlayAnimDisk PlayLayer PlayModule PlayMusic PlaySample PlaySubsong PlayVideo Plot Poke PolarDistortBrush PollSerialQueue Polygon Pow Print QuantizeBrush Rad RaiseOnError RasterizeBrush RawDiv RawEqual RawGet RawSet ReadBrushPixel ReadByte ReadBytes ReadChr ReadDirectory ReadFloat ReadFunction ReadInt ReadLine ReadMem ReadPen ReadPixel ReadRegistryKey ReadSerialData ReadShort ReadString ReadTable ReceiveData ReceiveUDPData Red ReduceAlphaChannel RefreshDisplay RelCurveTo RelLineTo RelMoveTo RemapBrush RemoveBrushPalette RemoveButton RemoveIconImage RemoveItem RemoveKeyDown RemoveLayer RemoveLayerFX RemoveLayers RemoveSprite RemoveSprites Rename RepeatStr ReplaceColors ReplaceStr ResetKeyStates ResetTabs ResetTimer ResolveHostName ResumeCoroutine ResumeLayer ResumeModule ResumeMusic ResumeTimer ResumeVideo ReverseFindStr ReverseStr RewindDirectory RGB RGBArrayToBrush RightStr Rnd RndF RndStrong Rol Ror RotateBrush RotateLayer RotateTextObject Round Rt Run RunCallback RunRexxScript Sar SaveAnim SaveBrush SaveIcon SavePalette SavePrefs SaveSample SaveSnapshot ScaleAnim ScaleBGPic ScaleBrush ScaleLayer ScaleSprite ScaleTextObject Seek SeekLayer SeekMusic SeekVideo SelectAlphaChannel SelectAnim SelectBGPic SelectBrush SelectDisplay SelectLayer SelectMask SelectMenuItem SelectPalette SendApplicationMessage SendData SendMessage SendRexxCommand SendUDPData SepiaToneBrush SerializeTable SetAlphaIntensity SetAnimFrameDelay SetBaudRate SetBorderPen SetBrushDepth SetBrushPalette SetBrushPen SetBrushTransparency SetBrushTransparentPen SetBulletColor SetBulletPen SetChannelVolume SetClipboard SetClipRegion SetCycleTable SetDash SetDataBits SetDefaultEncoding SetDepth SetDisplayAttributes SetDitherMode SetDrawPen SetDrawTagsDefault SetDTR SetEnv SetEventTimeout SetFileAttributes SetFileEncoding SetFillRule SetFillStyle SetFlowControl SetFont SetFontColor SetFontStyle SetFormStyle SetFPSLimit SetGradientPalette SetIconProperties SetInterval SetIOMode SetLayerAnchor SetLayerBorder SetLayerDepth SetLayerFilter SetLayerLight SetLayerName SetLayerPalette SetLayerPen SetLayerShadow SetLayerStyle SetLayerTint SetLayerTransparency SetLayerTransparentPen SetLayerVolume SetLayerZPos SetLineCap SetLineJoin SetLineWidth SetListItems SetMargins SetMaskMode SetMasterVolume SetMetaTable SetMiterLimit SetMusicVolume SetNetworkProtocol SetNetworkTimeout SetObjectData SetPalette SetPaletteDepth SetPaletteMode SetPalettePen SetPaletteTransparentPen SetPanning SetParity SetPen SetPitch SetPointer SetRTS SetScreenTitle SetShadowPen SetSpriteZPos SetStandardIconImage SetStandardPalette SetStopBits SetSubtitle SetTimeout SetTimerElapse SetTitle SetTransparentPen SetTransparentThreshold SetTrayIcon SetVarType SetVectorEngine SetVideoPosition SetVideoSize SetVideoVolume SetVolume SetWBIcon Sgn SharpenBrush Shl ShowDisplay ShowKeyboard ShowLayer ShowLayerFX ShowNotification ShowPointer ShowRinghioMessage ShowScreen ShowToast Shr Sin SolarizeBrush SolarizePalette Sort SplitStr Sqrt StartPath StartSubPath StartTimer StartsWith StopAnim StopChannel StopLayer StopModule StopMusic StopSample StopTimer StopVideo StringRequest StringToFile StripStr StrLen StrStr StrToArray Sub SwapLayers SwirlBrush SystemRequest TableItems TableToMem Tan TextExtent TextHeight TextOut TextWidth TimerElapsed TimestampToDate TintBrush TintPalette ToHostName ToIP ToNumber ToString ToUserData TransformBrush TransformLayer TranslateLayer TranslatePath TrimBrush TrimStr UndefineVirtualStringFile Undo UndoFX UnleftStr UnmidStr Unpack UnrightStr UnsetEnv UploadFile UpperStr Usage UseCarriageReturn UseFont UTCToDate Val ValidateDate ValidateStr Vibrate VWait Wait WaitAnimEnd WaitEvent WaitKeyDown WaitLeftMouse WaitMidMouse WaitPatternPosition WaitRightMouse WaitSampleEnd WaitSongPosition WaitTimer WaterRippleBrush WhileKeyDown WhileMouseDown WhileMouseOn WhileRightMouseDown Wrap WriteAnimFrame WriteBrushPixel WriteByte WriteBytes WriteChr WriteFloat WriteFunction WriteInt WriteLine WriteMem WritePen WriteRegistryKey WriteSerialData WriteShort WriteString WriteTable YieldCoroutine " user-defined constants syn match hwUserConstant "#\<\u\+\>" diff --git a/runtime/syntax/html.vim b/runtime/syntax/html.vim index 9061bdee90..605db3ae1c 100644 --- a/runtime/syntax/html.vim +++ b/runtime/syntax/html.vim @@ -3,7 +3,7 @@ " Maintainer: Doug Kearns <dougkearns@gmail.com> " Previous Maintainers: Jorge Maldonado Ventura <jorgesumle@freakspot.net> " Claudio Fleiner <claudio@fleiner.com> -" Last Change: 2022 Jul 20 +" Last Change: 2022 Nov 18 " Please check :help html.vim for some comments and a description of the options @@ -272,6 +272,16 @@ if main_syntax == "html" syn sync minlines=10 endif +" Folding +" Originally by Ingo Karkat and Marcus Zanona +if get(g:, "html_syntax_folding", 0) + syn region htmlFold start="<\z(\<\%(area\|base\|br\|col\|command\|embed\|hr\|img\|input\|keygen\|link\|meta\|param\|source\|track\|wbr\>\)\@![a-z-]\+\>\)\%(\_s*\_[^/]\?>\|\_s\_[^>]*\_[^>/]>\)" end="</\z1\_s*>" fold transparent keepend extend containedin=htmlHead,htmlH\d + " fold comments (the real ones and the old Netscape ones) + if exists("html_wrong_comments") + syn region htmlComment start=+<!--+ end=+--\s*>\%(\n\s*<!--\)\@!+ contains=@Spell fold + endif +endif + " The default highlighting. hi def link htmlTag Function hi def link htmlEndTag Identifier diff --git a/runtime/syntax/make.vim b/runtime/syntax/make.vim index 68f7ee21ea..b4573044ca 100644 --- a/runtime/syntax/make.vim +++ b/runtime/syntax/make.vim @@ -3,7 +3,7 @@ " Maintainer: Roland Hieber <rohieb+vim-iR0jGdkV@rohieb.name>, <https://github.com/rohieb> " Previous Maintainer: Claudio Fleiner <claudio@fleiner.com> " URL: https://github.com/vim/vim/blob/master/runtime/syntax/make.vim -" Last Change: 2020 Oct 16 +" Last Change: 2022 Nov 06 " quit when a syntax file was already loaded if exists("b:current_syntax") @@ -45,11 +45,11 @@ syn match makeImplicit "^\.[A-Za-z0-9_./\t -]\+\s*:$"me=e-1 syn match makeImplicit "^\.[A-Za-z0-9_./\t -]\+\s*:[^=]"me=e-2 syn region makeTarget transparent matchgroup=makeTarget - \ start="^[~A-Za-z0-9_./$()%-][A-Za-z0-9_./\t $()%-]*&\?:\?:\{1,2}[^:=]"rs=e-1 + \ start="^[~A-Za-z0-9_./$(){}%-][A-Za-z0-9_./\t ${}()%-]*&\?:\?:\{1,2}[^:=]"rs=e-1 \ end="[^\\]$" \ keepend contains=makeIdent,makeSpecTarget,makeNextLine,makeComment,makeDString \ skipnl nextGroup=makeCommands -syn match makeTarget "^[~A-Za-z0-9_./$()%*@-][A-Za-z0-9_./\t $()%*@-]*&\?::\=\s*$" +syn match makeTarget "^[~A-Za-z0-9_./$(){}%*@-][A-Za-z0-9_./\t $(){}%*@-]*&\?::\=\s*$" \ contains=makeIdent,makeSpecTarget,makeComment \ skipnl nextgroup=makeCommands,makeCommandError diff --git a/runtime/syntax/markdown.vim b/runtime/syntax/markdown.vim index 17b61c2fa4..44187ff18c 100644 --- a/runtime/syntax/markdown.vim +++ b/runtime/syntax/markdown.vim @@ -1,8 +1,8 @@ " Vim syntax file " Language: Markdown -" Maintainer: Tim Pope <vimNOSPAM@tpope.org> +" Maintainer: Tim Pope <https://github.com/tpope/vim-markdown> " Filenames: *.markdown -" Last Change: 2020 Jan 14 +" Last Change: 2022 Oct 13 if exists("b:current_syntax") finish @@ -12,6 +12,12 @@ if !exists('main_syntax') let main_syntax = 'markdown' endif +if has('folding') + let s:foldmethod = &l:foldmethod + let s:foldtext = &l:foldtext +endif +let s:iskeyword = &l:iskeyword + runtime! syntax/html.vim unlet! b:current_syntax @@ -26,17 +32,33 @@ for s:type in map(copy(g:markdown_fenced_languages),'matchstr(v:val,"[^=]*$")') if s:type =~ '\.' let b:{matchstr(s:type,'[^.]*')}_subtype = matchstr(s:type,'\.\zs.*') endif - exe 'syn include @markdownHighlight'.substitute(s:type,'\.','','g').' syntax/'.matchstr(s:type,'[^.]*').'.vim' + syn case match + exe 'syn include @markdownHighlight_'.tr(s:type,'.','_').' syntax/'.matchstr(s:type,'[^.]*').'.vim' unlet! b:current_syntax let s:done_include[matchstr(s:type,'[^.]*')] = 1 endfor unlet! s:type unlet! s:done_include +syn spell toplevel +if exists('s:foldmethod') && s:foldmethod !=# &l:foldmethod + let &l:foldmethod = s:foldmethod + unlet s:foldmethod +endif +if exists('s:foldtext') && s:foldtext !=# &l:foldtext + let &l:foldtext = s:foldtext + unlet s:foldtext +endif +if s:iskeyword !=# &l:iskeyword + let &l:iskeyword = s:iskeyword +endif +unlet s:iskeyword + if !exists('g:markdown_minlines') let g:markdown_minlines = 50 endif execute 'syn sync minlines=' . g:markdown_minlines +syn sync linebreaks=1 syn case ignore syn match markdownValid '[<>]\c[a-z/$!]\@!' transparent contains=NONE @@ -52,16 +74,16 @@ syn match markdownH2 "^.\+\n-\+$" contained contains=@markdownInline,markdownHea syn match markdownHeadingRule "^[=-]\+$" contained -syn region markdownH1 matchgroup=markdownH1Delimiter start="##\@!" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained -syn region markdownH2 matchgroup=markdownH2Delimiter start="###\@!" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained -syn region markdownH3 matchgroup=markdownH3Delimiter start="####\@!" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained -syn region markdownH4 matchgroup=markdownH4Delimiter start="#####\@!" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained -syn region markdownH5 matchgroup=markdownH5Delimiter start="######\@!" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained -syn region markdownH6 matchgroup=markdownH6Delimiter start="#######\@!" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained +syn region markdownH1 matchgroup=markdownH1Delimiter start=" \{,3}#\s" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained +syn region markdownH2 matchgroup=markdownH2Delimiter start=" \{,3}##\s" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained +syn region markdownH3 matchgroup=markdownH3Delimiter start=" \{,3}###\s" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained +syn region markdownH4 matchgroup=markdownH4Delimiter start=" \{,3}####\s" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained +syn region markdownH5 matchgroup=markdownH5Delimiter start=" \{,3}#####\s" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained +syn region markdownH6 matchgroup=markdownH6Delimiter start=" \{,3}######\s" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained syn match markdownBlockquote ">\%(\s\|$\)" contained nextgroup=@markdownBlock -syn region markdownCodeBlock start=" \|\t" end="$" contained +syn region markdownCodeBlock start="^\n\( \{4,}\|\t\)" end="^\ze \{,3}\S.*$" keepend " TODO: real nesting syn match markdownListMarker "\%(\t\| \{0,4\}\)[-*+]\%(\s\+\S\)\@=" contained @@ -79,7 +101,7 @@ syn region markdownUrlTitle matchgroup=markdownUrlTitleDelimiter start=+"+ end=+ syn region markdownUrlTitle matchgroup=markdownUrlTitleDelimiter start=+'+ end=+'+ keepend contained syn region markdownUrlTitle matchgroup=markdownUrlTitleDelimiter start=+(+ end=+)+ keepend contained -syn region markdownLinkText matchgroup=markdownLinkTextDelimiter start="!\=\[\%(\%(\_[^][]\|\[\_[^][]*\]\)*]\%( \=[[(]\)\)\@=" end="\]\%( \=[[(]\)\@=" nextgroup=markdownLink,markdownId skipwhite contains=@markdownInline,markdownLineStart +syn region markdownLinkText matchgroup=markdownLinkTextDelimiter start="!\=\[\%(\_[^][]*\%(\[\_[^][]*\]\_[^][]*\)*]\%( \=[[(]\)\)\@=" end="\]\%( \=[[(]\)\@=" nextgroup=markdownLink,markdownId skipwhite contains=@markdownInline,markdownLineStart syn region markdownLink matchgroup=markdownLinkDelimiter start="(" end=")" contains=markdownUrl keepend contained syn region markdownId matchgroup=markdownIdDelimiter start="\[" end="\]" keepend contained syn region markdownAutomaticLink matchgroup=markdownUrlDelimiter start="<\%(\w\+:\|[[:alnum:]_+-]\+@\)\@=" end=">" keepend oneline @@ -88,31 +110,38 @@ let s:concealends = '' if has('conceal') && get(g:, 'markdown_syntax_conceal', 1) == 1 let s:concealends = ' concealends' endif -exe 'syn region markdownItalic matchgroup=markdownItalicDelimiter start="\S\@<=\*\|\*\S\@=" end="\S\@<=\*\|\*\S\@=" skip="\\\*" contains=markdownLineStart,@Spell' . s:concealends -exe 'syn region markdownItalic matchgroup=markdownItalicDelimiter start="\w\@<!_\S\@=" end="\S\@<=_\w\@!" skip="\\_" contains=markdownLineStart,@Spell' . s:concealends -exe 'syn region markdownBold matchgroup=markdownBoldDelimiter start="\S\@<=\*\*\|\*\*\S\@=" end="\S\@<=\*\*\|\*\*\S\@=" skip="\\\*" contains=markdownLineStart,markdownItalic,@Spell' . s:concealends -exe 'syn region markdownBold matchgroup=markdownBoldDelimiter start="\w\@<!__\S\@=" end="\S\@<=__\w\@!" skip="\\_" contains=markdownLineStart,markdownItalic,@Spell' . s:concealends -exe 'syn region markdownBoldItalic matchgroup=markdownBoldItalicDelimiter start="\S\@<=\*\*\*\|\*\*\*\S\@=" end="\S\@<=\*\*\*\|\*\*\*\S\@=" skip="\\\*" contains=markdownLineStart,@Spell' . s:concealends -exe 'syn region markdownBoldItalic matchgroup=markdownBoldItalicDelimiter start="\w\@<!___\S\@=" end="\S\@<=___\w\@!" skip="\\_" contains=markdownLineStart,@Spell' . s:concealends +exe 'syn region markdownItalic matchgroup=markdownItalicDelimiter start="\*\S\@=" end="\S\@<=\*\|^$" skip="\\\*" contains=markdownLineStart,@Spell' . s:concealends +exe 'syn region markdownItalic matchgroup=markdownItalicDelimiter start="\w\@<!_\S\@=" end="\S\@<=_\w\@!\|^$" skip="\\_" contains=markdownLineStart,@Spell' . s:concealends +exe 'syn region markdownBold matchgroup=markdownBoldDelimiter start="\*\*\S\@=" end="\S\@<=\*\*\|^$" skip="\\\*" contains=markdownLineStart,markdownItalic,@Spell' . s:concealends +exe 'syn region markdownBold matchgroup=markdownBoldDelimiter start="\w\@<!__\S\@=" end="\S\@<=__\w\@!\|^$" skip="\\_" contains=markdownLineStart,markdownItalic,@Spell' . s:concealends +exe 'syn region markdownBoldItalic matchgroup=markdownBoldItalicDelimiter start="\*\*\*\S\@=" end="\S\@<=\*\*\*\|^$" skip="\\\*" contains=markdownLineStart,@Spell' . s:concealends +exe 'syn region markdownBoldItalic matchgroup=markdownBoldItalicDelimiter start="\w\@<!___\S\@=" end="\S\@<=___\w\@!\|^$" skip="\\_" contains=markdownLineStart,@Spell' . s:concealends +exe 'syn region markdownStrike matchgroup=markdownStrikeDelimiter start="\~\~\S\@=" end="\S\@<=\~\~\|^$" contains=markdownLineStart,@Spell' . s:concealends syn region markdownCode matchgroup=markdownCodeDelimiter start="`" end="`" keepend contains=markdownLineStart syn region markdownCode matchgroup=markdownCodeDelimiter start="`` \=" end=" \=``" keepend contains=markdownLineStart -syn region markdownCode matchgroup=markdownCodeDelimiter start="^\s*````*.*$" end="^\s*````*\ze\s*$" keepend +syn region markdownCodeBlock matchgroup=markdownCodeDelimiter start="^\s*\z(`\{3,\}\).*$" end="^\s*\z1\ze\s*$" keepend +syn region markdownCodeBlock matchgroup=markdownCodeDelimiter start="^\s*\z(\~\{3,\}\).*$" end="^\s*\z1\ze\s*$" keepend syn match markdownFootnote "\[^[^\]]\+\]" syn match markdownFootnoteDefinition "^\[^[^\]]\+\]:" -if main_syntax ==# 'markdown' - let s:done_include = {} - for s:type in g:markdown_fenced_languages - if has_key(s:done_include, matchstr(s:type,'[^.]*')) - continue - endif - exe 'syn region markdownHighlight'.substitute(matchstr(s:type,'[^=]*$'),'\..*','','').' matchgroup=markdownCodeDelimiter start="^\s*````*\s*\%({.\{-}\.\)\='.matchstr(s:type,'[^=]*').'}\=\S\@!.*$" end="^\s*````*\ze\s*$" keepend contains=@markdownHighlight'.substitute(matchstr(s:type,'[^=]*$'),'\.','','g') . s:concealends - let s:done_include[matchstr(s:type,'[^.]*')] = 1 - endfor - unlet! s:type - unlet! s:done_include +let s:done_include = {} +for s:type in g:markdown_fenced_languages + if has_key(s:done_include, matchstr(s:type,'[^.]*')) + continue + endif + exe 'syn region markdownHighlight_'.substitute(matchstr(s:type,'[^=]*$'),'\..*','','').' matchgroup=markdownCodeDelimiter start="^\s*\z(`\{3,\}\)\s*\%({.\{-}\.\)\='.matchstr(s:type,'[^=]*').'}\=\S\@!.*$" end="^\s*\z1\ze\s*$" keepend contains=@markdownHighlight_'.tr(matchstr(s:type,'[^=]*$'),'.','_') . s:concealends + exe 'syn region markdownHighlight_'.substitute(matchstr(s:type,'[^=]*$'),'\..*','','').' matchgroup=markdownCodeDelimiter start="^\s*\z(\~\{3,\}\)\s*\%({.\{-}\.\)\='.matchstr(s:type,'[^=]*').'}\=\S\@!.*$" end="^\s*\z1\ze\s*$" keepend contains=@markdownHighlight_'.tr(matchstr(s:type,'[^=]*$'),'.','_') . s:concealends + let s:done_include[matchstr(s:type,'[^.]*')] = 1 +endfor +unlet! s:type +unlet! s:done_include + +if get(b:, 'markdown_yaml_head', get(g:, 'markdown_yaml_head', main_syntax ==# 'markdown')) + syn include @markdownYamlTop syntax/yaml.vim + unlet! b:current_syntax + syn region markdownYamlHead start="\%^---$" end="^\%(---\|\.\.\.\)\s*$" keepend contains=@markdownYamlTop,@Spell endif syn match markdownEscape "\\[][\\`*_{}()<>#+.!-]" @@ -156,6 +185,8 @@ hi def link markdownBold htmlBold hi def link markdownBoldDelimiter markdownBold hi def link markdownBoldItalic htmlBoldItalic hi def link markdownBoldItalicDelimiter markdownBoldItalic +hi def link markdownStrike htmlStrike +hi def link markdownStrikeDelimiter markdownStrike hi def link markdownCodeDelimiter Delimiter hi def link markdownEscape Special diff --git a/runtime/syntax/mermaid.vim b/runtime/syntax/mermaid.vim new file mode 100644 index 0000000000..afdbcc3d62 --- /dev/null +++ b/runtime/syntax/mermaid.vim @@ -0,0 +1,155 @@ +" Vim syntax file +" Language: Mermaid +" Maintainer: Craig MacEahern <https://github.com/craigmac/vim-mermaid> +" Filenames: *.mmd +" Last Change: 2022 Nov 22 + +if exists("b:current_syntax") + finish +endif + +let s:cpo_save = &cpo +set cpo&vim + +syntax iskeyword @,48-57,192-255,$,_,-,: +syntax keyword mermaidKeyword + \ _blank + \ _self + \ _parent + \ _top + \ ::icon + \ accDescr + \ accTitle + \ actor + \ activate + \ alt + \ and + \ as + \ autonumber + \ branch + \ break + \ callback + \ checkout + \ class + \ classDef + \ classDiagram + \ click + \ commit + \ commitgitGraph + \ critical + \ dataFormat + \ dateFormat + \ deactivate + \ direction + \ element + \ else + \ end + \ erDiagram + \ flowchart + \ gantt + \ gitGraph + \ graph + \ journey + \ link + \ LR + \ TD + \ TB + \ RL + \ loop + \ merge + \ mindmap root + \ Note + \ Note right of + \ Note left of + \ Note over + \ note + \ note right of + \ note left of + \ note over + \ opt + \ option + \ par + \ participant + \ pie + \ rect + \ requirement + \ rgb + \ section + \ sequenceDiagram + \ state + \ stateDiagram + \ stateDiagram-v2 + \ style + \ subgraph + \ title +highlight link mermaidKeyword Keyword + +syntax match mermaidStatement "|" +syntax match mermaidStatement "--\?[>x)]>\?+\?-\?" +syntax match mermaidStatement "\~\~\~" +syntax match mermaidStatement "--" +syntax match mermaidStatement "---" +syntax match mermaidStatement "-->" +syntax match mermaidStatement "-\." +syntax match mermaidStatement "\.->" +syntax match mermaidStatement "-\.-" +syntax match mermaidStatement "-\.\.-" +syntax match mermaidStatement "-\.\.\.-" +syntax match mermaidStatement "==" +syntax match mermaidStatement "==>" +syntax match mermaidStatement "===>" +syntax match mermaidStatement "====>" +syntax match mermaidStatement "&" +syntax match mermaidStatement "--o" +syntax match mermaidStatement "--x" +syntax match mermaidStatement "x--x" +syntax match mermaidStatement "-----" +syntax match mermaidStatement "---->" +syntax match mermaidStatement "===" +syntax match mermaidStatement "====" +syntax match mermaidStatement "=====" +syntax match mermaidStatement ":::" +syntax match mermaidStatement "<|--" +syntax match mermaidStatement "\*--" +syntax match mermaidStatement "o--" +syntax match mermaidStatement "o--o" +syntax match mermaidStatement "<--" +syntax match mermaidStatement "<-->" +syntax match mermaidStatement "\.\." +syntax match mermaidStatement "<\.\." +syntax match mermaidStatement "<|\.\." +syntax match mermaidStatement "--|>" +syntax match mermaidStatement "--\*" +syntax match mermaidStatement "--o" +syntax match mermaidStatement "\.\.>" +syntax match mermaidStatement "\.\.|>" +syntax match mermaidStatement "<|--|>" +syntax match mermaidStatement "||--o{" +highlight link mermaidStatement Statement + +syntax match mermaidIdentifier "[\+-]\?\w\+(.*)[\$\*]\?" +highlight link mermaidIdentifier Identifier + +syntax match mermaidType "[\+-\#\~]\?\cint\>" +syntax match mermaidType "[\+-\#\~]\?\cString\>" +syntax match mermaidType "[\+-\#\~]\?\cbool\>" +syntax match mermaidType "[\+-\#\~]\?\cBigDecimal\>" +syntax match mermaidType "[\+-\#\~]\?\cList\~.\+\~" +syntax match mermaidType "<<\w\+>>" +highlight link mermaidType Type + +syntax match mermaidComment "%%.*$" +highlight link mermaidComment Comment + +syntax region mermaidDirective start="%%{" end="\}%%" +highlight link mermaidDirective PreProc + +syntax region mermaidString start=/"/ skip=/\\"/ end=/"/ +highlight link mermaidString String + +let b:current_syntax = "mermaid" + +let &cpo = s:cpo_save +unlet s:cpo_save + +" vim:set sw=2: diff --git a/runtime/syntax/modula3.vim b/runtime/syntax/modula3.vim index b179303799..390a1a90ff 100644 --- a/runtime/syntax/modula3.vim +++ b/runtime/syntax/modula3.vim @@ -2,18 +2,29 @@ " Language: Modula-3 " Maintainer: Doug Kearns <dougkearns@gmail.com> " Previous Maintainer: Timo Pedersen <dat97tpe@ludat.lth.se> -" Last Change: 2021 Apr 08 +" Last Change: 2022 Oct 31 if exists("b:current_syntax") finish endif -" Modula-3 keywords -syn keyword modula3Keyword ANY ARRAY AS BITS BRANDED BY CASE CONST DEFINITION -syn keyword modula3Keyword EVAL EXIT EXCEPT EXCEPTION EXIT EXPORTS FINALLY -syn keyword modula3Keyword FROM GENERIC IMPORT LOCK METHOD OF RAISE RAISES -syn keyword modula3Keyword READONLY RECORD REF RETURN SET TRY TYPE TYPECASE -syn keyword modula3Keyword UNSAFE VALUE VAR WITH +" Whitespace errors {{{1 +if exists("modula3_space_errors") + if !exists("modula3_no_trail_space_error") + syn match modula3SpaceError display excludenl "\s\+$" + endif + if !exists("modula3_no_tab_space_error") + syn match modula3SpaceError display " \+\t"me=e-1 + endif +endif + +" Keywords {{{1 +syn keyword modula3Keyword ANY ARRAY AS BITS BRANDED BY CASE CONST +syn keyword modula3Keyword DEFINITION EVAL EXIT EXCEPT EXCEPTION EXIT +syn keyword modula3Keyword EXPORTS FINALLY FROM GENERIC IMPORT LOCK METHOD +syn keyword modula3Keyword OF RAISE RAISES READONLY RECORD REF +syn keyword modula3Keyword RETURN SET TRY TYPE TYPECASE UNSAFE +syn keyword modula3Keyword VALUE VAR WITH syn match modula3keyword "\<UNTRACED\>" @@ -22,44 +33,73 @@ syn keyword modula3Block PROCEDURE FUNCTION MODULE INTERFACE REPEAT THEN syn keyword modula3Block BEGIN END OBJECT METHODS OVERRIDES RECORD REVEAL syn keyword modula3Block WHILE UNTIL DO TO IF FOR ELSIF ELSE LOOP -" Reserved identifiers +" Reserved identifiers {{{1 syn keyword modula3Identifier ABS ADR ADRSIZE BITSIZE BYTESIZE CEILING DEC syn keyword modula3Identifier DISPOSE FIRST FLOAT FLOOR INC ISTYPE LAST syn keyword modula3Identifier LOOPHOLE MAX MIN NARROW NEW NUMBER ORD ROUND syn keyword modula3Identifier SUBARRAY TRUNC TYPECODE VAL -" Predefined types +" Predefined types {{{1 syn keyword modula3Type ADDRESS BOOLEAN CARDINAL CHAR EXTENDED INTEGER syn keyword modula3Type LONGCARD LONGINT LONGREAL MUTEX NULL REAL REFANY TEXT syn keyword modula3Type WIDECHAR syn match modula3Type "\<\%(UNTRACED\s\+\)\=ROOT\>" -" Operators -syn keyword modulaOperator DIV MOD IN AND OR NOT +" Operators {{{1 +syn keyword modula3Operator DIV MOD +syn keyword modula3Operator IN +syn keyword modula3Operator NOT AND OR +" TODO: exclude = from declarations if exists("modula3_operators") syn match modula3Operator "\^" - syn match modula3Operator "+\|-\|\*\|/\|&" - " TODO: need to exclude = in procedure definitions - syn match modula3Operator "<=\|<\|>=\|>\|:\@<!=\|#" + syn match modula3Operator "[-+/*]" + syn match modula3Operator "&" + syn match modula3Operator "<=\|<:\@!\|>=\|>" + syn match modula3Operator ":\@<!=\|#" endif +" Literals {{{1 + " Booleans syn keyword modula3Boolean TRUE FALSE " Nil syn keyword modula3Nil NIL -" Integers -syn match modula3Integer "\<\d\+L\=\>" -syn match modula3Integer "\<\d\d\=_\x\+L\=\>" +" Numbers {{{2 + +" NOTE: Negated numbers are constant expressions not literals + +syn case ignore + + " Integers + + syn match modula3Integer "\<\d\+L\=\>" -" Reals -syn match modula3Real "\c\<\d\+\.\d\+\%([EDX][+-]\=\d\+\)\=\>" + if exists("modula3_number_errors") + syn match modula3IntegerError "\<\d\d\=_\x\+L\=\>" + endif + + let s:digits = "0123456789ABCDEF" + for s:radix in range(2, 16) + " Nvim does not support interpolated strings yet. + " exe $'syn match modula3Integer "\<{s:radix}_[{s:digits[:s:radix - 1]}]\+L\=\>"' + exe 'syn match modula3Integer "\<' .. s:radix .. '_[' .. s:digits[:s:radix - 1] .. ']\+L\=\>"' + endfor + unlet s:digits s:radix + + " Reals + syn match modula3Real "\<\d\+\.\d\+\%([EDX][+-]\=\d\+\)\=\>" + +syn case match + +" Strings and characters {{{2 " String escape sequences syn match modula3Escape "\\['"ntrf]" contained display +" TODO: limit to <= 377 (255) syn match modula3Escape "\\\o\{3}" contained display syn match modula3Escape "\\\\" contained display @@ -69,13 +109,23 @@ syn match modula3Character "'\%([^']\|\\.\|\\\o\{3}\)'" contains=modula3Escape " Strings syn region modula3String start=+"+ end=+"+ contains=modula3Escape -" Pragmas +" Pragmas {{{1 +" EXTERNAL INLINE ASSERT TRACE FATAL UNUSED OBSOLETE CALLBACK EXPORTED PRAGMA NOWARN LINE LL LL.sup SPEC +" Documented: INLINE ASSERT TRACE FATAL UNUSED OBSOLETE NOWARN syn region modula3Pragma start="<\*" end="\*>" -" Comments -syn region modula3Comment start="(\*" end="\*)" contains=modula3Comment,@Spell +" Comments {{{1 +if !exists("modula3_no_comment_fold") + syn region modula3Comment start="(\*" end="\*)" contains=modula3Comment,@Spell fold + syn region modula3LineCommentBlock start="^\s*(\*.*\*)\s*\n\%(^\s*(\*.*\*)\s*$\)\@=" end="^\s*(\*.*\*)\s*\n\%(^\s*(\*.*\*)\s*$\)\@!" contains=modula3Comment transparent fold keepend +else + syn region modula3Comment start="(\*" end="\*)" contains=modula3Comment,@Spell +endif + +" Syncing "{{{1 +syn sync minlines=100 -" Default highlighting +" Default highlighting {{{1 hi def link modula3Block Statement hi def link modula3Boolean Boolean hi def link modula3Character Character @@ -85,12 +135,13 @@ hi def link modula3Identifier Keyword hi def link modula3Integer Number hi def link modula3Keyword Statement hi def link modula3Nil Constant +hi def link modula3IntegerError Error hi def link modula3Operator Operator hi def link modula3Pragma PreProc hi def link modula3Real Float hi def link modula3String String -hi def link modula3Type Type +hi def link modula3Type Type "}}} let b:current_syntax = "modula3" -" vim: nowrap sw=2 sts=2 ts=8 noet: +" vim: nowrap sw=2 sts=2 ts=8 noet fdm=marker: diff --git a/runtime/syntax/nix.vim b/runtime/syntax/nix.vim new file mode 100644 index 0000000000..c07676a4a8 --- /dev/null +++ b/runtime/syntax/nix.vim @@ -0,0 +1,210 @@ +" Vim syntax file +" Language: Nix +" Maintainer: James Fleming <james@electronic-quill.net> +" Original Author: Daiderd Jordan <daiderd@gmail.com> +" Acknowledgement: Based on vim-nix maintained by Daiderd Jordan <daiderd@gmail.com> +" https://github.com/LnL7/vim-nix +" License: MIT +" Last Change: 2022 Dec 06 + +if exists("b:current_syntax") + finish +endif + +let s:cpo_save = &cpo +set cpo&vim + +syn keyword nixBoolean true false +syn keyword nixNull null +syn keyword nixRecKeyword rec + +syn keyword nixOperator or +syn match nixOperator '!=\|!' +syn match nixOperator '<=\?' +syn match nixOperator '>=\?' +syn match nixOperator '&&' +syn match nixOperator '//\=' +syn match nixOperator '==' +syn match nixOperator '?' +syn match nixOperator '||' +syn match nixOperator '++\=' +syn match nixOperator '-' +syn match nixOperator '\*' +syn match nixOperator '->' + +syn match nixParen '[()]' +syn match nixInteger '\d\+' + +syn keyword nixTodo FIXME NOTE TODO OPTIMIZE XXX HACK contained +syn match nixComment '#.*' contains=nixTodo,@Spell +syn region nixComment start=+/\*+ end=+\*/+ contains=nixTodo,@Spell + +syn region nixInterpolation matchgroup=nixInterpolationDelimiter start="\${" end="}" contained contains=@nixExpr,nixInterpolationParam + +syn match nixSimpleStringSpecial /\\\%([nrt"\\$]\|$\)/ contained +syn match nixStringSpecial /''['$]/ contained +syn match nixStringSpecial /\$\$/ contained +syn match nixStringSpecial /''\\[nrt]/ contained + +syn match nixSimpleStringSpecial /\$\$/ contained + +syn match nixInvalidSimpleStringEscape /\\[^nrt"\\$]/ contained +syn match nixInvalidStringEscape /''\\[^nrt]/ contained + +syn region nixSimpleString matchgroup=nixStringDelimiter start=+"+ skip=+\\"+ end=+"+ contains=nixInterpolation,nixSimpleStringSpecial,nixInvalidSimpleStringEscape +syn region nixString matchgroup=nixStringDelimiter start=+''+ skip=+''['$\\]+ end=+''+ contains=nixInterpolation,nixStringSpecial,nixInvalidStringEscape + +syn match nixFunctionCall "[a-zA-Z_][a-zA-Z0-9_'-]*" + +syn match nixPath "[a-zA-Z0-9._+-]*\%(/[a-zA-Z0-9._+-]\+\)\+" +syn match nixHomePath "\~\%(/[a-zA-Z0-9._+-]\+\)\+" +syn match nixSearchPath "[a-zA-Z0-9._+-]\+\%(\/[a-zA-Z0-9._+-]\+\)*" contained +syn match nixPathDelimiter "[<>]" contained +syn match nixSearchPathRef "<[a-zA-Z0-9._+-]\+\%(\/[a-zA-Z0-9._+-]\+\)*>" contains=nixSearchPath,nixPathDelimiter +syn match nixURI "[a-zA-Z][a-zA-Z0-9.+-]*:[a-zA-Z0-9%/?:@&=$,_.!~*'+-]\+" + +syn match nixAttributeDot "\." contained +syn match nixAttribute "[a-zA-Z_][a-zA-Z0-9_'-]*\ze\%([^a-zA-Z0-9_'.-]\|$\)" contained +syn region nixAttributeAssignment start="=" end="\ze;" contained contains=@nixExpr +syn region nixAttributeDefinition start=/\ze[a-zA-Z_"$]/ end=";" contained contains=nixComment,nixAttribute,nixInterpolation,nixSimpleString,nixAttributeDot,nixAttributeAssignment + +syn region nixInheritAttributeScope start="(" end="\ze)" contained contains=@nixExpr +syn region nixAttributeDefinition matchgroup=nixInherit start="\<inherit\>" end=";" contained contains=nixComment,nixInheritAttributeScope,nixAttribute + +syn region nixAttributeSet start="{" end="}" contains=nixComment,nixAttributeDefinition + +" vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv +syn region nixArgumentDefinitionWithDefault matchgroup=nixArgumentDefinition start="[a-zA-Z_][a-zA-Z0-9_'-]*\ze\%(\s\|#.\{-\}\n\|\n\|/\*\_.\{-\}\*/\)*?\@=" matchgroup=NONE end="[,}]\@=" transparent contained contains=@nixExpr +" vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv +syn match nixArgumentDefinition "[a-zA-Z_][a-zA-Z0-9_'-]*\ze\%(\s\|#.\{-\}\n\|\n\|/\*\_.\{-\}\*/\)*[,}]\@=" contained +syn match nixArgumentEllipsis "\.\.\." contained +syn match nixArgumentSeparator "," contained + +" vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv +syn match nixArgOperator '@\%(\s\|#.\{-\}\n\|\n\|/\*\_.\{-\}\*/\)*[a-zA-Z_][a-zA-Z0-9_'-]*\%(\s\|#.\{-\}\n\|\n\|/\*\_.\{-\}\*/\)*:'he=s+1 contained contains=nixAttribute + +" vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv +syn match nixArgOperator '[a-zA-Z_][a-zA-Z0-9_'-]*\%(\s\|#.\{-\}\n\|\n\|/\*\_.\{-\}\*/\)*@'hs=e-1 contains=nixAttribute nextgroup=nixFunctionArgument + +" This is a bit more complicated, because function arguments can be passed in a +" very similar form on how attribute sets are defined and two regions with the +" same start patterns will shadow each other. Instead of a region we could use a +" match on {\_.\{-\}}, which unfortunately doesn't take nesting into account. +" +" So what we do instead is that we look forward until we are sure that it's a +" function argument. Unfortunately, we need to catch comments and both vertical +" and horizontal white space, which the following regex should hopefully do: +" +" "\%(\s\|#.\{-\}\n\|\n\|/\*\_.\{-\}\*/\)*" +" +" It is also used throught the whole file and is marked with 'v's as well. +" +" Fortunately the matching rules for function arguments are much simpler than +" for real attribute sets, because we can stop when we hit the first ellipsis or +" default value operator, but we also need to paste the "whitespace & comments +" eating" regex all over the place (marked with 'v's): +" +" Region match 1: { foo ? ... } or { foo, ... } or { ... } (ellipsis) +" vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv {----- identifier -----}vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv +syn region nixFunctionArgument start="{\ze\%(\s\|#.\{-\}\n\|\n\|/\*\_.\{-\}\*/\)*\%([a-zA-Z_][a-zA-Z0-9_'-]*\%(\s\|#.\{-\}\n\|\n\|/\*\_.\{-\}\*/\)*[,?}]\|\.\.\.\)" end="}" contains=nixComment,nixArgumentDefinitionWithDefault,nixArgumentDefinition,nixArgumentEllipsis,nixArgumentSeparator nextgroup=nixArgOperator + +" Now it gets more tricky, because we need to look forward for the colon, but +" there could be something like "{}@foo:", even though it's highly unlikely. +" +" Region match 2: {} +" vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv@vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv{----- identifier -----} vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv +syn region nixFunctionArgument start="{\ze\%(\s\|#.\{-\}\n\|\n\|/\*\_.\{-\}\*/\)*}\%(\%(\s\|#.\{-\}\n\|\n\|/\*\_.\{-\}\*/\)*@\%(\s\|#.\{-\}\n\|\n\|/\*\_.\{-\}\*/\)*[a-zA-Z_][a-zA-Z0-9_'-]*\)\%(\s\|#.\{-\}\n\|\n\|/\*\_.\{-\}\*/\)*:" end="}" contains=nixComment nextgroup=nixArgOperator + +" vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv +syn match nixSimpleFunctionArgument "[a-zA-Z_][a-zA-Z0-9_'-]*\ze\%(\s\|#.\{-\}\n\|\n\|/\*\_.\{-\}\*/\)*:\([\n ]\)\@=" + +syn region nixList matchgroup=nixListBracket start="\[" end="\]" contains=@nixExpr + +syn region nixLetExpr matchgroup=nixLetExprKeyword start="\<let\>" end="\<in\>" contains=nixComment,nixAttributeDefinition + +syn keyword nixIfExprKeyword then contained +syn region nixIfExpr matchgroup=nixIfExprKeyword start="\<if\>" end="\<else\>" contains=@nixExpr,nixIfExprKeyword + +syn region nixWithExpr matchgroup=nixWithExprKeyword start="\<with\>" matchgroup=NONE end=";" contains=@nixExpr + +syn region nixAssertExpr matchgroup=nixAssertKeyword start="\<assert\>" matchgroup=NONE end=";" contains=@nixExpr + +syn cluster nixExpr contains=nixBoolean,nixNull,nixOperator,nixParen,nixInteger,nixRecKeyword,nixConditional,nixBuiltin,nixSimpleBuiltin,nixComment,nixFunctionCall,nixFunctionArgument,nixArgOperator,nixSimpleFunctionArgument,nixPath,nixHomePath,nixSearchPathRef,nixURI,nixAttributeSet,nixList,nixSimpleString,nixString,nixLetExpr,nixIfExpr,nixWithExpr,nixAssertExpr,nixInterpolation + +" These definitions override @nixExpr and have to come afterwards: + +syn match nixInterpolationParam "[a-zA-Z_][a-zA-Z0-9_'-]*\%(\.[a-zA-Z_][a-zA-Z0-9_'-]*\)*" contained + +" Non-namespaced Nix builtins as of version 2.0: +syn keyword nixSimpleBuiltin + \ abort baseNameOf derivation derivationStrict dirOf fetchGit + \ fetchMercurial fetchTarball import isNull map mapAttrs placeholder removeAttrs + \ scopedImport throw toString + + +" Namespaced and non-namespaced Nix builtins as of version 2.0: +syn keyword nixNamespacedBuiltin contained + \ abort add addErrorContext all any attrNames attrValues baseNameOf + \ catAttrs compareVersions concatLists concatStringsSep currentSystem + \ currentTime deepSeq derivation derivationStrict dirOf div elem elemAt + \ fetchGit fetchMercurial fetchTarball fetchurl filter \ filterSource + \ findFile foldl' fromJSON functionArgs genList \ genericClosure getAttr + \ getEnv hasAttr hasContext hashString head import intersectAttrs isAttrs + \ isBool isFloat isFunction isInt isList isNull isString langVersion + \ length lessThan listToAttrs map mapAttrs match mul nixPath nixVersion + \ parseDrvName partition path pathExists placeholder readDir readFile + \ removeAttrs replaceStrings scopedImport seq sort split splitVersion + \ storeDir storePath stringLength sub substring tail throw toFile toJSON + \ toPath toString toXML trace tryEval typeOf unsafeDiscardOutputDependency + \ unsafeDiscardStringContext unsafeGetAttrPos valueSize fromTOML bitAnd + \ bitOr bitXor floor ceil + +syn match nixBuiltin "builtins\.[a-zA-Z']\+"he=s+9 contains=nixComment,nixNamespacedBuiltin + +hi def link nixArgOperator Operator +hi def link nixArgumentDefinition Identifier +hi def link nixArgumentEllipsis Operator +hi def link nixAssertKeyword Keyword +hi def link nixAttribute Identifier +hi def link nixAttributeDot Operator +hi def link nixBoolean Boolean +hi def link nixBuiltin Special +hi def link nixComment Comment +hi def link nixConditional Conditional +hi def link nixHomePath Include +hi def link nixIfExprKeyword Keyword +hi def link nixInherit Keyword +hi def link nixInteger Integer +hi def link nixInterpolation Macro +hi def link nixInterpolationDelimiter Delimiter +hi def link nixInterpolationParam Macro +hi def link nixInvalidSimpleStringEscape Error +hi def link nixInvalidStringEscape Error +hi def link nixLetExprKeyword Keyword +hi def link nixNamespacedBuiltin Special +hi def link nixNull Constant +hi def link nixOperator Operator +hi def link nixPath Include +hi def link nixPathDelimiter Delimiter +hi def link nixRecKeyword Keyword +hi def link nixSearchPath Include +hi def link nixSimpleBuiltin Keyword +hi def link nixSimpleFunctionArgument Identifier +hi def link nixSimpleString String +hi def link nixSimpleStringSpecial SpecialChar +hi def link nixString String +hi def link nixStringDelimiter Delimiter +hi def link nixStringSpecial Special +hi def link nixTodo Todo +hi def link nixURI Include +hi def link nixWithExprKeyword Keyword + +" This could lead up to slow syntax highlighting for large files, but usually +" large files such as all-packages.nix are one large attribute set, so if we'd +" use sync patterns we'd have to go back to the start of the file anyway +syn sync fromstart + +let b:current_syntax = "nix" + +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/runtime/syntax/nsis.vim b/runtime/syntax/nsis.vim index 3a73fe0989..49fa17abf1 100644 --- a/runtime/syntax/nsis.vim +++ b/runtime/syntax/nsis.vim @@ -3,7 +3,7 @@ " Maintainer: Ken Takata " URL: https://github.com/k-takata/vim-nsis " Previous Maintainer: Alex Jakushev <Alex.Jakushev@kemek.lt> -" Last Change: 2020-10-18 +" Last Change: 2022-11-05 " quit when a syntax file was already loaded if exists("b:current_syntax") @@ -394,9 +394,13 @@ syn keyword nsisInstruction contained CreateShortcut nextgroup=nsisCreateShortcu syn region nsisCreateShortcutOpt contained start="" end="$" transparent keepend contains=@nsisAnyOpt,nsisCreateShortcutKwd syn match nsisCreateShortcutKwd contained "/NoWorkingDir\>" +syn keyword nsisInstruction contained GetWinVer nextgroup=nsisGetWinVerOpt skipwhite +syn region nsisGetWinVerOpt contained start="" end="$" transparent keepend contains=@nsisAnyOpt,nsisGetWinVerKwd +syn keyword nsisGetWinVerKwd contained Major Minor Build ServicePack + syn keyword nsisInstruction contained GetDLLVersion GetDLLVersionLocal nextgroup=nsisGetDLLVersionOpt skipwhite -syn region nsisGetDLLVersionOpt contained start="" end="$" transparent keepend contains=@nsisAnyOpt,nsisGetDLLVersionKwd -syn match nsisGetDLLVersionKwd contained "/ProductVersion\>" +syn region nsisGetDLLVersionOpt contained start="" end="$" transparent keepend contains=@nsisAnyOpt,nsisGetDLLVersionKwd +syn match nsisGetDLLVersionKwd contained "/ProductVersion\>" syn keyword nsisInstruction contained GetFullPathName nextgroup=nsisGetFullPathNameOpt skipwhite syn region nsisGetFullPathNameOpt contained start="" end="$" transparent keepend contains=@nsisAnyOpt,nsisGetFullPathNameKwd @@ -562,10 +566,19 @@ syn match nsisSystem contained "!execute\>" syn match nsisSystem contained "!makensis\>" syn match nsisSystem contained "!packhdr\>" syn match nsisSystem contained "!finalize\>" +syn match nsisSystem contained "!uninstfinalize\>" syn match nsisSystem contained "!system\>" syn match nsisSystem contained "!tempfile\>" -syn match nsisSystem contained "!getdllversion\>" -syn match nsisSystem contained "!gettlbversion\>" + +" Add 'P' to avoid conflicts with nsisGetDLLVersionOpt. ('P' for preprocessor.) +syn match nsisSystem contained "!getdllversion\>" nextgroup=nsisPGetdllversionOpt skipwhite +syn region nsisPGetdllversionOpt contained start="" end="$" transparent keepend contains=@nsisAnyOpt,nsisPGetdllversionKwd +syn match nsisPGetdllversionKwd contained "/\%(noerrors\|packed\|productversion\)\>" + +syn match nsisSystem contained "!gettlbversion\>" nextgroup=nsisPGettlbversionOpt skipwhite +syn region nsisPGettlbversionOpt contained start="" end="$" transparent keepend contains=@nsisAnyOpt,nsisPGettlbversionKwd +syn match nsisPGettlbversionKwd contained "/\%(noerrors\|packed\)\>" + syn match nsisSystem contained "!warning\>" syn match nsisSystem contained "!pragma\>" nextgroup=nsisPragmaOpt skipwhite @@ -581,7 +594,10 @@ syn match nsisDefine contained "!define\>" nextgroup=nsisDefineOpt skipwhite syn region nsisDefineOpt contained start="" end="$" transparent keepend contains=@nsisAnyOpt,nsisDefineKwd syn match nsisDefineKwd contained "/\%(ifndef\|redef\|date\|utcdate\|file\|intfmt\|math\)\>" -syn match nsisDefine contained "!undef\>" +syn match nsisDefine contained "!undef\>" nextgroup=nsisUndefineOpt skipwhite +syn region nsisUndefineOpt contained start="" end="$" transparent keepend contains=@nsisAnyOpt,nsisUndefineKwd +syn match nsisUndefineKwd contained "/noerrors\>" + syn match nsisPreCondit contained "!ifdef\>" syn match nsisPreCondit contained "!ifndef\>" @@ -659,6 +675,7 @@ hi def link nsisWriteRegMultiStrKwd Constant hi def link nsisSetRegViewKwd Constant hi def link nsisCopyFilesKwd Constant hi def link nsisCreateShortcutKwd Constant +hi def link nsisGetWinVerKwd Constant hi def link nsisGetDLLVersionKwd Constant hi def link nsisGetFullPathNameKwd Constant hi def link nsisFileAttrib Constant @@ -696,9 +713,12 @@ hi def link nsisIncludeKwd Constant hi def link nsisAddplugindirKwd Constant hi def link nsisAppendfileKwd Constant hi def link nsisDelfileKwd Constant +hi def link nsisPGetdllversionKwd Constant +hi def link nsisPGettlbversionKwd Constant hi def link nsisPragmaKwd Constant hi def link nsisVerboseKwd Constant hi def link nsisDefineKwd Constant +hi def link nsisUndefineKwd Constant hi def link nsisIfKwd Constant hi def link nsisSearchparseKwd Constant hi def link nsisSearchreplaceKwd Constant diff --git a/runtime/syntax/obse.vim b/runtime/syntax/obse.vim new file mode 100644 index 0000000000..4ff04281f3 --- /dev/null +++ b/runtime/syntax/obse.vim @@ -0,0 +1,3360 @@ +" Vim syntax file +" Language: Oblivion Language (obl) +" Original Creator: Ulthar Seramis +" Maintainer: Kat <katisntgood@gmail.com> +" Latest Revision: 13 November 2022 + +if exists("b:current_syntax") + finish +endif + +let s:cpo_save = &cpo +set cpo&vim + +" obse is case insensitive +syntax case ignore + +" Statements {{{ +syn keyword obseStatement set let to skipwhite +" the second part needs to be separate as to not mess up the next group +syn match obseStatementTwo ":=" +" }}} + +" Regex matched objects {{{ +" these are matched with regex and thus must be set first +syn match obseNames '\w\+' +syn match obseScriptNameRegion '\i\+' contained +syn match obseVariable '\w*\S' contained +syn match obseReference '\zs\w\+\>\ze\.' +" }}} + +" Operators {{{ +syn match obseOperator "\v\*" +syn match obseOperator "\v\-" +syn match obseOperator "\v\+" +syn match obseOperator "\v\/" +syn match obseOperator "\v\^" +syn match obseOperator "\v\=" +syn match obseOperator "\v\>" +syn match obseOperator "\v\<" +syn match obseOperator "\v\!" +syn match obseOperator "\v\&" +syn match obseOperator "\v\|" +" }}} + +" Numbers {{{ +syn match obseInt '\d\+' +syn match obseInt '[-+]\d\+' +syn match obseFloat '\d\+\.\d*' +syn match obseFloat '[-+]\d\+\.\d*' +" }}} + +" Comments and strings {{{ +syn region obseComment start=";" end="$" keepend fold contains=obseToDo +syn region obseString start=/"/ end=/"/ keepend fold contains=obseStringFormatting +syn match obseStringFormatting "%%" contained +syn match obseStringFormatting "%a" contained +syn match obseStringFormatting "%B" contained +syn match obseStringFormatting "%b" contained +syn match obseStringFormatting "%c" contained +syn match obseStringFormatting "%e" contained +syn match obseStringFormatting "%g" contained +syn match obseStringFormatting "%i" contained +syn match obseStringFormatting "%k" contained +syn match obseStringFormatting "%n" contained +syn match obseStringFormatting "%p" contained +syn match obseStringFormatting "%ps" contained +syn match obseStringFormatting "%pp" contained +syn match obseStringFormatting "%po" contained +syn match obseStringFormatting "%q" contained +syn match obseStringFormatting "%r" contained +syn match obseStringFormatting "%v" contained +syn match obseStringFormatting "%x" contained +syn match obseStringFormatting "%z" contained +syn match obseStringFormatting "%{" contained +syn match obseStringFormatting "%}" contained +syn match obseStringFormatting "%\d*.\d*f" contained +syn match obseStringFormatting "% \d*.\d*f" contained +syn match obseStringFormatting "%-\d*.\d*f" contained +syn match obseStringFormatting "%+\d*.\d*f" contained +syn match obseStringFormatting "%\d*.\d*e" contained +syn match obseStringFormatting "%-\d*.\d*e" contained +syn match obseStringFormatting "% \d*.\d*e" contained +syn match obseStringFormatting "%+\d*.\d*e" contained +syn keyword obseToDo contained TODO todo Todo ToDo FIXME fixme NOTE note +" }}} + + +" Conditionals {{{ +syn match obseCondition "If" +syn match obseCondition "Eval" +syn match obseCondition "Return" +syn match obseCondition "EndIf" +syn match obseCondition "ElseIf" +syn match obseCondition "Else" +" }}} + +" Repeat loops {{{ +syn match obseRepeat "Label" +syn match obseRepeat "GoTo" +syn match obseRepeat "While" +syn match obseRepeat "Loop" +syn match obseRepeat "ForEach" +syn match obseRepeat "Break" +syn match obseRepeat "Continue" +" }}} + +" Basic Types {{{ +syn keyword obseTypes array_var float int long ref reference short string_var nextgroup=obseNames skipwhite +syn keyword obseOtherKey Player player playerRef playerREF PlayerRef PlayerREF +syn keyword obseScriptName ScriptName scriptname Scriptname scn nextgroup=obseScriptNameRegion skipwhite +syn keyword obseBlock Begin End +" }}} + +" Fold {{{ +setlocal foldmethod=syntax +syn cluster obseNoFold contains=obseComment,obseString +syn region obseFoldIfContainer + \ start="^\s*\<if\>" + \ end="^\s*\<endif\>" + \ keepend extend + \ containedin=ALLBUT,@obseNoFold + \ contains=ALLBUT,obseScriptName,obseScriptNameRegion +syn region obseFoldIf + \ start="^\s*\<if\>" + \ end="^\s*\<endif\>" + \ fold + \ keepend + \ contained containedin=obseFoldIfContainer + \ nextgroup=obseFoldElseIf,obseFoldElse + \ contains=TOP,NONE +syn region obseFoldElseIf + \ start="^\s*\<elseif\>" + \ end="^\s*\<endif\>" + \ fold + \ keepend + \ contained containedin=obseFoldIfContainer + \ nextgroup=obseFoldElseIf,obseFoldElse + \ contains=TOP +syn region obseFoldElse + \ start="^\s*\<else\>" + \ end="^\s*\<endif\>" + \ fold + \ keepend + \ contained containedin=obseFoldIfContainer + \ contains=TOP +syn region obseFoldWhile + \ start="^\s*\<while\>" + \ end="^\s*\<loop\>" + \ fold + \ keepend extend + \ contains=TOP + \ containedin=ALLBUT,@obseNoFold +" fold for loops +syn region obseFoldFor + \ start="^\s*\<foreach\>" + \ end="^\s*\<loop\>" + \ fold + \ keepend extend + \ contains=TOP + \ containedin=ALLBUT,@obseNoFold + \ nextgroup=obseVariable +" }}} + +" Skills and Attributes {{{ +syn keyword skillAttribute + \ Strength + \ Willpower + \ Speed + \ Personality + \ Intelligence + \ Agility + \ Endurance + \ Luck + \ Armorer + \ Athletics + \ Blade + \ Block + \ Blunt + \ HandToHand + \ HeavyArmor + \ Alchemy + \ Alteration + \ Conjuration + \ Destruction + \ Illusion + \ Mysticism + \ Restoration + \ Acrobatics + \ LightArmor + \ Marksman + \ Mercantile + \ Security + \ Sneak + \ Speechcraft +" }}} + +" Block Types {{{ +syn keyword obseBlockType + \ ExitGame + \ ExitToMainMenu + \ Function + \ GameMode + \ LoadGame + \ MenuMode + \ OnActivate + \ OnActorDrop + \ OnActorEquip + \ OnActorUnequip + \ OnAdd + \ OnAlarm + \ OnAlarmTrespass + \ OnAlarmVictim + \ OnAttack + \ OnBlock + \ OnBowAttack + \ OnClick + \ OnClose + \ OnCreatePotion + \ OnCreateSpell + \ OnDeath + \ OnDodge + \ OnDrinkPotion + \ OnDrop + \ OnEatIngredient + \ OnEnchant + \ OnEquip + \ OnFallImpact + \ OnHealthDamage + \ OnHit + \ OnHitWith + \ OnKnockout + \ OnLoad + \ OnMagicApply + \ OnMagicCast + \ OnMagicEffectHit + \ OnMagicEffectHit2 + \ OnMapMarkerAdd + \ OnMouseover + \ OnMurder + \ OnNewGame + \ OnOpen + \ OnPackageChange + \ OnPackageDone + \ OnPackageStart + \ OnQuestComplete + \ OnRecoil + \ OnRelease + \ OnReset + \ OnSaveIni + \ OnScriptedSkillUp + \ OnScrollCast + \ OnSell + \ OnSkillUp + \ OnSoulTrap + \ OnSpellCast + \ OnStagger + \ OnStartCombat + \ OnTrigger + \ OnTriggerActor + \ OnTriggerMob + \ OnUnequip + \ OnVampireFeed + \ OnWaterDive + \ OnWaterSurface + \ PostLoadGame + \ QQQ + \ SaveGame + \ ScriptEffectFinish + \ ScriptEffectStart + \ ScriptEffectUpdate +" }}} + +" Functions {{{ +" CS functions {{{ +syn keyword csFunction + \ Activate + \ AddAchievement + \ AddFlames + \ AddItem + \ AddScriptPackage + \ AddSpell + \ AddTopic + \ AdvSkill + \ AdvancePCLevel + \ AdvancePCSkill + \ Autosave + \ CanHaveFlames + \ CanPayCrimeGold + \ Cast + \ ClearOwnership + \ CloseCurrentOblivionGate + \ CloseOblivionGate + \ CompleteQuest + \ CreateFullActorCopy + \ DeleteFullActorCopy + \ Disable + \ DisableLinkedPathPoints + \ DisablePlayerControls + \ Dispel + \ DispelAllSpells + \ Drop + \ DropMe + \ DuplicateAllItems + \ DuplicateNPCStats + \ Enable + \ EnableFastTravel + \ EnableLinkedPathPoints + \ EnablePlayerControls + \ EquipItem + \ EssentialDeathReload + \ EvaluatePackage + \ ForceAV + \ ForceActorValue + \ ForceCloseOblivionGate + \ ForceFlee + \ ForceTakeCover + \ ForceWeather + \ GetAV + \ GetActionRef + \ GetActorValue + \ GetAlarmed + \ GetAmountSoldStolen + \ GetAngle + \ GetArmorRating + \ GetArmorRatingUpperBody + \ GetAttacked + \ GetBarterGold + \ GetBaseAV + \ GetBaseActorValue + \ GetButtonPressed + \ GetClassDefaultMatch + \ GetClothingValue + \ GetContainer + \ GetCrime + \ GetCrimeGold + \ GetCrimeKnown + \ GetCurrentAIPackage + \ GetCurrentAIProcedure + \ GetCurrentTime + \ GetCurrentWeatherPercent + \ GetDayOfWeek + \ GetDead + \ GetDeadCount + \ GetDestroyed + \ GetDetected + \ GetDetectionLevel + \ GetDisabled + \ GetDisposition + \ GetDistance + \ GetDoorDefaultOpen + \ GetEquipped + \ GetFactionRank + \ GetFactionRankDifference + \ GetFactionReaction + \ GetFatiguePercentage + \ GetForceRun + \ GetForceSneak + \ GetFriendHit + \ GetFurnitureMarkerID + \ GetGS + \ GetGameSetting + \ GetGlobalValue + \ GetGold + \ GetHeadingAngle + \ GetIdleDoneOnce + \ GetIgnoreFriendlyHits + \ GetInCell + \ GetInCellParam + \ GetInFaction + \ GetInSameCell + \ GetInWorldspace + \ GetInvestmentGold + \ GetIsAlerted + \ GetIsClass + \ GetIsClassDefault + \ GetIsCreature + \ GetIsCurrentPackage + \ GetIsCurrentWeather + \ GetIsGhost + \ GetIsID + \ GetIsPlayableRace + \ GetIsPlayerBirthsign + \ GetIsRace + \ GetIsReference + \ GetIsSex + \ GetIsUsedItem + \ GetIsUsedItemType + \ GetItemCount + \ GetKnockedState + \ GetLOS + \ GetLevel + \ GetLockLevel + \ GetLocked + \ GetMenuHasTrait + \ GetName + \ GetNoRumors + \ GetOffersServicesNow + \ GetOpenState + \ GetPCExpelled + \ GetPCFactionAttack + \ GetPCFactionMurder + \ GetPCFactionSteal + \ GetPCFactionSubmitAuthority + \ GetPCFame + \ GetPCInFaction + \ GetPCInfamy + \ GetPCIsClass + \ GetPCIsRace + \ GetPCIsSex + \ GetPCMiscStat + \ GetPCSleepHours + \ GetPackageTarget + \ GetParentRef + \ GetPersuasionNumber + \ GetPlayerControlsDisabled + \ GetPlayerHasLastRiddenHorse + \ GetPlayerInSEWorld + \ GetPos + \ GetQuestRunning + \ GetQuestVariable + \ GetRandomPercent + \ GetRestrained + \ GetScale + \ GetScriptVariable + \ GetSecondsPassed + \ GetSelf + \ GetShouldAttack + \ GetSitting + \ GetSleeping + \ GetStage + \ GetStageDone + \ GetStartingAngle + \ GetStartingPos + \ GetTalkedToPC + \ GetTalkedToPCParam + \ GetTimeDead + \ GetTotalPersuasionNumber + \ GetTrespassWarningLevel + \ GetUnconscious + \ GetUsedItemActivate + \ GetUsedItemLevel + \ GetVampire + \ GetWalkSpeed + \ GetWeaponAnimType + \ GetWeaponSkillType + \ GetWindSpeed + \ GoToJail + \ HasFlames + \ HasMagicEffect + \ HasVampireFed + \ IsActionRef + \ IsActor + \ IsActorAVictim + \ IsActorDetected + \ IsActorEvil + \ IsActorUsingATorch + \ IsActorsAIOff + \ IsAnimPlayer + \ IsCellOwner + \ IsCloudy + \ IsContinuingPackagePCNear + \ IsCurrentFurnitureObj + \ IsCurrentFurnitureRef + \ IsEssential + \ IsFacingUp + \ IsGuard + \ IsHorseStolen + \ IsIdlePlaying + \ IsInCombat + \ IsInDangerousWater + \ IsInInterior + \ IsInMyOwnedCell + \ IsLeftUp + \ IsOwner + \ IsPCAMurderer + \ IsPCSleeping + \ IsPlayerInJail + \ IsPlayerMovingIntoNewSpace + \ IsPlayersLastRiddenHorse + \ IsPleasant + \ IsRaining + \ IsRidingHorse + \ IsRunning + \ IsShieldOut + \ IsSneaking + \ IsSnowing + \ IsSpellTarget + \ IsSwimming + \ IsTalking + \ IsTimePassing + \ IsTorchOut + \ IsTrespassing + \ IsTurnArrest + \ IsWaiting + \ IsWeaponOut + \ IsXBox + \ IsYielding + \ Kill + \ KillActor + \ KillAllActors + \ Lock + \ Look + \ LoopGroup + \ Message + \ MessageBox + \ ModAV + \ ModActorValue + \ ModAmountSoldStolen + \ ModBarterGold + \ ModCrimeGold + \ ModDisposition + \ ModFactionRank + \ ModFactionReaction + \ ModPCAttribute + \ ModPCA + \ ModPCFame + \ ModPCInfamy + \ ModPCMiscStat + \ ModPCSkill + \ ModPCS + \ ModScale + \ MoveTo + \ MoveToMarker + \ PCB + \ PayFine + \ PayFineThief + \ PickIdle + \ PlaceAtMe + \ PlayBink + \ PlayGroup + \ PlayMagicEffectVisuals + \ PlayMagicShaderVisuals + \ PlaySound + \ PlaySound3D + \ PositionCell + \ PositionWorld + \ PreloadMagicEffect + \ PurgeCellBuffers + \ PushActorAway + \ RefreshTopicList + \ ReleaseWeatherOverride + \ RemoveAllItems + \ RemoveFlames + \ RemoveItem + \ RemoveMe + \ RemoveScriptPackage + \ RemoveSpell + \ Reset3DState + \ ResetFallDamageTimer + \ ResetHealth + \ ResetInterior + \ Resurrect + \ Rotate + \ SCAOnActor + \ SameFaction + \ SameFactionAsPC + \ SameRace + \ SameRaceAsPC + \ SameSex + \ SameSexAsPC + \ Say + \ SayTo + \ ScriptEffectElapsedSeconds + \ SelectPlayerSpell + \ SendTrespassAlarm + \ SetAV + \ SetActorAlpha + \ SetActorFullName + \ SetActorRefraction + \ SetActorValue + \ SetActorsAI + \ SetAlert + \ SetAllReachable + \ SetAllVisible + \ SetAngle + \ SetAtStart + \ SetBarterGold + \ SetCellFullName + \ SetCellOwnership + \ SetCellPublicFlag + \ SetClass + \ SetCrimeGold + \ SetDestroyed + \ SetDoorDefaultOpen + \ SetEssential + \ SetFactionRank + \ SetFactionReaction + \ SetForceRun + \ SetForceSneak + \ SetGhost + \ SetIgnoreFriendlyHits + \ SetInCharGen + \ SetInvestmentGold + \ SetItemValue + \ SetLevel + \ SetNoAvoidance + \ SetNoRumors + \ SetOpenState + \ SetOwnership + \ SetPCExpelled + \ SetPCFactionAttack + \ SetPCFactionMurder + \ SetPCFactionSteal + \ SetPCFactionSubmitAuthority + \ SetPCFame + \ SetPCInfamy + \ SetPCSleepHours + \ SetPackDuration + \ SetPlayerBirthsign + \ SetPlayerInSEWorld + \ SetPos + \ SetQuestObject + \ SetRestrained + \ SetRigidBodyMass + \ SetScale + \ SetSceneIsComplex + \ SetShowQuestItems + \ SetSize + \ SetStage + \ SetUnconscious + \ SetWeather + \ ShowBirthsignMenu + \ ShowClassMenu + \ ShowDialogSubtitles + \ ShowEnchantment + \ ShowMap + \ ShowRaceMenu + \ ShowSpellMaking + \ SkipAnim + \ StartCombat + \ StartConversation + \ StartQuest + \ StopCombat + \ StopCombatAlarmOnActor + \ StopLook + \ StopMagicEffectVisuals + \ StopMagicShaderVisuals + \ StopQuest + \ StopWaiting + \ StreamMusic + \ This + \ ToggleActorsAI + \ TrapUpdate + \ TriggerHitShader + \ UnequipItem + \ Unlock + \ VampireFeed + \ Wait + \ WakeUpPC + \ WhichServiceMenu + \ Yield + \ evp + \ pms + \ saa + \ sms +" }}} + +" OBSE Functions {{{ +syn keyword obseFunction + \ abs + \ acos + \ activate2 + \ actorvaluetocode + \ actorvaluetostring + \ actorvaluetostringc + \ addeffectitem + \ addeffectitemc + \ addfulleffectitem + \ addfulleffectitemc + \ additemns + \ addmagiceffectcounter + \ addmagiceffectcounterc + \ addmecounter + \ addmecounterc + \ addspellns + \ addtoleveledlist + \ ahammerkey + \ animpathincludes + \ appendtoname + \ asciitochar + \ asin + \ atan + \ atan2 + \ avstring + \ calcleveleditem + \ calclevitemnr + \ calclevitems + \ cancastpower + \ cancorpsecheck + \ canfasttravelfromworld + \ cantraveltomapmarker + \ ceil + \ chartoascii + \ clearactivequest + \ clearhotkey + \ clearleveledlist + \ clearownershipt + \ clearplayerslastriddenhorse + \ clickmenubutton + \ cloneform + \ closeallmenus + \ closetextinput + \ colvec + \ comparefemalebipedpath + \ comparefemalegroundpath + \ comparefemaleiconpath + \ compareiconpath + \ comparemalebipedpath + \ comparemalegroundpath + \ comparemaleiconpath + \ comparemodelpath + \ comparename + \ comparenames + \ comparescripts + \ con_cal + \ con_getinisetting + \ con_hairtint + \ con_loadgame + \ con_modwatershader + \ con_playerspellbook + \ con_quitgame + \ con_refreshini + \ con_runmemorypass + \ con_save + \ con_saveini + \ con_setcamerafov + \ con_setclipdist + \ con_setfog + \ con_setgamesetting + \ con_setgamma + \ con_sethdrparam + \ con_setimagespaceglow + \ con_setinisetting + \ con_setskyparam + \ con_settargetrefraction + \ con_settargetrefractionfire + \ con_sexchange + \ con_tcl + \ con_tfc + \ con_tgm + \ con_toggleai + \ con_togglecombatai + \ con_toggledetection + \ con_togglemapmarkers + \ con_togglemenus + \ con_waterdeepcolor + \ con_waterreflectioncolor + \ con_watershallowcolor + \ copyalleffectitems + \ copyeyes + \ copyfemalebipedpath + \ copyfemalegroundpath + \ copyfemaleiconpath + \ copyhair + \ copyiconpath + \ copyir + \ copymalebipedpath + \ copymalegroundpath + \ copymaleiconpath + \ copymodelpath + \ copyname + \ copyntheffectitem + \ copyrace + \ cos + \ cosh + \ createtempref + \ creaturehasnohead + \ creaturehasnoleftarm + \ creaturehasnomovement + \ creaturehasnorightarm + \ creaturenocombatinwater + \ creatureusesweaponandshield + \ dacos + \ dasin + \ datan + \ datan2 + \ dcos + \ dcosh + \ debugprint + \ deletefrominputtext + \ deletereference + \ disablecontrol + \ disablekey + \ disablemouse + \ dispatchevent + \ dispelnthactiveeffect + \ dispelnthae + \ dsin + \ dsinh + \ dtan + \ dtanh + \ enablecontrol + \ enablekey + \ enablemouse + \ equipitem2 + \ equipitem2ns + \ equipitemns + \ equipitemsilent + \ equipme + \ eval + \ evaluatepackage + \ eventhandlerexist + \ exp + \ factionhasspecialcombat + \ fileexists + \ floor + \ fmod + \ forcecolumnvector + \ forcerowvector + \ generateidentitymatrix + \ generaterotationmatrix + \ generatezeromatrix + \ getactiveeffectcasters + \ getactiveeffectcodes + \ getactiveeffectcount + \ getactivemenucomponentid + \ getactivemenufilter + \ getactivemenumode + \ getactivemenuobject + \ getactivemenuref + \ getactivemenuselection + \ getactivequest + \ getactiveuicomponentfullname + \ getactiveuicomponentid + \ getactiveuicomponentname + \ getactoralpha + \ getactorbaselevel + \ getactorlightamount + \ getactormaxlevel + \ getactormaxswimbreath + \ getactorminlevel + \ getactorpackages + \ getactorsoullevel + \ getactorvaluec + \ getalchmenuapparatus + \ getalchmenuingredient + \ getalchmenuingredientcount + \ getallies + \ getallmodlocaldata + \ getaltcontrol2 + \ getapbowench + \ getapench + \ getapparatustype + \ getappoison + \ getarmorar + \ getarmortype + \ getarrayvariable + \ getarrowprojectilebowenchantment + \ getarrowprojectileenchantment + \ getarrowprojectilepoison + \ getattackdamage + \ getavc + \ getavforbaseactor + \ getavforbaseactorc + \ getavmod + \ getavmodc + \ getavskillmastery + \ getavskillmasteryc + \ getbarteritem + \ getbarteritemquantity + \ getbaseactorvaluec + \ getbaseav2 + \ getbaseav2c + \ getbaseav3 + \ getbaseav3c + \ getbaseitems + \ getbaseobject + \ getbipediconpath + \ getbipedmodelpath + \ getbipedslotmask + \ getbirthsignspells + \ getbookcantbetaken + \ getbookisscroll + \ getbooklength + \ getbookskilltaught + \ getbooktext + \ getboundingbox + \ getboundingradius + \ getcalcalllevels + \ getcalceachincount + \ getcallingscript + \ getcellbehavesasexterior + \ getcellchanged + \ getcellclimate + \ getcelldetachtime + \ getcellfactionrank + \ getcelllighting + \ getcellmusictype + \ getcellnorthrotation + \ getcellresethours + \ getcellwatertype + \ getchancenone + \ getclass + \ getclassattribute + \ getclassmenuhighlightedclass + \ getclassmenuselectedclass + \ getclassskill + \ getclassskills + \ getclassspecialization + \ getclimatehasmasser + \ getclimatehassecunda + \ getclimatemoonphaselength + \ getclimatesunrisebegin + \ getclimatesunriseend + \ getclimatesunsetbegin + \ getclimatesunsetend + \ getclimatevolatility + \ getclosesound + \ getcloudspeedlower + \ getcloudspeedupper + \ getcombatspells + \ getcombatstyle + \ getcombatstyleacrobaticsdodgechance + \ getcombatstyleattackchance + \ getcombatstyleattackduringblockmult + \ getcombatstyleattacknotunderattackmult + \ getcombatstyleattackskillmodbase + \ getcombatstyleattackskillmodmult + \ getcombatstyleattackunderattackmult + \ getcombatstyleblockchance + \ getcombatstyleblocknotunderattackmult + \ getcombatstyleblockskillmodbase + \ getcombatstyleblockskillmodmult + \ getcombatstyleblockunderattackmult + \ getcombatstylebuffstandoffdist + \ getcombatstyledodgebacknotunderattackmult + \ getcombatstyledodgebacktimermax + \ getcombatstyledodgebacktimermin + \ getcombatstyledodgebackunderattackmult + \ getcombatstyledodgechance + \ getcombatstyledodgefatiguemodbase + \ getcombatstyledodgefatiguemodmult + \ getcombatstyledodgefwattackingmult + \ getcombatstyledodgefwnotattackingmult + \ getcombatstyledodgefwtimermax + \ getcombatstyledodgefwtimermin + \ getcombatstyledodgelrchance + \ getcombatstyledodgelrtimermax + \ getcombatstyledodgelrtimermin + \ getcombatstyledodgenotunderattackmult + \ getcombatstyledodgeunderattackmult + \ getcombatstyleencumberedspeedmodbase + \ getcombatstyleencumberedspeedmodmult + \ getcombatstylefleeingdisabled + \ getcombatstylegroupstandoffdist + \ getcombatstyleh2hbonustoattack + \ getcombatstyleholdtimermax + \ getcombatstyleholdtimermin + \ getcombatstyleidletimermax + \ getcombatstyleidletimermin + \ getcombatstyleignorealliesinarea + \ getcombatstylekobonustoattack + \ getcombatstylekobonustopowerattack + \ getcombatstylemeleealertok + \ getcombatstylepowerattackchance + \ getcombatstylepowerattackfatiguemodbase + \ getcombatstylepowerattackfatiguemodmult + \ getcombatstyleprefersranged + \ getcombatstylerangedstandoffdist + \ getcombatstylerangemaxmult + \ getcombatstylerangeoptimalmult + \ getcombatstylerejectsyields + \ getcombatstylerushattackchance + \ getcombatstylerushattackdistmult + \ getcombatstylestaggerbonustoattack + \ getcombatstylestaggerbonustopowerattack + \ getcombatstyleswitchdistmelee + \ getcombatstyleswitchdistranged + \ getcombatstylewillyield + \ getcombattarget + \ getcompletedquests + \ getcontainermenuview + \ getcontainerrespawns + \ getcontrol + \ getcreaturebasescale + \ getcreaturecombatskill + \ getcreatureflies + \ getcreaturemagicskill + \ getcreaturemodelpaths + \ getcreaturereach + \ getcreaturesoullevel + \ getcreaturesound + \ getcreaturesoundbase + \ getcreaturestealthskill + \ getcreatureswims + \ getcreaturetype + \ getcreaturewalks + \ getcrosshairref + \ getcurrentcharge + \ getcurrentclimateid + \ getcurrenteditorpackage + \ getcurrenteventname + \ getcurrenthealth + \ getcurrentpackage + \ getcurrentpackageprocedure + \ getcurrentquests + \ getcurrentregion + \ getcurrentregions + \ getcurrentscript + \ getcurrentsoullevel + \ getcurrentweatherid + \ getcursorpos + \ getdebugselection + \ getdescription + \ getdoorteleportrot + \ getdoorteleportx + \ getdoorteleporty + \ getdoorteleportz + \ geteditorid + \ geteditorsize + \ getenchantment + \ getenchantmentcharge + \ getenchantmentcost + \ getenchantmenttype + \ getenchmenubaseitem + \ getenchmenuenchitem + \ getenchmenusoulgem + \ getequipmentslot + \ getequipmentslotmask + \ getequippedcurrentcharge + \ getequippedcurrenthealth + \ getequippeditems + \ getequippedobject + \ getequippedtorchtimeleft + \ getequippedweaponpoison + \ geteyes + \ getfactions + \ getfalltimer + \ getfirstref + \ getfirstrefincell + \ getfogdayfar + \ getfogdaynear + \ getfognightfar + \ getfognightnear + \ getfollowers + \ getformfrommod + \ getformidstring + \ getfps + \ getfullgoldvalue + \ getgamedifficulty + \ getgameloaded + \ getgamerestarted + \ getgodmode + \ getgoldvalue + \ getgridstoload + \ getgroundsurfacematerial + \ gethair + \ gethaircolor + \ gethdrvalue + \ gethidesamulet + \ gethidesrings + \ gethighactors + \ gethorse + \ gethotkeyitem + \ geticonpath + \ getignoresresistance + \ getingredient + \ getingredientchance + \ getinputtext + \ getinventoryobject + \ getinvrefsforitem + \ getitems + \ getkeyname + \ getkeypress + \ getlastcreatedpotion + \ getlastcreatedspell + \ getlastenchanteditem + \ getlastsigilstonecreateditem + \ getlastsigilstoneenchanteditem + \ getlastss + \ getlastsscreated + \ getlastssitem + \ getlasttransactionitem + \ getlasttransactionquantity + \ getlastuniquecreatedpotion + \ getlastusedsigilstone + \ getlevcreaturetemplate + \ getleveledspells + \ getlevitembylevel + \ getlevitemindexbyform + \ getlevitemindexbylevel + \ getlightduration + \ getlightningfrequency + \ getlightradius + \ getlightrgb + \ getlinkeddoor + \ getloadedtypearray + \ getlocalgravity + \ getloopsound + \ getlowactors + \ getluckmodifiedskill + \ getmagiceffectareasound + \ getmagiceffectareasoundc + \ getmagiceffectbarterfactor + \ getmagiceffectbarterfactorc + \ getmagiceffectbasecost + \ getmagiceffectbasecostc + \ getmagiceffectboltsound + \ getmagiceffectboltsoundc + \ getmagiceffectcastingsound + \ getmagiceffectcastingsoundc + \ getmagiceffectchars + \ getmagiceffectcharsc + \ getmagiceffectcode + \ getmagiceffectcounters + \ getmagiceffectcountersc + \ getmagiceffectenchantfactor + \ getmagiceffectenchantfactorc + \ getmagiceffectenchantshader + \ getmagiceffectenchantshaderc + \ getmagiceffecthitshader + \ getmagiceffecthitshaderc + \ getmagiceffecthitsound + \ getmagiceffecthitsoundc + \ getmagiceffecticon + \ getmagiceffecticonc + \ getmagiceffectlight + \ getmagiceffectlightc + \ getmagiceffectmodel + \ getmagiceffectmodelc + \ getmagiceffectname + \ getmagiceffectnamec + \ getmagiceffectnumcounters + \ getmagiceffectnumcountersc + \ getmagiceffectotheractorvalue + \ getmagiceffectotheractorvaluec + \ getmagiceffectprojectilespeed + \ getmagiceffectprojectilespeedc + \ getmagiceffectresistvalue + \ getmagiceffectresistvaluec + \ getmagiceffectschool + \ getmagiceffectschoolc + \ getmagiceffectusedobject + \ getmagiceffectusedobjectc + \ getmagicitemeffectcount + \ getmagicitemtype + \ getmagicprojectilespell + \ getmapmarkers + \ getmapmarkertype + \ getmapmenumarkername + \ getmapmenumarkerref + \ getmaxav + \ getmaxavc + \ getmaxlevel + \ getmeareasound + \ getmeareasoundc + \ getmebarterc + \ getmebasecost + \ getmebasecostc + \ getmeboltsound + \ getmeboltsoundc + \ getmecastingsound + \ getmecastingsoundc + \ getmecounters + \ getmecountersc + \ getmeebarter + \ getmeebarterc + \ getmeenchant + \ getmeenchantc + \ getmeenchantshader + \ getmeenchantshaderc + \ getmehitshader + \ getmehitshaderc + \ getmehitsound + \ getmehitsoundc + \ getmeicon + \ getmeiconc + \ getmelight + \ getmelightc + \ getmemodel + \ getmemodelc + \ getmename + \ getmenamec + \ getmenufloatvalue + \ getmenumcounters + \ getmenumcountersc + \ getmenustringvalue + \ getmeotheractorvalue + \ getmeotheractorvaluec + \ getmeprojspeed + \ getmeprojspeedc + \ getmerchantcontainer + \ getmeresistvalue + \ getmeresistvaluec + \ getmeschool + \ getmeschoolc + \ getmessageboxtype + \ getmeusedobject + \ getmeusedobjectc + \ getmiddlehighactors + \ getmieffectcount + \ getminlevel + \ getmitype + \ getmodelpath + \ getmodindex + \ getmodlocaldata + \ getmousebuttonpress + \ getmousebuttonsswapped + \ getmpspell + \ getnextref + \ getnthacitveeffectmagnitude + \ getnthactiveeffectactorvalue + \ getnthactiveeffectbounditem + \ getnthactiveeffectcaster + \ getnthactiveeffectcode + \ getnthactiveeffectdata + \ getnthactiveeffectduration + \ getnthactiveeffectenchantobject + \ getnthactiveeffectmagicenchantobject + \ getnthactiveeffectmagicitem + \ getnthactiveeffectmagicitemindex + \ getnthactiveeffectmagnitude + \ getnthactiveeffectsummonref + \ getnthactiveeffecttimeelapsed + \ getnthaeav + \ getnthaebounditem + \ getnthaecaster + \ getnthaecode + \ getnthaedata + \ getnthaeduration + \ getnthaeindex + \ getnthaemagicenchantobject + \ getnthaemagicitem + \ getnthaemagnitude + \ getnthaesummonref + \ getnthaetime + \ getnthchildref + \ getnthdetectedactor + \ getntheffectitem + \ getntheffectitemactorvalue + \ getntheffectitemarea + \ getntheffectitemcode + \ getntheffectitemduration + \ getntheffectitemmagnitude + \ getntheffectitemname + \ getntheffectitemrange + \ getntheffectitemscript + \ getntheffectitemscriptname + \ getntheffectitemscriptschool + \ getntheffectitemscriptvisualeffect + \ getntheiarea + \ getntheiav + \ getntheicode + \ getntheiduration + \ getntheimagnitude + \ getntheiname + \ getntheirange + \ getntheiscript + \ getntheisschool + \ getntheisvisualeffect + \ getnthexplicitref + \ getnthfaction + \ getnthfactionrankname + \ getnthfollower + \ getnthlevitem + \ getnthlevitemcount + \ getnthlevitemlevel + \ getnthmagiceffectcounter + \ getnthmagiceffectcounterc + \ getnthmecounter + \ getnthmecounterc + \ getnthmodname + \ getnthpackage + \ getnthplayerspell + \ getnthracebonusskill + \ getnthracespell + \ getnthspell + \ getnumchildrefs + \ getnumdetectedactors + \ getnumericinisetting + \ getnumexplicitrefs + \ getnumfactions + \ getnumfollowers + \ getnumitems + \ getnumkeyspressed + \ getnumlevitems + \ getnumloadedmods + \ getnumloadedplugins + \ getnummousebuttonspressed + \ getnumpackages + \ getnumranks + \ getnumrefs + \ getnumrefsincell + \ getobjectcharge + \ getobjecthealth + \ getobjecttype + \ getobliviondirectory + \ getoblrevision + \ getoblversion + \ getopenkey + \ getopensound + \ getowner + \ getowningfactionrank + \ getowningfactionrequiredrank + \ getpackageallowfalls + \ getpackageallowswimming + \ getpackagealwaysrun + \ getpackagealwayssneak + \ getpackagearmorunequipped + \ getpackagecontinueifpcnear + \ getpackagedata + \ getpackagedefensivecombat + \ getpackagelocationdata + \ getpackagelockdoorsatend + \ getpackagelockdoorsatlocation + \ getpackagelockdoorsatstart + \ getpackagemustcomplete + \ getpackagemustreachlocation + \ getpackagenoidleanims + \ getpackageoffersservices + \ getpackageonceperday + \ getpackagescheduledata + \ getpackageskipfalloutbehavior + \ getpackagetargetdata + \ getpackageunlockdoorsatend + \ getpackageunlockdoorsatlocation + \ getpackageunlockdoorsatstart + \ getpackageusehorse + \ getpackageweaponsunequipped + \ getparentcell + \ getparentcellowner + \ getparentcellowningfactionrank + \ getparentcellowningfactionrequiredrank + \ getparentcellwaterheight + \ getparentworldspace + \ getpathnodelinkedref + \ getpathnodepos + \ getpathnodesinradius + \ getpathnodesinrect + \ getpcattributebonus + \ getpcattributebonusc + \ getpclastdroppeditem + \ getpclastdroppeditemref + \ getpclasthorse + \ getpclastloaddoor + \ getpcmajorskillups + \ getpcmovementspeedmodifier + \ getpcspelleffectivenessmodifier + \ getpctrainingsessionsused + \ getplayerbirthsign + \ getplayerskilladvances + \ getplayerskilladvancesc + \ getplayerskilluse + \ getplayerskillusec + \ getplayerslastactivatedloaddoor + \ getplayerslastriddenhorse + \ getplayerspell + \ getplayerspellcount + \ getpluginversion + \ getplyerspellcount + \ getprocesslevel + \ getprojectile + \ getprojectiledistancetraveled + \ getprojectilelifetime + \ getprojectilesource + \ getprojectilespeed + \ getprojectiletype + \ getqmcurrent + \ getqmitem + \ getqmmaximum + \ getqr + \ getquality + \ getquantitymenucurrentquantity + \ getquantitymenuitem + \ getquantitymenumaximumquantity + \ getrace + \ getraceattribute + \ getraceattributec + \ getracedefaulthair + \ getraceeyes + \ getracehairs + \ getracereaction + \ getracescale + \ getraceskillbonus + \ getraceskillbonusc + \ getracespellcount + \ getracevoice + \ getraceweight + \ getrawformidstring + \ getrefcount + \ getrefvariable + \ getrequiredskillexp + \ getrequiredskillexpc + \ getrider + \ getscript + \ getscriptactiveeffectindex + \ getselectedspells + \ getservicesmask + \ getsigilstoneuses + \ getskillgoverningattribute + \ getskillgoverningattributec + \ getskillspecialization + \ getskillspecializationc + \ getskilluseincrement + \ getskilluseincrementc + \ getsoulgemcapacity + \ getsoullevel + \ getsoundattenuation + \ getsoundplaying + \ getsourcemodindex + \ getspecialanims + \ getspellareaeffectignoreslos + \ getspellcount + \ getspelldisallowabsorbreflect + \ getspelleffectiveness + \ getspellexplodeswithnotarget + \ getspellhostile + \ getspellimmunetosilence + \ getspellmagickacost + \ getspellmasterylevel + \ getspellpcstart + \ getspells + \ getspellschool + \ getspellscripteffectalwaysapplies + \ getspelltype + \ getstageentries + \ getstageids + \ getstringgamesetting + \ getstringinisetting + \ getsundamage + \ getsunglare + \ gettailmodelpath + \ gettargets + \ gettelekinesisref + \ getteleportcell + \ getteleportcellname + \ getterrainheight + \ gettextinputcontrolpressed + \ gettextinputcursorpos + \ gettexturepath + \ gettilechildren + \ gettiletraits + \ gettimeleft + \ gettotalactiveeffectmagnitude + \ gettotalactiveeffectmagnitudec + \ gettotalaeabilitymagnitude + \ gettotalaeabilitymagnitudec + \ gettotalaealchemymagnitude + \ gettotalaealchemymagnitudec + \ gettotalaeallspellsmagnitude + \ gettotalaeallspellsmagnitudec + \ gettotalaediseasemagnitude + \ gettotalaediseasemagnitudec + \ gettotalaeenchantmentmagnitude + \ gettotalaeenchantmentmagnitudec + \ gettotalaelesserpowermagnitude + \ gettotalaelesserpowermagnitudec + \ gettotalaemagnitude + \ gettotalaemagnitudec + \ gettotalaenonabilitymagnitude + \ gettotalaenonabilitymagnitudec + \ gettotalaepowermagnitude + \ gettotalaepowermagnitudec + \ gettotalaespellmagnitude + \ gettotalaespellmagnitudec + \ gettotalpcattributebonus + \ gettrainerlevel + \ gettrainerskill + \ gettransactioninfo + \ gettransdelta + \ gettravelhorse + \ getusedpowers + \ getusertime + \ getvariable + \ getvelocity + \ getverticalvelocity + \ getwaterheight + \ getwatershader + \ getweahtercloudspeedupper + \ getweaponreach + \ getweaponspeed + \ getweapontype + \ getweatherclassification + \ getweathercloudspeedlower + \ getweathercloudspeedupper + \ getweathercolor + \ getweatherfogdayfar + \ getweatherfogdaynear + \ getweatherfognightfar + \ getweatherfognightnear + \ getweatherhdrvalue + \ getweatherlightningfrequency + \ getweatheroverride + \ getweathersundamage + \ getweathersunglare + \ getweathertransdelta + \ getweatherwindspeed + \ getweight + \ getworldparentworld + \ getworldspaceparentworldspace + \ globalvariableexists + \ hammerkey + \ hasbeenpickedup + \ haseffectshader + \ haslowlevelprocessing + \ hasmodel + \ hasname + \ hasnopersuasion + \ hasspell + \ hastail + \ hasvariable + \ haswater + \ holdkey + \ iconpathincludes + \ identitymat + \ incrementplayerskilluse + \ incrementplayerskillusec + \ ininvertfasttravel + \ insertininputtext + \ isactivatable + \ isactivator + \ isactorrespawning + \ isalchemyitem + \ isammo + \ isanimgroupplaying + \ isanimplaying + \ isapparatus + \ isarmor + \ isattacking + \ isautomaticdoor + \ isbartermenuactive + \ isbipediconpathvalid + \ isbipedmodelpathvalid + \ isblocking + \ isbook + \ iscantwait + \ iscasting + \ iscellpublic + \ isclassattribute + \ isclassattributec + \ isclassskill + \ isclassskillc + \ isclonedform + \ isclothing + \ isconsoleopen + \ iscontainer + \ iscontrol + \ iscontroldisabled + \ iscontrolpressed + \ iscreature + \ iscreaturebiped + \ isdigit + \ isdiseased + \ isdodging + \ isdoor + \ isequipped + \ isfactionevil + \ isfactionhidden + \ isfemale + \ isflora + \ isflying + \ isfood + \ isformvalid + \ isfurniture + \ isgamemessagebox + \ isglobalcollisiondisabled + \ isharvested + \ ishiddendoor + \ isiconpathvalid + \ isinair + \ isingredient + \ isinoblivion + \ isjumping + \ iskey + \ iskeydisabled + \ iskeypressed + \ iskeypressed2 + \ iskeypressed3 + \ isletter + \ islight + \ islightcarriable + \ isloaddoor + \ ismagiceffectcanrecover + \ ismagiceffectcanrecoverc + \ ismagiceffectdetrimental + \ ismagiceffectdetrimentalc + \ ismagiceffectforenchanting + \ ismagiceffectforenchantingc + \ ismagiceffectforspellmaking + \ ismagiceffectforspellmakingc + \ ismagiceffecthostile + \ ismagiceffecthostilec + \ ismagiceffectmagnitudepercent + \ ismagiceffectmagnitudepercentc + \ ismagiceffectonselfallowed + \ ismagiceffectonselfallowedc + \ ismagiceffectontargetallowed + \ ismagiceffectontargetallowedc + \ ismagiceffectontouchallowed + \ ismagiceffectontouchallowedc + \ ismagicitemautocalc + \ ismajor + \ ismajorc + \ ismajorref + \ ismapmarkervisible + \ ismecanrecover + \ ismecanrecoverc + \ ismedetrimental + \ ismedetrimentalc + \ ismeforenchanting + \ ismeforenchantingc + \ ismeforspellmaking + \ ismeforspellmakingc + \ ismehostile + \ ismehostilec + \ ismemagnitudepercent + \ ismemagnitudepercentc + \ ismeonselfallowed + \ ismeonselfallowedc + \ ismeontargetallowed + \ ismeontargetallowedc + \ ismeontouchallowed + \ ismeontouchallowedc + \ isminimalusedoor + \ ismiscitem + \ ismodelpathvalid + \ ismodloaded + \ ismovingbackward + \ ismovingforward + \ ismovingleft + \ ismovingright + \ isnaked + \ isnthactiveeffectapplied + \ isntheffectitemscripted + \ isntheffectitemscripthostile + \ isntheishostile + \ isobliviongate + \ isoblivioninterior + \ isoblivionworld + \ isofflimits + \ isonground + \ ispathnodedisabled + \ ispcleveloffset + \ ispersistent + \ isplayable + \ isplayable2 + \ isplugininstalled + \ ispoison + \ ispotion + \ ispowerattacking + \ isprintable + \ ispunctuation + \ isquestcomplete + \ isquestitem + \ isracebonusskill + \ isracebonusskillc + \ israceplayable + \ isrecoiling + \ isrefdeleted + \ isreference + \ isrefessential + \ isscripted + \ issigilstone + \ issoulgem + \ isspellhostile + \ isstaggered + \ issummonable + \ istaken + \ istextinputinuse + \ isthirdperson + \ isturningleft + \ isturningright + \ isunderwater + \ isunsaferespawns + \ isuppercase + \ isweapon + \ leftshift + \ linktodoor + \ loadgameex + \ log + \ log10 + \ logicaland + \ logicalnot + \ logicalor + \ logicalxor + \ magiceffectcodefromchars + \ magiceffectfromchars + \ magiceffectfromcode + \ magiceffectfxpersists + \ magiceffectfxpersistsc + \ magiceffecthasnoarea + \ magiceffecthasnoareac + \ magiceffecthasnoduration + \ magiceffecthasnodurationc + \ magiceffecthasnohiteffect + \ magiceffecthasnohiteffectc + \ magiceffecthasnoingredient + \ magiceffecthasnoingredientc + \ magiceffecthasnomagnitude + \ magiceffecthasnomagnitudec + \ magiceffectusesarmor + \ magiceffectusesarmorc + \ magiceffectusesattribute + \ magiceffectusesattributec + \ magiceffectusescreature + \ magiceffectusescreaturec + \ magiceffectusesotheractorvalue + \ magiceffectusesotheractorvaluec + \ magiceffectusesskill + \ magiceffectusesskillc + \ magiceffectusesweapon + \ magiceffectusesweaponc + \ magichaseffect + \ magichaseffectc + \ magicitemhaseffect + \ magicitemhaseffectcode + \ magicitemhaseffectcount + \ magicitemhaseffectcountc + \ magicitemhaseffectcountcode + \ magicitemhaseffectitemscript + \ matadd + \ matchpotion + \ matinv + \ matmult + \ matrixadd + \ matrixdeterminant + \ matrixinvert + \ matrixmultiply + \ matrixrref + \ matrixscale + \ matrixsubtract + \ matrixtrace + \ matrixtranspose + \ matscale + \ matsubtract + \ mecodefromchars + \ mefxpersists + \ mefxpersistsc + \ mehasnoarea + \ mehasnoareac + \ mehasnoduration + \ mehasnodurationc + \ mehasnohiteffect + \ mehasnohiteffectc + \ mehasnoingredient + \ mehasnoingredientc + \ mehasnomagnitude + \ mehasnomagnitudec + \ menuholdkey + \ menumode + \ menureleasekey + \ menutapkey + \ messageboxex + \ messageex + \ meusesarmor + \ meusesarmorc + \ meusesattribute + \ meusesattributec + \ meusescreature + \ meusescreaturec + \ meusesotheractorvalue + \ meusesotheractorvaluec + \ meusesskill + \ meusesskillc + \ meusesweapon + \ meusesweaponc + \ modactorvalue2 + \ modactorvaluec + \ modarmorar + \ modattackdamage + \ modav2 + \ modavc + \ modavmod + \ modavmodc + \ modcurrentcharge + \ modelpathincludes + \ modenchantmentcharge + \ modenchantmentcost + \ modequippedcurrentcharge + \ modequippedcurrenthealth + \ modfemalebipedpath + \ modfemalegroundpath + \ modfemaleiconpath + \ modgoldvalue + \ modiconpath + \ modlocaldataexists + \ modmalebipedpath + \ modmalegroundpath + \ modmaleiconpath + \ modmodelpath + \ modname + \ modnthactiveeffectmagnitude + \ modnthaemagnitude + \ modntheffectitemarea + \ modntheffectitemduration + \ modntheffectitemmagnitude + \ modntheffectitemscriptname + \ modntheiarea + \ modntheiduration + \ modntheimagnitude + \ modntheisname + \ modobjectcharge + \ modobjecthealth + \ modpcmovementspeed + \ modpcspelleffectiveness + \ modplayerskillexp + \ modplayerskillexpc + \ modquality + \ modsigilstoneuses + \ modspellmagickacost + \ modweaponreach + \ modweaponspeed + \ modweight + \ movemousex + \ movemousey + \ movetextinputcursor + \ nameincludes + \ numtohex + \ offersapparatus + \ offersarmor + \ offersbooks + \ offersclothing + \ offersingredients + \ offerslights + \ offersmagicitems + \ offersmiscitems + \ offerspotions + \ offersrecharging + \ offersrepair + \ offersservicesc + \ offersspells + \ offerstraining + \ offersweapons + \ oncontroldown + \ onkeydown + \ opentextinput + \ outputlocalmappicturesoverride + \ overrideactorswimbreath + \ parentcellhaswater + \ pathedgeexists + \ playidle + \ pow + \ print + \ printactivetileinfo + \ printc + \ printd + \ printtileinfo + \ printtoconsole + \ questexists + \ racos + \ rand + \ rasin + \ ratan + \ ratan2 + \ rcos + \ rcosh + \ refreshcurrentclimate + \ releasekey + \ removealleffectitems + \ removebasespell + \ removeenchantment + \ removeequippedweaponpoison + \ removeeventhandler + \ removefromleveledlist + \ removeitemns + \ removelevitembylevel + \ removemeir + \ removemodlocaldata + \ removentheffect + \ removentheffectitem + \ removenthlevitem + \ removenthmagiceffectcounter + \ removenthmagiceffectcounterc + \ removenthmecounter + \ removenthmecounterc + \ removescript + \ removescr + \ removespellns + \ resetallvariables + \ resetfalrior + \ resolvemodindex + \ rightshift + \ rotmat + \ rowvec + \ rsin + \ rsinh + \ rtan + \ rtanh + \ runbatchscript + \ runscriptline + \ saespassalarm + \ setactivequest + \ setactrfullname + \ setactormaxswimbreath + \ setactorrespawns + \ setactorswimbreath + \ setactorvaluec + \ setalvisible + \ setaltcontrol2 + \ setapparatustype + \ setarmorar + \ setarmortype + \ setarrowprojectilebowenchantment + \ setarrowprojectileenchantment + \ setarrowprojectilepoison + \ setattackdamage + \ setavc + \ setavmod + \ setavmodc + \ setbaseform + \ setbipediconpathex + \ setbipedmodelpathex + \ setbipedslotmask + \ setbookcantbetaken + \ setbookisscroll + \ setbookskilltaught + \ setbuttonpressed + \ setcalcalllevels + \ setcamerafov2 + \ setcancastpower + \ setcancorpsecheck + \ setcanfasttravelfromworld + \ setcantraveltomapmarker + \ setcantwait + \ setcellbehavesasexterior + \ setcellclimate + \ setcellhaswater + \ setcellispublic + \ setcelllighting + \ setcellmusictype + \ setcellublicflag + \ setcellresethours + \ setcellwaterheight + \ setcellwatertype + \ setchancenone + \ setclassattribute + \ setclassattributec + \ setclassskills + \ setclassskills2 + \ setclassspecialization + \ setclimatehasmasser + \ setclimatehasmassser + \ setclimatehassecunda + \ setclimatemoonphaselength + \ setclimatesunrisebegin + \ setclimatesunriseend + \ setclimatesunsetbegin + \ setclimatesunsetend + \ setclimatevolatility + \ setclosesound + \ setcloudspeedlower + \ setcloudspeedupper + \ setcombatstyle + \ setcombatstyleacrobaticsdodgechance + \ setcombatstyleattackchance + \ setcombatstyleattackduringblockmult + \ setcombatstyleattacknotunderattackmult + \ setcombatstyleattackskillmodbase + \ setcombatstyleattackskillmodmult + \ setcombatstyleattackunderattackmult + \ setcombatstyleblockchance + \ setcombatstyleblocknotunderattackmult + \ setcombatstyleblockskillmodbase + \ setcombatstyleblockskillmodmult + \ setcombatstyleblockunderattackmult + \ setcombatstylebuffstandoffdist + \ setcombatstyledodgebacknotunderattackmult + \ setcombatstyledodgebacktimermax + \ setcombatstyledodgebacktimermin + \ setcombatstyledodgebackunderattackmult + \ setcombatstyledodgechance + \ setcombatstyledodgefatiguemodbase + \ setcombatstyledodgefatiguemodmult + \ setcombatstyledodgefwattackingmult + \ setcombatstyledodgefwnotattackingmult + \ setcombatstyledodgefwtimermax + \ setcombatstyledodgefwtimermin + \ setcombatstyledodgelrchance + \ setcombatstyledodgelrtimermax + \ setcombatstyledodgelrtimermin + \ setcombatstyledodgenotunderattackmult + \ setcombatstyledodgeunderattackmult + \ setcombatstyleencumberedspeedmodbase + \ setcombatstyleencumberedspeedmodmult + \ setcombatstylefleeingdisabled + \ setcombatstylegroupstandoffdist + \ setcombatstyleh2hbonustoattack + \ setcombatstyleholdtimermax + \ setcombatstyleholdtimermin + \ setcombatstyleidletimermax + \ setcombatstyleidletimermin + \ setcombatstyleignorealliesinarea + \ setcombatstylekobonustoattack + \ setcombatstylekobonustopowerattack + \ setcombatstylemeleealertok + \ setcombatstylepowerattackchance + \ setcombatstylepowerattackfatiguemodbase + \ setcombatstylepowerattackfatiguemodmult + \ setcombatstyleprefersranged + \ setcombatstylerangedstandoffdist + \ setcombatstylerangemaxmult + \ setcombatstylerangeoptimalmult + \ setcombatstylerejectsyields + \ setcombatstylerushattackchance + \ setcombatstylerushattackdistmult + \ setcombatstylestaggerbonustoattack + \ setcombatstylestaggerbonustopowerattack + \ setcombatstyleswitchdistmelee + \ setcombatstyleswitchdistranged + \ setcombatstylewillyield + \ setcontainerrespawns + \ setcontrol + \ setcreatureskill + \ setcreaturesoundbase + \ setcreaturetype + \ setcurrentcharge + \ setcurrenthealth + \ setcurrentsoullevel + \ setdebugmode + \ setdescription + \ setdetectionstate + \ setdisableglobalcollision + \ setdoorteleport + \ setenchantment + \ setenchantmentcharge + \ setenchantmentcost + \ setenchantmenttype + \ setequipmentslot + \ setequippedcurrentcharge + \ setequippedcurrenthealth + \ setequippedweaponpoison + \ seteventhandler + \ seteyes + \ setfactionevil + \ setfactionhasspecialcombat + \ setfactionhidden + \ setfactonreaction + \ setfactionspecialcombat + \ setfemale + \ setfemalebipedpath + \ setfemalegroundpath + \ setfemaleiconpath + \ setflycameraspeedmult + \ setfogdayfar + \ setfogdaynear + \ setfognightfar + \ setfognightnear + \ setforcsneak + \ setfunctionvalue + \ setgamedifficulty + \ setgoldvalue + \ setgoldvalue_t + \ setgoldvaluet + \ sethair + \ setharvested + \ sethasbeenpickedup + \ sethdrvalue + \ sethidesamulet + \ sethidesrings + \ sethotkeyitem + \ seticonpath + \ setignoresresistance + \ setingredient + \ setingredientchance + \ setinputtext + \ setinvertfasttravel + \ setisautomaticdoor + \ setiscontrol + \ setisfood + \ setishiddendoor + \ setisminimalusedoor + \ setisobliviongate + \ setisplayable + \ setlevcreaturetemplate + \ setlightduration + \ setlightningfrequency + \ setlightradius + \ setlightrgb + \ setlocalgravity + \ setlocalgravityvector + \ setloopsound + \ setlowlevelprocessing + \ setmaagiceffectuseactorvalue + \ setmagiceffectareasound + \ setmagiceffectareasoundc + \ setmagiceffectbarterfactor + \ setmagiceffectbarterfactorc + \ setmagiceffectbasecost + \ setmagiceffectbasecostc + \ setmagiceffectboltsound + \ setmagiceffectboltsoundc + \ setmagiceffectcanrecover + \ setmagiceffectcanrecoverc + \ setmagiceffectcastingsound + \ setmagiceffectcastingsoundc + \ setmagiceffectcounters + \ setmagiceffectcountersc + \ setmagiceffectenchantfactor + \ setmagiceffectenchantfactorc + \ setmagiceffectenchantshader + \ setmagiceffectenchantshaderc + \ setmagiceffectforenchanting + \ setmagiceffectforenchantingc + \ setmagiceffectforspellmaking + \ setmagiceffectforspellmakingc + \ setmagiceffectfxpersists + \ setmagiceffectfxpersistsc + \ setmagiceffecthitshader + \ setmagiceffecthitshaderc + \ setmagiceffecthitsound + \ setmagiceffecthitsoundc + \ setmagiceffecticon + \ setmagiceffecticonc + \ setmagiceffectisdetrimental + \ setmagiceffectisdetrimentalc + \ setmagiceffectishostile + \ setmagiceffectishostilec + \ setmagiceffectlight + \ setmagiceffectlightc + \ setmagiceffectmagnitudepercent + \ setmagiceffectmagnitudepercentc + \ setmagiceffectmodel + \ setmagiceffectmodelc + \ setmagiceffectname + \ setmagiceffectnamec + \ setmagiceffectnoarea + \ setmagiceffectnoareac + \ setmagiceffectnoduration + \ setmagiceffectnodurationc + \ setmagiceffectnohiteffect + \ setmagiceffectnohiteffectc + \ setmagiceffectnoingredient + \ setmagiceffectnoingredientc + \ setmagiceffectnomagnitude + \ setmagiceffectnomagnitudec + \ setmagiceffectonselfallowed + \ setmagiceffectonselfallowedc + \ setmagiceffectontargetallowed + \ setmagiceffectontargetallowedc + \ setmagiceffectontouchallowed + \ setmagiceffectontouchallowedc + \ setmagiceffectotheractorvalue + \ setmagiceffectotheractorvaluec + \ setmagiceffectprojectilespeed + \ setmagiceffectprojectilespeedc + \ setmagiceffectresistvalue + \ setmagiceffectresistvaluec + \ setmagiceffectschool + \ setmagiceffectschoolc + \ setmagiceffectuseactorvaluec + \ setmagiceffectusedobject + \ setmagiceffectusedobjectc + \ setmagiceffectusesactorvalue + \ setmagiceffectusesactorvaluec + \ setmagiceffectusesarmor + \ setmagiceffectusesarmorc + \ setmagiceffectusesattribute + \ setmagiceffectusesattributec + \ setmagiceffectusescreature + \ setmagiceffectusescreaturec + \ setmagiceffectusesskill + \ setmagiceffectusesskillc + \ setmagiceffectusesweapon + \ setmagiceffectusesweaponc + \ setmagicitemautocalc + \ setmagicprojectilespell + \ setmalebipedpath + \ setmalegroundpath + \ setmaleiconpath + \ setmapmarkertype + \ setmapmarkervisible + \ setmeareasound + \ setmeareasoundc + \ setmebarterfactor + \ setmebarterfactorc + \ setmebasecost + \ setmebasecostc + \ setmeboltsound + \ setmeboltsoundc + \ setmecanrecover + \ setmecanrecoverc + \ setmecastingsound + \ setmecastingsoundc + \ setmeenchantfactor + \ setmeenchantfactorc + \ setmeenchantshader + \ setmeenchantshaderc + \ setmeforenchanting + \ setmeforenchantingc + \ setmeforspellmaking + \ setmeforspellmakingc + \ setmefxpersists + \ setmefxpersistsc + \ setmehitshader + \ setmehitshaderc + \ setmehitsound + \ setmehitsoundc + \ setmeicon + \ setmeiconc + \ setmeisdetrimental + \ setmeisdetrimentalc + \ setmeishostile + \ setmeishostilec + \ setmelight + \ setmelightc + \ setmemagnitudepercent + \ setmemagnitudepercentc + \ setmemodel + \ setmemodelc + \ setmename + \ setmenamec + \ setmenoarea + \ setmenoareac + \ setmenoduration + \ setmenodurationc + \ setmenohiteffect + \ setmenohiteffectc + \ setmenoingredient + \ setmenoingredientc + \ setmenomagnitude + \ setmenomagnitudec + \ setmenufloatvalue + \ setmenustringvalue + \ setmeonselfallowed + \ setmeonselfallowedc + \ setmeontargetallowed + \ setmeontargetallowedc + \ setmeontouchallowed + \ setmeontouchallowedc + \ setmeotheractorvalue + \ setmeotheractorvaluec + \ setmeprojectilespeed + \ setmeprojectilespeedc + \ setmerchantcontainer + \ setmeresistvalue + \ setmeresistvaluec + \ setmeschool + \ setmeschoolc + \ setmessageicon + \ setmessagesound + \ setmeuseactorvalue + \ setmeuseactorvaluec + \ setmeusedobject + \ setmeusedobjectc + \ setmeusesarmor + \ setmeusesarmorc + \ setmeusesattribute + \ setmeusesattributec + \ setmeusescreature + \ setmeusescreaturec + \ setmeusesskill + \ setmeusesskillc + \ setmeusesweapon + \ setmeusesweaponc + \ setmodelpath + \ setmodlocaldata + \ setmousespeedx + \ setmousespeedy + \ setmpspell + \ setname + \ setnameex + \ setnopersuasion + \ setnthactiveeffectmagnitude + \ setnthaemagnitude + \ setntheffectitemactorvalue + \ setntheffectitemactorvaluec + \ setntheffectitemarea + \ setntheffectitemduration + \ setntheffectitemmagnitude + \ setntheffectitemrange + \ setntheffectitemscript + \ setntheffectitemscripthostile + \ setntheffectitemscriptname + \ setntheffectitemscriptnameex + \ setntheffectitemscriptschool + \ setntheffectitemscriptvisualeffect + \ setntheffectitemscriptvisualeffectc + \ setntheiarea + \ setntheiav + \ setntheiavc + \ setntheiduration + \ setntheimagnitude + \ setntheirange + \ setntheiscript + \ setntheishostile + \ setntheisname + \ setntheisschool + \ setntheisvisualeffect + \ setntheisvisualeffectc + \ setnthfactionranknameex + \ setnumericgamesetting + \ setnumericinisetting + \ setobjectcharge + \ setobjecthealth + \ setoffersapparatus + \ setoffersarmor + \ setoffersbooks + \ setoffersclothing + \ setoffersingredients + \ setofferslights + \ setoffersmagicitems + \ setoffersmiscitems + \ setofferspotions + \ setoffersrecharging + \ setoffersrepair + \ setoffersservicesc + \ setoffersspells + \ setofferstraining + \ setoffersweapons + \ setolmpgrids + \ setopenkey + \ setopensound + \ setopenstip + \ setownership_t + \ setowningrequiredrank + \ setpackageallowfalls + \ setpackageallowswimming + \ setpackagealwaysrun + \ setpackagealwayssneak + \ setpackagearmorunequipped + \ setpackagecontinueifpcnear + \ setpackagedata + \ setpackagedefensivecombat + \ setpackagelocationdata + \ setpackagelockdoorsatend + \ setpackagelockdoorsatlocation + \ setpackagelockdoorsatstart + \ setpackagemustcomplete + \ setpackagemustreachlocation + \ setpackagenoidleanims + \ setpackageoffersservices + \ setpackageonceperday + \ setpackagescheduledata + \ setpackageskipfalloutbehavior + \ setpackagetarget + \ setpackagetargetdata + \ setpackageunlockdoorsatend + \ setpackageunlockdoorsatlocation + \ setpackageunlockdoorsatstart + \ setpackageusehorse + \ setpackageweaponsunequipped + \ setparentcellowningfactionrequiredrank + \ setpathnodedisabled + \ setpcamurderer + \ setpcattributebonus + \ setpcattributebonusc + \ setpcexpy + \ setpcleveloffset + \ setpcmajorskillups + \ setpctrainingsessionsused + \ setplayerbseworld + \ setplayerprojectile + \ setplayerskeletonpath + \ setplayerskilladvances + \ setplayerskilladvancesc + \ setplayerslastriddenhorse + \ setpos_t + \ setpowertimer + \ setprojectilesource + \ setprojectilespeed + \ setquality + \ setquestitem + \ setracealias + \ setraceplayable + \ setracescale + \ setracevoice + \ setraceweight + \ setrefcount + \ setrefessential + \ setreale + \ setscaleex + \ setscript + \ setsigilstoneuses + \ setskillgoverningattribute + \ setskillgoverningattributec + \ setskillspecialization + \ setskillspecializationc + \ setskilluseincrement + \ setskilluseincrementc + \ setsoulgemcapacity + \ setsoullevel + \ setsoundattenuation + \ setspellareaeffectignoreslos + \ setspelldisallowabsorbreflect + \ setspellexplodeswithnotarget + \ setspellhostile + \ setspellimmunetosilence + \ setspellmagickacost + \ setspellmasterylevel + \ setspellpcstart + \ setspellscripteffectalwaysapplies + \ setspelltype + \ setstagedate + \ setstagetext + \ setstringgamesettingex + \ setstringinisetting + \ setsummonable + \ setsundamage + \ setsunglare + \ settaken + \ settextinputcontrolhandler + \ settextinputdefaultcontrolsdisabled + \ settextinputhandler + \ settexturepath + \ settimeleft + \ settrainerlevel + \ settrainerskill + \ settransdelta + \ settravelhorse + \ setunsafecontainer + \ setvelocity + \ setverticalvelocity + \ setweaponreach + \ setweaponspeed + \ setweapontype + \ setweathercloudspeedlower + \ setweathercloudspeedupper + \ setweathercolor + \ setweatherfogdayfar + \ setweatherfogdaynear + \ setweatherfognightfar + \ setweatherfognightnear + \ setweatherhdrvalue + \ setweatherlightningfrequency + \ setweathersundamage + \ setweathersunglare + \ setweathertransdelta + \ setweatherwindspeed + \ setweight + \ setwindspeed + \ showellmaking + \ sin + \ sinh + \ skipansqrt + \ squareroot + \ startcc + \ stringtoactorvalue + \ tan + \ tanh + \ tapcontrol + \ tapkey + \ testexpr + \ thiactorsai + \ togglecreaturemodel + \ togglefirstperson + \ toggleskillperk + \ togglespecialanim + \ tolower + \ tonumber + \ tostring + \ toupper + \ trapuphitshader + \ triggerplayerskilluse + \ triggerplayerskillusec + \ typeof + \ uncompletequest + \ unequipitemns + \ unequipitemsilent + \ unequipme + \ unhammerkey + \ unsetstagetext + \ update3d + \ updatecontainermenu + \ updatespellpurchasemenu + \ updatetextinput + \ vecmag + \ vecnorm + \ vectorcross + \ vectordot + \ vectormagnitude + \ vectornormalize + \ zeromat +" }}} + +" Array Functions {{{ +syn keyword obseArrayFunction + \ ar_Append + \ ar_BadNumericIndex + \ ar_BadStringIndex + \ ar_Construct + \ ar_Copy + \ ar_CustomSort + \ ar_DeepCopy + \ ar_Dump + \ ar_DumpID + \ ar_Erase + \ ar_Find + \ ar_First + \ ar_HasKey + \ ar_Insert + \ ar_InsertRange + \ ar_Keys + \ ar_Last + \ ar_List + \ ar_Map + \ ar_Next + \ ar_Null + \ ar_Prev + \ ar_Range + \ ar_Resize + \ ar_Size + \ ar_Sort + \ ar_SortAlpha +" }}} + +" String Functions {{{ +syn keyword obseStringFunction + \ sv_ToLower + \ sv_ToUpper + \ sv_Compare + \ sv_Construct + \ sv_Count + \ sv_Destruct + \ sv_Erase + \ sv_Find + \ sv_Insert + \ sv_Length + \ sv_Percentify + \ sv_Replace + \ sv_Split + \ sv_ToNumeric +" }}} + +" Pluggy Functions {{{ +syn keyword pluggyFunction + \ ArrayCmp + \ ArrayCount + \ ArrayEsp + \ ArrayProtect + \ ArraySize + \ AutoSclHudS + \ AutoSclHudT + \ CopyArray + \ CopyString + \ CreateArray + \ CreateEspBook + \ CreateString + \ DelAllHudSs + \ DelAllHudTs + \ DelFile + \ DelHudS + \ DelHudT + \ DelTxtFile + \ DestroyAllArrays + \ DestroyAllStrings + \ DestroyArray + \ DestroyString + \ DupArray + \ EspToString + \ FileToString + \ FindFirstFile + \ FindFloatInArray + \ FindInArray + \ FindNextFile + \ FindRefInArray + \ FirstFreeInArray + \ FirstInArray + \ FixName + \ FixNameEx + \ FloatToString + \ FmtString + \ FromOBSEString + \ FromTSFC + \ GetEsp + \ GetFileSize + \ GetInArray + \ GetRefEsp + \ GetTypeInArray + \ Halt + \ HasFixedName + \ HudSEsp + \ HudSProtect + \ HudS_Align + \ HudS_L + \ HudS_Opac + \ HudS_SclX + \ HudS_SclY + \ HudS_Show + \ HudS_Tex + \ HudS_X + \ HudS_Y + \ HudTEsp + \ HudTInfo + \ HudTProtect + \ HudT_Align + \ HudT_Font + \ HudT_L + \ HudT_Opac + \ HudT_SclX + \ HudT_SclY + \ HudT_Show + \ HudT_Text + \ HudT_X + \ HudT_Y + \ HudsInfo + \ IniDelKey + \ IniGetNthSection + \ IniKeyExists + \ IniReadFloat + \ IniReadInt + \ IniReadRef + \ IniReadString + \ IniSectionsCount + \ IniWriteFloat + \ IniWriteInt + \ IniWriteRef + \ IniWriteString + \ IntToHex + \ IntToString + \ IsHUDEnabled + \ IsPluggyDataReset + \ KillMenu + \ LC + \ LongToRef + \ ModRefEsp + \ NewHudS + \ NewHudT + \ PackArray + \ PauseBox + \ PlgySpcl + \ RefToLong + \ RefToString + \ RemInArray + \ RenFile + \ RenTxtFile + \ ResetName + \ RunBatString + \ SanString + \ ScreenInfo + \ SetFloatInArray + \ SetHudT + \ SetInArray + \ SetRefInArray + \ SetString + \ StrLC + \ StringCat + \ StringCmp + \ StringEsp + \ StringGetName + \ StringGetNameEx + \ StringIns + \ StringLen + \ StringMsg + \ StringMsgBox + \ StringPos + \ StringProtect + \ StringRep + \ StringSetName + \ StringSetNameEx + \ StringToFloat + \ StringToInt + \ StringToRef + \ StringToTxtFile + \ ToOBSE + \ ToOBSEString + \ ToTSFC + \ TxtFileExists + \ UserFileExists + \ csc + \ rcsc +" }}} + +" tfscFunction {{{ +syn keyword tfscFunction + \ StrAddNewLine + \ StrAppend + \ StrAppendCharCode + \ StrCat + \ StrClear + \ StrClearLast + \ StrCompare + \ StrCopy + \ StrDel + \ StrDeleteAll + \ StrExpr + \ StrGetFemaleBipedPath + \ StrGetFemaleGroundPath + \ StrGetFemaleIconPath + \ StrGetMaleBipedPath + \ StrGetMaleIconPath + \ StrGetModelPath + \ StrGetName + \ StrGetNthEffectItemScriptName + \ StrGetNthFactionRankName + \ StrGetRandomName + \ StrIDReplace + \ StrLength + \ StrLoad + \ StrMessageBox + \ StrNew + \ StrPrint + \ StrReplace + \ StrSave + \ StrSet + \ StrSetFemaleBipedPath + \ StrSetFemaleGroundPath + \ StrSetFemaleIconPath + \ StrSetMaleBipedPath + \ StrSetMaleIconPath + \ StrSetModelPath + \ StrSetName + \ StrSetNthEffectItemScriptName +" }}} + +" Blockhead Functions {{{ +syn keyword blockheadFunction + \ GetBodyAssetOverride + \ GetFaceGenAge + \ GetHeadAssetOverride + \ RefreshAnimData + \ RegisterEquipmentOverrideHandler + \ ResetAgeTextureOverride + \ ResetBodyAssetOverride + \ ResetHeadAssetOverride + \ SetAgeTextureOverride + \ SetBodyAssetOverride + \ SetFaceGenAge + \ SetHeadAssetOverride + \ ToggleAnimOverride + \ UnregisterEquipmentOverrideHandler +" }}} + +" switchNightEyeShaderFunction {{{ +syn keyword switchNightEyeShaderFunction + \ EnumNightEyeShader + \ SetNightEyeShader +" }}} + +" Oblivion Reloaded Functions {{{ +syn keyword obseivionReloadedFunction + \ cameralookat + \ cameralookatposition + \ camerareset + \ camerarotate + \ camerarotatetoposition + \ cameratranslate + \ cameratranslatetoposition + \ getlocationname + \ getsetting + \ getversion + \ getweathername + \ isthirdperson + \ setcustomconstant + \ setextraeffectenabled + \ setsetting +" }}} +" menuQue Functions {{{ +syn keyword menuQueFunction + \ GetAllSkills + \ GetAVSkillMasteryLevelC + \ GetAVSkillMasteryLevelF + \ GetFontLoaded + \ GetGenericButtonPressed + \ GetLoadedFonts + \ GetLocalMapSeen + \ GetMenuEventType + \ GetMenuFloatValue + \ GetMenuStringValue + \ GetMouseImage + \ GetMousePos + \ GetPlayerSkillAdvancesF + \ GetPlayerSkillUseF + \ GetRequiredSkillExpC + \ GetRequiredSkillExpF + \ GetSkillCode + \ GetSkillForm + \ GetSkillGoverningAttributeF + \ GetSkillSpecializationC + \ GetSkillSpecializationF + \ GetSkillUseIncrementF + \ GetTextEditBox + \ GetTextEditString + \ GetTrainingMenuCost + \ GetTrainingMenuLevel + \ GetTrainingMenuSkill + \ GetWorldMapData + \ GetWorldMapDoor + \ IncrementPlayerSkillUseF + \ InsertXML + \ InsertXMLTemplate + \ IsTextEditInUse + \ Kyoma_Test + \ ModPlayerSkillExpF + \ mqCreateMenuFloatValue + \ mqCreateMenuStringValue + \ mqGetActiveQuest + \ mqGetActiveQuestTargets + \ mqGetCompletedQuests + \ mqGetCurrentQuests + \ mqGetEnchMenuBaseItem + \ mqGetHighlightedClass + \ mqGetMapMarkers + \ mqGetMenuActiveChildIndex + \ mqGetMenuActiveFloatValue + \ mqGetMenuActiveStringValue + \ mqGetMenuChildCount + \ mqGetMenuChildFloatValue + \ mqGetMenuChildHasTrait + \ mqGetMenuChildName + \ mqGetMenuChildStringValue + \ mqGetMenuGlobalFloatValue + \ mqGetMenuGlobalStringValue + \ mqGetQuestCompleted + \ mqGetSelectedClass + \ mqSetActiveQuest + \ mqSetMenuActiveFloatValue + \ mqSetMenuActiveStringValue + \ mqSetMenuChildFloatValue + \ mqSetMenuChildStringValue + \ mqSetMenuGlobalStringValue + \ mqSetMenuGlobalFloatValue + \ mqSetMessageBoxSource + \ mqUncompleteQuest + \ RemoveMenuEventHandler + \ SetMenuEventHandler + \ SetMouseImage + \ SetPlayerSkillAdvancesF + \ SetSkillGoverningAttributeF + \ SetSkillSpecializationC + \ SetSkillSpecializationF + \ SetSkillUseIncrementF + \ SetTextEditString + \ SetTrainerSkillC + \ SetWorldMapData + \ ShowGenericMenu + \ ShowLevelUpMenu + \ ShowMagicPopupMenu + \ ShowTextEditMenu + \ ShowTrainingMenu + \ tile_FadeFloat + \ tile_GetFloat + \ tile_GetInfo + \ tile_GetName + \ tile_GetString + \ tile_GetVar + \ tile_HasTrait + \ tile_SetFloat + \ tile_SetString + \ TriggerPlayerSkillUseF + \ UpdateLocalMap +" }}} + +" eaxFunction {{{ +syn keyword eaxFunction + \ CreateEAXeffect + \ DeleteEAXeffect + \ DisableEAX + \ EAXcopyEffect + \ EAXeffectExists + \ EAXeffectsAreEqual + \ EAXgetActiveEffect + \ EAXnumEffects + \ EAXpushEffect + \ EAXpopEffect + \ EAXremoveAllInstances + \ EAXremoveFirstInstance + \ EAXstackIsEmpty + \ EAXstackSize + \ EnableEAX + \ GetEAXAirAbsorptionHF + \ GetEAXDecayHFRatio + \ GetEAXDecayTime + \ GetEAXEnvironment + \ GetEAXEnvironmentSize + \ GetEAXEnvironmentDiffusion + \ GetEAXReflections + \ GetEAXReflectionsDelay + \ GetEAXReverb + \ GetEAXReverbDelay + \ GetEAXRoom + \ GetEAXRoomHF + \ GetEAXRoomRolloffFactor + \ InitializeEAX + \ IsEAXEnabled + \ IsEAXInitialized + \ SetEAXAirAbsorptionHF + \ SetEAXallProperties + \ SetEAXDecayTime + \ SetEAXDecayHFRatio + \ SetEAXEnvironment + \ SetEAXEnvironmentSize + \ SetEAXEnvironmentDiffusion + \ SetEAXReflections + \ SetEAXReflectionsDelay + \ SetEAXReverb + \ SetEAXReverbDelay + \ SetEAXRoom + \ SetEAXRoomHF + \ SetEAXRoomRolloffFactor +" }}} + +" networkPipeFunction {{{ +syn keyword networkPipeFunction + \ NetworkPipe_CreateClient + \ NetworkPipe_GetData + \ NetworkPipe_IsNewGame + \ NetworkPipe_KillClient + \ NetworkPipe_Receive + \ NetworkPipe_SetData + \ NetworkPipe_Send + \ NetworkPipe_StartService + \ NetworkPipe_StopService +" }}} + +" nifseFunction {{{ +syn keyword nifseFunction + \ BSFurnitureMarkerGetPositionRefs + \ BSFurnitureMarkerSetPositionRefs + \ GetNifTypeIndex + \ NiAVObjectAddProperty + \ NiAVObjectClearCollisionObject + \ NiAVObjectCopyCollisionObject + \ NiAVObjectDeleteProperty + \ NiAVObjectGetCollisionMode + \ NiAVObjectGetCollisionObject + \ NiAVObjectGetLocalRotation + \ NiAVObjectGetLocalScale + \ NiAVObjectGetLocalTransform + \ NiAVObjectGetLocalTranslation + \ NiAVObjectGetNumProperties + \ NiAVObjectGetProperties + \ NiAVObjectGetPropertyByType + \ NiAVObjectSetCollisionMode + \ NiAVObjectSetLocalRotation + \ NiAVObjectSetLocalScale + \ NiAVObjectSetLocalTransform + \ NiAVObjectSetLocalTranslation + \ NiAlphaPropertyGetBlendState + \ NiAlphaPropertyGetDestinationBlendFunction + \ NiAlphaPropertyGetSourceBlendFunction + \ NiAlphaPropertyGetTestFunction + \ NiAlphaPropertyGetTestState + \ NiAlphaPropertyGetTestThreshold + \ NiAlphaPropertyGetTriangleSortMode + \ NiAlphaPropertySetBlendState + \ NiAlphaPropertySetDestinationBlendFunction + \ NiAlphaPropertySetSourceBlendFunction + \ NiAlphaPropertySetTestFunction + \ NiAlphaPropertySetTestState + \ NiAlphaPropertySetTestThreshold + \ NiAlphaPropertySetTriangleSortMode + \ NiExtraDataGetArray + \ NiExtraDataGetName + \ NiExtraDataGetNumber + \ NiExtraDataGetString + \ NiExtraDataSetArray + \ NiExtraDataSetName + \ NiExtraDataSetNumber + \ NiExtraDataSetString + \ NiMaterialPropertyGetAmbientColor + \ NiMaterialPropertyGetDiffuseColor + \ NiMaterialPropertyGetEmissiveColor + \ NiMaterialPropertyGetGlossiness + \ NiMaterialPropertyGetSpecularColor + \ NiMaterialPropertyGetTransparency + \ NiMaterialPropertySetAmbientColor + \ NiMaterialPropertySetDiffuseColor + \ NiMaterialPropertySetEmissiveColor + \ NiMaterialPropertySetGlossiness + \ NiMaterialPropertySetSpecularColor + \ NiMaterialPropertySetTransparency + \ NiNodeAddChild + \ NiNodeCopyChild + \ NiNodeDeleteChild + \ NiNodeGetChildByName + \ NiNodeGetChildren + \ NiNodeGetNumChildren + \ NiObjectGetType + \ NiObjectGetTypeName + \ NiObjectNETAddExtraData + \ NiObjectNETDeleteExtraData + \ NiObjectNETGetExtraData + \ NiObjectNETGetExtraDataByName + \ NiObjectNETGetName + \ NiObjectNETGetNumExtraData + \ NiObjectNETSetName + \ NiObjectTypeDerivesFrom + \ NiSourceTextureGetFile + \ NiSourceTextureIsExternal + \ NiSourceTextureSetExternalTexture + \ NiStencilPropertyGetFaceDrawMode + \ NiStencilPropertyGetFailAction + \ NiStencilPropertyGetPassAction + \ NiStencilPropertyGetStencilFunction + \ NiStencilPropertyGetStencilMask + \ NiStencilPropertyGetStencilRef + \ NiStencilPropertyGetStencilState + \ NiStencilPropertyGetZFailAction + \ NiStencilPropertySetFaceDrawMode + \ NiStencilPropertySetFailAction + \ NiStencilPropertySetPassAction + \ NiStencilPropertySetStencilFunction + \ NiStencilPropertySetStencilMask + \ NiStencilPropertySetStencilRef + \ NiStencilPropertySetStencilState + \ NiStencilPropertySetZFailAction + \ NiTexturingPropertyAddTextureSource + \ NiTexturingPropertyDeleteTextureSource + \ NiTexturingPropertyGetTextureCenterOffset + \ NiTexturingPropertyGetTextureClampMode + \ NiTexturingPropertyGetTextureCount + \ NiTexturingPropertyGetTextureFilterMode + \ NiTexturingPropertyGetTextureFlags + \ NiTexturingPropertyGetTextureRotation + \ NiTexturingPropertyGetTextureSource + \ NiTexturingPropertyGetTextureTiling + \ NiTexturingPropertyGetTextureTranslation + \ NiTexturingPropertyGetTextureUVSet + \ NiTexturingPropertyHasTexture + \ NiTexturingPropertySetTextureCenterOffset + \ NiTexturingPropertySetTextureClampMode + \ NiTexturingPropertySetTextureCount + \ NiTexturingPropertySetTextureFilterMode + \ NiTexturingPropertySetTextureFlags + \ NiTexturingPropertySetTextureHasTransform + \ NiTexturingPropertySetTextureRotation + \ NiTexturingPropertySetTextureTiling + \ NiTexturingPropertySetTextureTranslation + \ NiTexturingPropertySetTextureUVSet + \ NiTexturingPropertyTextureHasTransform + \ NiVertexColorPropertyGetLightingMode + \ NiVertexColorPropertyGetVertexMode + \ NiVertexColorPropertySetLightingMode + \ NiVertexColorPropertySetVertexMode + \ NifClose + \ NifGetAltGrip + \ NifGetBackShield + \ NifGetNumBlocks + \ NifGetOffHand + \ NifGetOriginalPath + \ NifGetPath + \ NifOpen + \ NifWriteToDisk +" }}} + +" reidFunction {{{ +syn keyword reidFunction + \ GetRuntimeEditorID +" }}} + +" runtimeDebuggerFunction {{{ +syn keyword runtimeDebuggerFunction + \ DebugBreak + \ ToggleDebugBreaking +" }}} + +" addActorValuesFunction {{{ +syn keyword addActorValuesFunction + \ DumpActorValueC + \ DumpActorValueF + \ GetActorValueBaseCalcC + \ GetActorValueBaseCalcF + \ GetActorValueCurrentC + \ GetActorValueCurrentF + \ GetActorValueMaxC + \ GetActorValueMaxF + \ GetActorValueModC + \ GetActorValueModF + \ ModActorValueModC + \ ModActorValueModF + \ SetActorValueModC + \ SetActorValueModF + \ DumpAVC + \ DumpAVF + \ GetAVModC + \ GetAVModF + \ ModAVModC + \ ModAVModF + \ SetAVModC + \ SetAVModF + \ GetAVBaseCalcC + \ GetAVBaseCalcF + \ GetAVMaxC + \ GetAVMaxF + \ GetAVCurrentC + \ GetAVCurrent +" }}} + +" memoryDumperFunction {{{ +syn keyword memoryDumperFunction + \ SetDumpAddr + \ SetDumpType + \ SetFadeAmount + \ SetObjectAddr + \ ShowMemoryDump +" }}} + +" algoholFunction {{{ +syn keyword algoholFunction + \ QFromAxisAngle + \ QFromEuler + \ QInterpolate + \ QMultQuat + \ QMultVector3 + \ QNormalize + \ QToEuler + \ V3Crossproduct + \ V3Length + \ V3Normalize +" }}} + +" soundCommandsFunction {{{ +syn keyword soundCommandsFunction + \ FadeMusic + \ GetEffectsVolume + \ GetFootVolume + \ GetMasterVolume + \ GetMusicVolume + \ GetVoiceVolume + \ PlayMusicFile + \ SetEffectsVolume + \ SetFootVolume + \ SetMasterVolume + \ SetMusicVolume + \ SetVoiceVolume +" }}} + +" emcFunction {{{ +syn keyword emcFunction + \ emcAddPathToPlaylist + \ emcCreatePlaylist + \ emcGetAllPlaylists + \ emcGetAfterBattleDelay + \ emcGetBattleDelay + \ emcGetEffectsVolume + \ emcGetFadeTime + \ emcGetFootVolume + \ emcGetMasterVolume + \ emcGetMaxRestoreTime + \ emcGetMusicSpeed + \ emcGetMusicType + \ emcGetMusicVolume + \ emcGetPauseTime + \ emcGetPlaylist + \ emcGetPlaylistTracks + \ emcGetTrackName + \ emcGetTrackDuration + \ emcGetTrackPosition + \ emcGetVoiceVolume + \ emcIsBattleOverridden + \ emcIsMusicOnHold + \ emcIsMusicSwitching + \ emcIsPlaylistActive + \ emcMusicNextTrack + \ emcMusicPause + \ emcMusicRestart + \ emcMusicResume + \ emcMusicStop + \ emcPlaylistExists + \ emcPlayTrack + \ emcRestorePlaylist + \ emcSetAfterBattleDelay + \ emcSetBattleDelay + \ emcSetBattleOverride + \ emcSetEffectsVolume + \ emcSetFadeTime + \ emcSetFootVolume + \ emcSetMasterVolume + \ emcSetMaxRestoreTime + \ emcSetMusicHold + \ emcSetMusicSpeed + \ emcSetMusicVolume + \ emcSetPauseTime + \ emcSetPlaylist + \ emcSetTrackPosition + \ emcSetMusicType + \ emcSetVoiceVolume +" }}} + +" vipcxjFunction {{{ +syn keyword vipcxjFunction + \ vcAddMark + \ vcGetFilePath + \ vcGetHairColorRGB + \ vcGetValueNumeric + \ vcGetValueString + \ vcIsMarked + \ vcPrintIni + \ vcSetActorState + \ vcSetHairColor + \ vcSetHairColorRGB + \ vcSetHairColorRGB3P +" }}} + +" cameraCommandsFunction {{{ +syn keyword cameraCommandsFunction + \ CameraGetRef + \ CameraLookAt + \ CameraLookAtPosition + \ CameraMove + \ CameraMoveToPosition + \ CameraReset + \ CameraRotate + \ CameraRotateToPosition + \ CameraSetRef + \ CameraStopLook +" }}} + +" obmeFunction {{{ +syn keyword obmeFunction + \ ClearNthEIBaseCost + \ ClearNthEIEffectName + \ ClearNthEIHandlerParam + \ ClearNthEIHostility + \ ClearNthEIIconPath + \ ClearNthEIResistAV + \ ClearNthEISchool + \ ClearNthEIVFXCode + \ CreateMgef + \ GetMagicEffectHandlerC + \ GetMagicEffectHandlerParamC + \ GetMagicEffectHostilityC + \ GetNthEIBaseCost + \ GetNthEIEffectName + \ GetNthEIHandlerParam + \ GetNthEIHostility + \ GetNthEIIconPath + \ GetNthEIResistAV + \ GetNthEISchool + \ GetNthEIVFXCode + \ ResolveMgefCode + \ SetMagicEffectHandlerC + \ SetMagicEffectHandlerIntParamC + \ SetMagicEffectHandlerRefParamC + \ SetMagicEffectHostilityC + \ SetNthEIBaseCost + \ SetNthEIEffectName + \ SetNthEIHandlerIntParam + \ SetNthEIHandlerRefParam + \ SetNthEIHostility + \ SetNthEIIconPath + \ SetNthEIResistAV + \ SetNthEISchool + \ SetNthEIVFXCode +" }}} + +" conscribeFunction {{{ +syn keyword conscribeFunction + \ DeleteLinesFromLog + \ GetLogLineCount + \ GetRegisteredLogNames + \ ReadFromLog + \ RegisterLog + \ Scribe + \ UnregisterLog +" }}} + +" systemDialogFunction {{{ +syn keyword systemDialogFunction + \ Sysdlg_Browser + \ Sysdlg_ReadBrowser + \ Sysdlg_TextInput +" }}} + +" csiFunction {{{ +syn keyword csiFunction + \ ClearSpellIcon + \ HasAssignedIcon + \ OverwriteSpellIcon + \ SetSpellIcon +" }}} + +" haelFunction {{{ +syn keyword haelFunction + \ GetHUDActiveEffectLimit + \ SetHUDActiveEffectLimit +" }}} + +" lcdFunction {{{ +syn keyword lcdFunction + \ lcd_addinttobuffer + \ lcd_addtexttobuffer + \ lcd_clearrect + \ lcd_cleartextbuffer + \ lcd_close + \ lcd_drawcircle + \ lcd_drawgrid + \ lcd_drawint + \ lcd_drawline + \ lcd_drawprogressbarh + \ lcd_drawprogressbarv + \ lcd_drawprogresscircle + \ lcd_drawrect + \ lcd_drawtext + \ lcd_drawtextbuffer + \ lcd_drawtexture + \ lcd_flush + \ lcd_getbuttonstate + \ lcd_getheight + \ lcd_getwidth + \ lcd_ismulti + \ lcd_isopen + \ lcd_open + \ lcd_refresh + \ lcd_savebuttonsnapshot + \ lcd_scale + \ lcd_setfont +" }}} + +" Deprecated: {{{ +syn keyword obDeprecated + \ SetAltControl + \ GetAltControl + \ RefreshControlMap +" }}} +" }}} + +if !exists("did_obl_inits") + + let did_obl_inits = 1 + hi def link obseStatement Statement + hi def link obseStatementTwo Statement + hi def link obseDescBlock String + hi def link obseComment Comment + hi def link obseString String + hi def link obseStringFormatting Keyword + hi def link obseFloat Float + hi def link obseInt Number + hi def link obseToDo Todo + hi def link obseTypes Type + hi def link obseCondition Conditional + hi def link obseOperator Operator + hi def link obseOtherKey Special + hi def link obseScriptName Special + hi def link obseBlock Conditional + hi def link obseBlockType Structure + hi def link obseScriptNameRegion Underlined + hi def link obseNames Identifier + hi def link obseVariable Identifier + hi def link obseReference Special + hi def link obseRepeat Repeat + + hi def link csFunction Function + hi def link obseFunction Function + hi def link obseArrayFunction Function + hi def link pluggyFunction Function + hi def link obseStringFunction Function + hi def link obseArrayFunction Function + hi def link tsfcFunction Function + hi def link blockheadFunction Function + hi def link switchNightEyeShaderFunction Function + hi def link obseivionReloadedFunction Function + hi def link menuQueFunction Function + hi def link eaxFunction Function + hi def link networkPipeFunction Function + hi def link nifseFunction Function + hi def link reidFunction Function + hi def link runtimeDebuggerFunction Function + hi def link addActorValuesFunction Function + hi def link memoryDumperFunction Function + hi def link algoholFunction Function + hi def link soundCommandsFunction Function + hi def link emcFunction Function + hi def link vipcxjFunction Function + hi def link cameraCommands Function + hi def link obmeFunction Function + hi def link conscribeFunction Function + hi def link systemDialogFunction Function + hi def link csiFunction Function + hi def link haelFunction Function + hi def link lcdFunction Function + hi def link skillAttribute String + hi def link obDeprecated WarningMsg + +endif + +let b:current_syntax = 'obse' + +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/runtime/syntax/openvpn.vim b/runtime/syntax/openvpn.vim new file mode 100644 index 0000000000..02fd24bf39 --- /dev/null +++ b/runtime/syntax/openvpn.vim @@ -0,0 +1,72 @@ +" Vim syntax file +" Language: OpenVPN +" Maintainer: ObserverOfTime <chronobserver@disroot.org> +" Filenames: *.ovpn +" Last Change: 2022 Oct 16 + +if exists('b:current_syntax') + finish +endif + +let s:cpo_save = &cpoptions +set cpoptions&vim + +" Options +syntax match openvpnOption /^[a-z-]\+/ + \ skipwhite nextgroup=openvpnArgList +syntax match openvpnArgList /.*$/ transparent contained + \ contains=openvpnArgument,openvpnNumber, + \ openvpnIPv4Address,openvpnIPv6Address, + \ openvpnSignal,openvpnComment + +" Arguments +syntax match openvpnArgument /[^\\"' \t]\+/ + \ contained contains=openvpnEscape +syntax region openvpnArgument matchgroup=openvpnQuote + \ start=/"/ skip=/\\"/ end=/"/ + \ oneline contained contains=openvpnEscape +syntax region openvpnArgument matchgroup=openvpnQuote + \ start=/'/ skip=/\\'/ end=/'/ + \ oneline contained +syntax match openvpnEscape /\\[\\" \t]/ contained + +" Numbers +syntax match openvpnNumber /\<[1-9][0-9]*\(\.[0-9]\+\)\?\>/ contained + +" Signals +syntax match openvpnSignal /SIG\(HUP\|INT\|TERM\|USER[12]\)/ contained + +" IP addresses +syntax match openvpnIPv4Address /\(\d\{1,3}\.\)\{3}\d\{1,3}/ + \ contained nextgroup=openvpnSlash +syntax match openvpnIPv6Address /\([A-F0-9]\{1,4}:\)\{7}\[A-F0-9]\{1,4}/ + \ contained nextgroup=openvpnSlash +syntax match openvpnSlash "/" contained + \ nextgroup=openvpnIPv4Address,openvpnIPv6Address,openvpnNumber + +" Inline files +syntax region openvpnInline matchgroup=openvpnTag + \ start=+^<\z([a-z-]\+\)>+ end=+^</\z1>+ + +" Comments +syntax keyword openvpnTodo contained TODO FIXME NOTE XXX +syntax match openvpnComment /^[;#].*$/ contains=openvpnTodo +syntax match openvpnComment /\s\+\zs[;#].*$/ contains=openvpnTodo + +hi def link openvpnArgument String +hi def link openvpnComment Comment +hi def link openvpnEscape SpecialChar +hi def link openvpnIPv4Address Constant +hi def link openvpnIPv6Address Constant +hi def link openvpnNumber Number +hi def link openvpnOption Keyword +hi def link openvpnQuote Quote +hi def link openvpnSignal Special +hi def link openvpnSlash Delimiter +hi def link openvpnTag Tag +hi def link openvpnTodo Todo + +let b:current_syntax = 'openvpn' + +let &cpoptions = s:cpo_save +unlet s:cpo_save diff --git a/runtime/syntax/poefilter.vim b/runtime/syntax/poefilter.vim new file mode 100644 index 0000000000..f7e92034ee --- /dev/null +++ b/runtime/syntax/poefilter.vim @@ -0,0 +1,167 @@ +" Vim syntax file +" Language: PoE item filter +" Maintainer: ObserverOfTime <chronobserver@disroot.org> +" Filenames: *.filter +" Last Change: 2022 Oct 07 + +if exists('b:current_syntax') + finish +endif + +let s:cpo_save = &cpoptions +set cpoptions&vim + +" Comment +syn keyword poefilterTodo TODO NOTE XXX contained +syn match poefilterCommentTag /\[[0-9A-Z\[\]]\+\]/ contained +syn match poefilterComment /#.*$/ contains=poefilterTodo,poefilterCommentTag,@Spell + +" Blocks +syn keyword poefilterBlock Show Hide + +" Conditions +syn keyword poefilterCondition + \ AlternateQuality + \ AnyEnchantment + \ BlightedMap + \ Corrupted + \ ElderItem + \ ElderMap + \ FracturedItem + \ Identified + \ Mirrored + \ Replica + \ Scourged + \ ShapedMap + \ ShaperItem + \ SynthesisedItem + \ UberBlightedMap + \ skipwhite nextgroup=poefilterBoolean +syn keyword poefilterCondition + \ ArchnemesisMod + \ BaseType + \ Class + \ EnchantmentPassiveNode + \ HasEnchantment + \ HasExplicitMod + \ ItemLevel + \ SocketGroup + \ Sockets + \ skipwhite nextgroup=poefilterOperator,poefilterString +syn keyword poefilterCondition + \ AreaLevel + \ BaseArmour + \ BaseDefencePercentile + \ BaseEnergyShield + \ BaseEvasion + \ BaseWard + \ CorruptedMods + \ DropLevel + \ EnchantmentPassiveNum + \ GemLevel + \ HasEaterOfWorldsImplicit + \ HasSearingExarchImplicit + \ Height + \ LinkedSockets + \ MapTier + \ Quality + \ StackSize + \ Width + \ skipwhite nextgroup=poefilterOperator,poefilterNumber +syn keyword poefilterCondition + \ GemQualityType + \ skipwhite nextgroup=poefilterString,poefilterQuality +syn keyword poefilterCondition + \ HasInfluence + \ skipwhite nextgroup=poefilterString,poefilterInfluence +syn keyword poefilterCondition + \ Rarity + \ skipwhite nextgroup=poefilterString,poefilterRarity + +" Actions +syn keyword poefilterAction + \ PlayAlertSound + \ PlayAlertSoundPositional + \ skipwhite nextgroup=poefilterNumber,poefilterDisable +syn keyword poefilterAction + \ CustomAlertSound + \ CustomAlertSoundOptional + \ skipwhite nextgroup=poefilterString +syn keyword poefilterAction + \ DisableDropSound + \ EnableDropSound + \ DisableDropSoundIfAlertSound + \ EnableDropSoundIfAlertSound + \ skipwhite nextgroup=poefilterBoolean +syn keyword poefilterAction + \ MinimapIcon + \ SetBackgroundColor + \ SetBorderColor + \ SetFontSize + \ SetTextColor + \ skipwhite nextgroup=poefilterNumber +syn keyword poefilterAction + \ PlayEffect + \ skipwhite nextgroup=poefilterColour + +" Operators +syn match poefilterOperator /!\|[<>=]=\?/ contained + \ skipwhite nextgroup=poefilterString,poefilterNumber, + \ poefilterQuality,poefilterRarity,poefilterInfluence + +" Arguments +syn match poefilterString /[-a-zA-Z0-9:,']/ contained contains=@Spell + \ skipwhite nextgroup=poefilterString,poefilterNumber, + \ poefilterQuality,poefilterRarity,poefilterInfluence +syn region poefilterString matchgroup=poefilterQuote keepend + \ start=/"/ end=/"/ concealends contained contains=@Spell + \ skipwhite nextgroup=poefilterString,poefilterNumber, + \ poefilterQuality,poefilterRarity,poefilterInfluence +syn match poefilterNumber /-1\|0\|[1-9][0-9]*/ contained + \ skipwhite nextgroup=poefilterString,poefilterNumber, + \ poefilterQuality,poefilterRarity,poefilterInfluence,poefilterColour +syn keyword poefilterBoolean True False contained + +" Special arguments (conditions) +syn keyword poefilterQuality Superior Divergent Anomalous Phantasmal + \ contained skipwhite nextgroup=poefilterString,poefilterQuality +syn keyword poefilterRarity Normal Magic Rare Unique + \ contained skipwhite nextgroup=poefilterString,poefilterRarity +syn keyword poefilterInfluence Shaper Elder + \ Crusader Hunter Redeemer Warlord None + \ contained skipwhite nextgroup=poefilterString,poefilterInfluence + +" Special arguments (actions) +syn keyword poefilterColour Red Green Blue Brown + \ White Yellow Cyan Grey Orange Pink Purple + \ contained skipwhite nextgroup=poefilterShape,poefilterTemp +syn keyword poefilterShape Circle Diamond Hecagon Square Star Triangle + \ Cross Moon Raindrop Kite Pentagon UpsideDownHouse contained +syn keyword poefilterDisable None contained +syn keyword poefilterTemp Temp contained + +" Colours + +hi def link poefilterAction Statement +hi def link poefilterBlock Structure +hi def link poefilterBoolean Boolean +hi def link poefilterColour Special +hi def link poefilterComment Comment +hi def link poefilterCommentTag SpecialComment +hi def link poefilterCondition Conditional +hi def link poefilterDisable Constant +hi def link poefilterInfluence Special +hi def link poefilterNumber Number +hi def link poefilterOperator Operator +hi def link poefilterQuality Special +hi def link poefilterQuote Delimiter +hi def link poefilterRarity Special +hi def link poefilterShape Special +hi def link poefilterString String +hi def link poefilterTemp StorageClass +hi def link poefilterTodo Todo + +let b:current_syntax = 'poefilter' + +let &cpoptions = s:cpo_save +unlet s:cpo_save diff --git a/runtime/syntax/ptcap.vim b/runtime/syntax/ptcap.vim index 1ebeb5227b..5db7bda896 100644 --- a/runtime/syntax/ptcap.vim +++ b/runtime/syntax/ptcap.vim @@ -53,7 +53,7 @@ syn match ptcapNumberError "#0x\x*[^[:xdigit:]:\\]"lc=1 contained " The `=' operator assigns a string to the preceding flag syn match ptcapOperator "[@#=]" contained -" Some terminal capabilites have special names like `#5' and `@1', and we +" Some terminal capabilities have special names like `#5' and `@1', and we " need special rules to match these properly syn match ptcapSpecialCap "\W[#@]\d" contains=ptcapDelimiter contained diff --git a/runtime/syntax/rego.vim b/runtime/syntax/rego.vim index a04fc7007b..bc82030488 100644 --- a/runtime/syntax/rego.vim +++ b/runtime/syntax/rego.vim @@ -2,7 +2,7 @@ " Language: rego policy language " Maintainer: Matt Dunford (zenmatic@gmail.com) " URL: https://github.com/zenmatic/vim-syntax-rego -" Last Change: 2019 Dec 12 +" Last Change: 2022 Dec 4 " https://www.openpolicyagent.org/docs/latest/policy-language/ @@ -14,36 +14,56 @@ endif syn case match syn keyword regoDirective package import allow deny -syn keyword regoKeywords as default else false not null true with some +syn keyword regoKeywords as default else every false if import package not null true with some in print syn keyword regoFuncAggregates count sum product max min sort all any -syn match regoFuncArrays "\<array\.\(concat\|slice\)\>" +syn match regoFuncArrays "\<array\.\(concat\|slice\|reverse\)\>" syn keyword regoFuncSets intersection union -syn keyword regoFuncStrings concat /\<contains\>/ endswith format_int indexof lower replace split sprintf startswith substring trim trim_left trim_prefix trim_right trim_suffix trim_space upper -syn match regoFuncStrings2 "\<strings\.replace_n\>" +syn keyword regoFuncStrings concat /\<contains\>/ endswith format_int indexof indexof_n lower replace split sprintf startswith substring trim trim_left trim_prefix trim_right trim_suffix trim_space upper +syn match regoFuncStrings2 "\<strings\.\(replace_n\|reverse\|any_prefix_match\|any_suffix_match\)\>" syn match regoFuncStrings3 "\<contains\>" syn keyword regoFuncRegex re_match -syn match regoFuncRegex2 "\<regex\.\(split\|globs_match\|template_match\|find_n\|find_all_string_submatch_n\)\>" +syn match regoFuncRegex2 "\<regex\.\(is_valid\|split\|globs_match\|template_match\|find_n\|find_all_string_submatch_n\|replace\)\>" +syn match regoFuncUuid "\<uuid.rfc4122\>" +syn match regoFuncBits "\<bits\.\(or\|and\|negate\|xor\|lsh\|rsh\)\>" +syn match regoFuncObject "\<object\.\(get\|remove\|subset\|union\|union_n\|filter\)\>" syn match regoFuncGlob "\<glob\.\(match\|quote_meta\)\>" -syn match regoFuncUnits "\<units\.parse_bytes\>" +syn match regoFuncUnits "\<units\.parse\(_bytes\)\=\>" syn keyword regoFuncTypes is_number is_string is_boolean is_array is_set is_object is_null type_name -syn match regoFuncEncoding1 "\<\(base64\|base64url\)\.\(encode\|decode\)\>" -syn match regoFuncEncoding2 "\<urlquery\.\(encode\|decode\|encode_object\)\>" -syn match regoFuncEncoding3 "\<\(json\|yaml\)\.\(marshal\|unmarshal\)\>" +syn match regoFuncEncoding1 "\<base64\.\(encode\|decode\|is_valid\)\>" +syn match regoFuncEncoding2 "\<base64url\.\(encode\(_no_pad\)\=\|decode\)\>" +syn match regoFuncEncoding3 "\<urlquery\.\(encode\|decode\|\(en\|de\)code_object\)\>" +syn match regoFuncEncoding4 "\<\(json\|yaml\)\.\(is_valid\|marshal\|unmarshal\)\>" +syn match regoFuncEncoding5 "\<json\.\(filter\|patch\|remove\)\>" syn match regoFuncTokenSigning "\<io\.jwt\.\(encode_sign_raw\|encode_sign\)\>" -syn match regoFuncTokenVerification "\<io\.jwt\.\(verify_rs256\|verify_ps256\|verify_es256\|verify_hs256\|decode\|decode_verify\)\>" -syn match regoFuncTime "\<time\.\(now_ns\|parse_ns\|parse_rfc3339_ns\|parse_duration_ns\|date\|clock\|weekday\)\>" -syn match regoFuncCryptography "\<crypto\.x509\.parse_certificates\>" +syn match regoFuncTokenVerification1 "\<io\.jwt\.\(decode\|decode_verify\)\>" +syn match regoFuncTokenVerification2 "\<io\.jwt\.verify_\(rs\|ps\|es\|hs\)\(256\|384\|512\)\>" +syn match regoFuncTime "\<time\.\(now_ns\|parse_ns\|parse_rfc3339_ns\|parse_duration_ns\|date\|clock\|weekday\|diff\|add_date\)\>" +syn match regoFuncCryptography "\<crypto\.x509\.\(parse_certificates\|parse_certificate_request\|parse_and_verify_certificates\|parse_rsa_private_key\)\>" +syn match regoFuncCryptography "\<crypto\.\(md5\|sha1\|sha256\)" +syn match regoFuncCryptography "\<crypto\.hmac\.\(md5\|sha1\|sha256\|sha512\)" syn keyword regoFuncGraphs walk +syn match regoFuncGraphs2 "\<graph\.reachable\(_paths\)\=\>" +syn match regoFuncGraphQl "\<graphql\.\(\(schema_\)\=is_valid\|parse\(_\(and_verify\|query\|schema\)\)\=\)\>" syn match regoFuncHttp "\<http\.send\>" -syn match regoFuncNet "\<net\.\(cidr_contains\|cidr_intersects\)\>" -syn match regoFuncRego "\<rego\.parse_module\>" +syn match regoFuncNet "\<net\.\(cidr_merge\|cidr_contains\|cidr_contains_matches\|cidr_intersects\|cidr_expand\|lookup_ip_addr\|cidr_is_valid\)\>" +syn match regoFuncRego "\<rego\.\(parse_module\|metadata\.\(rule\|chain\)\)\>" syn match regoFuncOpa "\<opa\.runtime\>" syn keyword regoFuncDebugging trace +syn match regoFuncRand "\<rand\.intn\>" +syn match regoFuncNumbers "\<numbers\.\(range\|intn\)\>" +syn keyword regoFuncNumbers round ceil floor abs + +syn match regoFuncSemver "\<semver\.\(is_valid\|compare\)\>" +syn keyword regoFuncConversions to_number +syn match regoFuncHex "\<hex\.\(encode\|decode\)\>" + +hi def link regoFuncUuid Statement +hi def link regoFuncBits Statement hi def link regoDirective Statement hi def link regoKeywords Statement hi def link regoFuncAggregates Statement @@ -60,16 +80,27 @@ hi def link regoFuncTypes Statement hi def link regoFuncEncoding1 Statement hi def link regoFuncEncoding2 Statement hi def link regoFuncEncoding3 Statement +hi def link regoFuncEncoding4 Statement +hi def link regoFuncEncoding5 Statement hi def link regoFuncTokenSigning Statement -hi def link regoFuncTokenVerification Statement +hi def link regoFuncTokenVerification1 Statement +hi def link regoFuncTokenVerification2 Statement hi def link regoFuncTime Statement hi def link regoFuncCryptography Statement hi def link regoFuncGraphs Statement +hi def link regoFuncGraphQl Statement +hi def link regoFuncGraphs2 Statement hi def link regoFuncHttp Statement hi def link regoFuncNet Statement hi def link regoFuncRego Statement hi def link regoFuncOpa Statement hi def link regoFuncDebugging Statement +hi def link regoFuncObject Statement +hi def link regoFuncNumbers Statement +hi def link regoFuncSemver Statement +hi def link regoFuncConversions Statement +hi def link regoFuncHex Statement +hi def link regoFuncRand Statement " https://www.openpolicyagent.org/docs/latest/policy-language/#strings syn region regoString start=+"+ skip=+\\\\\|\\"+ end=+"+ diff --git a/runtime/syntax/sed.vim b/runtime/syntax/sed.vim index 63b39db81f..d1f631df4b 100644 --- a/runtime/syntax/sed.vim +++ b/runtime/syntax/sed.vim @@ -1,30 +1,42 @@ " Vim syntax file -" Language: sed -" Maintainer: Haakon Riiser <hakonrk@fys.uio.no> -" URL: http://folk.uio.no/hakonrk/vim/syntax/sed.vim -" Last Change: 2010 May 29 +" Language: sed +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Previous Maintainer: Haakon Riiser <hakonrk@fys.uio.no> +" Contributor: Jack Haden-Enneking +" Last Change: 2022 Oct 15 " quit when a syntax file was already loaded if exists("b:current_syntax") - finish + finish endif +syn keyword sedTodo contained TODO FIXME XXX + syn match sedError "\S" syn match sedWhitespace "\s\+" contained syn match sedSemicolon ";" syn match sedAddress "[[:digit:]$]" syn match sedAddress "\d\+\~\d\+" -syn region sedAddress matchgroup=Special start="[{,;]\s*/\(\\/\)\="lc=1 skip="[^\\]\(\\\\\)*\\/" end="/I\=" contains=sedTab,sedRegexpMeta -syn region sedAddress matchgroup=Special start="^\s*/\(\\/\)\=" skip="[^\\]\(\\\\\)*\\/" end="/I\=" contains=sedTab,sedRegexpMeta -syn match sedComment "^\s*#.*$" -syn match sedFunction "[dDgGhHlnNpPqQx=]\s*\($\|;\)" contains=sedSemicolon,sedWhitespace +syn region sedAddress matchgroup=Special start="[{,;]\s*/\%(\\/\)\="lc=1 skip="[^\\]\%(\\\\\)*\\/" end="/I\=" contains=sedTab,sedRegexpMeta +syn region sedAddress matchgroup=Special start="^\s*/\%(\\/\)\=" skip="[^\\]\%(\\\\\)*\\/" end="/I\=" contains=sedTab,sedRegexpMeta +syn match sedFunction "[dDgGhHlnNpPqQx=]\s*\%($\|;\)" contains=sedSemicolon,sedWhitespace +if exists("g:sed_dialect") && g:sed_dialect ==? "bsd" + syn match sedComment "^\s*#.*$" contains=sedTodo +else + syn match sedFunction "[dDgGhHlnNpPqQx=]\s*\ze#" contains=sedSemicolon,sedWhitespace + syn match sedComment "#.*$" contains=sedTodo +endif syn match sedLabel ":[^;]*" -syn match sedLineCont "^\(\\\\\)*\\$" contained -syn match sedLineCont "[^\\]\(\\\\\)*\\$"ms=e contained +syn match sedLineCont "^\%(\\\\\)*\\$" contained +syn match sedLineCont "[^\\]\%(\\\\\)*\\$"ms=e contained syn match sedSpecial "[{},!]" -if exists("highlight_sedtabs") - syn match sedTab "\t" contained + +" continue to silently support the old name +let s:highlight_tabs = v:false +if exists("g:highlight_sedtabs") || get(g:, "sed_highlight_tabs", 0) + let s:highlight_tabs = v:true + syn match sedTab "\t" contained endif " Append/Change/Insert @@ -34,39 +46,39 @@ syn region sedBranch matchgroup=sedFunction start="[bt]" matchgroup=sedSemicolon syn region sedRW matchgroup=sedFunction start="[rw]" matchgroup=sedSemicolon end=";\|$" contains=sedWhitespace " Substitution/transform with various delimiters -syn region sedFlagwrite matchgroup=sedFlag start="w" matchgroup=sedSemicolon end=";\|$" contains=sedWhitespace contained -syn match sedFlag "[[:digit:]gpI]*w\=" contains=sedFlagwrite contained +syn region sedFlagWrite matchgroup=sedFlag start="w" matchgroup=sedSemicolon end=";\|$" contains=sedWhitespace contained +syn match sedFlag "[[:digit:]gpI]*w\=" contains=sedFlagWrite contained syn match sedRegexpMeta "[.*^$]" contained syn match sedRegexpMeta "\\." contains=sedTab contained syn match sedRegexpMeta "\[.\{-}\]" contains=sedTab contained syn match sedRegexpMeta "\\{\d\*,\d*\\}" contained -syn match sedRegexpMeta "\\(.\{-}\\)" contains=sedTab contained -syn match sedReplaceMeta "&\|\\\($\|.\)" contains=sedTab contained +syn match sedRegexpMeta "\\%(.\{-}\\)" contains=sedTab contained +syn match sedReplaceMeta "&\|\\\%($\|.\)" contains=sedTab contained " Metacharacters: $ * . \ ^ [ ~ " @ is used as delimiter and treated on its own below -let __at = char2nr("@") -let __sed_i = char2nr(" ") " ASCII: 32, EBCDIC: 64 +let s:at = char2nr("@") +let s:i = char2nr(" ") " ASCII: 32, EBCDIC: 64 if has("ebcdic") - let __sed_last = 255 + let s:last = 255 else - let __sed_last = 126 + let s:last = 126 endif -let __sed_metacharacters = '$*.\^[~' -while __sed_i <= __sed_last - let __sed_delimiter = escape(nr2char(__sed_i), __sed_metacharacters) - if __sed_i != __at - exe 'syn region sedAddress matchgroup=Special start=@\\'.__sed_delimiter.'\(\\'.__sed_delimiter.'\)\=@ skip=@[^\\]\(\\\\\)*\\'.__sed_delimiter.'@ end=@'.__sed_delimiter.'I\=@ contains=sedTab' - exe 'syn region sedRegexp'.__sed_i 'matchgroup=Special start=@'.__sed_delimiter.'\(\\\\\|\\'.__sed_delimiter.'\)*@ skip=@[^\\'.__sed_delimiter.']\(\\\\\)*\\'.__sed_delimiter.'@ end=@'.__sed_delimiter.'@me=e-1 contains=sedTab,sedRegexpMeta keepend contained nextgroup=sedReplacement'.__sed_i - exe 'syn region sedReplacement'.__sed_i 'matchgroup=Special start=@'.__sed_delimiter.'\(\\\\\|\\'.__sed_delimiter.'\)*@ skip=@[^\\'.__sed_delimiter.']\(\\\\\)*\\'.__sed_delimiter.'@ end=@'.__sed_delimiter.'@ contains=sedTab,sedReplaceMeta keepend contained nextgroup=sedFlag' - endif - let __sed_i = __sed_i + 1 +let s:metacharacters = '$*.\^[~' +while s:i <= s:last + let s:delimiter = escape(nr2char(s:i), s:metacharacters) + if s:i != s:at + exe 'syn region sedAddress matchgroup=Special start=@\\'.s:delimiter.'\%(\\'.s:delimiter.'\)\=@ skip=@[^\\]\%(\\\\\)*\\'.s:delimiter.'@ end=@'.s:delimiter.'[IM]\=@ contains=sedTab' + exe 'syn region sedRegexp'.s:i 'matchgroup=Special start=@'.s:delimiter.'\%(\\\\\|\\'.s:delimiter.'\)*@ skip=@[^\\'.s:delimiter.']\%(\\\\\)*\\'.s:delimiter.'@ end=@'.s:delimiter.'@me=e-1 contains=sedTab,sedRegexpMeta keepend contained nextgroup=sedReplacement'.s:i + exe 'syn region sedReplacement'.s:i 'matchgroup=Special start=@'.s:delimiter.'\%(\\\\\|\\'.s:delimiter.'\)*@ skip=@[^\\'.s:delimiter.']\%(\\\\\)*\\'.s:delimiter.'@ end=@'.s:delimiter.'@ contains=sedTab,sedReplaceMeta keepend contained nextgroup=@sedFlags' + endif + let s:i = s:i + 1 endwhile -syn region sedAddress matchgroup=Special start=+\\@\(\\@\)\=+ skip=+[^\\]\(\\\\\)*\\@+ end=+@I\=+ contains=sedTab,sedRegexpMeta -syn region sedRegexp64 matchgroup=Special start=+@\(\\\\\|\\@\)*+ skip=+[^\\@]\(\\\\\)*\\@+ end=+@+me=e-1 contains=sedTab,sedRegexpMeta keepend contained nextgroup=sedReplacement64 -syn region sedReplacement64 matchgroup=Special start=+@\(\\\\\|\\@\)*+ skip=+[^\\@]\(\\\\\)*\\@+ end=+@+ contains=sedTab,sedReplaceMeta keepend contained nextgroup=sedFlag +syn region sedAddress matchgroup=Special start=+\\@\%(\\@\)\=+ skip=+[^\\]\%(\\\\\)*\\@+ end=+@I\=+ contains=sedTab,sedRegexpMeta +syn region sedRegexp64 matchgroup=Special start=+@\%(\\\\\|\\@\)*+ skip=+[^\\@]\%(\\\\\)*\\@+ end=+@+me=e-1 contains=sedTab,sedRegexpMeta keepend contained nextgroup=sedReplacement64 +syn region sedReplacement64 matchgroup=Special start=+@\%(\\\\\|\\@\)*+ skip=+[^\\@]\%(\\\\\)*\\@+ end=+@+ contains=sedTab,sedReplaceMeta keepend contained nextgroup=sedFlag -" Since the syntax for the substituion command is very similar to the +" Since the syntax for the substitution command is very similar to the " syntax for the transform command, I use the same pattern matching " for both commands. There is one problem -- the transform command " (y) does not allow any flags. To save memory, I ignore this problem. @@ -80,7 +92,7 @@ hi def link sedComment Comment hi def link sedDelete Function hi def link sedError Error hi def link sedFlag Type -hi def link sedFlagwrite Constant +hi def link sedFlagWrite Constant hi def link sedFunction Function hi def link sedLabel Label hi def link sedLineCont Special @@ -88,23 +100,24 @@ hi def link sedPutHoldspc Function hi def link sedReplaceMeta Special hi def link sedRegexpMeta Special hi def link sedRW Constant -hi def link sedSemicolon Special +hi def link sedSemicolon Special hi def link sedST Function hi def link sedSpecial Special +hi def link sedTodo Todo hi def link sedWhitespace NONE -if exists("highlight_sedtabs") -hi def link sedTab Todo +if s:highlight_tabs + hi def link sedTab Todo endif -let __sed_i = char2nr(" ") " ASCII: 32, EBCDIC: 64 -while __sed_i <= __sed_last -exe "hi def link sedRegexp".__sed_i "Macro" -exe "hi def link sedReplacement".__sed_i "NONE" -let __sed_i = __sed_i + 1 +let s:i = char2nr(" ") " ASCII: 32, EBCDIC: 64 +while s:i <= s:last + exe "hi def link sedRegexp".s:i "Macro" + exe "hi def link sedReplacement".s:i "NONE" + let s:i = s:i + 1 endwhile - -unlet __sed_i __sed_last __sed_delimiter __sed_metacharacters +unlet s:i s:last s:delimiter s:metacharacters s:at +unlet s:highlight_tabs let b:current_syntax = "sed" -" vim: sts=4 sw=4 ts=8 +" vim: nowrap sw=2 sts=2 ts=8 noet: diff --git a/runtime/syntax/sh.vim b/runtime/syntax/sh.vim index 822b1a9ed2..6722d62c89 100644 --- a/runtime/syntax/sh.vim +++ b/runtime/syntax/sh.vim @@ -2,8 +2,8 @@ " Language: shell (sh) Korn shell (ksh) bash (sh) " Maintainer: Charles E. Campbell <NcampObell@SdrPchip.AorgM-NOSPAM> " Previous Maintainer: Lennart Schultz <Lennart.Schultz@ecmwf.int> -" Last Change: Jul 08, 2022 -" Version: 203 +" Last Change: Nov 25, 2022 +" Version: 204 " URL: http://www.drchip.org/astronaut/vim/index.html#SYNTAX_SH " For options and settings, please use: :help ft-sh-syntax " This file includes many ideas from Eric Brunet (eric.brunet@ens.fr) and heredoc fixes from Felipe Contreras @@ -84,15 +84,9 @@ elseif g:sh_fold_enabled != 0 && !has("folding") let g:sh_fold_enabled= 0 echomsg "Ignoring g:sh_fold_enabled=".g:sh_fold_enabled."; need to re-compile vim for +fold support" endif -if !exists("s:sh_fold_functions") - let s:sh_fold_functions= and(g:sh_fold_enabled,1) -endif -if !exists("s:sh_fold_heredoc") - let s:sh_fold_heredoc = and(g:sh_fold_enabled,2) -endif -if !exists("s:sh_fold_ifdofor") - let s:sh_fold_ifdofor = and(g:sh_fold_enabled,4) -endif +let s:sh_fold_functions= and(g:sh_fold_enabled,1) +let s:sh_fold_heredoc = and(g:sh_fold_enabled,2) +let s:sh_fold_ifdofor = and(g:sh_fold_enabled,4) if g:sh_fold_enabled && &fdm == "manual" " Given that the user provided g:sh_fold_enabled " AND g:sh_fold_enabled is manual (usual default) @@ -113,6 +107,9 @@ endif " Set up folding commands for shell {{{1 " ================================= +sil! delc ShFoldFunctions +sil! delc ShFoldHereDoc +sil! delc ShFoldIfDoFor if s:sh_fold_functions com! -nargs=* ShFoldFunctions <args> fold else @@ -415,22 +412,22 @@ syn match shBQComment contained "#.\{-}\ze`" contains=@shCommentGroup " Here Documents: {{{1 " (modified by Felipe Contreras) " ========================================= -ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc01 start="<<\s*\z([^ \t|>]\+\)" matchgroup=shHereDoc01 end="^\z1\s*$" contains=@shDblQuoteList -ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc02 start="<<-\s*\z([^ \t|>]\+\)" matchgroup=shHereDoc02 end="^\s*\z1\s*$" contains=@shDblQuoteList -ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc03 start="<<\s*\\\z([^ \t|>]\+\)" matchgroup=shHereDoc03 end="^\z1\s*$" -ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc04 start="<<-\s*\\\z([^ \t|>]\+\)" matchgroup=shHereDoc04 end="^\s*\z1\s*$" -ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc05 start="<<\s*'\z([^']\+\)'" matchgroup=shHereDoc05 end="^\z1\s*$" -ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc06 start="<<-\s*'\z([^']\+\)'" matchgroup=shHereDoc06 end="^\s*\z1\s*$" -ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc07 start="<<\s*\"\z([^"]\+\)\"" matchgroup=shHereDoc07 end="^\z1\s*$" -ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc08 start="<<-\s*\"\z([^"]\+\)\"" matchgroup=shHereDoc08 end="^\s*\z1\s*$" -ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc09 start="<<\s*\\\_$\_s*\z([^ \t|>]\+\)" matchgroup=shHereDoc09 end="^\z1\s*$" contains=@shDblQuoteList -ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc10 start="<<-\s*\\\_$\_s*\z([^ \t|>]\+\)" matchgroup=shHereDoc10 end="^\s*\z1\s*$" contains=@shDblQuoteList -ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc11 start="<<\s*\\\_$\_s*\\\z([^ \t|>]\+\)" matchgroup=shHereDoc11 end="^\z1\s*$" -ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc12 start="<<-\s*\\\_$\_s*\\\z([^ \t|>]\+\)" matchgroup=shHereDoc12 end="^\s*\z1\s*$" -ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc13 start="<<\s*\\\_$\_s*'\z([^']\+\)'" matchgroup=shHereDoc13 end="^\z1\s*$" -ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc14 start="<<-\s*\\\_$\_s*'\z([^']\+\)'" matchgroup=shHereDoc14 end="^\s*\z1\s*$" -ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc15 start="<<\s*\\\_$\_s*\"\z([^"]\+\)\"" matchgroup=shHereDoc15 end="^\z1\s*$" -ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc16 start="<<-\s*\\\_$\_s*\"\z([^"]\+\)\"" matchgroup=shHereDoc16 end="^\s*\z1\s*$" +ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc01 start="<<\s*\z([^ \t|>]\+\)" matchgroup=shHereDoc01 end="^\z1$" contains=@shDblQuoteList +ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc02 start="<<-\s*\z([^ \t|>]\+\)" matchgroup=shHereDoc02 end="^\s*\z1$" contains=@shDblQuoteList +ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc03 start="<<\s*\\\z([^ \t|>]\+\)" matchgroup=shHereDoc03 end="^\z1$" +ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc04 start="<<-\s*\\\z([^ \t|>]\+\)" matchgroup=shHereDoc04 end="^\s*\z1$" +ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc05 start="<<\s*'\z([^']\+\)'" matchgroup=shHereDoc05 end="^\z1$" +ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc06 start="<<-\s*'\z([^']\+\)'" matchgroup=shHereDoc06 end="^\s*\z1$" +ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc07 start="<<\s*\"\z([^"]\+\)\"" matchgroup=shHereDoc07 end="^\z1$" +ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc08 start="<<-\s*\"\z([^"]\+\)\"" matchgroup=shHereDoc08 end="^\s*\z1$" +ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc09 start="<<\s*\\\_$\_s*\z([^ \t|>]\+\)" matchgroup=shHereDoc09 end="^\z1$" contains=@shDblQuoteList +ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc10 start="<<-\s*\\\_$\_s*\z([^ \t|>]\+\)" matchgroup=shHereDoc10 end="^\s*\z1$" contains=@shDblQuoteList +ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc11 start="<<\s*\\\_$\_s*\\\z([^ \t|>]\+\)" matchgroup=shHereDoc11 end="^\z1$" +ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc12 start="<<-\s*\\\_$\_s*\\\z([^ \t|>]\+\)" matchgroup=shHereDoc12 end="^\s*\z1$" +ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc13 start="<<\s*\\\_$\_s*'\z([^']\+\)'" matchgroup=shHereDoc13 end="^\z1$" +ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc14 start="<<-\s*\\\_$\_s*'\z([^']\+\)'" matchgroup=shHereDoc14 end="^\s*\z1$" +ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc15 start="<<\s*\\\_$\_s*\"\z([^"]\+\)\"" matchgroup=shHereDoc15 end="^\z1$" +ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc16 start="<<-\s*\\\_$\_s*\"\z([^"]\+\)\"" matchgroup=shHereDoc16 end="^\s*\z1$" " Here Strings: {{{1 diff --git a/runtime/syntax/shared/hgcommitDiff.vim b/runtime/syntax/shared/hgcommitDiff.vim new file mode 100644 index 0000000000..949cdf0b1c --- /dev/null +++ b/runtime/syntax/shared/hgcommitDiff.vim @@ -0,0 +1,390 @@ +" Vim syntax file +" Language: Sapling / Mecurial Diff (context or unified) +" Maintainer: Max Coplan <mchcopl@gmail.com> +" Translations by Jakson Alves de Aquino. +" Last Change: 2022-12-08 +" Copied from: runtime/syntax/diff.vim + +" Quit when a (custom) syntax file was already loaded +if exists("b:current_syntax") + finish +endif +scriptencoding utf-8 + +syn match hgDiffOnly "^\%(SL\|HG\): Only in .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Files .* and .* are identical$" +syn match hgDiffDiffer "^\%(SL\|HG\): Files .* and .* differ$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Binary files .* and .* differ$" +syn match hgDiffIsA "^\%(SL\|HG\): File .* is a .* while file .* is a .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ No newline at end of file .*" +syn match hgDiffCommon "^\%(SL\|HG\): Common subdirectories: .*" + +" Disable the translations by setting diff_translations to zero. +if !exists("diff_translations") || diff_translations + +" ca +syn match hgDiffOnly "^\%(SL\|HG\): Només a .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Els fitxers .* i .* són idèntics$" +syn match hgDiffDiffer "^\%(SL\|HG\): Els fitxers .* i .* difereixen$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Els fitxers .* i .* difereixen$" +syn match hgDiffIsA "^\%(SL\|HG\): El fitxer .* és un .* mentre que el fitxer .* és un .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ No hi ha cap caràcter de salt de línia al final del fitxer" +syn match hgDiffCommon "^\%(SL\|HG\): Subdirectoris comuns: .* i .*" + +" cs +syn match hgDiffOnly "^\%(SL\|HG\): Pouze v .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Soubory .* a .* jsou identické$" +syn match hgDiffDiffer "^\%(SL\|HG\): Soubory .* a .* jsou různé$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Binární soubory .* a .* jsou rozdílné$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Soubory .* a .* jsou různé$" +syn match hgDiffIsA "^\%(SL\|HG\): Soubor .* je .* pokud soubor .* je .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Chybí znak konce řádku na konci souboru" +syn match hgDiffCommon "^\%(SL\|HG\): Společné podadresáře: .* a .*" + +" da +syn match hgDiffOnly "^\%(SL\|HG\): Kun i .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Filerne .* og .* er identiske$" +syn match hgDiffDiffer "^\%(SL\|HG\): Filerne .* og .* er forskellige$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Binære filer .* og .* er forskellige$" +syn match hgDiffIsA "^\%(SL\|HG\): Filen .* er en .* mens filen .* er en .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Intet linjeskift ved filafslutning" +syn match hgDiffCommon "^\%(SL\|HG\): Identiske underkataloger: .* og .*" + +" de +syn match hgDiffOnly "^\%(SL\|HG\): Nur in .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Dateien .* und .* sind identisch.$" +syn match hgDiffDiffer "^\%(SL\|HG\): Dateien .* und .* sind verschieden.$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Binärdateien .* and .* sind verschieden.$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Binärdateien .* und .* sind verschieden.$" +syn match hgDiffIsA "^\%(SL\|HG\): Datei .* ist ein .* während Datei .* ein .* ist.$" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Kein Zeilenumbruch am Dateiende." +syn match hgDiffCommon "^\%(SL\|HG\): Gemeinsame Unterverzeichnisse: .* und .*.$" + +" el +syn match hgDiffOnly "^\%(SL\|HG\): Μόνο στο .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Τα αρχεία .* καί .* είναι πανομοιότυπα$" +syn match hgDiffDiffer "^\%(SL\|HG\): Τα αρχεία .* και .* διαφέρουν$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Τα αρχεία .* και .* διαφέρουν$" +syn match hgDiffIsA "^\%(SL\|HG\): Το αρχείο .* είναι .* ενώ το αρχείο .* είναι .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Δεν υπάρχει χαρακτήρας νέας γραμμής στο τέλος του αρχείου" +syn match hgDiffCommon "^\%(SL\|HG\): Οι υποκατάλογοι .* και .* είναι ταυτόσημοι$" + +" eo +syn match hgDiffOnly "^\%(SL\|HG\): Nur en .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Dosieroj .* kaj .* estas samaj$" +syn match hgDiffDiffer "^\%(SL\|HG\): Dosieroj .* kaj .* estas malsamaj$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Dosieroj .* kaj .* estas malsamaj$" +syn match hgDiffIsA "^\%(SL\|HG\): Dosiero .* estas .*, dum dosiero .* estas .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Mankas linifino ĉe fino de dosiero" +syn match hgDiffCommon "^\%(SL\|HG\): Komunaj subdosierujoj: .* kaj .*" + +" es +syn match hgDiffOnly "^\%(SL\|HG\): Sólo en .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Los ficheros .* y .* son idénticos$" +syn match hgDiffDiffer "^\%(SL\|HG\): Los ficheros .* y .* son distintos$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Los ficheros binarios .* y .* son distintos$" +syn match hgDiffIsA "^\%(SL\|HG\): El fichero .* es un .* mientras que el .* es un .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ No hay ningún carácter de nueva línea al final del fichero" +syn match hgDiffCommon "^\%(SL\|HG\): Subdirectorios comunes: .* y .*" + +" fi +syn match hgDiffOnly "^\%(SL\|HG\): Vain hakemistossa .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Tiedostot .* ja .* ovat identtiset$" +syn match hgDiffDiffer "^\%(SL\|HG\): Tiedostot .* ja .* eroavat$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Binääritiedostot .* ja .* eroavat$" +syn match hgDiffIsA "^\%(SL\|HG\): Tiedosto .* on .*, kun taas tiedosto .* on .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Ei rivinvaihtoa tiedoston lopussa" +syn match hgDiffCommon "^\%(SL\|HG\): Yhteiset alihakemistot: .* ja .*" + +" fr +syn match hgDiffOnly "^\%(SL\|HG\): Seulement dans .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Les fichiers .* et .* sont identiques.*" +syn match hgDiffDiffer "^\%(SL\|HG\): Les fichiers .* et .* sont différents.*" +syn match hgDiffBDiffer "^\%(SL\|HG\): Les fichiers binaires .* et .* sont différents.*" +syn match hgDiffIsA "^\%(SL\|HG\): Le fichier .* est un .* alors que le fichier .* est un .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Pas de fin de ligne à la fin du fichier.*" +syn match hgDiffCommon "^\%(SL\|HG\): Les sous-répertoires .* et .* sont identiques.*" + +" ga +syn match hgDiffOnly "^\%(SL\|HG\): I .* amháin: .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Is comhionann iad na comhaid .* agus .*" +syn match hgDiffDiffer "^\%(SL\|HG\): Tá difríocht idir na comhaid .* agus .*" +syn match hgDiffBDiffer "^\%(SL\|HG\): Tá difríocht idir na comhaid .* agus .*" +syn match hgDiffIsA "^\%(SL\|HG\): Tá comhad .* ina .* ach tá comhad .* ina .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Gan líne nua ag an chomhadchríoch" +syn match hgDiffCommon "^\%(SL\|HG\): Fochomhadlanna i gcoitianta: .* agus .*" + +" gl +syn match hgDiffOnly "^\%(SL\|HG\): Só en .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Os ficheiros .* e .* son idénticos$" +syn match hgDiffDiffer "^\%(SL\|HG\): Os ficheiros .* e .* son diferentes$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Os ficheiros binarios .* e .* son diferentes$" +syn match hgDiffIsA "^\%(SL\|HG\): O ficheiro .* é un .* mentres que o ficheiro .* é un .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Non hai un salto de liña na fin da liña" +syn match hgDiffCommon "^\%(SL\|HG\): Subdirectorios comúns: .* e .*" + +" he +" ^\%(SL\|HG\): .* are expansive patterns for long lines, so disabled unless we can match +" some specific hebrew chars +if search('\%u05d5\|\%u05d1', 'nw', '', 100) + syn match hgDiffOnly "^\%(SL\|HG\): .*-ב קר אצמנ .*" + syn match hgDiffIdentical "^\%(SL\|HG\): םיהז םניה .*-ו .* םיצבקה$" + syn match hgDiffDiffer "^\%(SL\|HG\): הזמ הז םינוש `.*'-ו `.*' םיצבקה$" + syn match hgDiffBDiffer "^\%(SL\|HG\): הזמ הז םינוש `.*'-ו `.*' םיירניב םיצבק$" + syn match hgDiffIsA "^\%(SL\|HG\): .* .*-ל .* .* תוושהל ןתינ אל$" + syn match hgDiffNoEOL "^\%(SL\|HG\): \\ ץבוקה ףוסב השד.-הרוש ות רס." + syn match hgDiffCommon "^\%(SL\|HG\): .*-ו .* :תוהז תויקית-תת$" +endif + +" hr +syn match hgDiffOnly "^\%(SL\|HG\): Samo u .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Datoteke .* i .* su identične$" +syn match hgDiffDiffer "^\%(SL\|HG\): Datoteke .* i .* se razlikuju$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Binarne datoteke .* i .* se razlikuju$" +syn match hgDiffIsA "^\%(SL\|HG\): Datoteka .* je .*, a datoteka .* je .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Nema novog retka na kraju datoteke" +syn match hgDiffCommon "^\%(SL\|HG\): Uobičajeni poddirektoriji: .* i .*" + +" hu +syn match hgDiffOnly "^\%(SL\|HG\): Csak .* -ben: .*" +syn match hgDiffIdentical "^\%(SL\|HG\): .* és .* fájlok azonosak$" +syn match hgDiffDiffer "^\%(SL\|HG\): A(z) .* és a(z) .* fájlok különböznek$" +syn match hgDiffBDiffer "^\%(SL\|HG\): A(z) .* és a(z) .* fájlok különböznek$" +syn match hgDiffIsA "^\%(SL\|HG\): A(z) .* fájl egy .*, viszont a(z) .* fájl egy .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Nincs újsor a fájl végén" +syn match hgDiffCommon "^\%(SL\|HG\): Közös alkönyvtárak: .* és .*" + +" id +syn match hgDiffOnly "^\%(SL\|HG\): Hanya dalam .*" +syn match hgDiffIdentical "^\%(SL\|HG\): File .* dan .* identik$" +syn match hgDiffDiffer "^\%(SL\|HG\): Berkas .* dan .* berbeda$" +syn match hgDiffBDiffer "^\%(SL\|HG\): File biner .* dan .* berbeda$" +syn match hgDiffIsA "^\%(SL\|HG\): File .* adalah .* sementara file .* adalah .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Tidak ada baris-baru di akhir dari berkas" +syn match hgDiffCommon "^\%(SL\|HG\): Subdirektori sama: .* dan .*" + +" it +syn match hgDiffOnly "^\%(SL\|HG\): Solo in .*" +syn match hgDiffIdentical "^\%(SL\|HG\): I file .* e .* sono identici$" +syn match hgDiffDiffer "^\%(SL\|HG\): I file .* e .* sono diversi$" +syn match hgDiffBDiffer "^\%(SL\|HG\): I file .* e .* sono diversi$" +syn match hgDiffBDiffer "^\%(SL\|HG\): I file binari .* e .* sono diversi$" +syn match hgDiffIsA "^\%(SL\|HG\): File .* è un .* mentre file .* è un .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Manca newline alla fine del file" +syn match hgDiffCommon "^\%(SL\|HG\): Sottodirectory in comune: .* e .*" + +" ja +syn match hgDiffOnly "^\%(SL\|HG\): .*だけに発見: .*" +syn match hgDiffIdentical "^\%(SL\|HG\): ファイル.*と.*は同一$" +syn match hgDiffDiffer "^\%(SL\|HG\): ファイル.*と.*は違います$" +syn match hgDiffBDiffer "^\%(SL\|HG\): バイナリー・ファイル.*と.*は違います$" +syn match hgDiffIsA "^\%(SL\|HG\): ファイル.*は.*、ファイル.*は.*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ ファイル末尾に改行がありません" +syn match hgDiffCommon "^\%(SL\|HG\): 共通の下位ディレクトリー: .*と.*" + +" ja DiffUtils 3.3 +syn match hgDiffOnly "^\%(SL\|HG\): .* のみに存在: .*" +syn match hgDiffIdentical "^\%(SL\|HG\): ファイル .* と .* は同一です$" +syn match hgDiffDiffer "^\%(SL\|HG\): ファイル .* と .* は異なります$" +syn match hgDiffBDiffer "^\%(SL\|HG\): バイナリーファイル .* と.* は異なります$" +syn match hgDiffIsA "^\%(SL\|HG\): ファイル .* は .* です。一方、ファイル .* は .* です$" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ ファイル末尾に改行がありません" +syn match hgDiffCommon "^\%(SL\|HG\): 共通のサブディレクトリー: .* と .*" + +" lv +syn match hgDiffOnly "^\%(SL\|HG\): Tikai iekš .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Fails .* un .* ir identiski$" +syn match hgDiffDiffer "^\%(SL\|HG\): Faili .* un .* atšķiras$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Faili .* un .* atšķiras$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Binārie faili .* un .* atšķiras$" +syn match hgDiffIsA "^\%(SL\|HG\): Fails .* ir .* kamēr fails .* ir .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Nav jaunu rindu faila beigās" +syn match hgDiffCommon "^\%(SL\|HG\): Kopējās apakšdirektorijas: .* un .*" + +" ms +syn match hgDiffOnly "^\%(SL\|HG\): Hanya dalam .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Fail .* dan .* adalah serupa$" +syn match hgDiffDiffer "^\%(SL\|HG\): Fail .* dan .* berbeza$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Fail .* dan .* berbeza$" +syn match hgDiffIsA "^\%(SL\|HG\): Fail .* adalah .* manakala fail .* adalah .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Tiada baris baru pada penghujung fail" +syn match hgDiffCommon "^\%(SL\|HG\): Subdirektori umum: .* dan .*" + +" nl +syn match hgDiffOnly "^\%(SL\|HG\): Alleen in .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Bestanden .* en .* zijn identiek$" +syn match hgDiffDiffer "^\%(SL\|HG\): Bestanden .* en .* zijn verschillend$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Bestanden .* en .* zijn verschillend$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Binaire bestanden .* en .* zijn verschillend$" +syn match hgDiffIsA "^\%(SL\|HG\): Bestand .* is een .* terwijl bestand .* een .* is$" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Geen regeleindeteken (LF) aan einde van bestand" +syn match hgDiffCommon "^\%(SL\|HG\): Gemeenschappelijke submappen: .* en .*" + +" pl +syn match hgDiffOnly "^\%(SL\|HG\): Tylko w .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Pliki .* i .* są identyczne$" +syn match hgDiffDiffer "^\%(SL\|HG\): Pliki .* i .* różnią się$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Pliki .* i .* różnią się$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Binarne pliki .* i .* różnią się$" +syn match hgDiffIsA "^\%(SL\|HG\): Plik .* jest .*, podczas gdy plik .* jest .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Brak znaku nowej linii na końcu pliku" +syn match hgDiffCommon "^\%(SL\|HG\): Wspólne podkatalogi: .* i .*" + +" pt_BR +syn match hgDiffOnly "^\%(SL\|HG\): Somente em .*" +syn match hgDiffOnly "^\%(SL\|HG\): Apenas em .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Os aquivos .* e .* são idênticos$" +syn match hgDiffDiffer "^\%(SL\|HG\): Os arquivos .* e .* são diferentes$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Os arquivos binários .* e .* são diferentes$" +syn match hgDiffIsA "^\%(SL\|HG\): O arquivo .* é .* enquanto o arquivo .* é .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Falta o caracter nova linha no final do arquivo" +syn match hgDiffCommon "^\%(SL\|HG\): Subdiretórios idênticos: .* e .*" + +" ro +syn match hgDiffOnly "^\%(SL\|HG\): Doar în .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Fişierele .* şi .* sunt identice$" +syn match hgDiffDiffer "^\%(SL\|HG\): Fişierele .* şi .* diferă$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Fişierele binare .* şi .* diferă$" +syn match hgDiffIsA "^\%(SL\|HG\): Fişierul .* este un .* pe când fişierul .* este un .*.$" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Nici un element de linie nouă la sfârşitul fişierului" +syn match hgDiffCommon "^\%(SL\|HG\): Subdirectoare comune: .* şi .*.$" + +" ru +syn match hgDiffOnly "^\%(SL\|HG\): Только в .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Файлы .* и .* идентичны$" +syn match hgDiffDiffer "^\%(SL\|HG\): Файлы .* и .* различаются$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Файлы .* и .* различаются$" +syn match hgDiffIsA "^\%(SL\|HG\): Файл .* это .*, тогда как файл .* -- .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ В конце файла нет новой строки" +syn match hgDiffCommon "^\%(SL\|HG\): Общие подкаталоги: .* и .*" + +" sr +syn match hgDiffOnly "^\%(SL\|HG\): Само у .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Датотеке „.*“ и „.*“ се подударају$" +syn match hgDiffDiffer "^\%(SL\|HG\): Датотеке .* и .* различите$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Бинарне датотеке .* и .* различите$" +syn match hgDiffIsA "^\%(SL\|HG\): Датотека „.*“ је „.*“ док је датотека „.*“ „.*“$" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Без новог реда на крају датотеке" +syn match hgDiffCommon "^\%(SL\|HG\): Заједнички поддиректоријуми: .* и .*" + +" sv +syn match hgDiffOnly "^\%(SL\|HG\): Endast i .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Filerna .* och .* är lika$" +syn match hgDiffDiffer "^\%(SL\|HG\): Filerna .* och .* skiljer$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Filerna .* och .* skiljer$" +syn match hgDiffIsA "^\%(SL\|HG\): Fil .* är en .* medan fil .* är en .*" +syn match hgDiffBDiffer "^\%(SL\|HG\): De binära filerna .* och .* skiljer$" +syn match hgDiffIsA "^\%(SL\|HG\): Filen .* är .* medan filen .* är .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Ingen nyrad vid filslut" +syn match hgDiffCommon "^\%(SL\|HG\): Lika underkataloger: .* och .*" + +" tr +syn match hgDiffOnly "^\%(SL\|HG\): Yalnızca .*'da: .*" +syn match hgDiffIdentical "^\%(SL\|HG\): .* ve .* dosyaları birbirinin aynı$" +syn match hgDiffDiffer "^\%(SL\|HG\): .* ve .* dosyaları birbirinden farklı$" +syn match hgDiffBDiffer "^\%(SL\|HG\): .* ve .* dosyaları birbirinden farklı$" +syn match hgDiffBDiffer "^\%(SL\|HG\): İkili .* ve .* birbirinden farklı$" +syn match hgDiffIsA "^\%(SL\|HG\): .* dosyası, bir .*, halbuki .* dosyası bir .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Dosya sonunda yenisatır yok." +syn match hgDiffCommon "^\%(SL\|HG\): Ortak alt dizinler: .* ve .*" + +" uk +syn match hgDiffOnly "^\%(SL\|HG\): Лише у .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Файли .* та .* ідентичні$" +syn match hgDiffDiffer "^\%(SL\|HG\): Файли .* та .* відрізняються$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Файли .* та .* відрізняються$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Двійкові файли .* та .* відрізняються$" +syn match hgDiffIsA "^\%(SL\|HG\): Файл .* це .*, тоді як файл .* -- .*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Наприкінці файлу немає нового рядка" +syn match hgDiffCommon "^\%(SL\|HG\): Спільні підкаталоги: .* та .*" + +" vi +syn match hgDiffOnly "^\%(SL\|HG\): Chỉ trong .*" +syn match hgDiffIdentical "^\%(SL\|HG\): Hai tập tin .* và .* là bằng nhau.$" +syn match hgDiffIdentical "^\%(SL\|HG\): Cả .* và .* là cùng một tập tin$" +syn match hgDiffDiffer "^\%(SL\|HG\): Hai tập tin .* và .* là khác nhau.$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Hai tập tin nhị phân .* và .* khác nhau$" +syn match hgDiffIsA "^\%(SL\|HG\): Tập tin .* là một .* trong khi tập tin .* là một .*.$" +syn match hgDiffBDiffer "^\%(SL\|HG\): Hai tập tin .* và .* là khác nhau.$" +syn match hgDiffIsA "^\%(SL\|HG\): Tập tin .* là một .* còn tập tin .* là một .*.$" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ Không có ký tự dòng mới tại kêt thức tập tin." +syn match hgDiffCommon "^\%(SL\|HG\): Thư mục con chung: .* và .*" + +" zh_CN +syn match hgDiffOnly "^\%(SL\|HG\): 只在 .* 存在:.*" +syn match hgDiffIdentical "^\%(SL\|HG\): 檔案 .* 和 .* 相同$" +syn match hgDiffDiffer "^\%(SL\|HG\): 文件 .* 和 .* 不同$" +syn match hgDiffBDiffer "^\%(SL\|HG\): 文件 .* 和 .* 不同$" +syn match hgDiffIsA "^\%(SL\|HG\): 文件 .* 是.*而文件 .* 是.*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ 文件尾没有 newline 字符" +syn match hgDiffCommon "^\%(SL\|HG\): .* 和 .* 有共同的子目录$" + +" zh_TW +syn match hgDiffOnly "^\%(SL\|HG\): 只在 .* 存在:.*" +syn match hgDiffIdentical "^\%(SL\|HG\): 檔案 .* 和 .* 相同$" +syn match hgDiffDiffer "^\%(SL\|HG\): 檔案 .* 與 .* 不同$" +syn match hgDiffBDiffer "^\%(SL\|HG\): 二元碼檔 .* 與 .* 不同$" +syn match hgDiffIsA "^\%(SL\|HG\): 檔案 .* 是.*而檔案 .* 是.*" +syn match hgDiffNoEOL "^\%(SL\|HG\): \\ 檔案末沒有 newline 字元" +syn match hgDiffCommon "^\%(SL\|HG\): .* 和 .* 有共同的副目錄$" + +endif + + +syn match hgDiffRemoved "^\%(SL\|HG\): -.*" +syn match hgDiffRemoved "^\%(SL\|HG\): <.*" +syn match hgDiffAdded "^\%(SL\|HG\): +.*" +syn match hgDiffAdded "^\%(SL\|HG\): >.*" +syn match hgDiffChanged "^\%(SL\|HG\): ! .*" + +syn match hgDiffSubname " @@..*"ms=s+3 contained +syn match hgDiffLine "^\%(SL\|HG\): @.*" contains=hgDiffSubname +syn match hgDiffLine "^\%(SL\|HG\): \<\d\+\>.*" +syn match hgDiffLine "^\%(SL\|HG\): \*\*\*\*.*" +syn match hgDiffLine "^\%(SL\|HG\): ---$" + +" Some versions of diff have lines like "#c#" and "#d#" (where # is a number) +syn match hgDiffLine "^\%(SL\|HG\): \d\+\(,\d\+\)\=[cda]\d\+\>.*" + +syn match hgDiffFile "^\%(SL\|HG\): diff\>.*" +syn match hgDiffFile "^\%(SL\|HG\): Index: .*" +syn match hgDiffFile "^\%(SL\|HG\): ==== .*" + +if search('^\%(SL\|HG\): @@ -\S\+ +\S\+ @@', 'nw', '', 100) + " unified + syn match hgDiffOldFile "^\%(SL\|HG\): --- .*" + syn match hgDiffNewFile "^\%(SL\|HG\): +++ .*" +else + " context / old style + syn match hgDiffOldFile "^\%(SL\|HG\): \*\*\* .*" + syn match hgDiffNewFile "^\%(SL\|HG\): --- .*" +endif + +" Used by git +syn match hgDiffIndexLine "^\%(SL\|HG\): index \x\x\x\x.*" + +syn match hgDiffComment "^\%(SL\|HG\): #.*" + +" Define the default highlighting. +" Only used when an item doesn't have highlighting yet +hi def link hgDiffOldFile hgDiffFile +hi def link hgDiffNewFile hgDiffFile +hi def link hgDiffIndexLine PreProc +hi def link hgDiffFile Type +hi def link hgDiffOnly Constant +hi def link hgDiffIdentical Constant +hi def link hgDiffDiffer Constant +hi def link hgDiffBDiffer Constant +hi def link hgDiffIsA Constant +hi def link hgDiffNoEOL Constant +hi def link hgDiffCommon Constant +hi def link hgDiffRemoved Special +hi def link hgDiffChanged PreProc +hi def link hgDiffAdded Identifier +hi def link hgDiffLine Statement +hi def link hgDiffSubname PreProc +hi def link hgDiffComment Comment + +let b:current_syntax = "hgcommitDiff" + +" vim: ts=8 sw=2 diff --git a/runtime/syntax/ssa.vim b/runtime/syntax/ssa.vim new file mode 100644 index 0000000000..a5dbf37c30 --- /dev/null +++ b/runtime/syntax/ssa.vim @@ -0,0 +1,63 @@ +" Vim syntax file +" Language: SubStation Alpha +" Maintainer: ObserverOfTime <chronobserver@disroot.org> +" Filenames: *.ass,*.ssa +" Last Change: 2022 Oct 10 + +if exists('b:current_syntax') + finish +endif + +" Comments +syn keyword ssaTodo TODO FIXME NOTE XXX contained +syn match ssaComment /^\(;\|!:\).*$/ contains=ssaTodo,@Spell +syn match ssaTextComment /{[^}]*}/ contained contains=@Spell + +" Sections +syn match ssaSection /^\[[a-zA-Z0-9+ ]\+\]$/ + +" Headers +syn match ssaHeader /^[^;!:]\+:/ skipwhite nextgroup=ssaField + +" Fields +syn match ssaField /[^,]*/ contained skipwhite nextgroup=ssaDelimiter + +" Time +syn match ssaTime /\d:\d\d:\d\d\.\d\d/ contained skipwhite nextgroup=ssaDelimiter + +" Delimiter +syn match ssaDelimiter /,/ contained skipwhite nextgroup=ssaField,ssaTime,ssaText + +" Text +syn match ssaText /\(^Dialogue:\(.*,\)\{9}\)\@<=.*$/ contained contains=@ssaTags,@Spell +syn cluster ssaTags contains=ssaOverrideTag,ssaEscapeChar,ssaTextComment,ssaItalics,ssaBold,ssaUnderline,ssaStrikeout + +" Override tags +syn match ssaOverrideTag /{\\[^}]\+}/ contained contains=@NoSpell + +" Special characters +syn match ssaEscapeChar /\\[nNh{}]/ contained contains=@NoSpell + +" Markup +syn region ssaItalics start=/{\\i1}/ end=/{\\i0}/ matchgroup=ssaOverrideTag keepend oneline contained contains=@ssaTags,@Spell +syn region ssaBold start=/{\\b1}/ end=/{\\b0}/ matchgroup=ssaOverrideTag keepend oneline contained contains=@ssaTags,@Spell +syn region ssaUnderline start=/{\\u1}/ end=/{\\u0}/ matchgroup=ssaOverrideTag keepend oneline contained contains=@ssaTags,@Spell +syn region ssaStrikeout start=/{\\s1}/ end=/{\\s0}/ matchgroup=ssaOverrideTag keepend oneline contained contains=@ssaTags,@Spell + +hi def link ssaDelimiter Delimiter +hi def link ssaComment Comment +hi def link ssaEscapeChar SpecialChar +hi def link ssaField String +hi def link ssaHeader Label +hi def link ssaSection StorageClass +hi def link ssaOverrideTag Special +hi def link ssaTextComment Comment +hi def link ssaTime Number +hi def link ssaTodo Todo + +hi ssaBold cterm=bold gui=bold +hi ssaItalics cterm=italic gui=italic +hi ssaStrikeout cterm=strikethrough gui=strikethrough +hi ssaUnderline cterm=underline gui=underline + +let b:current_syntax = 'srt' diff --git a/runtime/syntax/sshconfig.vim b/runtime/syntax/sshconfig.vim index ae3c7dd8cc..750289d83e 100644 --- a/runtime/syntax/sshconfig.vim +++ b/runtime/syntax/sshconfig.vim @@ -6,9 +6,10 @@ " Contributor: Leonard Ehrenfried <leonard.ehrenfried@web.de> " Contributor: Karsten Hopp <karsten@redhat.com> " Contributor: Dean, Adam Kenneth <adam.ken.dean@hpe.com> -" Last Change: 2021 Mar 29 +" Last Change: 2022 Nov 10 " Added RemoteCommand from pull request #4809 " Included additional keywords from Martin. +" Included PR #5753 " SSH Version: 8.5p1 " @@ -57,12 +58,12 @@ syn match sshconfigCiphers "\<aes256-gcm@openssh\.com\>" syn match sshconfigCiphers "\<chacha20-poly1305@openssh\.com\>" syn keyword sshconfigMAC hmac-sha1 -syn keyword sshconfigMAC mac-sha1-96 -syn keyword sshconfigMAC mac-sha2-256 -syn keyword sshconfigMAC mac-sha2-512 -syn keyword sshconfigMAC mac-md5 -syn keyword sshconfigMAC mac-md5-96 -syn keyword sshconfigMAC mac-ripemd160 +syn keyword sshconfigMAC hmac-sha1-96 +syn keyword sshconfigMAC hmac-sha2-256 +syn keyword sshconfigMAC hmac-sha2-512 +syn keyword sshconfigMAC hmac-md5 +syn keyword sshconfigMAC hmac-md5-96 +syn keyword sshconfigMAC hmac-ripemd160 syn match sshconfigMAC "\<hmac-ripemd160@openssh\.com\>" syn match sshconfigMAC "\<umac-64@openssh\.com\>" syn match sshconfigMAC "\<umac-128@openssh\.com\>" @@ -78,16 +79,24 @@ syn match sshconfigMAC "\<umac-128-etm@openssh\.com\>" syn keyword sshconfigHostKeyAlgo ssh-ed25519 syn match sshconfigHostKeyAlgo "\<ssh-ed25519-cert-v01@openssh\.com\>" +syn match sshconfigHostKeyAlgo "\<sk-ssh-ed25519@openssh\.com\>" +syn match sshconfigHostKeyAlgo "\<sk-ssh-ed25519-cert-v01@openssh\.com\>" syn keyword sshconfigHostKeyAlgo ssh-rsa +syn keyword sshconfigHostKeyAlgo rsa-sha2-256 +syn keyword sshconfigHostKeyAlgo rsa-sha2-512 syn keyword sshconfigHostKeyAlgo ssh-dss syn keyword sshconfigHostKeyAlgo ecdsa-sha2-nistp256 syn keyword sshconfigHostKeyAlgo ecdsa-sha2-nistp384 syn keyword sshconfigHostKeyAlgo ecdsa-sha2-nistp521 +syn match sshconfigHostKeyAlgo "\<sk-ecdsa-sha2-nistp256@openssh\.com\>" syn match sshconfigHostKeyAlgo "\<ssh-rsa-cert-v01@openssh\.com\>" +syn match sshconfigHostKeyAlgo "\<rsa-sha2-256-cert-v01@openssh\.com\>" +syn match sshconfigHostKeyAlgo "\<rsa-sha2-512-cert-v01@openssh\.com\>" syn match sshconfigHostKeyAlgo "\<ssh-dss-cert-v01@openssh\.com\>" syn match sshconfigHostKeyAlgo "\<ecdsa-sha2-nistp256-cert-v01@openssh\.com\>" syn match sshconfigHostKeyAlgo "\<ecdsa-sha2-nistp384-cert-v01@openssh\.com\>" syn match sshconfigHostKeyAlgo "\<ecdsa-sha2-nistp521-cert-v01@openssh\.com\>" +syn match sshconfigHostKeyAlgo "\<sk-ecdsa-sha2-nistp256-cert-v01@openssh\.com\>" syn keyword sshconfigPreferredAuth hostbased publickey password gssapi-with-mic syn keyword sshconfigPreferredAuth keyboard-interactive @@ -162,6 +171,7 @@ syn keyword sshconfigKeyword EnableSSHKeysign syn keyword sshconfigKeyword EscapeChar syn keyword sshconfigKeyword ExitOnForwardFailure syn keyword sshconfigKeyword FingerprintHash +syn keyword sshconfigKeyword ForkAfterAuthentication syn keyword sshconfigKeyword ForwardAgent syn keyword sshconfigKeyword ForwardX11 syn keyword sshconfigKeyword ForwardX11Timeout @@ -212,13 +222,16 @@ syn keyword sshconfigKeyword RekeyLimit syn keyword sshconfigKeyword RemoteCommand syn keyword sshconfigKeyword RemoteForward syn keyword sshconfigKeyword RequestTTY +syn keyword sshconfigKeyword RequiredRSASize syn keyword sshconfigKeyword RevokedHostKeys syn keyword sshconfigKeyword SecurityKeyProvider syn keyword sshconfigKeyword SendEnv syn keyword sshconfigKeyword ServerAliveCountMax syn keyword sshconfigKeyword ServerAliveInterval +syn keyword sshconfigKeyword SessionType syn keyword sshconfigKeyword SmartcardDevice syn keyword sshconfigKeyword SetEnv +syn keyword sshconfigKeyword StdinNull syn keyword sshconfigKeyword StreamLocalBindMask syn keyword sshconfigKeyword StreamLocalBindUnlink syn keyword sshconfigKeyword StrictHostKeyChecking diff --git a/runtime/syntax/sshdconfig.vim b/runtime/syntax/sshdconfig.vim index 6b0d2af4d1..c0d9c3f598 100644 --- a/runtime/syntax/sshdconfig.vim +++ b/runtime/syntax/sshdconfig.vim @@ -7,7 +7,7 @@ " Contributor: Leonard Ehrenfried <leonard.ehrenfried@web.de> " Contributor: Karsten Hopp <karsten@redhat.com> " Originally: 2009-07-09 -" Last Change: 2021-03-29 +" Last Change: 2022 Nov 10 " SSH Version: 8.5p1 " @@ -59,12 +59,12 @@ syn match sshdconfigCiphers "\<aes256-gcm@openssh\.com\>" syn match sshdconfigCiphers "\<chacha20-poly1305@openssh\.com\>" syn keyword sshdconfigMAC hmac-sha1 -syn keyword sshdconfigMAC mac-sha1-96 -syn keyword sshdconfigMAC mac-sha2-256 -syn keyword sshdconfigMAC mac-sha2-512 -syn keyword sshdconfigMAC mac-md5 -syn keyword sshdconfigMAC mac-md5-96 -syn keyword sshdconfigMAC mac-ripemd160 +syn keyword sshdconfigMAC hmac-sha1-96 +syn keyword sshdconfigMAC hmac-sha2-256 +syn keyword sshdconfigMAC hmac-sha2-512 +syn keyword sshdconfigMAC hmac-md5 +syn keyword sshdconfigMAC hmac-md5-96 +syn keyword sshdconfigMAC hmac-ripemd160 syn match sshdconfigMAC "\<hmac-ripemd160@openssh\.com\>" syn match sshdconfigMAC "\<umac-64@openssh\.com\>" syn match sshdconfigMAC "\<umac-128@openssh\.com\>" @@ -221,6 +221,7 @@ syn keyword sshdconfigKeyword Match syn keyword sshdconfigKeyword MaxAuthTries syn keyword sshdconfigKeyword MaxSessions syn keyword sshdconfigKeyword MaxStartups +syn keyword sshdconfigKeyword ModuliFile syn keyword sshdconfigKeyword PasswordAuthentication syn keyword sshdconfigKeyword PerSourceMaxStartups syn keyword sshdconfigKeyword PerSourceNetBlockSize @@ -244,6 +245,7 @@ syn keyword sshdconfigKeyword PubkeyAuthentication syn keyword sshdconfigKeyword PubkeyAuthOptions syn keyword sshdconfigKeyword RSAAuthentication syn keyword sshdconfigKeyword RekeyLimit +syn keyword sshdconfigKeyword RequiredRSASize syn keyword sshdconfigKeyword RevokedKeys syn keyword sshdconfigKeyword RDomain syn keyword sshdconfigKeyword RhostsRSAAuthentication @@ -258,6 +260,8 @@ syn keyword sshdconfigKeyword Subsystem syn keyword sshdconfigKeyword SyslogFacility syn keyword sshdconfigKeyword TCPKeepAlive syn keyword sshdconfigKeyword TrustedUserCAKeys +syn keyword sshdconfigKeyword UseBlacklist +syn keyword sshdconfigKeyword UseBlocklist syn keyword sshdconfigKeyword UseDNS syn keyword sshdconfigKeyword UseLogin syn keyword sshdconfigKeyword UsePAM diff --git a/runtime/syntax/swayconfig.vim b/runtime/syntax/swayconfig.vim index d9f31da47b..996b8f596c 100644 --- a/runtime/syntax/swayconfig.vim +++ b/runtime/syntax/swayconfig.vim @@ -2,7 +2,8 @@ " Language: sway window manager config " Original Author: James Eapen <james.eapen@vai.org> " Maintainer: James Eapen <james.eapen@vai.org> -" Version: 0.11.1 +" Version: 0.1.6 +" Reference version (jamespeapen/swayconfig.vim): 0.11.6 " Last Change: 2022 Aug 08 " References: @@ -23,10 +24,6 @@ scriptencoding utf-8 " Error "syn match swayConfigError /.*/ -" Group mode/bar -syn keyword swayConfigBlockKeyword set input contained -syn region swayConfigBlock start=+.*s\?{$+ end=+^}$+ contains=i3ConfigBlockKeyword,swayConfigBlockKeyword,i3ConfigString,i3ConfigBind,i3ConfigComment,i3ConfigFont,i3ConfigFocusWrappingType,i3ConfigColor,i3ConfigVariable transparent keepend extend - " binding syn keyword swayConfigBindKeyword bindswitch bindgesture contained syn match swayConfigBind /^\s*\(bindswitch\)\s\+.*$/ contains=i3ConfigVariable,i3ConfigBindKeyword,swayConfigBindKeyword,i3ConfigVariableAndModifier,i3ConfigNumber,i3ConfigUnit,i3ConfigUnitOr,i3ConfigBindArgument,i3ConfigModifier,i3ConfigAction,i3ConfigString,i3ConfigGapStyleKeyword,i3ConfigBorderStyleKeyword @@ -45,7 +42,7 @@ syn match swayConfigFloating /^\s*floating\s\+\(enable\|disable\|toggle\)\s*$/ c syn clear i3ConfigFloatingModifier syn keyword swayConfigFloatingModifier floating_modifier contained -syn match swayConfigFloatingMouseAction /^\s\?.*floating_modifier\s.*\(normal\|inverted\)$/ contains=swayConfigFloatingModifier,i3ConfigVariable +syn match swayConfigFloatingMouseAction /^\s\?.*floating_modifier\s\S\+\s\?\(normal\|inverted\|none\)\?$/ contains=swayConfigFloatingModifier,i3ConfigVariable " Gaps syn clear i3ConfigSmartBorderKeyword @@ -57,6 +54,10 @@ syn match swayConfigSmartBorder /^\s*smart_borders\s\+\(on\|no_gaps\|off\)\s\?$/ syn keyword swayConfigClientColorKeyword focused_tab_title contained syn match swayConfigClientColor /^\s*client.\w\+\s\+.*$/ contains=i3ConfigClientColorKeyword,i3ConfigColor,i3ConfigVariable,i3ConfigClientColorKeyword,swayConfigClientColorKeyword +" Input config +syn keyword swayConfigInputKeyword input contained +syn match swayConfigInput /^\s*input\s\+.*$/ contains=swayConfigInputKeyword + " set display outputs syn match swayConfigOutput /^\s*output\s\+.*$/ contains=i3ConfigOutput @@ -65,21 +66,34 @@ syn keyword swayConfigFocusKeyword focus contained syn keyword swayConfigFocusType output contained syn match swayConfigFocus /^\s*focus\soutput\s.*$/ contains=swayConfigFocusKeyword,swayConfigFocusType +" focus follows mouse +syn clear i3ConfigFocusFollowsMouseType +syn clear i3ConfigFocusFollowsMouse + +syn keyword swayConfigFocusFollowsMouseType yes no always contained +syn match swayConfigFocusFollowsMouse /^\s*focus_follows_mouse\s\+\(yes\|no\|always\)\s\?$/ contains=i3ConfigFocusFollowsMouseKeyword,swayConfigFocusFollowsMouseType + + " xwayland syn keyword swayConfigXwaylandKeyword xwayland contained syn match swayConfigXwaylandModifier /^\s*xwayland\s\+\(enable\|disable\|force\)\s\?$/ contains=swayConfigXwaylandKeyword +" Group mode/bar +syn clear i3ConfigBlock +syn region swayConfigBlock start=+.*s\?{$+ end=+^}$+ contains=i3ConfigBlockKeyword,i3ConfigString,i3ConfigBind,i3ConfigInitializeKeyword,i3ConfigComment,i3ConfigFont,i3ConfigFocusWrappingType,i3ConfigColor,i3ConfigVariable,swayConfigInputKeyword,i3ConfigOutput transparent keepend extend + "hi def link swayConfigError Error hi def link i3ConfigFloating Error hi def link swayConfigFloating Type hi def link swayConfigFloatingMouseAction Type hi def link swayConfigFocusKeyword Type hi def link swayConfigSmartBorderKeyword Type +hi def link swayConfigInputKeyword Type +hi def link swayConfigFocusFollowsMouseType Type hi def link swayConfigBindGestureCommand Identifier hi def link swayConfigBindGestureDirection Constant hi def link swayConfigBindGesturePinchDirection Constant hi def link swayConfigBindKeyword Identifier -hi def link swayConfigBlockKeyword Identifier hi def link swayConfigClientColorKeyword Identifier hi def link swayConfigFloatingKeyword Identifier hi def link swayConfigFloatingModifier Identifier diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 6ace5ffef3..a1c39e4dcf 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -451,7 +451,7 @@ if !exists("g:vimsyn_noerror") && !exists("g:vimsyn_novimfunctionerror") syn match vimBufnrWarn /\<bufnr\s*(\s*["']\.['"]\s*)/ endif -syn match vimNotFunc "\<if\>\|\<el\%[seif]\>\|\<return\>\|\<while\>" skipwhite nextgroup=vimOper,vimOperParen,vimVar,vimFunc,vimNotation +syn match vimNotFunc "\<if\>\|\<el\%[seif]\>\|\<retu\%[rn]\>\|\<while\>" skipwhite nextgroup=vimOper,vimOperParen,vimVar,vimFunc,vimNotation " Norm: {{{2 " ==== @@ -619,7 +619,7 @@ syn match vimCtrlChar "[--]" " Beginners - Patterns that involve ^ {{{2 " ========= -syn match vimLineComment +^[ \t:]*"\("[^"]*"\|[^"]\)*$+ contains=@vimCommentGroup,vimCommentString,vimCommentTitle +syn match vimLineComment +^[ \t:]*".*$+ contains=@vimCommentGroup,vimCommentString,vimCommentTitle syn match vim9LineComment +^[ \t:]\+#.*$+ contains=@vimCommentGroup,vimCommentString,vimCommentTitle syn match vimCommentTitle '"\s*\%([sS]:\|\h\w*#\)\=\u\w*\(\s\+\u\w*\)*:'hs=s+1 contained contains=vimCommentTitleLeader,vimTodo,@vimCommentGroup syn match vimContinue "^\s*\\" diff --git a/runtime/syntax/wdl.vim b/runtime/syntax/wdl.vim new file mode 100644 index 0000000000..3b8369e8bd --- /dev/null +++ b/runtime/syntax/wdl.vim @@ -0,0 +1,41 @@ +" Vim syntax file +" Language: wdl +" Maintainer: Matt Dunford (zenmatic@gmail.com) +" URL: https://github.com/zenmatic/vim-syntax-wdl +" Last Change: 2022 Nov 24 + +" https://github.com/openwdl/wdl + +" quit when a (custom) syntax file was already loaded +if exists("b:current_syntax") + finish +endif + +syn case match + +syn keyword wdlStatement alias task input command runtime input output workflow call scatter import as meta parameter_meta in version +syn keyword wdlConditional if then else +syn keyword wdlType struct Array String File Int Float Boolean Map Pair Object + +syn keyword wdlFunctions stdout stderr read_lines read_tsv read_map read_object read_objects read_json read_int read_string read_float read_boolean write_lines write_tsv write_map write_object write_objects write_json size sub range transpose zip cross length flatten prefix select_first defined basename floor ceil round + +syn region wdlCommandSection start="<<<" end=">>>" + +syn region wdlString start=+"+ skip=+\\\\\|\\"+ end=+"+ +syn region wdlString start=+'+ skip=+\\\\\|\\'+ end=+'+ + +" Comments; their contents +syn keyword wdlTodo contained TODO FIXME XXX BUG +syn cluster wdlCommentGroup contains=wdlTodo +syn region wdlComment start="#" end="$" contains=@wdlCommentGroup + +hi def link wdlStatement Statement +hi def link wdlConditional Conditional +hi def link wdlType Type +hi def link wdlFunctions Function +hi def link wdlString String +hi def link wdlCommandSection String +hi def link wdlComment Comment +hi def link wdlTodo Todo + +let b:current_syntax = 'wdl' diff --git a/runtime/syntax/zig.vim b/runtime/syntax/zig.vim new file mode 100644 index 0000000000..e09b5e8815 --- /dev/null +++ b/runtime/syntax/zig.vim @@ -0,0 +1,292 @@ +" Vim syntax file +" Language: Zig +" Upstream: https://github.com/ziglang/zig.vim + +if exists("b:current_syntax") + finish +endif + +let s:cpo_save = &cpo +set cpo&vim + +let s:zig_syntax_keywords = { + \ 'zigBoolean': ["true" + \ , "false"] + \ , 'zigNull': ["null"] + \ , 'zigType': ["bool" + \ , "f16" + \ , "f32" + \ , "f64" + \ , "f80" + \ , "f128" + \ , "void" + \ , "type" + \ , "anytype" + \ , "anyerror" + \ , "anyframe" + \ , "volatile" + \ , "linksection" + \ , "noreturn" + \ , "allowzero" + \ , "i0" + \ , "u0" + \ , "isize" + \ , "usize" + \ , "comptime_int" + \ , "comptime_float" + \ , "c_short" + \ , "c_ushort" + \ , "c_int" + \ , "c_uint" + \ , "c_long" + \ , "c_ulong" + \ , "c_longlong" + \ , "c_ulonglong" + \ , "c_longdouble" + \ , "anyopaque"] + \ , 'zigConstant': ["undefined" + \ , "unreachable"] + \ , 'zigConditional': ["if" + \ , "else" + \ , "switch"] + \ , 'zigRepeat': ["while" + \ , "for"] + \ , 'zigComparatorWord': ["and" + \ , "or" + \ , "orelse"] + \ , 'zigStructure': ["struct" + \ , "enum" + \ , "union" + \ , "error" + \ , "packed" + \ , "opaque"] + \ , 'zigException': ["error"] + \ , 'zigVarDecl': ["var" + \ , "const" + \ , "comptime" + \ , "threadlocal"] + \ , 'zigDummyVariable': ["_"] + \ , 'zigKeyword': ["fn" + \ , "try" + \ , "test" + \ , "pub" + \ , "usingnamespace"] + \ , 'zigExecution': ["return" + \ , "break" + \ , "continue"] + \ , 'zigMacro': ["defer" + \ , "errdefer" + \ , "async" + \ , "nosuspend" + \ , "await" + \ , "suspend" + \ , "resume" + \ , "export" + \ , "extern"] + \ , 'zigPreProc': ["catch" + \ , "inline" + \ , "noinline" + \ , "asm" + \ , "callconv" + \ , "noalias"] + \ , 'zigBuiltinFn': ["align" + \ , "@addWithOverflow" + \ , "@as" + \ , "@atomicLoad" + \ , "@atomicStore" + \ , "@bitCast" + \ , "@breakpoint" + \ , "@alignCast" + \ , "@alignOf" + \ , "@cDefine" + \ , "@cImport" + \ , "@cInclude" + \ , "@cUndef" + \ , "@clz" + \ , "@cmpxchgWeak" + \ , "@cmpxchgStrong" + \ , "@compileError" + \ , "@compileLog" + \ , "@ctz" + \ , "@popCount" + \ , "@divExact" + \ , "@divFloor" + \ , "@divTrunc" + \ , "@embedFile" + \ , "@export" + \ , "@extern" + \ , "@tagName" + \ , "@TagType" + \ , "@errorName" + \ , "@call" + \ , "@errorReturnTrace" + \ , "@fence" + \ , "@fieldParentPtr" + \ , "@field" + \ , "@unionInit" + \ , "@frameAddress" + \ , "@import" + \ , "@newStackCall" + \ , "@asyncCall" + \ , "@intToPtr" + \ , "@max" + \ , "@min" + \ , "@memcpy" + \ , "@memset" + \ , "@mod" + \ , "@mulAdd" + \ , "@mulWithOverflow" + \ , "@splat" + \ , "@src" + \ , "@bitOffsetOf" + \ , "@byteOffsetOf" + \ , "@offsetOf" + \ , "@OpaqueType" + \ , "@panic" + \ , "@prefetch" + \ , "@ptrCast" + \ , "@ptrToInt" + \ , "@rem" + \ , "@returnAddress" + \ , "@setCold" + \ , "@Type" + \ , "@shuffle" + \ , "@reduce" + \ , "@select" + \ , "@setRuntimeSafety" + \ , "@setEvalBranchQuota" + \ , "@setFloatMode" + \ , "@shlExact" + \ , "@This" + \ , "@hasDecl" + \ , "@hasField" + \ , "@shlWithOverflow" + \ , "@shrExact" + \ , "@sizeOf" + \ , "@bitSizeOf" + \ , "@sqrt" + \ , "@byteSwap" + \ , "@subWithOverflow" + \ , "@intCast" + \ , "@floatCast" + \ , "@intToFloat" + \ , "@floatToInt" + \ , "@boolToInt" + \ , "@errSetCast" + \ , "@truncate" + \ , "@typeInfo" + \ , "@typeName" + \ , "@TypeOf" + \ , "@atomicRmw" + \ , "@intToError" + \ , "@errorToInt" + \ , "@intToEnum" + \ , "@enumToInt" + \ , "@setAlignStack" + \ , "@frame" + \ , "@Frame" + \ , "@frameSize" + \ , "@bitReverse" + \ , "@Vector" + \ , "@sin" + \ , "@cos" + \ , "@tan" + \ , "@exp" + \ , "@exp2" + \ , "@log" + \ , "@log2" + \ , "@log10" + \ , "@fabs" + \ , "@floor" + \ , "@ceil" + \ , "@trunc" + \ , "@wasmMemorySize" + \ , "@wasmMemoryGrow" + \ , "@round"] + \ } + +function! s:syntax_keyword(dict) + for key in keys(a:dict) + execute 'syntax keyword' key join(a:dict[key], ' ') + endfor +endfunction + +call s:syntax_keyword(s:zig_syntax_keywords) + +syntax match zigType "\v<[iu][1-9]\d*>" +syntax match zigOperator display "\V\[-+/*=^&?|!><%~]" +syntax match zigArrowCharacter display "\V->" + +" 12_34 (. but not ..)? (12_34)? (exponent 12_34)? +syntax match zigDecNumber display "\v<\d%(_?\d)*%(\.\.@!)?%(\d%(_?\d)*)?%([eE][+-]?\d%(_?\d)*)?" +syntax match zigHexNumber display "\v<0x\x%(_?\x)*%(\.\.@!)?%(\x%(_?\x)*)?%([pP][+-]?\d%(_?\d)*)?" +syntax match zigOctNumber display "\v<0o\o%(_?\o)*" +syntax match zigBinNumber display "\v<0b[01]%(_?[01])*" + +syntax match zigCharacterInvalid display contained /b\?'\zs[\n\r\t']\ze'/ +syntax match zigCharacterInvalidUnicode display contained /b'\zs[^[:cntrl:][:graph:][:alnum:][:space:]]\ze'/ +syntax match zigCharacter /b'\([^\\]\|\\\(.\|x\x\{2}\)\)'/ contains=zigEscape,zigEscapeError,zigCharacterInvalid,zigCharacterInvalidUnicode +syntax match zigCharacter /'\([^\\]\|\\\(.\|x\x\{2}\|u\x\{4}\|U\x\{6}\)\)'/ contains=zigEscape,zigEscapeUnicode,zigEscapeError,zigCharacterInvalid + +syntax region zigBlock start="{" end="}" transparent fold + +syntax region zigCommentLine start="//" end="$" contains=zigTodo,@Spell +syntax region zigCommentLineDoc start="//[/!]/\@!" end="$" contains=zigTodo,@Spell + +syntax match zigMultilineStringPrefix /c\?\\\\/ contained containedin=zigMultilineString +syntax region zigMultilineString matchgroup=zigMultilineStringDelimiter start="c\?\\\\" end="$" contains=zigMultilineStringPrefix display + +syntax keyword zigTodo contained TODO + +syntax region zigString matchgroup=zigStringDelimiter start=+c\?"+ skip=+\\\\\|\\"+ end=+"+ oneline contains=zigEscape,zigEscapeUnicode,zigEscapeError,@Spell +syntax match zigEscapeError display contained /\\./ +syntax match zigEscape display contained /\\\([nrt\\'"]\|x\x\{2}\)/ +syntax match zigEscapeUnicode display contained /\\\(u\x\{4}\|U\x\{6}\)/ + +highlight default link zigDecNumber zigNumber +highlight default link zigHexNumber zigNumber +highlight default link zigOctNumber zigNumber +highlight default link zigBinNumber zigNumber + +highlight default link zigBuiltinFn Statement +highlight default link zigKeyword Keyword +highlight default link zigType Type +highlight default link zigCommentLine Comment +highlight default link zigCommentLineDoc Comment +highlight default link zigDummyVariable Comment +highlight default link zigTodo Todo +highlight default link zigString String +highlight default link zigStringDelimiter String +highlight default link zigMultilineString String +highlight default link zigMultilineStringContent String +highlight default link zigMultilineStringPrefix String +highlight default link zigMultilineStringDelimiter Delimiter +highlight default link zigCharacterInvalid Error +highlight default link zigCharacterInvalidUnicode zigCharacterInvalid +highlight default link zigCharacter Character +highlight default link zigEscape Special +highlight default link zigEscapeUnicode zigEscape +highlight default link zigEscapeError Error +highlight default link zigBoolean Boolean +highlight default link zigNull Boolean +highlight default link zigConstant Constant +highlight default link zigNumber Number +highlight default link zigArrowCharacter zigOperator +highlight default link zigOperator Operator +highlight default link zigStructure Structure +highlight default link zigExecution Special +highlight default link zigMacro Macro +highlight default link zigConditional Conditional +highlight default link zigComparatorWord Keyword +highlight default link zigRepeat Repeat +highlight default link zigSpecial Special +highlight default link zigVarDecl Function +highlight default link zigPreProc PreProc +highlight default link zigException Exception + +delfunction s:syntax_keyword + +let b:current_syntax = "zig" + +let &cpo = s:cpo_save +unlet! s:cpo_save diff --git a/runtime/syntax/zir.vim b/runtime/syntax/zir.vim new file mode 100644 index 0000000000..6553d322b7 --- /dev/null +++ b/runtime/syntax/zir.vim @@ -0,0 +1,49 @@ +" Vim syntax file +" Language: Zir +" Upstream: https://github.com/ziglang/zig.vim + +if exists("b:current_syntax") + finish +endif +let b:current_syntax = "zir" + +syn region zirCommentLine start=";" end="$" contains=zirTodo,@Spell + +syn region zirBlock start="{" end="}" transparent fold + +syn keyword zirKeyword primitive fntype int str as ptrtoint fieldptr deref asm unreachable export ref fn + +syn keyword zirTodo contained TODO + +syn region zirString start=+c\?"+ skip=+\\\\\|\\"+ end=+"+ oneline contains=zirEscape,zirEscapeUnicode,zirEscapeError,@Spell + +syn match zirEscapeError display contained /\\./ +syn match zirEscape display contained /\\\([nrt\\'"]\|x\x\{2}\)/ +syn match zirEscapeUnicode display contained /\\\(u\x\{4}\|U\x\{6}\)/ + +syn match zirDecNumber display "\<[0-9]\+\%(.[0-9]\+\)\=\%([eE][+-]\?[0-9]\+\)\=" +syn match zirHexNumber display "\<0x[a-fA-F0-9]\+\%([a-fA-F0-9]\+\%([pP][+-]\?[0-9]\+\)\?\)\=" +syn match zirOctNumber display "\<0o[0-7]\+" +syn match zirBinNumber display "\<0b[01]\+\%(.[01]\+\%([eE][+-]\?[0-9]\+\)\?\)\=" + +syn match zirGlobal display "[^a-zA-Z0-9_]\?\zs@[a-zA-Z0-9_]\+" +syn match zirLocal display "[^a-zA-Z0-9_]\?\zs%[a-zA-Z0-9_]\+" + +hi def link zirCommentLine Comment +hi def link zirTodo Todo + +hi def link zirKeyword Keyword + +hi def link zirString Constant + +hi def link zirEscape Special +hi def link zirEscapeUnicode zirEscape +hi def link zirEscapeError Error + +hi def link zirDecNumber Constant +hi def link zirHexNumber Constant +hi def link zirOctNumber Constant +hi def link zirBinNumber Constant + +hi def link zirGlobal Identifier +hi def link zirLocal Identifier diff --git a/runtime/tutor/tutor.tutor.json b/runtime/tutor/tutor.tutor.json index bf3eae8586..e8628e2f0e 100644 --- a/runtime/tutor/tutor.tutor.json +++ b/runtime/tutor/tutor.tutor.json @@ -2,8 +2,8 @@ "expect": { "63": "This is text with **important information**", "64": "This is text with **important information**", - "71": "Document '&variable'", - "72": "Document '&variable'", + "71": "TODO: Document '&variable'", + "72": "TODO: Document '&variable'", "78": "# This is a level 1 header", "79": "# This is a level 1 header", "80": "### This is a level 3 header", diff --git a/scripts/bump-deps.sh b/scripts/bump-deps.sh deleted file mode 100755 index e725608b39..0000000000 --- a/scripts/bump-deps.sh +++ /dev/null @@ -1,108 +0,0 @@ -#!/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 Nvim dependencies" - echo - echo "Usage: ${BASENAME} [ -h | --pr | --branch=<dep> | --dep=<dependency> ]" - echo " Update a dependency:" - echo " ./scripts/bump-deps.sh --dep Luv --version 1.43.0-0" - echo " Create a PR:" - echo " ./scripts/bump-deps.sh --pr" - 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 index 17e3fd35d6..1873c3cd0d 100644..100755 --- a/scripts/bump_deps.lua +++ b/scripts/bump_deps.lua @@ -1,35 +1,24 @@ +#!/usr/bin/env -S nvim -l + -- 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()" +-- ./scripts/bump_deps.lua -h local M = {} local _trace = false -local required_branch_prefix = "bump-" -local commit_prefix = "build(deps): " +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") + 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") + p('') + vim.cmd('cquit 1') end -- Executes and returns the output of `cmd`, or nil on failure. @@ -37,307 +26,421 @@ end -- -- 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 + 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, "") + 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) + 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") + 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 }) + 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 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 + 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") + 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 + 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 } + 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 shacmd = (vim.fn.executable('sha256sum') == 1 + and{ 'sha256sum', archive_path } + or { 'shasum', '-a', '256', archive_path }) + local archive_sha = run(shacmd):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 .. "/" .. "cmake.deps/CMakeLists.txt" - run_die({ - "sed", - "-i", - "-e", - "s/set(" .. symbol .. "_" .. kind .. ".*$" .. "/set(" .. symbol .. "_" .. kind .. " " .. value .. ")" .. "/", - cmakelists_path, - }, "Failed to write " .. cmakelists_path) + require_executable('sed') + + local cmakelists_path = nvim_src_dir .. '/' .. 'cmake.deps/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") + 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 + require_executable('git') + + local checked_out_branch = assert(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 .. "/" .. "cmake.deps/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" - ) + require_executable('git') + + verify_branch(dependency.name) + + local changed_file = nvim_src_dir .. '/' .. 'cmake.deps/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") + require_executable('git') - local cmakelists_path = nvim_src_dir .. "/" .. "cmake.deps/CMakeLists.txt" - run_die({ "git", "diff", "--quiet", "HEAD", "--", cmakelists_path }, cmakelists_path .. " has uncommitted changes") + local cmakelists_path = nvim_src_dir .. '/' .. 'cmake.deps/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") + 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) + 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 + 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 + require_executable('git') + + local remotes = assert(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") + 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)) + local dependency = assert(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) + vim.validate{ + dependency_name={dependency_name,'s'}, + version={version,'s'}, + } + local dependency = assert(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)) + local dependency = assert(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) + 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) + require_executable('git') + + verify_branch('deps') + + local nvim_remote = find_git_remote(nil) + local relevant_commit = assert(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 + +local function usage() + local this_script = _G.arg[0]:match("[^/]*.lua$") + print(([=[ + Bump Nvim dependencies + + Usage: nvim -l %s [options] + Bump to HEAD, tagged version, commit, or branch: + nvim -l %s --dep Luv --head + nvim -l %s --dep Luv --version 1.43.0-0 + nvim -l %s --dep Luv --commit abc123 + nvim -l %s --dep Luv --branch + Create a PR: + nvim -l %s --pr + + Options: + -h show this message and exit. + --pr submit pr for bumping deps. + --branch <dep> create a branch bump-<dep> from current branch. + --dep <dependency> bump to a specific release or tag. + + Dependency Options: + --version <tag> bump to a specific release or tag. + --commit <hash> bump to a specific commit. + --HEAD bump to a current head. + + <dependency> is one of: + "LuaJIT", "libuv", "Luv", "tree-sitter" + ]=]):format(this_script, this_script, this_script, this_script, this_script, this_script)) +end + +local function parseargs() + local args = {} + for i = 1, #_G.arg do + if _G.arg[i] == '-h' then + args.h = true + elseif _G.arg[i] == '--pr' then + args.pr = true + elseif _G.arg[i] == '--branch' then + args.branch = _G.arg[i+1] + elseif _G.arg[i] == '--dep' then + args.dep = _G.arg[i+1] + elseif _G.arg[i] == '--version' then + args.version = _G.arg[i+1] + elseif _G.arg[i] == '--commit' then + args.commit = _G.arg[i+1] + elseif _G.arg[i] == '--head' then + args.head = true + end + end + return args end -return M +local is_main = _G.arg[0]:match('bump_deps.lua') + +if is_main then + local args = parseargs() + if args.h then + usage() + elseif args.pr then + M.submit_pr() + elseif args.head then + M.head(args.dep) + elseif args.branch then + M.create_branch(args.dep) + elseif args.version then + M.version(args.dep, args.version) + elseif args.commit then + M.commit(args.dep, args.commit) + elseif args.pr then + M.submit_pr() + else + print('missing required arg\n') + os.exit(1) + end +else + return M +end diff --git a/scripts/cliff.toml b/scripts/cliff.toml new file mode 100644 index 0000000000..3fc10e5d16 --- /dev/null +++ b/scripts/cliff.toml @@ -0,0 +1,73 @@ +# configuration file for git-cliff (0.1.0) + +[changelog] +# changelog header +header = """ +# Changelog\n +All notable changes to this project will be documented in this file.\n +""" +# template for the changelog body +# https://tera.netlify.app/docs/#introduction +body = """ +{% if version %}\ + ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} +{% else %}\ + ## [unreleased] +{% endif %}\ +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | upper_first }} + {% for commit in commits%}\ + {% if not commit.scope %}\ + - {{ commit.message | upper_first }} + {% endif %}\ + {% endfor %}\ + {% for group, commits in commits | group_by(attribute="scope") %}\ + {% for commit in commits %}\ + - **{{commit.scope}}**: {{ commit.message | upper_first }} + {% endfor %}\ + {% endfor %} +{% endfor %}\n +""" +# remove the leading and trailing whitespace from the template +trim = true +# changelog footer +footer = """ +<!-- generated by git-cliff --> +""" + +[git] +# parse the commits based on https://www.conventionalcommits.org +conventional_commits = true +# filter out the commits that are not conventional +filter_unconventional = true +# process each line of a commit as an individual commit +split_commits = false +# regex for preprocessing the commit messages +commit_preprocessors = [ +# { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/neovim/neovim/issues/${2}))"}, +] +# regex for parsing and grouping commits +commit_parsers = [ + { message = "!:", group = "Breaking"}, + { message = "^feat", group = "Features"}, + { message = "^fix", group = "Bug Fixes"}, + { message = "^doc", group = "Documentation"}, + { message = "^perf", group = "Performance"}, + { message = "^refactor", group = "Refactor"}, + { message = "^test", group = "Testing"}, + { message = "^chore", group = "Miscellaneous Tasks"}, + { message = "^build", group = "Build System"}, + { message = "^Revert", group = "Reverted Changes"}, +] +# filter out the commits that are not matched by commit parsers +filter_commits = true +# glob pattern for matching git tags +tag_pattern = "v[0-9]*" +# regex for skipping tags +skip_tags = "v0.1.0-beta.1" +# regex for ignoring tags +ignore_tags = "" +# sort the tags chronologically +date_order = false +# sort the commits inside sections by oldest/newest order +sort_commits = "oldest" diff --git a/scripts/download-unicode-files.sh b/scripts/download-unicode-files.sh index 4482cefa34..f0fd4c66ea 100755 --- a/scripts/download-unicode-files.sh +++ b/scripts/download-unicode-files.sh @@ -3,11 +3,12 @@ set -e data_files="UnicodeData.txt CaseFolding.txt EastAsianWidth.txt" emoji_files="emoji-data.txt" +files="'$data_files $emoji_files'" UNIDIR_DEFAULT=src/unicode DOWNLOAD_URL_BASE_DEFAULT='http://unicode.org/Public' -if test x$1 = 'x--help' ; then +if test "$1" = '--help' ; then echo 'Usage:' echo " $0[ TARGET_DIRECTORY[ URL_BASE]]" echo @@ -16,6 +17,7 @@ if test x$1 = 'x--help' ; then echo echo "Default target directory is $PWD/${UNIDIR_DEFAULT}." echo "Default URL base is ${DOWNLOAD_URL_BASE_DEFAULT}." + exit 0 fi UNIDIR=${1:-$UNIDIR_DEFAULT} @@ -23,21 +25,12 @@ DOWNLOAD_URL_BASE=${2:-$DOWNLOAD_URL_BASE_DEFAULT} for filename in $data_files ; do curl -L -o "$UNIDIR/$filename" "$DOWNLOAD_URL_BASE/UNIDATA/$filename" - ( - cd "$UNIDIR" - git add $filename - ) + git -C "$UNIDIR" add "$filename" done for filename in $emoji_files ; do curl -L -o "$UNIDIR/$filename" "$DOWNLOAD_URL_BASE/UNIDATA/emoji/$filename" - ( - cd "$UNIDIR" - git add $filename - ) + git -C "$UNIDIR" add "$filename" done -( - cd "$UNIDIR" - git commit -m "feat: update unicode tables" -- $files -) +git -C "$UNIDIR" commit -m "feat: update unicode tables" . diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index 06ea1831b0..2563f2f410 100644 --- a/scripts/gen_help_html.lua +++ b/scripts/gen_help_html.lua @@ -35,6 +35,7 @@ local spell_dict = { lua = 'Lua', VimL = 'Vimscript', } +local language = nil local M = {} @@ -59,19 +60,34 @@ local exclude_invalid = { ["'previewpopup'"] = "quickref.txt", ["'pvp'"] = "quickref.txt", ["'string'"] = "eval.txt", - Query = "treesitter.txt", - ["eq?"] = "treesitter.txt", - ["lsp-request"] = "lsp.txt", - matchit = "vim_diff.txt", - ["matchit.txt"] = "help.txt", + Query = 'treesitter.txt', + ['eq?'] = 'treesitter.txt', + ['lsp-request'] = 'lsp.txt', + matchit = 'vim_diff.txt', + ['matchit.txt'] = 'help.txt', ["set!"] = "treesitter.txt", - ["v:_null_blob"] = "builtin.txt", - ["v:_null_dict"] = "builtin.txt", - ["v:_null_list"] = "builtin.txt", - ["v:_null_string"] = "builtin.txt", - ["vim.lsp.buf_request()"] = "lsp.txt", - ["vim.lsp.util.get_progress_messages()"] = "lsp.txt", - ["vim.treesitter.start()"] = "treesitter.txt", + ['v:_null_blob'] = 'builtin.txt', + ['v:_null_dict'] = 'builtin.txt', + ['v:_null_list'] = 'builtin.txt', + ['v:_null_string'] = 'builtin.txt', + ['vim.lsp.buf_request()'] = 'lsp.txt', + ['vim.lsp.util.get_progress_messages()'] = 'lsp.txt', +} + +-- False-positive "invalid URLs". +local exclude_invalid_urls = { + ["http://"] = "usr_23.txt", + ["http://."] = "usr_23.txt", + ["http://aspell.net/man-html/Affix-Compression.html"] = "spell.txt", + ["http://aspell.net/man-html/Phonetic-Code.html"] = "spell.txt", + ["http://canna.sourceforge.jp/"] = "mbyte.txt", + ["http://gnuada.sourceforge.net"] = "ft_ada.txt", + ["http://lua-users.org/wiki/StringLibraryTutorial"] = "lua.txt", + ["http://michael.toren.net/code/"] = "pi_tar.txt", + ["http://papp.plan9.de"] = "syntax.txt", + ["http://wiki.services.openoffice.org/wiki/Dictionaries"] = "spell.txt", + ["http://www.adapower.com"] = "ft_ada.txt", + ["http://www.jclark.com/"] = "quickfix.txt", } local function tofile(fname, text) @@ -279,9 +295,11 @@ local function ignore_invalid(s) end local function ignore_parse_error(s) - -- Ignore parse errors for unclosed codespan/optionlink/tag. - -- This is common in vimdocs and is treated as plaintext by :help. - return s:find("^[`'|*]") + return ( + -- Ignore parse errors for unclosed tag. + -- This is common in vimdocs and is treated as plaintext by :help. + s:find("^[`'|*]") + ) end local function has_ancestor(node, ancestor_name) @@ -319,6 +337,17 @@ local function validate_link(node, bufnr, fname) return helppage, tagname, ignored end +-- TODO: port the logic from scripts/check_urls.vim +local function validate_url(text, fname) + local ignored = false + if vim.fs.basename(fname) == 'pi_netrw.txt' then + ignored = true + elseif text:find('http%:') and not exclude_invalid_urls[text] then + invalid_urls[text] = vim.fs.basename(fname) + end + return ignored +end + -- Traverses the tree at `root` and checks that |tag| links point to valid helptags. local function visit_validate(root, level, lang_tree, opt, stats) level = level or 0 @@ -353,14 +382,25 @@ local function visit_validate(root, level, lang_tree, opt, stats) end end elseif node_name == 'url' then - if text:find('http%:') then - invalid_urls[text] = vim.fs.basename(opt.fname) - end + local fixed_url, _ = fix_url(trim(text)) + validate_url(fixed_url, opt.fname) elseif node_name == 'taglink' or node_name == 'optionlink' then local _, _, _ = validate_link(root, opt.buf, opt.fname) end end +-- Fix tab alignment issues caused by concealed characters like |, `, * in tags +-- and code blocks. +local function fix_tab_after_conceal(text, next_node_text) + -- Vim tabs take into account the two concealed characters even though they + -- are invisible, so we need to add back in the two spaces if this is + -- followed by a tab to make the tab alignment to match Vim's behavior. + if string.sub(next_node_text,1,1) == '\t' then + text = text .. ' ' + end + return text +end + -- Generates HTML from node `root` recursively. local function visit_node(root, level, lang_tree, headings, opt, stats) level = level or 0 @@ -447,7 +487,7 @@ local function visit_node(root, level, lang_tree, headings, opt, stats) end return string.format('<div class="help-para">\n%s\n</div>\n', text) elseif node_name == 'line' then - if parent ~= 'codeblock' and (is_blank(text) or is_noise(text, stats.noise_lines)) then + if (parent ~= 'codeblock' or parent ~= 'code') and (is_blank(text) or is_noise(text, stats.noise_lines)) then return '' -- Discard common "noise" lines. end -- XXX: Avoid newlines (too much whitespace) after block elements in old (preformatted) layout. @@ -477,19 +517,39 @@ local function visit_node(root, level, lang_tree, headings, opt, stats) if ignored then return text end - return ('%s<a href="%s#%s">%s</a>'):format(ws(), helppage, url_encode(tagname), html_esc(tagname)) + local s = ('%s<a href="%s#%s">%s</a>'):format(ws(), helppage, url_encode(tagname), html_esc(tagname)) + if opt.old and node_name == 'taglink' then + s = fix_tab_after_conceal(s, node_text(root:next_sibling())) + end + return s elseif vim.tbl_contains({'codespan', 'keycode'}, node_name) then if root:has_error() then return text end - return ('%s<code>%s</code>'):format(ws(), trimmed) + local s = ('%s<code>%s</code>'):format(ws(), trimmed) + if opt.old and node_name == 'codespan' then + s = fix_tab_after_conceal(s, node_text(root:next_sibling())) + end + return s elseif node_name == 'argument' then return ('%s<code>{%s}</code>'):format(ws(), text) elseif node_name == 'codeblock' then + return text + elseif node_name == 'language' then + language = node_text(root) + return '' + elseif node_name == 'code' then if is_blank(text) then return '' end - return ('<pre>%s</pre>'):format(trim(trim_indent(text), 2)) + local code + if language then + code = ('<pre><code class="language-%s">%s</code></pre>'):format(language,trim(trim_indent(text), 2)) + language = nil + else + code = ('<pre>%s</pre>'):format(trim(trim_indent(text), 2)) + end + return code elseif node_name == 'tag' then -- anchor if root:has_error() then return text @@ -504,6 +564,10 @@ local function visit_node(root, level, lang_tree, headings, opt, stats) end local el = in_heading and 'span' or 'code' local s = ('%s<a name="%s"></a><%s class="%s">%s</%s>'):format(ws(), url_encode(tagname), el, cssclass, trimmed, el) + if opt.old then + s = fix_tab_after_conceal(s, node_text(root:next_sibling())) + end + if in_heading and prev ~= 'tag' then -- Start the <span> container for tags in a heading. -- This makes "justify-content:space-between" right-align the tags. @@ -631,6 +695,9 @@ local function gen_one(fname, to_fname, old, commit) <link href="/css/bootstrap.css" rel="stylesheet"> <link href="/css/main.css" rel="stylesheet"> <link href="help.css" rel="stylesheet"> + <link href="/highlight/styles/neovim.min.css" rel="stylesheet"> + <script src="/highlight/highlight.min.js"></script> + <script>hljs.highlightAll();</script> <title>%s - Neovim docs</title> </head> <body> @@ -773,8 +840,14 @@ end local function gen_css(fname) local css = [[ :root { - --code-color: #008B8B; - --tag-color: gray; + --code-color: #004b4b; + --tag-color: #095943; + } + @media (prefers-color-scheme: dark) { + :root { + --code-color: #00c243; + --tag-color: #00b7b7; + } } @media (min-width: 40em) { .toc { @@ -793,11 +866,6 @@ local function gen_css(fname) display: block; } } - @media (prefers-color-scheme: dark) { - :root { - --code-color: cyan; - } - } .toc { /* max-width: 12rem; */ height: 85%; /* Scroll if there are too many items. https://github.com/neovim/neovim.github.io/issues/297 */ @@ -817,7 +885,7 @@ local function gen_css(fname) } h1, h2, h3, h4, h5 { font-family: sans-serif; - border-bottom: 1px solid #41464bd6; /*rgba(0, 0, 0, .9);*/ + border-bottom: 1px solid var(--tag-color); /*rgba(0, 0, 0, .9);*/ } h3, h4, h5 { border-bottom-style: dashed; @@ -892,7 +960,7 @@ local function gen_css(fname) pre { /* Tabs are used in codeblocks only for indentation, not alignment, so we can aggressively shrink them. */ tab-size: 2; - white-space: pre; + white-space: pre-wrap; line-height: 1.3; /* Important for ascii art. */ overflow: visible; /* font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; */ diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py index 3ee9d8b5dd..8e1d6ef80a 100755 --- a/scripts/gen_vimdoc.py +++ b/scripts/gen_vimdoc.py @@ -12,15 +12,10 @@ Flow: update_params_map / render_node -This would be easier using lxml and XSLT, but: +TODO: eliminate this script and use Lua+treesitter (requires parsers for C and +Lua markdown-style docstrings). - 1. This should avoid needing Python dependencies, especially ones that are - C modules that have library dependencies (lxml requires libxml and - libxslt). - 2. I wouldn't know how to deal with nested indentation in <para> tags using - XSLT. - -Each function :help block is formatted as follows: +The generated :help text for each function is formatted as follows: - Max width of 78 columns (`text_width`). - Indent with spaces (not tabs). @@ -60,11 +55,19 @@ if sys.version_info < MIN_PYTHON_VERSION: doxygen_version = tuple((int(i) for i in subprocess.check_output(["doxygen", "-v"], universal_newlines=True).split()[0].split('.'))) +# Until 0.9 is released, need this hacky way to check that "nvim -l foo.lua" works. +nvim_version = list(line for line in subprocess.check_output(['nvim', '-h'], universal_newlines=True).split('\n') + if '-l ' in line) + if doxygen_version < MIN_DOXYGEN_VERSION: print("\nRequires doxygen {}.{}.{}+".format(*MIN_DOXYGEN_VERSION)) print("Your doxygen version is {}.{}.{}\n".format(*doxygen_version)) sys.exit(1) +if len(nvim_version) == 0: + print("\nRequires 'nvim -l' feature, see https://github.com/neovim/neovim/pull/18706") + sys.exit(1) + # DEBUG = ('DEBUG' in os.environ) INCLUDE_C_DECL = ('INCLUDE_C_DECL' in os.environ) INCLUDE_DEPRECATED = ('INCLUDE_DEPRECATED' in os.environ) @@ -84,7 +87,7 @@ base_dir = os.path.dirname(os.path.dirname(script_path)) out_dir = os.path.join(base_dir, 'tmp-{target}-doc') filter_cmd = '%s %s' % (sys.executable, script_path) msgs = [] # Messages to show on exit. -lua2dox_filter = os.path.join(base_dir, 'scripts', 'lua2dox_filter') +lua2dox = os.path.join(base_dir, 'scripts', 'lua2dox.lua') CONFIG = { 'api': { @@ -130,12 +133,14 @@ CONFIG = { 'filename': 'lua.txt', 'section_order': [ '_editor.lua', + '_inspector.lua', 'shared.lua', 'uri.lua', 'ui.lua', 'filetype.lua', 'keymap.lua', 'fs.lua', + 'secure.lua', ], 'files': [ 'runtime/lua/vim/_editor.lua', @@ -145,11 +150,14 @@ CONFIG = { 'runtime/lua/vim/filetype.lua', 'runtime/lua/vim/keymap.lua', 'runtime/lua/vim/fs.lua', + 'runtime/lua/vim/secure.lua', + 'runtime/lua/vim/_inspector.lua', ], 'file_patterns': '*.lua', 'fn_name_prefix': '', 'section_name': { 'lsp.lua': 'core', + '_inspector.lua': 'inspector', }, 'section_fmt': lambda name: ( 'Lua module: vim' @@ -166,11 +174,13 @@ CONFIG = { 'module_override': { # `shared` functions are exposed on the `vim` module. 'shared': 'vim', + '_inspector': 'vim', 'uri': 'vim', 'ui': 'vim.ui', 'filetype': 'vim.filetype', 'keymap': 'vim.keymap', 'fs': 'vim.fs', + 'secure': 'vim.secure', }, 'append_only': [ 'shared.lua', @@ -185,6 +195,7 @@ CONFIG = { 'diagnostic.lua', 'codelens.lua', 'tagfunc.lua', + 'semantic_tokens.lua', 'handlers.lua', 'util.lua', 'log.lua', @@ -260,17 +271,11 @@ CONFIG = { if name.lower() == 'treesitter' else f'*lua-treesitter-{name.lower()}*'), 'fn_helptag_fmt': lambda fstem, name: ( - f'*{name}()*' - if name != 'new' - else f'*{fstem}.{name}()*'), - # 'fn_helptag_fmt': lambda fstem, name: ( - # f'*vim.treesitter.{name}()*' - # if fstem == 'treesitter' - # else ( - # '*vim.lsp.client*' - # # HACK. TODO(justinmk): class/structure support in lua2dox - # if 'lsp.client' == f'{fstem}.{name}' - # else f'*vim.lsp.{fstem}.{name}()*')), + f'*vim.{fstem}.{name}()*' + if fstem == 'treesitter' + else f'*{name}()*' + if name[0].isupper() + else f'*vim.treesitter.{fstem}.{name}()*'), 'module_override': {}, 'append_only': [], } @@ -353,6 +358,17 @@ def self_or_child(n): return n.childNodes[0] +def align_tags(line): + tag_regex = r"\s(\*.+?\*)(?:\s|$)" + tags = re.findall(tag_regex, line) + + if len(tags) > 0: + line = re.sub(tag_regex, "", line) + tags = " " + " ".join(tags) + line = line + (" " * (78 - len(line) - len(tags))) + tags + return line + + def clean_lines(text): """Removes superfluous lines. @@ -498,7 +514,12 @@ def render_node(n, text, prefix='', indent='', width=text_width - indentation, if n.nodeName == 'preformatted': o = get_text(n, preformatted=True) ensure_nl = '' if o[-1] == '\n' else '\n' - text += '>{}{}\n<'.format(ensure_nl, o) + if o[0:4] == 'lua\n': + text += '>lua{}{}\n<'.format(ensure_nl, o[3:-1]) + elif o[0:4] == 'vim\n': + text += '>vim{}{}\n<'.format(ensure_nl, o[3:-1]) + else: + text += '>{}{}\n<'.format(ensure_nl, o) elif is_inline(n): text = doc_wrap(get_text(n), indent=indent, width=width) @@ -801,7 +822,8 @@ def extract_from_xml(filename, target, width, fmt_vimhelp): prefix = '%s(' % name suffix = '%s)' % ', '.join('{%s}' % a[1] for a in params - if a[0] not in ('void', 'Error', 'Arena')) + if a[0] not in ('void', 'Error', 'Arena', + 'lua_State')) if not fmt_vimhelp: c_decl = '%s %s(%s);' % (return_type, name, ', '.join(c_args)) @@ -951,7 +973,7 @@ def fmt_doxygen_xml_as_vimhelp(filename, target): start = end - func_doc = "\n".join(split_lines) + func_doc = "\n".join(map(align_tags, split_lines)) if (name.startswith(CONFIG[target]['fn_name_prefix']) and name != "nvim_error_event"): @@ -979,7 +1001,7 @@ def delete_lines_below(filename, tokenstr): fp.writelines(lines[0:i]) -def main(config, args): +def main(doxygen_config, args): """Generates: 1. Vim :help docs @@ -1007,7 +1029,7 @@ def main(config, args): # runtime/lua/vim/lsp.lua:209: warning: argument 'foo' not found stderr=(subprocess.STDOUT if debug else subprocess.DEVNULL)) p.communicate( - config.format( + doxygen_config.format( input=' '.join( [f'"{file}"' for file in CONFIG[target]['files']]), output=output_dir, @@ -1094,11 +1116,7 @@ def main(config, args): fn_map_full.update(fn_map) if len(sections) == 0: - if target == 'lua': - fail(f'no sections for target: {target} (this usually means' - + ' "luajit" was not found by scripts/lua2dox_filter)') - else: - fail(f'no sections for target: {target}') + fail(f'no sections for target: {target} (look for errors near "Preprocessing" log lines above)') if len(sections) > len(CONFIG[target]['section_order']): raise RuntimeError( 'found new modules "{}"; update the "section_order" map'.format( @@ -1145,7 +1163,7 @@ def main(config, args): def filter_source(filename): name, extension = os.path.splitext(filename) if extension == '.lua': - p = subprocess.run([lua2dox_filter, filename], stdout=subprocess.PIPE) + p = subprocess.run(['nvim', '-l', lua2dox, filename], stdout=subprocess.PIPE) op = ('?' if 0 != p.returncode else p.stdout.decode('utf-8')) print(op) else: diff --git a/scripts/genappimage.sh b/scripts/genappimage.sh index cc88ab5559..9944b5eb31 100755 --- a/scripts/genappimage.sh +++ b/scripts/genappimage.sh @@ -8,7 +8,8 @@ # App arch, used by generate_appimage. if [ -z "$ARCH" ]; then - export ARCH="$(arch)" + ARCH="$(arch)" + export ARCH fi TAG=$1 @@ -34,8 +35,9 @@ make install # App version, used by generate_appimage. VERSION=$("$ROOT_DIR"/build/bin/nvim --version | head -n 1 | grep -o 'v.*') +export VERSION -cd "$APP_BUILD_DIR" +cd "$APP_BUILD_DIR" || exit # Only downloads linuxdeploy if the remote file is different from local if [ -e "$APP_BUILD_DIR"/linuxdeploy-x86_64.AppImage ]; then @@ -53,7 +55,7 @@ chmod +x "$APP_BUILD_DIR"/linuxdeploy-x86_64.AppImage mkdir "$APP_DIR/usr/share/metainfo/" cp "$ROOT_DIR/runtime/nvim.appdata.xml" "$APP_DIR/usr/share/metainfo/" -cd "$APP_DIR" +cd "$APP_DIR" || exit ######################################################################## # AppDir complete. Now package it as an AppImage. @@ -71,7 +73,7 @@ exec "$(dirname "$(readlink -f "${0}")")/usr/bin/nvim" ${@+"$@"} EOF chmod 755 AppRun -cd "$APP_BUILD_DIR" # Get out of AppImage directory. +cd "$APP_BUILD_DIR" || exit # Get out of AppImage directory. # Set the name of the file generated by appimage export OUTPUT=nvim.appimage @@ -85,7 +87,7 @@ fi # - Expects: $ARCH, $APP, $VERSION env vars # - Expects: ./$APP.AppDir/ directory # - Produces: ./nvim.appimage -./linuxdeploy-x86_64.AppImage --appdir $APP.AppDir -d $ROOT_DIR/runtime/nvim.desktop -i \ +./linuxdeploy-x86_64.AppImage --appdir $APP.AppDir -d "$ROOT_DIR"/runtime/nvim.desktop -i \ "$ROOT_DIR/runtime/nvim.png" --output appimage # Moving the final executable to a different folder so it isn't in the diff --git a/scripts/genvimvim.lua b/scripts/genvimvim.lua index 868084a583..3e9e7077be 100644 --- a/scripts/genvimvim.lua +++ b/scripts/genvimvim.lua @@ -11,6 +11,8 @@ local funcs_file = arg[3] package.path = nvimsrcdir .. '/?.lua;' .. package.path +_G.vim = loadfile(nvimsrcdir..'/../../runtime/lua/vim/shared.lua')() + local lld = {} local syn_fd = io.open(syntax_file, 'w') lld.line_length = 0 @@ -115,7 +117,7 @@ end local nvimau_start = 'syn keyword nvimAutoEvent contained ' w('\n\n' .. nvimau_start) -for au, _ in pairs(auevents.nvim_specific) do +for au, _ in vim.spairs(auevents.nvim_specific) do if lld.line_length > 850 then w('\n' .. nvimau_start) end @@ -126,7 +128,7 @@ w('\n\nsyn case match') local vimfun_start = 'syn keyword vimFuncName contained ' w('\n\n' .. vimfun_start) local funcs = mpack.unpack(io.open(funcs_file, 'rb'):read("*all")) -for name, _ in pairs(funcs) do +for _, name in ipairs(funcs) do if name then if lld.line_length > 850 then w('\n' .. vimfun_start) diff --git a/scripts/git-log-pretty-since.sh b/scripts/git-log-pretty-since.sh index a0aa4354b6..95dcee23f5 100755 --- a/scripts/git-log-pretty-since.sh +++ b/scripts/git-log-pretty-since.sh @@ -16,9 +16,9 @@ __SINCE=$1 __INVMATCH=$2 is_merge_commit() { - git rev-parse $1 >/dev/null 2>&1 \ + git rev-parse "$1" >/dev/null 2>&1 \ || { echo "ERROR: invalid commit: $1"; exit 1; } - git log $1^2 >/dev/null 2>&1 && return 0 || return 1 + git log "$1"^2 >/dev/null 2>&1 && return 0 || return 1 } # Removes parens from issue/ticket/PR numbers. @@ -40,13 +40,13 @@ _format_ticketnums() { } for commit in $(git log --format='%H' --first-parent "$__SINCE"..HEAD); do - if is_merge_commit ${commit} ; then - if [ -z "$__INVMATCH" ] || ! git log --oneline ${commit}^1..${commit}^2 \ + if is_merge_commit "${commit}" ; then + if [ -z "$__INVMATCH" ] || ! git log --oneline "${commit}^1..${commit}^2" \ | >/dev/null 2>&1 grep -E "$__INVMATCH" ; then - git log -1 --oneline ${commit} - git log --format=' %h %s' ${commit}^1..${commit}^2 + git log -1 --oneline "${commit}" + git log --format=' %h %s' "${commit}^1..${commit}^2" fi else - git log -1 --oneline ${commit} + git log -1 --oneline "${commit}" fi done | _format_ticketnums diff --git a/scripts/lintcommit.lua b/scripts/lintcommit.lua index 16326cfe66..87a8e62503 100644 --- a/scripts/lintcommit.lua +++ b/scripts/lintcommit.lua @@ -86,11 +86,28 @@ local function validate_commit(commit_message) vim.inspect(allowed_types)) end - -- Check if scope is empty + -- Check if scope is appropriate if before_colon:match("%(") then local scope = vim.trim(before_colon:match("%((.*)%)")) + if scope == '' then - return [[Scope can't be empty.]] + return [[Scope can't be empty]] + end + + if vim.startswith(scope, "nvim_") then + return [[Scope should be "api" instead of "nvim_..."]] + end + + local alternative_scope = { + ['filetype.vim'] = 'filetype', + ['filetype.lua'] = 'filetype', + ['tree-sitter'] = 'treesitter', + ['ts'] = 'treesitter', + ['hl'] = 'highlight', + } + + if alternative_scope[scope] then + return ('Scope should be "%s" instead of "%s"'):format(alternative_scope[scope], scope) end end diff --git a/scripts/lua2dox.lua b/scripts/lua2dox.lua index 86afe97a7e..19f8f8141d 100644 --- a/scripts/lua2dox.lua +++ b/scripts/lua2dox.lua @@ -27,14 +27,13 @@ http://search.cpan.org/~alec/Doxygen-Lua-0.02/lib/Doxygen/Lua.pm Running ------- -This file "lua2dox.lua" gets called by "lua2dox_filter" (bash). +This script "lua2dox.lua" gets called by "gen_vimdoc.py". Doxygen must be on your system. You can experiment like so: - Run "doxygen -g" to create a default Doxyfile. -- Then alter it to let it recognise lua. Add the two following lines: +- Then alter it to let it recognise lua. Add the following line: FILE_PATTERNS = *.lua - FILTER_PATTERNS = *.lua=lua2dox_filter - Then run "doxygen". The core function reads the input file (filename or stdin) and outputs some pseudo C-ish language. @@ -117,26 +116,6 @@ local function string_split(Str, Pattern) return splitStr end ---! \class TCore_Commandline ---! \brief reads/parses commandline -local TCore_Commandline = class() - ---! \brief constructor -function TCore_Commandline.init(this) - this.argv = arg - this.parsed = {} - this.params = {} -end - ---! \brief get value -function TCore_Commandline.getRaw(this, Key, Default) - local val = this.argv[Key] - if not val then - val = Default - end - return val -end - ------------------------------- --! \brief file buffer --! @@ -147,7 +126,7 @@ local TStream_Read = class() --! --! \param Filename name of file to read (or nil == stdin) function TStream_Read.getContents(this, Filename) - assert(Filename) + assert(Filename, ('invalid file: %s'):format(Filename)) -- get lines from file -- syphon lines to our table local filecontents = {} @@ -497,14 +476,14 @@ function TLua2DoX_filter.readfile(this, AppStamp, Filename) else this:warning(inStream:getLineNo(), 'something weird here') end - fn_magic = nil -- mustn't indavertently use it again + fn_magic = nil -- mustn't inadvertently use it again -- TODO: If we can make this learn how to generate these, that would be helpful. -- elseif string.find(line, "^M%['.*'%] = function") then -- state = 'in_function' -- it's a function -- outStream:writeln("function textDocument/publishDiagnostics(...){}") - -- fn_magic = nil -- mustn't indavertently use it again + -- fn_magic = nil -- mustn't inadvertently use it again else state = '' -- unknown if #line > 0 then -- we don't know what this line means, so just comment it out @@ -548,15 +527,14 @@ end local This_app = TApp() --main -local cl = TCore_Commandline() -local argv1 = cl:getRaw(2) +local argv1 = arg[1] if argv1 == '--help' then TCore_IO_writeln(This_app:getVersion()) TCore_IO_writeln(This_app:getCopyright()) TCore_IO_writeln([[ run as: - lua2dox_filter <param> + nvim -l scripts/lua2dox.lua <param> -------------- Param: <filename> : interprets filename diff --git a/scripts/lua2dox_filter b/scripts/lua2dox_filter deleted file mode 100755 index e3fa95d0cf..0000000000 --- a/scripts/lua2dox_filter +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env bash - -########################################################################### -# Copyright (C) 2012 by Simon Dales # -# simon@purrsoft.co.uk # -# # -# This program is free software; you can redistribute it and/or modify # -# it under the terms of the GNU General Public License as published by # -# the Free Software Foundation; either version 2 of the License, or # -# (at your option) any later version. # -# # -# This program is distributed in the hope that it will be useful, # -# but WITHOUT ANY WARRANTY; without even the implied warranty of # -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # -# GNU General Public License for more details. # -# # -# You should have received a copy of the GNU General Public License # -# along with this program; if not, write to the # -# Free Software Foundation, Inc., # -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # -########################################################################### -LANG="" - -##! \brief test executable to see if it exists -test_executable() { - P_EXE="$1" - ######### - WHICH=$(which "$P_EXE") - if test -z "${WHICH}"; then - echo "not found \"${P_EXE}\"" - else - EXE="${P_EXE}" - fi -} - -##! \brief sets the lua interpreter -set_lua() { - if test -z "${EXE}"; then - test_executable '.deps/usr/bin/luajit' - fi - - if test -z "${EXE}"; then - test_executable 'luajit' - fi - - if test -z "${EXE}"; then - test_executable 'lua' - fi -} - -##! \brief makes canonical name of file -##! -##! Note that "readlink -f" doesn't work in MacOSX -##! -do_readlink() { - pushd . > /dev/null - TARGET_FILE=$1 - - cd "$(dirname $TARGET_FILE)" - TARGET_FILE=$(basename "$TARGET_FILE") - - # Iterate down a (possible) chain of symlinks - while [ -L "$TARGET_FILE" ]; do - TARGET_FILE=$(readlink "$TARGET_FILE") - cd $(dirname "$TARGET_FILE") - TARGET_FILE=$(basename "$TARGET_FILE") - done - - PHYS_DIR=$(pwd -P) - RESULT=$PHYS_DIR - popd > /dev/null -} - -##main -set_lua -if test -z "${EXE}"; then - echo "no lua interpreter found" - exit 1 -else - BASENAME=$(basename "$0") - do_readlink "$0" - DIRNAME="${RESULT}" - - LUASCRIPT="${DIRNAME}/lua2dox.lua ${BASENAME}" - #echo "lua[${LUASCRIPT}]" - - ${EXE} ${LUASCRIPT} $@ -fi - -##eof diff --git a/scripts/pvscheck.sh b/scripts/pvscheck.sh index 610c20eb48..97757c0848 100755 --- a/scripts/pvscheck.sh +++ b/scripts/pvscheck.sh @@ -328,7 +328,7 @@ realdir() {( patch_sources() {( local tgt="$1" ; shift - local only_bulid="${1}" ; shift + local only_build="${1}" ; shift get_pvs_comment "$tgt" diff --git a/scripts/release.sh b/scripts/release.sh index 380503662d..4321d96f62 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -59,8 +59,8 @@ _do_release_commit() { $__sed -i.bk 's/(NVIM_VERSION_PRERELEASE) "-dev"/\1 ""/' CMakeLists.txt if grep '(NVIM_API_PRERELEASE true)' CMakeLists.txt > /dev/null; then $__sed -i.bk 's/(NVIM_API_PRERELEASE) true/\1 false/' CMakeLists.txt - build/bin/nvim --api-info > test/functional/fixtures/api_level_$__API_LEVEL.mpack - git add test/functional/fixtures/api_level_$__API_LEVEL.mpack + build/bin/nvim --api-info > "test/functional/fixtures/api_level_$__API_LEVEL.mpack" + git add "test/functional/fixtures/api_level_${__API_LEVEL}.mpack" fi $__sed -i.bk 's,(<releases>),\1\ diff --git a/scripts/update_terminfo.sh b/scripts/update_terminfo.sh index 8a0937cc8c..775048f246 100755 --- a/scripts/update_terminfo.sh +++ b/scripts/update_terminfo.sh @@ -35,7 +35,7 @@ readonly -A entries=( db="$(mktemp -du)" print_bold() { - printf "\\e[1m$*\\e[0m" + printf "\\e[1m%b\\e[0m" "$*" } cd "$(git rev-parse --show-toplevel)" diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index ad1973603e..f9f7330097 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -35,7 +35,7 @@ usage() { echo " -m {vim-revision} List previous (older) missing Vim patches." echo " -M List all merged patch-numbers (at current v:version)." echo " -p {vim-revision} Download and generate a Vim patch. vim-revision" - echo " can be a Vim version (8.0.xxx) or a Git hash." + echo " can be a Vim version (8.1.xxx) or a Git hash." echo " -P {vim-revision} Download, generate and apply a Vim patch." echo " -g {vim-revision} Download a Vim patch." echo " -s [pr args] Create a vim-patch pull request." @@ -120,9 +120,9 @@ get_vim_sources() { commit_message() { if [[ -n "$vim_tag" ]]; then - printf '%s\n%s' "${vim_message}" "${vim_commit_url}" + printf '%s\n\n%s\n\n%s' "${vim_message}" "${vim_commit_url}" "${vim_coauthor}" else - printf 'vim-patch:%s\n\n%s\n%s' "$vim_version" "$vim_message" "$vim_commit_url" + printf 'vim-patch:%s\n\n%s\n\n%s\n\n%s' "$vim_version" "$vim_message" "$vim_commit_url" "$vim_coauthor" fi } @@ -175,6 +175,7 @@ assign_commit_details() { vim_commit_url="https://github.com/vim/vim/commit/${vim_commit}" vim_message="$(git -C "${VIM_SOURCE_DIR}" log -1 --pretty='format:%B' "${vim_commit}" \ | sed -e 's/\(#[0-9]\{1,\}\)/vim\/vim\1/g')" + vim_coauthor="$(git -C "${VIM_SOURCE_DIR}" log -1 --pretty='format:Co-authored-by: %an <%ae>' "${vim_commit}")" if [[ ${munge_commit_line} == "true" ]]; then # Remove first line of commit message. vim_message="$(echo "${vim_message}" | sed -e '1s/^patch /vim-patch:/')" @@ -192,11 +193,15 @@ 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' + local na_src='auto\|configure.*\|GvimExt\|hardcopy.*\|libvterm\|proto\|tee\|VisVim\|xpm\|xxd\|Make.*\|INSTALL.*\|beval.*\|gui.*\|if_cscop\|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 runtime files ported to Lua. + local na_rt='filetype\.vim\|scripts\.vim\|autoload\/ft\/dist\.vim\|print\/.*' + 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/runtime/\<\%('"${na_rt}"'\)\>@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' + local na_doc='channel\.txt\|if_cscop\.txt\|netbeans\.txt\|os_\w\+\.txt\|print\.txt\|term\.txt\|todo\.txt\|version\d\.txt\|vim9\.txt\|sponsor\.txt\|intro\.txt\|tags' 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/runtime/doc/\<\%('"${na_doc}"'\)\>@norm! d/\v(^diff)|%$
' +w +q "$file" # Remove "Last change ..." changes in doc files. @@ -207,7 +212,7 @@ preprocess_patch() { 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\|cscope\.vim\|gui.*\|hardcopy\.vim\|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 @@ -237,6 +242,14 @@ preprocess_patch() { LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/userfunc\.c/\1\/eval\/userfunc\.c/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" + # Rename evalbuffer.c to eval/buffer.c + LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/evalbuffer\.c/\1\/eval\/buffer\.c/g' \ + "$file" > "$file".tmp && mv "$file".tmp "$file" + + # Rename evalwindow.c to eval/window.c + LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/evalwindow\.c/\1\/eval\/window\.c/g' \ + "$file" > "$file".tmp && mv "$file".tmp "$file" + # Rename map.c to mapping.c LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/map\(\.[ch]\)/\1\/mapping\2/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" @@ -274,6 +287,35 @@ preprocess_patch() { "$file" > "$file".tmp && mv "$file".tmp "$file" } +uncrustify_patch() { + git diff --quiet || { + >&2 echo 'Vim source working tree dirty, aborting.' + exit 1 + } + + local patch_path=$NVIM_SOURCE_DIR/build/vim_patch + rm -rf "$patch_path" + mkdir -p "$patch_path"/{a,b} + + local commit="$1" + for file in $(git diff-tree --name-only --no-commit-id -r --diff-filter=a "$commit"); do + git --work-tree="$patch_path"/a checkout --quiet "$commit"~ -- "$file" + done + for file in $(git diff-tree --name-only --no-commit-id -r --diff-filter=d "$commit"); do + git --work-tree="$patch_path"/b checkout --quiet "$commit" -- "$file" + done + git reset --quiet --hard HEAD + + # If the difference are drastic enough uncrustify may need to be used more + # than once. This is obviously a bug that needs to be fixed on uncrustify's + # end, but in the meantime this workaround is sufficient. + for _ in {1..2}; do + uncrustify -c "$NVIM_SOURCE_DIR"/src/uncrustify.cfg -q --replace --no-backup "$patch_path"/{a,b}/src/*.[ch] + done + + (cd "$patch_path" && (git --no-pager diff --no-index --no-prefix --patch --unified=5 --color=never a/ b/ || true)) +} + get_vimpatch() { get_vim_sources @@ -282,7 +324,11 @@ get_vimpatch() { msg_ok "Found Vim revision '${vim_commit}'." local patch_content - patch_content="$(git --no-pager show --unified=5 --color=never -1 --pretty=medium "${vim_commit}")" + if check_executable uncrustify; then + patch_content="$(uncrustify_patch "${vim_commit}")" + else + patch_content="$(git --no-pager show --unified=5 --color=never -1 --pretty=medium "${vim_commit}")" + fi cd "${NVIM_SOURCE_DIR}" @@ -466,7 +512,7 @@ submit_pr() { # Gets all Vim commits since the "start" commit. list_vim_commits() { ( - cd "${VIM_SOURCE_DIR}" && git log --reverse v8.0.0000..HEAD "$@" + cd "${VIM_SOURCE_DIR}" && git log --reverse v8.1.0000..HEAD "$@" ) } # Prints all (sorted) "vim-patch:xxx" tokens found in the Nvim git log. @@ -484,7 +530,7 @@ list_vimpatch_tokens() { list_vimpatch_numbers() { # Transform "vim-patch:X.Y.ZZZZ" to "ZZZZ". list_vimpatch_tokens | while read -r vimpatch_token; do - echo "$vimpatch_token" | grep '8\.0\.' | sed 's/.*vim-patch:8\.0\.\([0-9a-z]\+\).*/\1/' + echo "$vimpatch_token" | grep '8\.1\.' | sed -E 's/.*vim-patch:8\.1\.([0-9a-z]+).*/\1/' done } @@ -581,7 +627,7 @@ _set_missing_vimpatches() { else info=${line#* } if [[ -n $info ]]; then - # Remove any "patch 8.0.0902: " prefixes, and prefix with ": ". + # Remove any "patch 8.1.0902: " prefixes, and prefix with ": ". info=": ${info#patch*: }" fi fi @@ -624,7 +670,7 @@ show_vimpatches() { Instructions: To port one of the above patches to Neovim, execute this script with the patch revision as argument and follow the instructions, e.g. - '${BASENAME} -p v8.0.1234', or '${BASENAME} -P v8.0.1234' + '${BASENAME} -p v8.1.1234', or '${BASENAME} -P v8.1.1234' NOTE: Please port the _oldest_ patch if you possibly can. You can use '${BASENAME} -l path/to/file' to see what patches are missing for a file. diff --git a/scripts/vimpatch.lua b/scripts/vimpatch.lua index 11eb285462..836f672f6e 100755 --- a/scripts/vimpatch.lua +++ b/scripts/vimpatch.lua @@ -1,7 +1,7 @@ -- Updates version.c list of applied Vim patches. -- -- Usage: --- VIM_SOURCE_DIR=~/neovim/.vim-src/ nvim -i NONE -u NONE --headless +'luafile ./scripts/vimpatch.lua' +q +-- VIM_SOURCE_DIR=~/neovim/.vim-src/ nvim -V1 -es -i NONE +'luafile ./scripts/vimpatch.lua' +q local nvim = vim.api @@ -22,12 +22,13 @@ end -- Generates the lines to be inserted into the src/version.c -- `included_patches[]` definition. local function gen_version_c_lines() - -- Set of merged Vim 8.0.zzzz patch numbers. + -- Set of merged Vim 8.1.zzzz patch numbers. local merged_patch_numbers = {} local highest = 0 for _, n in ipairs(vimpatch_sh_list_numbers()) do + n = tonumber(n) if n then - merged_patch_numbers[tonumber(n)] = true + merged_patch_numbers[n] = true highest = math.max(highest, n) end end diff --git a/src/Doxyfile b/src/Doxyfile deleted file mode 100644 index e085e4e198..0000000000 --- a/src/Doxyfile +++ /dev/null @@ -1,2566 +0,0 @@ -# Doxyfile 1.9.0 - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. -# -# All text after a double hash (##) is considered a comment and is placed in -# front of the TAG it is preceding. -# -# All text after a single hash (#) is considered a comment and will be ignored. -# The format is: -# TAG = value [value, ...] -# For lists, items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (\" \"). - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# This tag specifies the encoding used for all characters in the configuration -# file that follow. The default is UTF-8 which is also the encoding used for all -# text before the first occurrence of this tag. Doxygen uses libiconv (or the -# iconv built into libc) for the transcoding. See -# https://www.gnu.org/software/libiconv/ for the list of possible encodings. -# The default value is: UTF-8. - -DOXYFILE_ENCODING = UTF-8 - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by -# double-quotes, unless you are using Doxywizard) that should identify the -# project for which the documentation is generated. This name is used in the -# title of most generated pages and in a few other places. -# The default value is: My Project. - -PROJECT_NAME = Neovim - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. This -# could be handy for archiving the generated documentation or if some version -# control system is used. - -PROJECT_NUMBER = - -# Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a -# quick idea about the purpose of the project. Keep the description short. - -PROJECT_BRIEF = - -# With the PROJECT_LOGO tag one can specify a logo or an icon that is included -# in the documentation. The maximum height of the logo should not exceed 55 -# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy -# the logo to the output directory. - -PROJECT_LOGO = contrib/doxygen/logo-devdoc.png - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path -# into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If -# left blank the current directory will be used. - -OUTPUT_DIRECTORY = build/doxygen - -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this -# option can be useful when feeding doxygen a huge amount of source files, where -# putting all generated files in the same directory would otherwise causes -# performance problems for the file system. -# The default value is: NO. - -CREATE_SUBDIRS = NO - -# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII -# characters to appear in the names of generated files. If set to NO, non-ASCII -# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode -# U+3044. -# The default value is: NO. - -ALLOW_UNICODE_NAMES = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. -# The default value is: English. - -OUTPUT_LANGUAGE = English - -# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all generated output in the proper direction. -# Possible values are: None, LTR, RTL and Context. -# The default value is: None. - -OUTPUT_TEXT_DIRECTION = None - -# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member -# descriptions after the members that are listed in the file and class -# documentation (similar to Javadoc). Set to NO to disable this. -# The default value is: YES. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief -# description of a member or function before the detailed description -# -# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. -# The default value is: YES. - -REPEAT_BRIEF = YES - -# This tag implements a quasi-intelligent brief description abbreviator that is -# used to form the text in various listings. Each string in this list, if found -# as the leading text of the brief description, will be stripped from the text -# and the result, after processing the whole list, is used as the annotated -# text. Otherwise, the brief description is used as-is. If left blank, the -# following values are used ($name is automatically replaced with the name of -# the entity):The $name class, The $name widget, The $name file, is, provides, -# specifies, contains, represents, a, an and the. - -ABBREVIATE_BRIEF = - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# doxygen will generate a detailed section even if there is only a brief -# description. -# The default value is: NO. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. -# The default value is: NO. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path -# before files name in the file list and in the header files. If set to NO the -# shortest path that makes the file name unique will be used -# The default value is: YES. - -FULL_PATH_NAMES = YES - -# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. -# Stripping is only done if one of the specified strings matches the left-hand -# part of the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the path to -# strip. -# -# Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. -# This tag requires that the tag FULL_PATH_NAMES is set to YES. - -STRIP_FROM_PATH = - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the -# path mentioned in the documentation of a class, which tells the reader which -# header file to include in order to use a class. If left blank only the name of -# the header file containing the class definition is used. Otherwise one should -# specify the list of include paths that are normally passed to the compiler -# using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but -# less readable) file names. This can be useful is your file systems doesn't -# support long names like on DOS, Mac, or CD-ROM. -# The default value is: NO. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the -# first line (until the first dot) of a Javadoc-style comment as the brief -# description. If set to NO, the Javadoc-style will behave just like regular Qt- -# style comments (thus requiring an explicit @brief command for a brief -# description.) -# The default value is: NO. - -JAVADOC_AUTOBRIEF = NO - -# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line -# such as -# /*************** -# as being the beginning of a Javadoc-style comment "banner". If set to NO, the -# Javadoc-style will behave just like regular comments and it will not be -# interpreted by doxygen. -# The default value is: NO. - -JAVADOC_BANNER = NO - -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first -# line (until the first dot) of a Qt-style comment as the brief description. If -# set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) -# The default value is: NO. - -QT_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a -# multi-line C++ special comment block (i.e. a block of //! or /// comments) as -# a brief description. This used to be the default behavior. The new default is -# to treat a multi-line C++ comment block as a detailed description. Set this -# tag to YES if you prefer the old behavior instead. -# -# Note that setting this tag to YES also means that rational rose comments are -# not recognized any more. -# The default value is: NO. - -MULTILINE_CPP_IS_BRIEF = NO - -# By default Python docstrings are displayed as preformatted text and doxygen's -# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the -# doxygen's special commands can be used and the contents of the docstring -# documentation blocks is shown as doxygen documentation. -# The default value is: YES. - -PYTHON_DOCSTRING = YES - -# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the -# documentation from any documented member that it re-implements. -# The default value is: YES. - -INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new -# page for each member. If set to NO, the documentation of a member will be part -# of the file/class/namespace that contains it. -# The default value is: NO. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen -# uses this value to replace tabs by spaces in code fragments. -# Minimum value: 1, maximum value: 16, default value: 4. - -TAB_SIZE = 4 - -# This tag can be used to specify a number of aliases that act as commands in -# the documentation. An alias has the form: -# name=value -# For example adding -# "sideeffect=@par Side Effects:\n" -# will allow you to put the command \sideeffect (or @sideeffect) in the -# documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines (in the resulting output). You can put ^^ in the value part of an -# alias to insert a newline as if a physical newline was in the original file. -# When you need a literal { or } or , in the value part of an alias you have to -# escape them by means of a backslash (\), this can lead to conflicts with the -# commands \{ and \} for these it is advised to use the version @{ and @} or use -# a double escape (\\{ and \\}) - -ALIASES = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources -# only. Doxygen will then generate output that is more tailored for C. For -# instance, some of the names that are used will be different. The list of all -# members will be omitted, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_FOR_C = YES - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or -# Python sources only. Doxygen will then generate output that is more tailored -# for that language. For instance, namespaces will be presented as packages, -# qualified scopes will look different, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources. Doxygen will then generate output that is tailored for Fortran. -# The default value is: NO. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for VHDL. -# The default value is: NO. - -OPTIMIZE_OUTPUT_VHDL = NO - -# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice -# sources only. Doxygen will then generate output that is more tailored for that -# language. For instance, namespaces will be presented as modules, types will be -# separated into more groups, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_SLICE = NO - -# Doxygen selects the parser to use depending on the extension of the files it -# parses. With this tag you can assign which parser to use for a given -# extension. Doxygen has a built-in mapping, but you can override or extend it -# using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, -# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, -# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: -# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser -# tries to guess whether the code is fixed or free formatted code, this is the -# default for Fortran type files). For instance to make doxygen treat .inc files -# as Fortran files (default is PHP), and .f files as C (default is Fortran), -# use: inc=Fortran f=C. -# -# Note: For files without extension you can use no_extension as a placeholder. -# -# Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. When specifying no_extension you should add -# * to the FILE_PATTERNS. -# -# Note see also the list of default file extension mappings. - -EXTENSION_MAPPING = lua=C - -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments -# according to the Markdown format, which allows for more readable -# documentation. See https://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in -# case of backward compatibilities issues. -# The default value is: YES. - -MARKDOWN_SUPPORT = YES - -# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up -# to that level are automatically included in the table of contents, even if -# they do not have an id attribute. -# Note: This feature currently applies only to Markdown headings. -# Minimum value: 0, maximum value: 99, default value: 5. -# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. - -TOC_INCLUDE_HEADINGS = 5 - -# When enabled doxygen tries to link words that correspond to documented -# classes, or namespaces to their corresponding documentation. Such a link can -# be prevented in individual cases by putting a % sign in front of the word or -# globally by setting AUTOLINK_SUPPORT to NO. -# The default value is: YES. - -AUTOLINK_SUPPORT = YES - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should set this -# tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); -# versus func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. -# The default value is: NO. - -BUILTIN_STL_SUPPORT = NO - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. -# The default value is: NO. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen -# will parse them like normal C++ but will assume all classes use public instead -# of private inheritance when no explicit protection keyword is present. -# The default value is: NO. - -SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate -# getter and setter methods for a property. Setting this option to YES will make -# doxygen to replace the get and set methods by a property in the documentation. -# This will only work if the methods are indeed getting or setting a simple -# type. If this is not the case, or you want to show the methods anyway, you -# should set this option to NO. -# The default value is: YES. - -IDL_PROPERTY_SUPPORT = YES - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. -# The default value is: NO. - -DISTRIBUTE_GROUP_DOC = NO - -# If one adds a struct or class to a group and this option is enabled, then also -# any nested class or struct is added to the same group. By default this option -# is disabled and one has to add nested compounds explicitly via \ingroup. -# The default value is: NO. - -GROUP_NESTED_COMPOUNDS = NO - -# Set the SUBGROUPING tag to YES to allow class member groups of the same type -# (for instance a group of public functions) to be put as a subgroup of that -# type (e.g. under the Public Functions section). Set it to NO to prevent -# subgrouping. Alternatively, this can be done per class using the -# \nosubgrouping command. -# The default value is: YES. - -SUBGROUPING = YES - -# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions -# are shown inside the group in which they are included (e.g. using \ingroup) -# instead of on a separate page (for HTML and Man pages) or section (for LaTeX -# and RTF). -# -# Note that this feature does not work in combination with -# SEPARATE_MEMBER_PAGES. -# The default value is: NO. - -INLINE_GROUPED_CLASSES = NO - -# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions -# with only public data fields or simple typedef fields will be shown inline in -# the documentation of the scope in which they are defined (i.e. file, -# namespace, or group documentation), provided this scope is documented. If set -# to NO, structs, classes, and unions are shown on a separate page (for HTML and -# Man pages) or section (for LaTeX and RTF). -# The default value is: NO. - -INLINE_SIMPLE_STRUCTS = NO - -# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or -# enum is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically be -# useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. -# The default value is: NO. - -TYPEDEF_HIDES_STRUCT = NO - -# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This -# cache is used to resolve symbols given their name and scope. Since this can be -# an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The -# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range -# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest -# the optimal cache size from a speed point of view. -# Minimum value: 0, maximum value: 9, default value: 0. - -LOOKUP_CACHE_SIZE = 0 - -# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use -# during processing. When set to 0 doxygen will based this on the number of -# cores available in the system. You can set it explicitly to a value larger -# than 0 to get more control over the balance between CPU load and processing -# speed. At this moment only the input processing can be done using multiple -# threads. Since this is still an experimental feature the default is set to 1, -# which efficively disables parallel processing. Please report any issues you -# encounter. Generating dot graphs in parallel is controlled by the -# DOT_NUM_THREADS setting. -# Minimum value: 0, maximum value: 32, default value: 1. - -NUM_PROC_THREADS = 1 - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in -# documentation are documented, even if no documentation was available. Private -# class members and static file members will be hidden unless the -# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. -# Note: This will also disable the warnings about undocumented members that are -# normally produced when WARNINGS is set to YES. -# The default value is: NO. - -EXTRACT_ALL = YES - -# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will -# be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIVATE = NO - -# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual -# methods of a class will be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIV_VIRTUAL = NO - -# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal -# scope will be included in the documentation. -# The default value is: NO. - -EXTRACT_PACKAGE = NO - -# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be -# included in the documentation. -# The default value is: NO. - -EXTRACT_STATIC = NO - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined -# locally in source files will be included in the documentation. If set to NO, -# only classes defined in header files are included. Does not have any effect -# for Java sources. -# The default value is: YES. - -EXTRACT_LOCAL_CLASSES = YES - -# This flag is only useful for Objective-C code. If set to YES, local methods, -# which are defined in the implementation section but not in the interface are -# included in the documentation. If set to NO, only methods in the interface are -# included. -# The default value is: NO. - -EXTRACT_LOCAL_METHODS = NO - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base name of -# the file that contains the anonymous namespace. By default anonymous namespace -# are hidden. -# The default value is: NO. - -EXTRACT_ANON_NSPACES = NO - -# If this flag is set to YES, the name of an unnamed parameter in a declaration -# will be determined by the corresponding definition. By default unnamed -# parameters remain unnamed in the output. -# The default value is: YES. - -RESOLVE_UNNAMED_PARAMS = YES - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all -# undocumented members inside documented classes or files. If set to NO these -# members will be included in the various overviews, but no documentation -# section is generated. This option has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. If set -# to NO, these classes will be included in the various overviews. This option -# has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# declarations. If set to NO, these declarations will be included in the -# documentation. -# The default value is: NO. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any -# documentation blocks found inside the body of a function. If set to NO, these -# blocks will be appended to the function's detailed documentation block. -# The default value is: NO. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation that is typed after a -# \internal command is included. If the tag is set to NO then the documentation -# will be excluded. Set it to YES to include the internal documentation. -# The default value is: NO. - -INTERNAL_DOCS = NO - -# With the correct setting of option CASE_SENSE_NAMES doxygen will better be -# able to match the capabilities of the underlying filesystem. In case the -# filesystem is case sensitive (i.e. it supports files in the same directory -# whose names only differ in casing), the option must be set to YES to properly -# deal with such files in case they appear in the input. For filesystems that -# are not case sensitive the option should be be set to NO to properly deal with -# output files written for symbols that only differ in casing, such as for two -# classes, one named CLASS and the other named Class, and to also support -# references to files without having to specify the exact matching casing. On -# Windows (including Cygwin) and MacOS, users should typically set this option -# to NO, whereas on Linux or other Unix flavors it should typically be set to -# YES. -# The default value is: system dependent. - -CASE_SENSE_NAMES = YES - -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with -# their full class and namespace scopes in the documentation. If set to YES, the -# scope will be hidden. -# The default value is: NO. - -HIDE_SCOPE_NAMES = NO - -# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will -# append additional text to a page's title, such as Class Reference. If set to -# YES the compound reference will be hidden. -# The default value is: NO. - -HIDE_COMPOUND_REFERENCE= NO - -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of -# the files that are included by a file in the documentation of that file. -# The default value is: YES. - -SHOW_INCLUDE_FILES = YES - -# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each -# grouped member an include statement to the documentation, telling the reader -# which file to include in order to use the member. -# The default value is: NO. - -SHOW_GROUPED_MEMB_INC = NO - -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include -# files with double quotes in the documentation rather than with sharp brackets. -# The default value is: NO. - -FORCE_LOCAL_INCLUDES = NO - -# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the -# documentation for inline members. -# The default value is: YES. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the -# (detailed) documentation of file and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. -# The default value is: YES. - -SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief -# descriptions of file, namespace and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. Note that -# this will also influence the order of the classes in the class list. -# The default value is: NO. - -SORT_BRIEF_DOCS = NO - -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the -# (brief and detailed) documentation of class members so that constructors and -# destructors are listed first. If set to NO the constructors will appear in the -# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. -# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief -# member documentation. -# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting -# detailed member documentation. -# The default value is: NO. - -SORT_MEMBERS_CTORS_1ST = NO - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy -# of group names into alphabetical order. If set to NO the group names will -# appear in their defined order. -# The default value is: NO. - -SORT_GROUP_NAMES = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by -# fully-qualified names, including namespaces. If set to NO, the class list will -# be sorted only by class name, not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the alphabetical -# list. -# The default value is: NO. - -SORT_BY_SCOPE_NAME = NO - -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper -# type resolution of all parameters of a function it will reject a match between -# the prototype and the implementation of a member function even if there is -# only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still -# accept a match between prototype and implementation in such cases. -# The default value is: NO. - -STRICT_PROTO_MATCHING = NO - -# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo -# list. This list is created by putting \todo commands in the documentation. -# The default value is: YES. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test -# list. This list is created by putting \test commands in the documentation. -# The default value is: YES. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug -# list. This list is created by putting \bug commands in the documentation. -# The default value is: YES. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) -# the deprecated list. This list is created by putting \deprecated commands in -# the documentation. -# The default value is: YES. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional documentation -# sections, marked by \if <section_label> ... \endif and \cond <section_label> -# ... \endcond blocks. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the -# initial value of a variable or macro / define can have for it to appear in the -# documentation. If the initializer consists of more lines than specified here -# it will be hidden. Use a value of 0 to hide initializers completely. The -# appearance of the value of individual variables and macros / defines can be -# controlled using \showinitializer or \hideinitializer command in the -# documentation regardless of this setting. -# Minimum value: 0, maximum value: 10000, default value: 30. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at -# the bottom of the documentation of classes and structs. If set to YES, the -# list will mention the files that were used to generate the documentation. -# The default value is: YES. - -SHOW_USED_FILES = YES - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This -# will remove the Files entry from the Quick Index and from the Folder Tree View -# (if specified). -# The default value is: YES. - -SHOW_FILES = YES - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces -# page. This will remove the Namespaces entry from the Quick Index and from the -# Folder Tree View (if specified). -# The default value is: YES. - -SHOW_NAMESPACES = YES - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command command input-file, where command is the value of the -# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file -# version. For an example see the documentation. - -FILE_VERSION_FILTER = - -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated -# output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can -# optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. -# -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE -# tag is left empty. - -LAYOUT_FILE = - -# The CITE_BIB_FILES tag can be used to specify one or more bib files containing -# the reference definitions. This must be a list of .bib files. The .bib -# extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. -# For LaTeX the style of the bibliography can be controlled using -# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the -# search path. See also \cite for info how to create references. - -CITE_BIB_FILES = - -#--------------------------------------------------------------------------- -# Configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the -# messages are off. -# The default value is: NO. - -QUIET = NO - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES -# this implies that the warnings are on. -# -# Tip: Turn warnings on while writing the documentation. -# The default value is: YES. - -WARNINGS = YES - -# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate -# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag -# will automatically be disabled. -# The default value is: YES. - -WARN_IF_UNDOCUMENTED = YES - -# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. -# The default value is: YES. - -WARN_IF_DOC_ERROR = YES - -# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that -# are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. If -# EXTRACT_ALL is set to YES then this flag will automatically be disabled. -# The default value is: NO. - -WARN_NO_PARAMDOC = NO - -# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS -# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but -# at the end of the doxygen process doxygen will return with a non-zero status. -# Possible values are: NO, YES and FAIL_ON_WARNINGS. -# The default value is: NO. - -WARN_AS_ERROR = NO - -# The WARN_FORMAT tag determines the format of the warning messages that doxygen -# can produce. The string should contain the $file, $line, and $text tags, which -# will be replaced by the file and line number from which the warning originated -# and the warning text. Optionally the format may contain $version, which will -# be replaced by the version of the file (if it could be obtained via -# FILE_VERSION_FILTER) -# The default value is: $file:$line: $text. - -WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning and error -# messages should be written. If left blank the output is written to standard -# error (stderr). - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# Configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag is used to specify the files and/or directories that contain -# documented source files. You may enter file names like myfile.cpp or -# directories like /usr/src/myproject. Separate the files or directories with -# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING -# Note: If this tag is empty the current directory is searched. - -INPUT = src/ - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses -# libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: -# https://www.gnu.org/software/libiconv/) for the list of possible encodings. -# The default value is: UTF-8. - -INPUT_ENCODING = UTF-8 - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and -# *.h) to filter out the source-files in the directories. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# read by doxygen. -# -# Note the list of default checked file patterns might differ from the list of -# default file extension mappings. -# -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), -# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, -# *.ucf, *.qsf and *.ice. - -FILE_PATTERNS = *.h \ - *.c \ - *.lua - -# The RECURSIVE tag can be used to specify whether or not subdirectories should -# be searched for input files as well. -# The default value is: NO. - -RECURSIVE = YES - -# The EXCLUDE tag can be used to specify files and/or directories that should be -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. -# -# Note that relative paths are relative to the directory from which doxygen is -# run. - -EXCLUDE = - -# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or -# directories that are symbolic links (a Unix file system feature) are excluded -# from the input. -# The default value is: NO. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories for example use the pattern */test/* - -EXCLUDE_PATTERNS = - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* - -EXCLUDE_SYMBOLS = - -# The EXAMPLE_PATH tag can be used to specify one or more files or directories -# that contain example code fragments that are included (see the \include -# command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank all -# files are included. - -EXAMPLE_PATTERNS = - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude commands -# irrespective of the value of the RECURSIVE tag. -# The default value is: NO. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or directories -# that contain images that are to be included in the documentation (see the -# \image command). - -IMAGE_PATH = - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command: -# -# <filter> <input-file> -# -# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the -# name of an input file. Doxygen will then use the output that the filter -# program writes to standard output. If FILTER_PATTERNS is specified, this tag -# will be ignored. -# -# Note that the filter must not add or remove lines; it is applied before the -# code is scanned, but not when the output code is generated. If lines are added -# or removed, the anchors will not be placed correctly. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. - -INPUT_FILTER = - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: pattern=filter -# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how -# filters are used. If the FILTER_PATTERNS tag is empty or if none of the -# patterns match the file name, INPUT_FILTER is applied. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. - -FILTER_PATTERNS = *.lua=scripts/lua2dox_filter - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will also be used to filter the input files that are used for -# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). -# The default value is: NO. - -FILTER_SOURCE_FILES = NO - -# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file -# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and -# it is also possible to disable source filtering for a specific pattern using -# *.ext= (so without naming a filter). -# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. - -FILTER_SOURCE_PATTERNS = - -# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that -# is part of the input, its contents will be placed on the main page -# (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. - -USE_MDFILE_AS_MAINPAGE = - -#--------------------------------------------------------------------------- -# Configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will be -# generated. Documented entities will be cross-referenced with these sources. -# -# Note: To get rid of all source code in the generated output, make sure that -# also VERBATIM_HEADERS is set to NO. -# The default value is: NO. - -SOURCE_BROWSER = NO - -# Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. -# The default value is: NO. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any -# special comment blocks from generated source code fragments. Normal C, C++ and -# Fortran comments will always remain visible. -# The default value is: YES. - -STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# entity all documented functions referencing it will be listed. -# The default value is: NO. - -REFERENCED_BY_RELATION = NO - -# If the REFERENCES_RELATION tag is set to YES then for each documented function -# all documented entities called/used by that function will be listed. -# The default value is: NO. - -REFERENCES_RELATION = NO - -# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set -# to YES then the hyperlinks from functions in REFERENCES_RELATION and -# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will -# link to the documentation. -# The default value is: YES. - -REFERENCES_LINK_SOURCE = YES - -# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the -# source code will show a tooltip with additional information such as prototype, -# brief description and links to the definition and documentation. Since this -# will make the HTML file larger and loading of large files a bit slower, you -# can opt to disable this feature. -# The default value is: YES. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -SOURCE_TOOLTIPS = YES - -# If the USE_HTAGS tag is set to YES then the references to source code will -# point to the HTML generated by the htags(1) tool instead of doxygen built-in -# source browser. The htags tool is part of GNU's global source tagging system -# (see https://www.gnu.org/software/global/global.html). You will need version -# 4.8.6 or higher. -# -# To use it do the following: -# - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file -# - Make sure the INPUT points to the root of the source tree -# - Run doxygen as normal -# -# Doxygen will invoke htags (and that will in turn invoke gtags), so these -# tools must be available from the command line (i.e. in the search path). -# -# The result: instead of the source browser generated by doxygen, the links to -# source code will now point to the output of htags. -# The default value is: NO. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a -# verbatim copy of the header file for each class for which an include is -# specified. Set to NO to disable this. -# See also: Section \class. -# The default value is: YES. - -VERBATIM_HEADERS = YES - -#--------------------------------------------------------------------------- -# Configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all -# compounds will be generated. Enable this if the project contains a lot of -# classes, structs, unions or interfaces. -# The default value is: YES. - -ALPHABETICAL_INDEX = YES - -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output -# The default value is: YES. - -GENERATE_HTML = YES - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_OUTPUT = html - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each -# generated HTML page (for example: .htm, .php, .asp). -# The default value is: .html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a -# standard header. -# -# To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. -# the setting GENERATE_TREEVIEW). It is highly recommended to start with a -# default header using -# doxygen -w html new_header.html new_footer.html new_stylesheet.css -# YourConfigFile -# and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally -# uses. -# Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description -# of the possible markers and block names see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_HEADER = contrib/doxygen/header.html - -# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard -# footer. See HTML_HEADER for more information on how to generate a default -# footer and what special commands can be used inside the footer. See also -# section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FOOTER = contrib/doxygen/footer.html - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style -# sheet that is used by each HTML page. It can be used to fine-tune the look of -# the HTML output. If left blank doxygen will generate a default style sheet. -# See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. -# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as -# it is more robust and this tag (HTML_STYLESHEET) will in the future become -# obsolete. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_STYLESHEET = contrib/doxygen/customdoxygen.css - -# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined -# cascading style sheets that are included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. -# This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefore more robust against future updates. -# Doxygen will copy the style sheet files to the output directory. -# Note: The order of the extra style sheet files is of importance (e.g. the last -# style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_STYLESHEET = contrib/doxygen/extra.css - -# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the HTML output directory. Note -# that these files will be copied to the base HTML output directory. Use the -# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these -# files. In the HTML_STYLESHEET file, use the file name only. Also note that the -# files will be copied as-is; there are no commands or markers available. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_FILES = - -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen -# will adjust the colors in the style sheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see -# https://en.wikipedia.org/wiki/Hue for more information. For instance the value -# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 -# purple, and 360 is red again. -# Minimum value: 0, maximum value: 359, default value: 220. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_HUE = 220 - -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A -# value of 255 will produce the most vivid colors. -# Minimum value: 0, maximum value: 255, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_SAT = 100 - -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the -# luminance component of the colors in the HTML output. Values below 100 -# gradually make the output lighter, whereas values above 100 make the output -# darker. The value divided by 100 is the actual gamma applied, so 80 represents -# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not -# change the gamma. -# Minimum value: 40, maximum value: 240, default value: 80. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_GAMMA = 80 - -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = YES - -# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML -# documentation will contain a main index with vertical navigation menus that -# are dynamically created via JavaScript. If disabled, the navigation index will -# consists of multiple levels of tabs that are statically embedded in every HTML -# page. Disable this option to support browsers that do not have JavaScript, -# like the Qt help browser. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_MENUS = YES - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_SECTIONS = NO - -# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries -# shown in the various tree structured indices initially; the user can expand -# and collapse entries dynamically later on. Doxygen will expand the tree to -# such a level that at most the specified number of entries are visible (unless -# a fully collapsed tree already exceeds this amount). So setting the number of -# entries 1 will produce a full collapsed tree by default. 0 is a special value -# representing an infinite number of entries and will result in a full expanded -# tree by default. -# Minimum value: 0, maximum value: 9999, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_INDEX_NUM_ENTRIES = 100 - -# If the GENERATE_DOCSET tag is set to YES, additional index files will be -# generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: -# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To -# create a documentation set, doxygen will generate a Makefile in the HTML -# output directory. Running make will produce the docset in that directory and -# running make install will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy -# genXcode/_index.html for more information. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_DOCSET = NO - -# This tag determines the name of the docset feed. A documentation feed provides -# an umbrella under which multiple documentation sets from a single provider -# (such as a company or product suite) can be grouped. -# The default value is: Doxygen generated docs. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_FEEDNAME = "Doxygen generated docs" - -# This tag specifies a string that should uniquely identify the documentation -# set bundle. This should be a reverse domain-name style string, e.g. -# com.mycompany.MyDocSet. Doxygen will append .docset to the name. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_BUNDLE_ID = org.doxygen.Project - -# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify -# the documentation publisher. This should be a reverse domain-name style -# string, e.g. com.mycompany.MyDocSet.documentation. -# The default value is: org.doxygen.Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_ID = org.doxygen.Publisher - -# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. -# The default value is: Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_NAME = Publisher - -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three -# additional HTML index files: index.hhp, index.hhc, and index.hhk. The -# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: -# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. -# -# The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML -# files are now used as the Windows 98 help format, and will replace the old -# Windows help format (.hlp) on all Windows platforms in the future. Compressed -# HTML files also contain an index, a table of contents, and you can search for -# words in the documentation. The HTML workshop also contains a viewer for -# compressed HTML files. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_HTMLHELP = NO - -# The CHM_FILE tag can be used to specify the file name of the resulting .chm -# file. You can add a path in front of the file if the result should not be -# written to the html output directory. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_FILE = - -# The HHC_LOCATION tag can be used to specify the location (absolute path -# including file name) of the HTML help compiler (hhc.exe). If non-empty, -# doxygen will try to run the HTML help compiler on the generated index.hhp. -# The file has to be specified with full path. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -HHC_LOCATION = - -# The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the main .chm file (NO). -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -GENERATE_CHI = NO - -# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) -# and project file content. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_INDEX_ENCODING = - -# The BINARY_TOC flag controls whether a binary table of contents is generated -# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it -# enables the Previous and Next buttons. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members to -# the table of contents of the HTML help documentation and to the tree view. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -TOC_EXPAND = NO - -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and -# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that -# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help -# (.qch) of the generated HTML documentation. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_QHP = NO - -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify -# the file name of the resulting .qch file. The path specified is relative to -# the HTML output folder. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QCH_FILE = - -# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help -# Project output. For more information please see Qt Help Project / Namespace -# (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_NAMESPACE = org.doxygen.Project - -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt -# Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). -# The default value is: doc. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_VIRTUAL_FOLDER = doc - -# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom -# filter to add. For more information please see Qt Help Project / Custom -# Filters (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_NAME = - -# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the -# custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_ATTRS = - -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this -# project's filter section matches. Qt Help Project / Filter Attributes (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_SECT_FILTER_ATTRS = - -# The QHG_LOCATION tag can be used to specify the location (absolute path -# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to -# run qhelpgenerator on the generated .qhp file. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHG_LOCATION = - -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be -# generated, together with the HTML files, they form an Eclipse help plugin. To -# install this plugin and make it available under the help contents menu in -# Eclipse, the contents of the directory containing the HTML and XML files needs -# to be copied into the plugins directory of eclipse. The name of the directory -# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. -# After copying Eclipse needs to be restarted before the help appears. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_ECLIPSEHELP = NO - -# A unique identifier for the Eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have this -# name. Each documentation set should have its own identifier. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. - -ECLIPSE_DOC_ID = org.doxygen.Project - -# If you want full control over the layout of the generated HTML pages it might -# be necessary to disable the index and replace it with your own. The -# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top -# of each HTML page. A value of NO enables the index and the value YES disables -# it. Since the tabs in the index contain the same information as the navigation -# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -DISABLE_INDEX = NO - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. If the tag -# value is set to YES, a side panel will be generated containing a tree-like -# index structure (just like the one that is generated for HTML Help). For this -# to work a browser that supports JavaScript, DHTML, CSS and frames is required -# (i.e. any modern browser). Windows users are probably better off using the -# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_TREEVIEW = NO - -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. -# -# Note that a value of 0 will completely suppress the enum values from appearing -# in the overview section. -# Minimum value: 0, maximum value: 20, default value: 4. -# This tag requires that the tag GENERATE_HTML is set to YES. - -ENUM_VALUES_PER_LINE = 4 - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used -# to set the initial width (in pixels) of the frame in which the tree is shown. -# Minimum value: 0, maximum value: 1500, default value: 250. -# This tag requires that the tag GENERATE_HTML is set to YES. - -TREEVIEW_WIDTH = 250 - -# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to -# external symbols imported via tag files in a separate window. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -EXT_LINKS_IN_WINDOW = NO - -# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg -# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see -# https://inkscape.org) to generate formulas as SVG images instead of PNGs for -# the HTML output. These images will generally look nicer at scaled resolutions. -# Possible values are: png (the default) and svg (looks nicer but requires the -# pdf2svg or inkscape tool). -# The default value is: png. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FORMULA_FORMAT = png - -# Use this tag to change the font size of LaTeX formulas included as images in -# the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML -# output directory to force them to be regenerated. -# Minimum value: 8, maximum value: 50, default value: 10. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_FONTSIZE = 10 - -# Use the FORMULA_TRANSPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - -# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands -# to create new LaTeX commands to be used in formulas as building blocks. See -# the section "Including formulas" for details. - -FORMULA_MACROFILE = - -# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# https://www.mathjax.org) which uses client side JavaScript for the rendering -# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX -# installed or if you want to formulas look prettier in the HTML output. When -# enabled you may also need to install MathJax separately and configure the path -# to it using the MATHJAX_RELPATH option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -USE_MATHJAX = NO - -# When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. -# Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. -# The default value is: HTML-CSS. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_FORMAT = HTML-CSS - -# When MathJax is enabled you need to specify the location relative to the HTML -# output directory using the MATHJAX_RELPATH option. The destination directory -# should contain the MathJax.js script. For instance, if the mathjax directory -# is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax -# Content Delivery Network so you can quickly see the result without installing -# MathJax. However, it is strongly recommended to install a local copy of -# MathJax from https://www.mathjax.org before deployment. -# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest - -# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax -# extension names that should be enabled during MathJax rendering. For example -# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_EXTENSIONS = - -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces -# of code that will be used on startup of the MathJax code. See the MathJax site -# (see: -# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an -# example see the documentation. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_CODEFILE = - -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for -# the HTML output. The underlying search engine uses javascript and DHTML and -# should work on any modern browser. Note that when using HTML help -# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) -# there is already a search function so this one should typically be disabled. -# For large projects the javascript based search engine can be slow, then -# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to -# search using the keyboard; to jump to the search box use <access key> + S -# (what the <access key> is depends on the OS and browser, but it is typically -# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down -# key> to jump into the search results window, the results can be navigated -# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel -# the search. The filter options can be selected when the cursor is inside the -# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys> -# to select a filter and <Enter> or <escape> to activate or cancel the filter -# option. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -SEARCHENGINE = YES - -# When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a web server instead of a web client using JavaScript. There -# are two flavors of web server based searching depending on the EXTERNAL_SEARCH -# setting. When disabled, doxygen will generate a PHP script for searching and -# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing -# and searching needs to be provided by external tools. See the section -# "External Indexing and Searching" for details. -# The default value is: NO. -# This tag requires that the tag SEARCHENGINE is set to YES. - -SERVER_BASED_SEARCH = NO - -# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP -# script for searching. Instead the search results are written to an XML file -# which needs to be processed by an external indexer. Doxygen will invoke an -# external search engine pointed to by the SEARCHENGINE_URL option to obtain the -# search results. -# -# Doxygen ships with an example indexer (doxyindexer) and search engine -# (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: -# https://xapian.org/). -# -# See the section "External Indexing and Searching" for details. -# The default value is: NO. -# This tag requires that the tag SEARCHENGINE is set to YES. - -EXTERNAL_SEARCH = NO - -# The SEARCHENGINE_URL should point to a search engine hosted by a web server -# which will return the search results when EXTERNAL_SEARCH is enabled. -# -# Doxygen ships with an example indexer (doxyindexer) and search engine -# (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: -# https://xapian.org/). See the section "External Indexing and Searching" for -# details. -# This tag requires that the tag SEARCHENGINE is set to YES. - -SEARCHENGINE_URL = - -# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed -# search data is written to a file for indexing by an external tool. With the -# SEARCHDATA_FILE tag the name of this file can be specified. -# The default file is: searchdata.xml. -# This tag requires that the tag SEARCHENGINE is set to YES. - -SEARCHDATA_FILE = searchdata.xml - -# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the -# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is -# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple -# projects and redirect the results back to the right project. -# This tag requires that the tag SEARCHENGINE is set to YES. - -EXTERNAL_SEARCH_ID = - -# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen -# projects other than the one defined by this configuration file, but that are -# all added to the same external search index. Each project needs to have a -# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of -# to a relative location where the documentation can be found. The format is: -# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... -# This tag requires that the tag SEARCHENGINE is set to YES. - -EXTRA_SEARCH_MAPPINGS = - -#--------------------------------------------------------------------------- -# Configuration options related to the LaTeX output -#--------------------------------------------------------------------------- - -# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. -# The default value is: YES. - -GENERATE_LATEX = YES - -# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: latex. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_OUTPUT = latex - -# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be -# invoked. -# -# Note that when not enabling USE_PDFLATEX the default is latex when enabling -# USE_PDFLATEX the default is pdflatex and when in the later case latex is -# chosen this is overwritten by pdflatex. For specific output languages the -# default can have been set differently, this depends on the implementation of -# the output language. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_CMD_NAME = latex - -# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate -# index for LaTeX. -# Note: This tag is used in the Makefile / make.bat. -# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file -# (.tex). -# The default file is: makeindex. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -MAKEINDEX_CMD_NAME = makeindex - -# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to -# generate index for LaTeX. In case there is no backslash (\) as first character -# it will be automatically added in the LaTeX code. -# Note: This tag is used in the generated output file (.tex). -# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. -# The default value is: makeindex. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_MAKEINDEX_CMD = makeindex - -# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX -# documents. This may be useful for small projects and may help to save some -# trees in general. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -COMPACT_LATEX = NO - -# The PAPER_TYPE tag can be used to set the paper type that is used by the -# printer. -# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x -# 14 inches) and executive (7.25 x 10.5 inches). -# The default value is: a4. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -PAPER_TYPE = a4 - -# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names -# that should be included in the LaTeX output. The package can be specified just -# by its name or with the correct syntax as to be used with the LaTeX -# \usepackage command. To get the times font for instance you can specify : -# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} -# To use the option intlimits with the amsmath package you can specify: -# EXTRA_PACKAGES=[intlimits]{amsmath} -# If left blank no extra packages will be included. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -EXTRA_PACKAGES = - -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the -# generated LaTeX document. The header should contain everything until the first -# chapter. If it is left blank doxygen will generate a standard header. See -# section "Doxygen usage" for information on how to let doxygen write the -# default header to a separate file. -# -# Note: Only use a user-defined header if you know what you are doing! The -# following commands have a special meaning inside the header: $title, -# $datetime, $date, $doxygenversion, $projectname, $projectnumber, -# $projectbrief, $projectlogo. Doxygen will replace $title with the empty -# string, for the replacement values of the other commands the user is referred -# to HTML_HEADER. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_HEADER = - -# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the -# generated LaTeX document. The footer should contain everything after the last -# chapter. If it is left blank doxygen will generate a standard footer. See -# LATEX_HEADER for more information on how to generate a default footer and what -# special commands can be used inside the footer. -# -# Note: Only use a user-defined footer if you know what you are doing! -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_FOOTER = - -# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined -# LaTeX style sheets that are included after the standard style sheets created -# by doxygen. Using this option one can overrule certain style aspects. Doxygen -# will copy the style sheet files to the output directory. -# Note: The order of the extra style sheet files is of importance (e.g. the last -# style sheet in the list overrules the setting of the previous ones in the -# list). -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_EXTRA_STYLESHEET = - -# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the LATEX_OUTPUT output -# directory. Note that the files will be copied as-is; there are no commands or -# markers available. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_EXTRA_FILES = - -# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is -# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will -# contain links (just like the HTML output) instead of page references. This -# makes the output suitable for online browsing using a PDF viewer. -# The default value is: YES. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -PDF_HYPERLINKS = YES - -# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as -# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX -# files. Set this option to YES, to get a higher quality PDF documentation. -# -# See also section LATEX_CMD_NAME for selecting the engine. -# The default value is: YES. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -USE_PDFLATEX = YES - -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode -# command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. This option is also used -# when generating formulas in HTML. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_BATCHMODE = NO - -# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the -# index chapters (such as File Index, Compound Index, etc.) in the output. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_HIDE_INDICES = NO - -# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source -# code with syntax highlighting in the LaTeX output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_SOURCE_CODE = NO - -# The LATEX_BIB_STYLE tag can be used to specify the style to use for the -# bibliography, e.g. plainnat, or ieeetr. See -# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. -# The default value is: plain. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_BIB_STYLE = plain - -# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_TIMESTAMP = NO - -# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) -# path from which the emoji images will be read. If a relative path is entered, -# it will be relative to the LATEX_OUTPUT directory. If left blank the -# LATEX_OUTPUT directory will be used. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_EMOJI_DIRECTORY = - -#--------------------------------------------------------------------------- -# Configuration options related to the RTF output -#--------------------------------------------------------------------------- - -# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The -# RTF output is optimized for Word 97 and may not look too pretty with other RTF -# readers/editors. -# The default value is: NO. - -GENERATE_RTF = NO - -# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: rtf. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_OUTPUT = rtf - -# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF -# documents. This may be useful for small projects and may help to save some -# trees in general. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -COMPACT_RTF = NO - -# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will -# contain hyperlink fields. The RTF file will contain links (just like the HTML -# output) instead of page references. This makes the output suitable for online -# browsing using Word or some other Word compatible readers that support those -# fields. -# -# Note: WordPad (write) and others do not support links. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_HYPERLINKS = NO - -# Load stylesheet definitions from file. Syntax is similar to doxygen's -# configuration file, i.e. a series of assignments. You only have to provide -# replacements, missing definitions are set to their default value. -# -# See also section "Doxygen usage" for information on how to generate the -# default style sheet that doxygen normally uses. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_STYLESHEET_FILE = - -# Set optional variables used in the generation of an RTF document. Syntax is -# similar to doxygen's configuration file. A template extensions file can be -# generated using doxygen -e rtf extensionFile. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_EXTENSIONS_FILE = - -# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code -# with syntax highlighting in the RTF output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_SOURCE_CODE = NO - -#--------------------------------------------------------------------------- -# Configuration options related to the man page output -#--------------------------------------------------------------------------- - -# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for -# classes and files. -# The default value is: NO. - -GENERATE_MAN = NO - -# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. A directory man3 will be created inside the directory specified by -# MAN_OUTPUT. -# The default directory is: man. -# This tag requires that the tag GENERATE_MAN is set to YES. - -MAN_OUTPUT = man - -# The MAN_EXTENSION tag determines the extension that is added to the generated -# man pages. In case the manual section does not start with a number, the number -# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is -# optional. -# The default value is: .3. -# This tag requires that the tag GENERATE_MAN is set to YES. - -MAN_EXTENSION = .3 - -# The MAN_SUBDIR tag determines the name of the directory created within -# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by -# MAN_EXTENSION with the initial . removed. -# This tag requires that the tag GENERATE_MAN is set to YES. - -MAN_SUBDIR = - -# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it -# will generate one additional man file for each entity documented in the real -# man page(s). These additional files only source the real man page, but without -# them the man command would be unable to find the correct page. -# The default value is: NO. -# This tag requires that the tag GENERATE_MAN is set to YES. - -MAN_LINKS = NO - -#--------------------------------------------------------------------------- -# Configuration options related to the XML output -#--------------------------------------------------------------------------- - -# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that -# captures the structure of the code including all documentation. -# The default value is: NO. - -GENERATE_XML = NO - -# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: xml. -# This tag requires that the tag GENERATE_XML is set to YES. - -XML_OUTPUT = xml - -# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program -# listings (including syntax highlighting and cross-referencing information) to -# the XML output. Note that enabling this will significantly increase the size -# of the XML output. -# The default value is: YES. -# This tag requires that the tag GENERATE_XML is set to YES. - -XML_PROGRAMLISTING = YES - -# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include -# namespace members in file scope as well, matching the HTML output. -# The default value is: NO. -# This tag requires that the tag GENERATE_XML is set to YES. - -XML_NS_MEMB_FILE_SCOPE = NO - -#--------------------------------------------------------------------------- -# Configuration options related to the DOCBOOK output -#--------------------------------------------------------------------------- - -# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files -# that can be used to generate PDF. -# The default value is: NO. - -GENERATE_DOCBOOK = NO - -# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in -# front of it. -# The default directory is: docbook. -# This tag requires that the tag GENERATE_DOCBOOK is set to YES. - -DOCBOOK_OUTPUT = docbook - -# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the -# program listings (including syntax highlighting and cross-referencing -# information) to the DOCBOOK output. Note that enabling this will significantly -# increase the size of the DOCBOOK output. -# The default value is: NO. -# This tag requires that the tag GENERATE_DOCBOOK is set to YES. - -DOCBOOK_PROGRAMLISTING = NO - -#--------------------------------------------------------------------------- -# Configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- - -# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures -# the structure of the code including all documentation. Note that this feature -# is still experimental and incomplete at the moment. -# The default value is: NO. - -GENERATE_AUTOGEN_DEF = NO - -#--------------------------------------------------------------------------- -# Configuration options related to the Perl module output -#--------------------------------------------------------------------------- - -# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module -# file that captures the structure of the code including all documentation. -# -# Note that this feature is still experimental and incomplete at the moment. -# The default value is: NO. - -GENERATE_PERLMOD = NO - -# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary -# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI -# output from the Perl module output. -# The default value is: NO. -# This tag requires that the tag GENERATE_PERLMOD is set to YES. - -PERLMOD_LATEX = NO - -# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely -# formatted so it can be parsed by a human reader. This is useful if you want to -# understand what is going on. On the other hand, if this tag is set to NO, the -# size of the Perl module output will be much smaller and Perl will parse it -# just the same. -# The default value is: YES. -# This tag requires that the tag GENERATE_PERLMOD is set to YES. - -PERLMOD_PRETTY = YES - -# The names of the make variables in the generated doxyrules.make file are -# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful -# so different doxyrules.make files included by the same Makefile don't -# overwrite each other's variables. -# This tag requires that the tag GENERATE_PERLMOD is set to YES. - -PERLMOD_MAKEVAR_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the preprocessor -#--------------------------------------------------------------------------- - -# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all -# C-preprocessor directives found in the sources and include files. -# The default value is: YES. - -ENABLE_PREPROCESSING = YES - -# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names -# in the source code. If set to NO, only conditional compilation will be -# performed. Macro expansion can be done in a controlled way by setting -# EXPAND_ONLY_PREDEF to YES. -# The default value is: NO. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -MACRO_EXPANSION = NO - -# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then -# the macro expansion is limited to the macros specified with the PREDEFINED and -# EXPAND_AS_DEFINED tags. -# The default value is: NO. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -EXPAND_ONLY_PREDEF = NO - -# If the SEARCH_INCLUDES tag is set to YES, the include files in the -# INCLUDE_PATH will be searched if a #include is found. -# The default value is: YES. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -SEARCH_INCLUDES = YES - -# The INCLUDE_PATH tag can be used to specify one or more directories that -# contain include files that are not input files but should be processed by the -# preprocessor. -# This tag requires that the tag SEARCH_INCLUDES is set to YES. - -INCLUDE_PATH = - -# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard -# patterns (like *.h and *.hpp) to filter out the header-files in the -# directories. If left blank, the patterns specified with FILE_PATTERNS will be -# used. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -INCLUDE_FILE_PATTERNS = - -# The PREDEFINED tag can be used to specify one or more macro names that are -# defined before the preprocessor is started (similar to the -D option of e.g. -# gcc). The argument of the tag is a list of macros of the form: name or -# name=definition (no spaces). If the definition and the "=" are omitted, "=1" -# is assumed. To prevent a macro definition from being undefined via #undef or -# recursively expanded use the := operator instead of the = operator. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -PREDEFINED = - -# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this -# tag can be used to specify a list of macro names that should be expanded. The -# macro definition that is found in the sources will be used. Use the PREDEFINED -# tag if you want to use a different macro definition that overrules the -# definition found in the source code. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -EXPAND_AS_DEFINED = - -# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will -# remove all references to function-like macros that are alone on a line, have -# an all uppercase name, and do not end with a semicolon. Such function macros -# are typically used for boiler-plate code, and will confuse the parser if not -# removed. -# The default value is: YES. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -SKIP_FUNCTION_MACROS = YES - -#--------------------------------------------------------------------------- -# Configuration options related to external references -#--------------------------------------------------------------------------- - -# The TAGFILES tag can be used to specify one or more tag files. For each tag -# file the location of the external documentation should be added. The format of -# a tag file without this location is as follows: -# TAGFILES = file1 file2 ... -# Adding location for the tag files is done as follows: -# TAGFILES = file1=loc1 "file2 = loc2" ... -# where loc1 and loc2 can be relative or absolute paths or URLs. See the -# section "Linking to external documentation" for more information about the use -# of tag files. -# Note: Each tag file must have a unique name (where the name does NOT include -# the path). If a tag file is not located in the directory in which doxygen is -# run, you must also specify the path to the tagfile here. - -TAGFILES = - -# When a file name is specified after GENERATE_TAGFILE, doxygen will create a -# tag file that is based on the input files it reads. See section "Linking to -# external documentation" for more information about the usage of tag files. - -GENERATE_TAGFILE = - -# If the ALLEXTERNALS tag is set to YES, all external class will be listed in -# the class index. If set to NO, only the inherited external classes will be -# listed. -# The default value is: NO. - -ALLEXTERNALS = NO - -# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will be -# listed. -# The default value is: YES. - -EXTERNAL_GROUPS = YES - -# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in -# the related pages index. If set to NO, only the current project's pages will -# be listed. -# The default value is: YES. - -EXTERNAL_PAGES = YES - -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- - -# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram -# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to -# NO turns the diagrams off. Note that this option also works with HAVE_DOT -# disabled, but it is recommended to install and use dot, since it yields more -# powerful graphs. -# The default value is: YES. - -CLASS_DIAGRAMS = YES - -# You can include diagrams made with dia in doxygen documentation. Doxygen will -# then run dia to produce the diagram and insert it in the documentation. The -# DIA_PATH tag allows you to specify the directory where the dia binary resides. -# If left empty dia is assumed to be found in the default search path. - -DIA_PATH = - -# If set to YES the inheritance and collaboration graphs will hide inheritance -# and usage relations if the target is undocumented or is not a class. -# The default value is: YES. - -HIDE_UNDOC_RELATIONS = YES - -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is -# available from the path. This tool is part of Graphviz (see: -# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent -# Bell Labs. The other options in this section have no effect if this option is -# set to NO -# The default value is: NO. - -HAVE_DOT = NO - -# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed -# to run in parallel. When set to 0 doxygen will base this on the number of -# processors available in the system. You can set it explicitly to a value -# larger than 0 to get control over the balance between CPU load and processing -# speed. -# Minimum value: 0, maximum value: 32, default value: 0. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_NUM_THREADS = 0 - -# When you want a differently looking font in the dot files that doxygen -# generates you can specify the font name using DOT_FONTNAME. You need to make -# sure dot is able to find the font, which can be done by putting it in a -# standard location or by setting the DOTFONTPATH environment variable or by -# setting DOT_FONTPATH to the directory containing the font. -# The default value is: Helvetica. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_FONTNAME = Helvetica - -# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of -# dot graphs. -# Minimum value: 4, maximum value: 24, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_FONTSIZE = 10 - -# By default doxygen will tell dot to use the default font as specified with -# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set -# the path where dot can find it using this tag. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_FONTPATH = - -# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for -# each documented class showing the direct and indirect inheritance relations. -# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -CLASS_GRAPH = YES - -# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a -# graph for each documented class showing the direct and indirect implementation -# dependencies (inheritance, containment, and class references variables) of the -# class with other documented classes. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -COLLABORATION_GRAPH = YES - -# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -GROUP_GRAPHS = YES - -# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and -# collaboration diagrams in a style similar to the OMG's Unified Modeling -# Language. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -UML_LOOK = NO - -# If the UML_LOOK tag is enabled, the fields and methods are shown inside the -# class node. If there are many fields or methods and many nodes the graph may -# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the -# number of items for each type to make the size more manageable. Set this to 0 -# for no limit. Note that the threshold may be exceeded by 50% before the limit -# is enforced. So when you set the threshold to 10, up to 15 fields may appear, -# but if the number exceeds 15, the total amount of fields shown is limited to -# 10. -# Minimum value: 0, maximum value: 100, default value: 10. -# This tag requires that the tag UML_LOOK is set to YES. - -UML_LIMIT_NUM_FIELDS = 10 - -# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and -# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS -# tag is set to YES, doxygen will add type and arguments for attributes and -# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen -# will not generate fields with class member information in the UML graphs. The -# class diagrams will look similar to the default class diagrams but using UML -# notation for the relationships. -# Possible values are: NO, YES and NONE. -# The default value is: NO. -# This tag requires that the tag UML_LOOK is set to YES. - -DOT_UML_DETAILS = NO - -# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters -# to display on a single line. If the actual line length exceeds this threshold -# significantly it will wrapped across multiple lines. Some heuristics are apply -# to avoid ugly line breaks. -# Minimum value: 0, maximum value: 1000, default value: 17. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_WRAP_THRESHOLD = 17 - -# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and -# collaboration graphs will show the relations between templates and their -# instances. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -TEMPLATE_RELATIONS = NO - -# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to -# YES then doxygen will generate a graph for each documented file showing the -# direct and indirect include dependencies of the file with other documented -# files. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -INCLUDE_GRAPH = YES - -# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are -# set to YES then doxygen will generate a graph for each documented file showing -# the direct and indirect include dependencies of the file with other documented -# files. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -INCLUDED_BY_GRAPH = YES - -# If the CALL_GRAPH tag is set to YES then doxygen will generate a call -# dependency graph for every global function or class method. -# -# Note that enabling this option will significantly increase the time of a run. -# So in most cases it will be better to enable call graphs for selected -# functions only using the \callgraph command. Disabling a call graph can be -# accomplished by means of the command \hidecallgraph. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -CALL_GRAPH = NO - -# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller -# dependency graph for every global function or class method. -# -# Note that enabling this option will significantly increase the time of a run. -# So in most cases it will be better to enable caller graphs for selected -# functions only using the \callergraph command. Disabling a caller graph can be -# accomplished by means of the command \hidecallergraph. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -CALLER_GRAPH = NO - -# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical -# hierarchy of all classes instead of a textual one. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -GRAPHICAL_HIERARCHY = YES - -# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the -# dependencies a directory has on other directories in a graphical way. The -# dependency relations are determined by the #include relations between the -# files in the directories. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -DIRECTORY_GRAPH = YES - -# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. For an explanation of the image formats see the section -# output formats in the documentation of the dot tool (Graphviz (see: -# http://www.graphviz.org/)). -# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order -# to make the SVG files visible in IE 9+ (other browsers do not have this -# requirement). -# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, -# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and -# png:gdiplus:gdiplus. -# The default value is: png. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_IMAGE_FORMAT = png - -# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to -# enable generation of interactive SVG images that allow zooming and panning. -# -# Note that this requires a modern browser other than Internet Explorer. Tested -# and working are Firefox, Chrome, Safari, and Opera. -# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make -# the SVG files visible. Older versions of IE do not have SVG support. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -INTERACTIVE_SVG = NO - -# The DOT_PATH tag can be used to specify the path where the dot tool can be -# found. If left blank, it is assumed the dot tool can be found in the path. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_PATH = - -# The DOTFILE_DIRS tag can be used to specify one or more directories that -# contain dot files that are included in the documentation (see the \dotfile -# command). -# This tag requires that the tag HAVE_DOT is set to YES. - -DOTFILE_DIRS = - -# The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the \mscfile -# command). - -MSCFILE_DIRS = - -# The DIAFILE_DIRS tag can be used to specify one or more directories that -# contain dia files that are included in the documentation (see the \diafile -# command). - -DIAFILE_DIRS = - -# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the -# path where java can find the plantuml.jar file. If left blank, it is assumed -# PlantUML is not used or called during a preprocessing step. Doxygen will -# generate a warning when it encounters a \startuml command in this case and -# will not generate output for the diagram. - -PLANTUML_JAR_PATH = - -# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a -# configuration file for plantuml. - -PLANTUML_CFG_FILE = - -# When using plantuml, the specified paths are searched for files specified by -# the !include statement in a plantuml block. - -PLANTUML_INCLUDE_PATH = - -# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes -# that will be shown in the graph. If the number of nodes in a graph becomes -# larger than this value, doxygen will truncate the graph, which is visualized -# by representing a node as a red box. Note that doxygen if the number of direct -# children of the root node in a graph is already larger than -# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that -# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. -# Minimum value: 0, maximum value: 10000, default value: 50. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_GRAPH_MAX_NODES = 50 - -# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs -# generated by dot. A depth value of 3 means that only nodes reachable from the -# root by following a path via at most 3 edges will be shown. Nodes that lay -# further from the root node will be omitted. Note that setting this option to 1 -# or 2 may greatly reduce the computation time needed for large code bases. Also -# note that the size of a graph can be further restricted by -# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. -# Minimum value: 0, maximum value: 1000, default value: 0. -# This tag requires that the tag HAVE_DOT is set to YES. - -MAX_DOT_GRAPH_DEPTH = 0 - -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not seem -# to support this out of the box. -# -# Warning: Depending on the platform used, enabling this option may lead to -# badly anti-aliased labels on the edges of a graph (i.e. they become hard to -# read). -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_TRANSPARENT = NO - -# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output -# files in one run (i.e. multiple -o and -T options on the command line). This -# makes dot run faster, but since only newer versions of dot (>1.8.10) support -# this, this feature is disabled by default. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_MULTI_TARGETS = YES - -# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page -# explaining the meaning of the various boxes and arrows in the dot generated -# graphs. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -GENERATE_LEGEND = YES - -# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate -# files that are used to generate the various graphs. -# -# Note: This setting is not only used for dot files but also for msc and -# plantuml temporary files. -# The default value is: YES. - -DOT_CLEANUP = YES diff --git a/src/clint.py b/src/clint.py index 4829a50887..155f5f2ce5 100755 --- a/src/clint.py +++ b/src/clint.py @@ -157,7 +157,6 @@ _ERROR_CATEGORIES = [ 'build/printf_format', 'build/storage_class', 'readability/bool', - 'readability/braces', 'readability/multiline_comment', 'readability/multiline_string', 'readability/nul', @@ -2305,213 +2304,36 @@ def CheckBraces(filename, clean_lines, linenum, error): prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] if (not Search(r'[,;:}{(]\s*$', prevline) and not Match(r'\s*#', prevline)): - error(filename, linenum, 'whitespace/braces', 4, - '{ should almost always be at the end' - ' of the previous line') + return # Brace must appear after function signature, but on the *next* line if Match(r'^(?:\w+(?: ?\*+)? )+\w+\(', line): pos = line.find('(') - (endline, end_linenum, endpos) = CloseExpression( - clean_lines, linenum, pos) + (endline, end_linenum, _) = CloseExpression(clean_lines, linenum, pos) if endline.endswith('{'): - error(filename, end_linenum, 'readability/braces', 5, - 'Brace starting function body must be placed on its own line') - else: - func_start_linenum = end_linenum + 1 - while not clean_lines.lines[func_start_linenum] == "{": - attrline = Match( - r'^((?!# *define).*?)' - r'(?:FUNC_ATTR|FUNC_API|REAL_FATTR)_\w+' - r'(?:\(\d+(, \d+)*\))?', - clean_lines.lines[func_start_linenum], - ) - if attrline: - if len(attrline.group(1)) != 2: - error(filename, func_start_linenum, - 'whitespace/indent', 5, - 'Function attribute line should have 2-space ' - 'indent') - - func_start_linenum += 1 - else: - func_start = clean_lines.lines[func_start_linenum] - if not func_start.startswith('enum ') and func_start.endswith('{'): - error(filename, func_start_linenum, - 'readability/braces', 5, - 'Brace starting function body must be placed ' - 'after the function signature') - break - - # An else clause should be on the same line as the preceding closing brace. - # If there is no preceding closing brace, there should be one. - if Match(r'\s*else\s*', line): - prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] - if Match(r'\s*}\s*$', prevline): - error(filename, linenum, 'whitespace/newline', 4, - 'An else should appear on the same line as the preceding }') - else: - error(filename, linenum, 'readability/braces', 5, - 'An else should always have braces before it') - - # If should always have a brace - for blockstart in ('if', 'while', 'for'): - if Match(r'\s*{0}(?!\w)[^{{]*$'.format(blockstart), line): - pos = line.find(blockstart) - pos = line.find('(', pos) - if pos > 0: - (endline, _, endpos) = CloseExpression( - clean_lines, linenum, pos) - if endline[endpos:].find('{') == -1: - error(filename, linenum, 'readability/braces', 5, - '{} should always use braces'.format(blockstart)) - - # If braces come on one side of an else, they should be on both. - # However, we have to worry about "else if" that spans multiple lines! - if Search(r'}\s*else[^{]*$', line) or Match(r'[^}]*else\s*{', line): - if Search(r'}\s*else if([^{]*)$', line): # could be multi-line if - # find the ( after the if - pos = line.find('else if') - pos = line.find('(', pos) - if pos > 0: - (endline, _, endpos) = CloseExpression( - clean_lines, linenum, pos) - # must be brace after if - if endline[endpos:].find('{') == -1: - error(filename, linenum, 'readability/braces', 5, - 'If an else has a brace on one side,' - ' it should have it on both') - else: # common case: else not followed by a multi-line if - error(filename, linenum, 'readability/braces', 5, - 'If an else has a brace on one side,' - ' it should have it on both') - - # Likewise, an else should never have the else clause on the same line - if Search(r'\belse [^\s{]', line) and not Search(r'\belse if\b', line): - error(filename, linenum, 'whitespace/newline', 4, - 'Else clause should never be on same line as else (use 2 lines)') - - # In the same way, a do/while should never be on one line - if Match(r'\s*do [^\s{]', line): - error(filename, linenum, 'whitespace/newline', 4, - 'do/while clauses should not be on a single line') - - # Block bodies should not be followed by a semicolon. Due to C++11 - # brace initialization, there are more places where semicolons are - # required than not, so we use a whitelist approach to check these - # rather than a blacklist. These are the places where "};" should - # be replaced by just "}": - # 1. Some flavor of block following closing parenthesis: - # for (;;) {}; - # while (...) {}; - # switch (...) {}; - # Function(...) {}; - # if (...) {}; - # if (...) else if (...) {}; - # - # 2. else block: - # if (...) else {}; - # - # 3. const member function: - # Function(...) const {}; - # - # 4. Block following some statement: - # x = 42; - # {}; - # - # 5. Block at the beginning of a function: - # Function(...) { - # {}; - # } - # - # Note that naively checking for the preceding "{" will also match - # braces inside multi-dimensional arrays, but this is fine since - # that expression will not contain semicolons. - # - # 6. Block following another block: - # while (true) {} - # {}; - # - # 7. End of namespaces: - # namespace {}; - # - # These semicolons seems far more common than other kinds of - # redundant semicolons, possibly due to people converting classes - # to namespaces. For now we do not warn for this case. - # - # Try matching case 1 first. - match = Match(r'^(.*\)\s*)\{', line) - if match: - # Matched closing parenthesis (case 1). Check the token before the - # matching opening parenthesis, and don't warn if it looks like a - # macro. This avoids these false positives: - # - macro that defines a base class - # - multi-line macro that defines a base class - # - macro that defines the whole class-head - # - # But we still issue warnings for macros that we know are safe to - # warn, specifically: - # - TEST, TEST_F, TEST_P, MATCHER, MATCHER_P - # - TYPED_TEST - # - INTERFACE_DEF - # - EXCLUSIVE_LOCKS_REQUIRED, SHARED_LOCKS_REQUIRED, LOCKS_EXCLUDED: - # - # We implement a whitelist of safe macros instead of a blacklist of - # unsafe macros, even though the latter appears less frequently in - # google code and would have been easier to implement. This is because - # the downside for getting the whitelist wrong means some extra - # semicolons, while the downside for getting the blacklist wrong - # would result in compile errors. - # - # In addition to macros, we also don't want to warn on compound - # literals. - closing_brace_pos = match.group(1).rfind(')') - opening_parenthesis = ReverseCloseExpression( - clean_lines, linenum, closing_brace_pos) - if opening_parenthesis[2] > -1: - line_prefix = opening_parenthesis[0][0:opening_parenthesis[2]] - macro = Search(r'\b([A-Z_]+)\s*$', line_prefix) - if ((macro and - macro.group(1) not in ( - 'TEST', 'TEST_F', 'MATCHER', 'MATCHER_P', 'TYPED_TEST', - 'EXCLUSIVE_LOCKS_REQUIRED', 'SHARED_LOCKS_REQUIRED', - 'LOCKS_EXCLUDED', 'INTERFACE_DEF')) or - Search(r'\s+=\s*$', line_prefix) or - Search(r'^\s*return\s*$', line_prefix)): - match = None - - else: - # Try matching cases 2-3. - match = Match(r'^(.*(?:else|\)\s*const)\s*)\{', line) - if not match: - # Try matching cases 4-6. These are always matched on separate - # lines. - # - # Note that we can't simply concatenate the previous line to the - # current line and do a single match, otherwise we may output - # duplicate warnings for the blank line case: - # if (cond) { - # // blank line - # } - prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] - if prevline and Search(r'[;{}]\s*$', prevline): - match = Match(r'^(\s*)\{', line) - - # Check matching closing brace - if match: - (endline, endlinenum, endpos) = CloseExpression( - clean_lines, linenum, len(match.group(1))) - if endpos > -1 and Match(r'^\s*;', endline[endpos:]): - # Current {} pair is eligible for semicolon check, and we have found - # the redundant semicolon, output warning here. - # - # Note: because we are scanning forward for opening braces, and - # outputting warnings for the matching closing brace, if there are - # nested blocks with trailing semicolons, we will get the error - # messages in reversed order. - error(filename, endlinenum, 'readability/braces', 4, - "You don't need a ; after a }") + return + func_start_linenum = end_linenum + 1 + while not clean_lines.lines[func_start_linenum] == "{": + attrline = Match( + r'^((?!# *define).*?)' + r'(?:FUNC_ATTR|FUNC_API|REAL_FATTR)_\w+' + r'(?:\(\d+(, \d+)*\))?', + clean_lines.lines[func_start_linenum], + ) + if attrline: + if len(attrline.group(1)) != 2: + error(filename, func_start_linenum, + 'whitespace/indent', 5, + 'Function attribute line should have 2-space ' + 'indent') + + func_start_linenum += 1 + else: + func_start = clean_lines.lines[func_start_linenum] + if not func_start.startswith('enum ') and func_start.endswith('{'): + return + break def CheckStyle(filename, clean_lines, linenum, error): """Checks rules from the 'C++ style rules' section of cppguide.html. @@ -2547,23 +2369,10 @@ def CheckStyle(filename, clean_lines, linenum, error): # if(match(prev, " +for \\(")) complain = 0; # if(prevodd && match(prevprev, " +for \\(")) complain = 0; initial_spaces = 0 - cleansed_line = clean_lines.elided[linenum] while initial_spaces < len(line) and line[initial_spaces] == ' ': initial_spaces += 1 - if (cleansed_line.count(';') > 1 and - # for loops are allowed two ;'s (and may run over two lines). - cleansed_line.find('for') == -1 and - (GetPreviousNonBlankLine(clean_lines, linenum)[0].find('for') == -1 or - GetPreviousNonBlankLine(clean_lines, linenum)[0].find(';') != -1) and - # It's ok to have many commands in a switch case that fits in 1 line - not ((cleansed_line.find('case ') != -1 or - cleansed_line.find('default:') != -1) and - cleansed_line.find('break;') != -1)): - error(filename, linenum, 'whitespace/newline', 0, - 'More than one command on the same line') - # Some more style checks CheckBraces(filename, clean_lines, linenum, error) CheckSpacing(filename, clean_lines, linenum, error) @@ -2694,8 +2503,7 @@ def CheckLanguage(filename, clean_lines, linenum, error): # Check for suspicious usage of "if" like # } if (a == b) { if Search(r'\}\s*if\s*\(', line): - error(filename, linenum, 'readability/braces', 4, - 'Did you mean "else if"? If not, start a new line for "if".') + return # Check for potential format string bugs like printf(foo). # We constrain the pattern not to pick things like DocidForPrintf(foo). diff --git a/src/klib/kvec.h b/src/klib/kvec.h index b5b3adf7d2..f6674a0adf 100644 --- a/src/klib/kvec.h +++ b/src/klib/kvec.h @@ -111,7 +111,7 @@ (v).size = (v).size + len; \ } while (0) -#define kv_concat(v, str) kv_concat_len(v, str, STRLEN(str)) +#define kv_concat(v, str) kv_concat_len(v, str, strlen(str)) #define kv_splice(v1, v0) kv_concat_len(v1, (v0).items, (v0).size) #define kv_pushp(v) \ diff --git a/src/man/nvim.1 b/src/man/nvim.1 index 457d785365..c32bdeadc6 100644 --- a/src/man/nvim.1 +++ b/src/man/nvim.1 @@ -122,9 +122,6 @@ modifications. .It Fl b Binary mode. .Ic ":help edit-binary" -.It Fl l -Lisp mode. -Sets the 'lisp' and 'showmatch' options. .It Fl A Arabic mode. Sets the 'arabic' option. @@ -144,7 +141,7 @@ is specified, append messages to instead of printing them. .Ic ":help 'verbose'" .It Fl D -Debug mode for VimL (Vim script). +Vimscript debug mode. Started when executing the first command from a script. :help debug-mode .It Fl n @@ -268,10 +265,26 @@ but execute before processing any vimrc. Up to 10 instances of these can be used independently from instances of .Fl c . +.It Fl l Ar script Op Ar args +Execute Lua +.Ar script +with optional +.Op Ar args +after processing any preceding Nvim startup arguments. +All +.Op Ar args +are treated as script arguments and are passed literally to Lua, that is, +.Fl l +stops processing of Nvim arguments. +.Ic ":help -l" .It Fl S Op Ar session -Source +Execute +.Ar session +after the first file argument has been read. If .Ar session -after the first file argument has been read. +filename ends with +.Pa .lua +it is executed as Lua instead of Vimscript. Equivalent to .Cm -c \(dqsource session\(dq . .Ar session diff --git a/src/mpack/mpack_core.c b/src/mpack/mpack_core.c index 4ee67a032a..3424f444b9 100644 --- a/src/mpack/mpack_core.c +++ b/src/mpack/mpack_core.c @@ -173,6 +173,9 @@ MPACK_API int mpack_write(mpack_tokbuf_t *tokbuf, char **buf, size_t *buflen, int mpack_rtoken(const char **buf, size_t *buflen, mpack_token_t *tok) { + if (*buflen == 0) { + return MPACK_EOF; + } unsigned char t = ADVANCE(buf, buflen); if (t < 0x80) { /* positive fixint */ diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 2960e6d9d2..2361210e59 100755 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -1,25 +1,337 @@ -option(USE_GCOV "Enable gcov support" OFF) +add_library(main_lib INTERFACE) +add_executable(nvim main.c) + +add_library(libuv_lib INTERFACE) +find_package(LibUV 1.28.0 REQUIRED) +target_include_directories(libuv_lib SYSTEM BEFORE INTERFACE ${LIBUV_INCLUDE_DIRS}) +target_link_libraries(libuv_lib INTERFACE ${LIBUV_LIBRARIES}) + +find_package(Msgpack 1.0.0 REQUIRED) +target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${MSGPACK_INCLUDE_DIRS}) +target_link_libraries(main_lib INTERFACE ${MSGPACK_LIBRARIES}) + +find_package(LibLUV 1.43.0 REQUIRED) +target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LIBLUV_INCLUDE_DIRS}) +# Use "luv" as imported library, to work around CMake using "-lluv" for +# "luv.so". #10407 +add_library(luv UNKNOWN IMPORTED) +set_target_properties(luv PROPERTIES IMPORTED_LOCATION ${LIBLUV_LIBRARIES}) +target_link_libraries(main_lib INTERFACE luv) + +find_package(TreeSitter REQUIRED) +target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${TreeSitter_INCLUDE_DIRS}) +target_link_libraries(main_lib INTERFACE ${TreeSitter_LIBRARIES}) + +find_package(UNIBILIUM 2.0 REQUIRED) +target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${UNIBILIUM_INCLUDE_DIRS}) +target_link_libraries(main_lib INTERFACE ${UNIBILIUM_LIBRARIES}) + +find_package(LibTermkey 0.22 REQUIRED) +target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LIBTERMKEY_INCLUDE_DIRS}) +target_link_libraries(main_lib INTERFACE ${LIBTERMKEY_LIBRARIES}) + +find_package(LIBVTERM 0.3 REQUIRED) +target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LIBVTERM_INCLUDE_DIRS}) +target_link_libraries(main_lib INTERFACE ${LIBVTERM_LIBRARIES}) + +find_package(Iconv REQUIRED) +target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${Iconv_INCLUDE_DIRS}) +target_link_libraries(main_lib INTERFACE ${Iconv_LIBRARIES}) + +option(ENABLE_LIBINTL "enable libintl" ON) +if(ENABLE_LIBINTL) + # LibIntl (not Intl) selects our FindLibIntl.cmake script. #8464 + find_package(LibIntl REQUIRED) + target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LibIntl_INCLUDE_DIRS}) + if (LibIntl_FOUND) + target_link_libraries(main_lib INTERFACE ${LibIntl_LIBRARY}) + endif() +endif() + +# The unit test lib requires LuaJIT; it will be skipped if LuaJIT is missing. +option(PREFER_LUA "Prefer Lua over LuaJIT in the nvim executable." OFF) +if(PREFER_LUA) + find_package(Lua 5.1 EXACT REQUIRED) + target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LUA_INCLUDE_DIR}) + target_link_libraries(main_lib INTERFACE ${LUA_LIBRARIES}) + # Passive (not REQUIRED): if LUAJIT_FOUND is not set, nvim-test is skipped. + find_package(LuaJit) +else() + find_package(LuaJit REQUIRED) + target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LUAJIT_INCLUDE_DIRS}) + target_link_libraries(main_lib INTERFACE ${LUAJIT_LIBRARIES}) +endif() + +option(ENABLE_IWYU "Run include-what-you-use with the compiler." OFF) +if(ENABLE_IWYU) + find_program(IWYU_PRG NAMES include-what-you-use iwyu) + if(NOT IWYU_PRG) + message(FATAL_ERROR "ENABLE_IWYU is ON but include-what-you-use is not found!") + endif() + + set(iwyu_flags "${IWYU_PRG};") + string(APPEND iwyu_flags "-Xiwyu;--no_default_mappings;") + string(APPEND iwyu_flags "-Xiwyu;--mapping_file=${PROJECT_SOURCE_DIR}/cmake.config/iwyu/mapping.imp;") + string(APPEND iwyu_flags "-Xiwyu;--mapping_file=${PROJECT_SOURCE_DIR}/cmake.config/iwyu/gcc.libc.imp;") + string(APPEND iwyu_flags "-Xiwyu;--mapping_file=${PROJECT_SOURCE_DIR}/cmake.config/iwyu/gcc.symbols.imp") + + set_target_properties(nvim PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_flags}") + target_compile_definitions(main_lib INTERFACE EXITFREE) +endif() + +if(MSVC) + # TODO(dundargoc): bump warning level + target_compile_options(main_lib INTERFACE -W2) + + # Disable warnings that give too many false positives. + target_compile_options(main_lib INTERFACE -wd4311 -wd4146) + target_compile_definitions(main_lib INTERFACE _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE) + + target_sources(main_lib INTERFACE ${CMAKE_CURRENT_LIST_DIR}/os/nvim.manifest) +else() + target_compile_options(main_lib INTERFACE -Wall -Wextra -pedantic -Wno-unused-parameter + -Wstrict-prototypes -std=gnu99 -Wshadow -Wconversion + -Wdouble-promotion + -Wmissing-noreturn + -Wmissing-format-attribute + -Wmissing-prototypes) +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") + target_compile_options(main_lib INTERFACE -Wno-c11-extensions) +endif() + +check_c_compiler_flag(-Wimplicit-fallthrough HAVE_WIMPLICIT_FALLTHROUGH_FLAG) +if(HAVE_WIMPLICIT_FALLTHROUGH_FLAG) + target_compile_options(main_lib INTERFACE -Wimplicit-fallthrough) +endif() + +option(ENABLE_COMPILER_SUGGESTIONS "Enable -Wsuggest compiler warnings" OFF) +if(ENABLE_COMPILER_SUGGESTIONS) + # 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) + target_compile_options(main_lib INTERFACE -Wsuggest-attribute=pure) + endif() + + check_c_compiler_flag(-Wsuggest-attribute=const HAVE_WSUGGEST_ATTRIBUTE_CONST) + if(HAVE_WSUGGEST_ATTRIBUTE_CONST) + target_compile_options(main_lib INTERFACE -Wsuggest-attribute=const) + endif() + check_c_compiler_flag(-Wsuggest-attribute=malloc HAVE_WSUGGEST_ATTRIBUTE_MALLOC) + if(HAVE_WSUGGEST_ATTRIBUTE_MALLOC) + target_compile_options(main_lib INTERFACE -Wsuggest-attribute=malloc) + endif() + + check_c_compiler_flag(-Wsuggest-attribute=cold HAVE_WSUGGEST_ATTRIBUTE_COLD) + if(HAVE_WSUGGEST_ATTRIBUTE_COLD) + target_compile_options(main_lib INTERFACE -Wsuggest-attribute=cold) + endif() +endif() + +if(MINGW) + # Use POSIX compatible stdio in Mingw + target_compile_definitions(main_lib INTERFACE __USE_MINGW_ANSI_STDIO) +endif() +if(WIN32) + # Windows Vista is the minimum supported version + target_compile_definitions(main_lib INTERFACE _WIN32_WINNT=0x0600 MSWIN) +endif() + +# OpenBSD's GCC (4.2.1) doesn't have -Wvla +check_c_compiler_flag(-Wvla HAS_WVLA_FLAG) +if(HAS_WVLA_FLAG) + target_compile_options(main_lib INTERFACE -Wvla) +endif() + +check_c_compiler_flag(-fno-common HAVE_FNO_COMMON) +if (HAVE_FNO_COMMON) + target_compile_options(main_lib INTERFACE -fno-common) +endif() + +check_c_compiler_flag(-fdiagnostics-color=auto HAS_DIAG_COLOR_FLAG) +if(HAS_DIAG_COLOR_FLAG) + if(CMAKE_GENERATOR MATCHES "Ninja") + target_compile_options(main_lib INTERFACE -fdiagnostics-color=always) + else() + target_compile_options(main_lib INTERFACE -fdiagnostics-color=auto) + endif() +endif() + +option(CI_BUILD "CI, extra flags will be set" OFF) +if(CI_BUILD) + message(STATUS "CI build enabled") + if(MSVC) + target_compile_options(main_lib INTERFACE -WX) + else() + target_compile_options(main_lib INTERFACE -Werror) + if(DEFINED ENV{BUILD_UCHAR}) + # Get some test coverage for unsigned char + target_compile_options(main_lib INTERFACE -funsigned-char) + endif() + endif() +endif() + +list(APPEND CMAKE_REQUIRED_INCLUDES "${UNIBILIUM_INCLUDE_DIRS}") +list(APPEND CMAKE_REQUIRED_LIBRARIES "${UNIBILIUM_LIBRARIES}") +check_c_source_compiles(" +#include <unibilium.h> + +int +main(void) +{ + unibi_str_from_var(unibi_var_from_str(\"\")); + return unibi_num_from_var(unibi_var_from_num(0)); +} +" UNIBI_HAS_VAR_FROM) +list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${UNIBILIUM_INCLUDE_DIRS}") +list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES "${UNIBILIUM_LIBRARIES}") +if(UNIBI_HAS_VAR_FROM) + target_compile_definitions(main_lib INTERFACE NVIM_UNIBI_HAS_VAR_FROM) +endif() + +list(APPEND CMAKE_REQUIRED_INCLUDES "${MSGPACK_INCLUDE_DIRS}") +check_c_source_compiles(" +#include <msgpack.h> + +int +main(void) +{ + return MSGPACK_OBJECT_FLOAT32; +} +" MSGPACK_HAS_FLOAT32) +list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${MSGPACK_INCLUDE_DIRS}") +if(MSGPACK_HAS_FLOAT32) + target_compile_definitions(main_lib INTERFACE NVIM_MSGPACK_HAS_FLOAT32) +endif() + +list(APPEND CMAKE_REQUIRED_INCLUDES "${TreeSitter_INCLUDE_DIRS}") +list(APPEND CMAKE_REQUIRED_LIBRARIES "${TreeSitter_LIBRARIES}") +check_c_source_compiles(" +#include <tree_sitter/api.h> +int +main(void) +{ + TSQueryCursor *cursor = ts_query_cursor_new(); + ts_query_cursor_set_match_limit(cursor, 32); + return 0; +} +" TS_HAS_SET_MATCH_LIMIT) +if(TS_HAS_SET_MATCH_LIMIT) + target_compile_definitions(main_lib INTERFACE NVIM_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) + target_compile_definitions(main_lib INTERFACE NVIM_TS_HAS_SET_ALLOCATOR) +endif() +list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${TreeSitter_INCLUDE_DIRS}") +list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES "${TreeSitter_LIBRARIES}") + +# Include <string.h> because some toolchains define _FORTIFY_SOURCE=2 in +# internal header files, which should in turn be #included by <string.h>. +check_c_source_compiles(" +#include <string.h> + +#if defined(_FORTIFY_SOURCE) && _FORTIFY_SOURCE > 1 +#error \"_FORTIFY_SOURCE > 1\" +#endif +int +main(void) +{ + return 0; +} +" HAS_ACCEPTABLE_FORTIFY) + +if(NOT HAS_ACCEPTABLE_FORTIFY) + message(STATUS "Unsupported _FORTIFY_SOURCE found, forcing _FORTIFY_SOURCE=1") + # Extract possible prefix to _FORTIFY_SOURCE (e.g. -Wp,-D_FORTIFY_SOURCE). + string(REGEX MATCH "[^\ ]+-D_FORTIFY_SOURCE" _FORTIFY_SOURCE_PREFIX "${CMAKE_C_FLAGS}") + string(REPLACE "-D_FORTIFY_SOURCE" "" _FORTIFY_SOURCE_PREFIX "${_FORTIFY_SOURCE_PREFIX}" ) + if(NOT _FORTIFY_SOURCE_PREFIX STREQUAL "") + message(STATUS "Detected _FORTIFY_SOURCE Prefix=${_FORTIFY_SOURCE_PREFIX}") + endif() + # -U in add_definitions doesn't end up in the correct spot, so we add it to + # the flags variable instead. + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${_FORTIFY_SOURCE_PREFIX}-U_FORTIFY_SOURCE ${_FORTIFY_SOURCE_PREFIX}-D_FORTIFY_SOURCE=1") +endif() + +target_compile_definitions(main_lib INTERFACE INCLUDE_GENERATED_DECLARATIONS) + +# Remove --sort-common from linker flags, as this seems to cause bugs (see #2641, #3374). +# TODO: Figure out the root cause. +if(CMAKE_EXE_LINKER_FLAGS MATCHES "--sort-common" OR + CMAKE_SHARED_LINKER_FLAGS MATCHES "--sort-common" OR + CMAKE_MODULE_LINKER_FLAGS MATCHES "--sort-common") + message(STATUS "Removing --sort-common from linker flags") + string(REGEX REPLACE ",--sort-common(=[^,]+)?" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + string(REGEX REPLACE ",--sort-common(=[^,]+)?" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}") + string(REGEX REPLACE ",--sort-common(=[^,]+)?" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}") + + # If no linker flags remain for a -Wl argument, remove it. + # '-Wl$' will match LDFLAGS="-Wl,--sort-common", + # '-Wl ' will match LDFLAGS="-Wl,--sort-common -Wl,..." + string(REGEX REPLACE "-Wl($| )" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + string(REGEX REPLACE "-Wl($| )" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}") + string(REGEX REPLACE "-Wl($| )" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}") +endif() + +if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang") + if(CMAKE_SYSTEM_NAME STREQUAL "SunOS") + target_link_libraries(nvim PRIVATE -Wl,--no-undefined -lsocket) + elseif(NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin") + target_link_libraries(nvim PRIVATE -Wl,--no-undefined) + endif() + + # For O_CLOEXEC, O_DIRECTORY, and O_NOFOLLOW flags on older systems + # (pre POSIX.1-2008: glibc 2.11 and earlier). #4042 + # For ptsname(). #6743 + target_compile_definitions(main_lib INTERFACE _GNU_SOURCE) +endif() + +option(USE_GCOV "Enable gcov support" OFF) if(USE_GCOV) if(CLANG_TSAN) # GCOV and TSAN results in false data race reports message(FATAL_ERROR "USE_GCOV cannot be used with CLANG_TSAN") endif() message(STATUS "Enabling gcov support") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage") - add_definitions(-DUSE_GCOV) + target_compile_options(main_lib INTERFACE --coverage) + target_link_libraries(main_lib INTERFACE --coverage) + target_compile_definitions(main_lib INTERFACE USE_GCOV) endif() if(WIN32) if(MINGW) # Enable wmain - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -municode") + target_link_libraries(nvim PRIVATE -municode) endif() elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework CoreServices") - set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -framework CoreServices") + target_link_libraries(nvim PRIVATE "-framework CoreServices") +endif() + +if(UNIX) + # -fstack-protector breaks non Unix builds even in Mingw-w64 + check_c_compiler_flag(-fstack-protector-strong HAS_FSTACK_PROTECTOR_STRONG_FLAG) + check_c_compiler_flag(-fstack-protector HAS_FSTACK_PROTECTOR_FLAG) + if(HAS_FSTACK_PROTECTOR_STRONG_FLAG) + target_compile_options(main_lib INTERFACE -fstack-protector-strong) + target_link_libraries(main_lib INTERFACE -fstack-protector-strong) + elseif(HAS_FSTACK_PROTECTOR_FLAG) + target_compile_options(main_lib INTERFACE -fstack-protector --param ssp-buffer-size=4) + target_link_libraries(main_lib INTERFACE -fstack-protector --param ssp-buffer-size=4) + endif() endif() set(GENERATOR_DIR ${CMAKE_CURRENT_LIST_DIR}/generators) @@ -36,10 +348,8 @@ set(HEADER_GENERATOR ${GENERATOR_DIR}/gen_declarations.lua) set(GENERATED_INCLUDES_DIR ${PROJECT_BINARY_DIR}/include) set(GENERATED_API_DISPATCH ${GENERATED_DIR}/api/private/dispatch_wrappers.generated.h) set(GENERATED_FUNCS_METADATA ${GENERATED_DIR}/api/private/funcs_metadata.generated.h) -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) @@ -68,31 +378,21 @@ set(LUA_FILETYPE_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/filetype.lu 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") -set(LINT_SUPPRESS_URL "${LINT_SUPPRESS_URL_BASE}/errors.json") -set(LINT_PRG ${PROJECT_SOURCE_DIR}/src/clint.py) -set(DOWNLOAD_SCRIPT ${PROJECT_SOURCE_DIR}/cmake/Download.cmake) -set(LINT_SUPPRESSES_ROOT ${PROJECT_BINARY_DIR}/errors) -set(LINT_SUPPRESSES_URL "${LINT_SUPPRESS_URL_BASE}/errors.tar.gz") -set(LINT_SUPPRESSES_ARCHIVE ${LINT_SUPPRESSES_ROOT}/errors.tar.gz) -set(LINT_SUPPRESSES_TOUCH_FILE "${TOUCHES_DIR}/unpacked-clint-errors-archive") -set(CLINT_REPORT_PATH ${LINT_SUPPRESSES_ROOT}/src/home/runner/work/doc/doc/gh-pages/reports/clint) glob_wrapper(UNICODE_FILES ${UNICODE_DIR}/*.txt) glob_wrapper(API_HEADERS api/*.h) list(REMOVE_ITEM API_HEADERS ${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h) glob_wrapper(MSGPACK_RPC_HEADERS msgpack_rpc/*.h) -include_directories(${GENERATED_DIR}) -include_directories(${CACHED_GENERATED_DIR}) -include_directories(${GENERATED_INCLUDES_DIR}) +target_include_directories(main_lib INTERFACE ${GENERATED_DIR}) +target_include_directories(main_lib INTERFACE ${CACHED_GENERATED_DIR}) +target_include_directories(main_lib INTERFACE ${GENERATED_INCLUDES_DIR}) +target_include_directories(main_lib INTERFACE "${PROJECT_BINARY_DIR}/cmake.config") +target_include_directories(main_lib INTERFACE "${PROJECT_SOURCE_DIR}/src") file(MAKE_DIRECTORY ${TOUCHES_DIR}) file(MAKE_DIRECTORY ${GENERATED_DIR}) file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}) -file(MAKE_DIRECTORY ${LINT_SUPPRESSES_ROOT}) -file(MAKE_DIRECTORY ${LINT_SUPPRESSES_ROOT}/src) glob_wrapper(NVIM_SOURCES *.c) glob_wrapper(NVIM_HEADERS *.h) @@ -111,9 +411,6 @@ foreach(subdir viml viml/parser ) - if(${subdir} MATCHES "tui" AND NOT FEAT_TUI) - continue() - endif() file(MAKE_DIRECTORY ${GENERATED_DIR}/${subdir}) file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/${subdir}) @@ -123,8 +420,6 @@ foreach(subdir list(APPEND NVIM_HEADERS ${headers}) endforeach() -glob_wrapper(UNIT_TEST_FIXTURES ${PROJECT_SOURCE_DIR}/test/unit/fixtures/*.c) - # Sort file lists to ensure generated files are created in the same order from # build to build. list(SORT NVIM_SOURCES) @@ -156,29 +451,32 @@ endforeach() list(REMOVE_ITEM NVIM_SOURCES ${to_remove}) -if(NOT MSVC) - # xdiff, mpack, lua-cjson: inlined external project, we don't maintain it. #9306 +# xdiff, mpack, lua-cjson: inlined external project, we don't maintain it. #9306 +if(MSVC) + set_source_files_properties( + ${EXTERNAL_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} /wd4090 /wd4244") +else() set_source_files_properties( ${EXTERNAL_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion -Wno-missing-noreturn -Wno-missing-format-attribute -Wno-double-promotion -Wno-strict-prototypes") endif() if(NOT "${MIN_LOG_LEVEL}" MATCHES "^$") - add_definitions(-DMIN_LOG_LEVEL=${MIN_LOG_LEVEL}) + target_compile_definitions(main_lib INTERFACE MIN_LOG_LEVEL=${MIN_LOG_LEVEL}) endif() if(CLANG_ASAN_UBSAN OR CLANG_MSAN OR CLANG_TSAN) - add_definitions(-DEXITFREE) + target_compile_definitions(main_lib INTERFACE EXITFREE) endif() -get_directory_property(gen_cdefs COMPILE_DEFINITIONS) -foreach(gen_cdef ${gen_cdefs} DO_NOT_DEFINE_EMPTY_ATTRIBUTES) +get_target_property(prop main_lib INTERFACE_COMPILE_DEFINITIONS) +foreach(gen_cdef DO_NOT_DEFINE_EMPTY_ATTRIBUTES ${prop}) if(NOT ${gen_cdef} MATCHES "INCLUDE_GENERATED_DECLARATIONS") list(APPEND gen_cflags "-D${gen_cdef}") endif() endforeach() -get_directory_property(gen_includes INCLUDE_DIRECTORIES) -foreach(gen_include ${gen_includes} ${LUA_PREFERRED_INCLUDE_DIRS}) +get_target_property(prop main_lib INTERFACE_INCLUDE_DIRECTORIES) +foreach(gen_include ${prop}) list(APPEND gen_cflags "-I${gen_include}") endforeach() if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_OSX_SYSROOT) @@ -190,14 +488,6 @@ separate_arguments(C_FLAGS_ARRAY UNIX_COMMAND ${CMAKE_C_FLAGS}) separate_arguments(C_FLAGS_${build_type}_ARRAY UNIX_COMMAND ${CMAKE_C_FLAGS_${build_type}}) set(gen_cflags ${gen_cflags} ${C_FLAGS_${build_type}_ARRAY} ${C_FLAGS_ARRAY}) -function(get_preproc_output varname iname) - if(MSVC) - set(${varname} /P /Fi${iname} /nologo PARENT_SCOPE) - else() - set(${varname} -E -o ${iname} PARENT_SCOPE) - endif() -endfunction() - set(NVIM_VERSION_GIT_H ${PROJECT_BINARY_DIR}/cmake.config/auto/versiondef_git.h) add_custom_target(update_version_stamp COMMAND ${CMAKE_COMMAND} @@ -206,9 +496,8 @@ add_custom_target(update_version_stamp -DNVIM_VERSION_PATCH=${NVIM_VERSION_PATCH} -DNVIM_VERSION_PRERELEASE=${NVIM_VERSION_PRERELEASE} -DOUTPUT=${NVIM_VERSION_GIT_H} - -DCMAKE_MESSAGE_LOG_LEVEL=${CMAKE_MESSAGE_LOG_LEVEL} + -DNVIM_SOURCE_DIR=${CMAKE_SOURCE_DIR} -P ${PROJECT_SOURCE_DIR}/cmake/GenerateVersion.cmake - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} BYPRODUCTS ${NVIM_VERSION_GIT_H}) # NVIM_GENERATED_FOR_HEADERS: generated headers to be included in headers @@ -221,7 +510,6 @@ foreach(sfile ${NVIM_SOURCES} ${GENERATED_API_DISPATCH} "${GENERATED_UI_EVENTS_CALL}" "${GENERATED_UI_EVENTS_REMOTE}" - "${GENERATED_UI_EVENTS_BRIDGE}" "${GENERATED_KEYSETS}" "${GENERATED_UI_EVENTS_CLIENT}" ) @@ -240,7 +528,11 @@ foreach(sfile ${NVIM_SOURCES} set(gf_h_h "${GENERATED_INCLUDES_DIR}/${r}.h.generated.h") set(gf_i "${GENERATED_DIR}/${r}.i") - get_preproc_output(PREPROC_OUTPUT ${gf_i}) + if(MSVC) + set(PREPROC_OUTPUT /P /Fi${gf_i} /nologo) + else() + set(PREPROC_OUTPUT -E -o ${gf_i}) + endif() set(depends "${HEADER_GENERATOR}" "${sfile}") if("${f}" STREQUAL "version.c") @@ -271,11 +563,11 @@ add_custom_command(OUTPUT ${GENERATED_UNICODE_TABLES} add_custom_command( OUTPUT ${GENERATED_API_DISPATCH} ${GENERATED_FUNCS_METADATA} ${API_METADATA} ${LUA_API_C_BINDINGS} - COMMAND ${LUA_PRG} ${API_DISPATCH_GENERATOR} ${CMAKE_CURRENT_LIST_DIR} - ${GENERATED_API_DISPATCH} - ${GENERATED_FUNCS_METADATA} ${API_METADATA} - ${LUA_API_C_BINDINGS} - ${API_HEADERS} + COMMAND ${LUA_GEN_PRG} ${API_DISPATCH_GENERATOR} ${CMAKE_CURRENT_LIST_DIR} + ${GENERATED_API_DISPATCH} + ${GENERATED_FUNCS_METADATA} ${API_METADATA} + ${LUA_API_C_BINDINGS} + ${API_HEADERS} DEPENDS ${API_HEADERS} ${MSGPACK_RPC_HEADERS} @@ -316,20 +608,16 @@ list(APPEND NVIM_GENERATED_SOURCES ) add_custom_command( - OUTPUT ${GENERATED_UI_EVENTS} - ${GENERATED_UI_EVENTS_CALL} + OUTPUT ${GENERATED_UI_EVENTS_CALL} ${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} - ${GENERATED_UI_EVENTS_CALL} - ${GENERATED_UI_EVENTS_REMOTE} - ${GENERATED_UI_EVENTS_BRIDGE} - ${GENERATED_UI_EVENTS_METADATA} - ${GENERATED_UI_EVENTS_CLIENT} + COMMAND ${LUA_GEN_PRG} ${API_UI_EVENTS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h + ${GENERATED_UI_EVENTS_CALL} + ${GENERATED_UI_EVENTS_REMOTE} + ${GENERATED_UI_EVENTS_METADATA} + ${GENERATED_UI_EVENTS_CLIENT} DEPENDS ${API_UI_EVENTS_GENERATOR} ${GENERATOR_C_GRAMMAR} @@ -396,80 +684,45 @@ foreach(hfile ${NVIM_GENERATED_FOR_HEADERS}) endif() endforeach() -# Our dependencies come first. - if (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") - list(APPEND NVIM_LINK_LIBRARIES pthread c++abi) -endif() - -if (LibIntl_FOUND) - list(APPEND NVIM_LINK_LIBRARIES ${LibIntl_LIBRARY}) -endif() - -if(Iconv_LIBRARIES) - list(APPEND NVIM_LINK_LIBRARIES ${Iconv_LIBRARIES}) + target_link_libraries(main_lib INTERFACE pthread c++abi) endif() if(WIN32) - list(APPEND NVIM_LINK_LIBRARIES netapi32) + target_link_libraries(main_lib INTERFACE netapi32) endif() -# Use "luv" as imported library, to work around CMake using "-lluv" for -# "luv.so". #10407 -add_library(luv UNKNOWN IMPORTED) -set_property(TARGET luv PROPERTY IMPORTED_LOCATION ${LIBLUV_LIBRARIES}) - -# Put these last on the link line, since multiple things may depend on them. -list(APPEND NVIM_LINK_LIBRARIES - luv - ${LIBUV_LIBRARIES} - ${MSGPACK_LIBRARIES} - ${LIBVTERM_LIBRARIES} - ${LIBTERMKEY_LIBRARIES} - ${UNIBILIUM_LIBRARIES} - ${UTF8PROC_LIBRARIES} - ${TreeSitter_LIBRARIES} - ${CMAKE_THREAD_LIBS_INIT} -) - if(UNIX) - list(APPEND NVIM_LINK_LIBRARIES - m) + target_link_libraries(main_lib INTERFACE m) if (NOT CMAKE_SYSTEM_NAME STREQUAL "SunOS") - list(APPEND NVIM_LINK_LIBRARIES - util) + target_link_libraries(main_lib INTERFACE util) endif() endif() -set(NVIM_EXEC_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES} ${LUA_PREFERRED_LIBRARIES}) - -add_executable(nvim ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} +target_sources(nvim PRIVATE ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} ${NVIM_GENERATED_SOURCES} ${NVIM_SOURCES} ${NVIM_HEADERS} ${EXTERNAL_SOURCES} ${EXTERNAL_HEADERS}) set_target_properties(nvim PROPERTIES - EXPORT_COMPILE_COMMANDS ON) + EXPORT_COMPILE_COMMANDS ON + ENABLE_EXPORTS TRUE) if(${CMAKE_VERSION} VERSION_LESS 3.20) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) endif() -target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES}) +target_link_libraries(nvim PRIVATE main_lib PUBLIC libuv_lib) 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}) -set_property(TARGET nvim PROPERTY ENABLE_EXPORTS TRUE) - if(ENABLE_LTO) include(CheckIPOSupported) check_ipo_supported(RESULT IPO_SUPPORTED) if(IPO_SUPPORTED AND (NOT CMAKE_BUILD_TYPE MATCHES Debug)) - set_property(TARGET nvim PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) + set_target_properties(nvim PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) endif() endif() @@ -573,7 +826,10 @@ file(MAKE_DIRECTORY ${BINARY_LIB_DIR}) # install treesitter parser if bundled if(EXISTS ${DEPS_PREFIX}/lib/nvim/parser) - file(COPY ${DEPS_PREFIX}/lib/nvim/parser DESTINATION ${BINARY_LIB_DIR}) + glob_wrapper(TREESITTER_PARSERS ${DEPS_PREFIX}/lib/nvim/parser/*) + foreach(parser_lib ${TREESITTER_PARSERS}) + file(COPY ${parser_lib} DESTINATION ${BINARY_LIB_DIR}/parser) + endforeach() endif() install(DIRECTORY ${BINARY_LIB_DIR} @@ -588,8 +844,6 @@ add_library( ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} ${EXTERNAL_SOURCES} ${EXTERNAL_HEADERS} ) -set_property(TARGET libnvim APPEND PROPERTY - INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}) if(MSVC) set(LIBNVIM_NAME libnvim) else() @@ -598,15 +852,15 @@ endif() set_target_properties( libnvim PROPERTIES - POSITION_INDEPENDENT_CODE ON OUTPUT_NAME ${LIBNVIM_NAME} ) -target_compile_options(libnvim PRIVATE -DMAKE_LIB) +target_compile_definitions(libnvim PRIVATE MAKE_LIB) +target_link_libraries(libnvim PRIVATE main_lib PUBLIC libuv_lib) if(NOT LUAJIT_FOUND) message(STATUS "luajit not found, skipping nvim-test (unit tests) target") else() - set(NVIM_TEST_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES} ${LUAJIT_LIBRARIES}) + glob_wrapper(UNIT_TEST_FIXTURES ${PROJECT_SOURCE_DIR}/test/unit/fixtures/*.c) add_library( nvim-test MODULE @@ -616,49 +870,41 @@ else() ${EXTERNAL_SOURCES} ${EXTERNAL_HEADERS} ${UNIT_TEST_FIXTURES} ) - target_link_libraries(nvim-test ${NVIM_TEST_LINK_LIBRARIES}) - target_link_libraries(libnvim ${NVIM_TEST_LINK_LIBRARIES}) - set_property( - TARGET nvim-test - APPEND PROPERTY INCLUDE_DIRECTORIES ${LUAJIT_INCLUDE_DIRS} - ) - set_target_properties( - nvim-test - PROPERTIES - POSITION_INDEPENDENT_CODE ON - ) - target_compile_options(nvim-test PRIVATE -DUNIT_TESTING) + target_link_libraries(nvim-test PRIVATE ${LUAJIT_LIBRARIES} main_lib PUBLIC libuv_lib) + if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + target_link_libraries(nvim-test PRIVATE "-framework CoreServices") + endif() + target_include_directories(nvim-test PRIVATE ${LUAJIT_INCLUDE_DIRS}) + target_compile_definitions(nvim-test PRIVATE UNIT_TESTING) endif() if(CLANG_ASAN_UBSAN) message(STATUS "Enabling Clang address sanitizer and undefined behavior sanitizer for nvim.") - check_c_compiler_flag(-fno-sanitize-recover=all SANITIZE_RECOVER_ALL) - if(SANITIZE_RECOVER_ALL) - if(CI_BUILD) - # Try to recover from all sanitize issues so we get reports about all failures - set(SANITIZE_RECOVER -fsanitize-recover=all) # Clang 3.6+ - else() - set(SANITIZE_RECOVER -fno-sanitize-recover=all) # Clang 3.6+ - endif() + if(CI_BUILD) + # Try to recover from all sanitize issues so we get reports about all failures + target_compile_options(nvim PRIVATE -fsanitize-recover=all) else() - if(CI_BUILD) - # Try to recover from all sanitize issues so we get reports about all failures - set(SANITIZE_RECOVER -fsanitize-recover) # Clang 3.5- - else() - set(SANITIZE_RECOVER -fno-sanitize-recover) # Clang 3.5- - endif() + target_compile_options(nvim PRIVATE -fno-sanitize-recover=all) endif() - set_property(TARGET nvim APPEND PROPERTY COMPILE_OPTIONS ${SANITIZE_RECOVER} -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=address -fsanitize=undefined -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/src/.asan-blacklist) - set_property(TARGET nvim APPEND_STRING PROPERTY LINK_FLAGS "-fsanitize=address -fsanitize=undefined ") + target_compile_options(nvim PRIVATE + -fno-omit-frame-pointer + -fno-optimize-sibling-calls + -fsanitize=address + -fsanitize=undefined + -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/src/.asan-blacklist) + target_link_libraries(nvim PRIVATE -fsanitize=address -fsanitize=undefined) elseif(CLANG_MSAN) message(STATUS "Enabling Clang memory sanitizer for nvim.") - set_property(TARGET nvim APPEND PROPERTY COMPILE_OPTIONS -fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer -fno-optimize-sibling-calls) - set_property(TARGET nvim APPEND_STRING PROPERTY LINK_FLAGS "-fsanitize=memory -fsanitize-memory-track-origins ") + target_compile_options(nvim PRIVATE + -fsanitize=memory + -fsanitize-memory-track-origins + -fno-omit-frame-pointer + -fno-optimize-sibling-calls) + target_link_libraries(nvim PRIVATE -fsanitize=memory -fsanitize-memory-track-origins) elseif(CLANG_TSAN) message(STATUS "Enabling Clang thread sanitizer for nvim.") - set_property(TARGET nvim APPEND PROPERTY COMPILE_OPTIONS -fsanitize=thread) - set_property(TARGET nvim APPEND PROPERTY COMPILE_OPTIONS -fPIE) - set_property(TARGET nvim APPEND_STRING PROPERTY LINK_FLAGS "-fsanitize=thread ") + target_compile_options(nvim PRIVATE -fsanitize=thread -fPIE) + target_link_libraries(nvim PRIVATE -fsanitize=thread) endif() function(get_test_target prefix sfile relative_path_var target_var) @@ -701,10 +947,7 @@ foreach(hfile ${NVIM_HEADERS}) ${texe} EXCLUDE_FROM_ALL ${tsource} ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_HEADERS}) - set_property( - TARGET ${texe} - APPEND PROPERTY INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS} - ) + target_link_libraries(${texe} PRIVATE main_lib) set_target_properties(${texe} PROPERTIES FOLDER test) list(FIND NO_SINGLE_CHECK_HEADERS "${relative_path}" hfile_exclude_idx) @@ -715,55 +958,19 @@ foreach(hfile ${NVIM_HEADERS}) endforeach() add_custom_target(check-single-includes DEPENDS ${HEADER_CHECK_TARGETS}) -function(add_download output url allow_failure) - add_custom_command( - OUTPUT "${output}" - COMMAND - ${CMAKE_COMMAND} - -DURL=${url} -DFILE=${output} - -DALLOW_FAILURE=${allow_failure} - -P ${DOWNLOAD_SCRIPT} - DEPENDS ${DOWNLOAD_SCRIPT} - ) -endfunction() - -add_download(${LINT_SUPPRESSES_ARCHIVE} ${LINT_SUPPRESSES_URL} off) - -add_custom_command( - OUTPUT ${LINT_SUPPRESSES_TOUCH_FILE} - WORKING_DIRECTORY ${LINT_SUPPRESSES_ROOT}/src - COMMAND ${CMAKE_COMMAND} -E tar xfz ${LINT_SUPPRESSES_ARCHIVE} - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CLINT_REPORT_PATH} "${LINT_SUPPRESSES_ROOT}" - COMMAND ${CMAKE_COMMAND} -E touch ${LINT_SUPPRESSES_TOUCH_FILE} - DEPENDS ${LINT_SUPPRESSES_ARCHIVE} -) - -add_download(${LINT_SUPPRESS_FILE} ${LINT_SUPPRESS_URL} off) - if(CI_BUILD) set(LINT_OUTPUT_FORMAT gh_action) else() set(LINT_OUTPUT_FORMAT vs7) endif() -set(LINT_NVIM_REL_SOURCES) -foreach(sfile ${LINT_NVIM_SOURCES}) - get_test_target("" "${sfile}" r suffix) - set(suppress_file ${LINT_SUPPRESSES_ROOT}/${suffix}.json) - set(suppress_url "${LINT_SUPPRESS_URL_BASE}/${suffix}.json") - set(rsfile src/nvim/${r}) - set(touch_file "${TOUCHES_DIR}/ran-clint-${suffix}") - add_custom_command( - OUTPUT ${touch_file} - COMMAND ${LINT_PRG} --suppress-errors=${suppress_file} --output=${LINT_OUTPUT_FORMAT} ${rsfile} - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - COMMAND ${CMAKE_COMMAND} -E touch ${touch_file} - DEPENDS ${LINT_PRG} ${sfile} ${LINT_SUPPRESSES_TOUCH_FILE} - ) - list(APPEND LINT_TARGETS ${touch_file}) - list(APPEND LINT_NVIM_REL_SOURCES ${rsfile}) -endforeach() -add_custom_target(lintc DEPENDS ${LINT_TARGETS}) +add_glob_target( + TARGET lintc-clint + COMMAND ${PROJECT_SOURCE_DIR}/src/clint.py + FLAGS --output=${LINT_OUTPUT_FORMAT} + FILES ${LINT_NVIM_SOURCES} + EXCLUDE + tui/terminfo_defs.h) add_custom_target(uncrustify-version COMMAND ${CMAKE_COMMAND} @@ -771,13 +978,15 @@ add_custom_target(uncrustify-version -D CONFIG_FILE=${PROJECT_SOURCE_DIR}/src/uncrustify.cfg -P ${PROJECT_SOURCE_DIR}/cmake/CheckUncrustifyVersion.cmake) -add_glob_targets( - TARGET lintuncrustify +add_glob_target( + TARGET lintc-uncrustify COMMAND ${UNCRUSTIFY_PRG} FLAGS -c "${PROJECT_SOURCE_DIR}/src/uncrustify.cfg" -q --check - FILES ${LINT_NVIM_SOURCES} - ) -add_dependencies(lintuncrustify uncrustify-version) + FILES ${LINT_NVIM_SOURCES}) +add_dependencies(lintc-uncrustify uncrustify-version) + +add_custom_target(lintc) +add_dependencies(lintc lintc-clint lintc-uncrustify) add_custom_target(formatc COMMAND ${CMAKE_COMMAND} @@ -787,14 +996,6 @@ add_custom_target(formatc WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) add_dependencies(formatc uncrustify-version) -add_custom_target( - lintcfull - COMMAND - ${LINT_PRG} --suppress-errors=${LINT_SUPPRESS_FILE} --output=${LINT_OUTPUT_FORMAT} ${LINT_NVIM_REL_SOURCES} - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - DEPENDS ${LINT_PRG} ${LINT_NVIM_SOURCES} ${LINT_SUPPRESS_FILE} lintuncrustify -) - add_custom_target(generated-sources DEPENDS ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} diff --git a/src/nvim/README.md b/src/nvim/README.md index 91fb3ca2f6..6876227e48 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -18,6 +18,12 @@ The source files use extensions to hint about their purpose. - `*.h.generated.h` - exported functions’ declarations. - `*.c.generated.h` - static functions’ declarations. +Common structures +----------------- + +- StringBuilder +- kvec or garray.c for dynamic lists / vectors (use StringBuilder for strings) + Logs ---- @@ -391,8 +397,8 @@ implemented by libuv, the platform layer used by Nvim. Since Nvim inherited its code from Vim, the states are not prepared to receive "arbitrary events", so we use a special key to represent those (When a state -receives an "arbitrary event", it normally doesn't do anything other update the -screen). +receives an "arbitrary event", it normally doesn't do anything other than +update the screen). Main loop --------- diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index b5c695b9ce..931363e199 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -1,8 +1,12 @@ // 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 <assert.h> #include <stdbool.h> +#include <stdint.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> #include "lauxlib.h" #include "nvim/api/autocmd.h" @@ -12,7 +16,12 @@ #include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/globals.h" #include "nvim/lua/executor.h" +#include "nvim/memory.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/autocmd.c.generated.h" @@ -38,7 +47,7 @@ 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> +/// <pre>lua /// -- Matches all criteria /// autocommands = vim.api.nvim_get_autocmds({ /// group = "MyGroup", @@ -352,82 +361,49 @@ cleanup: 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" +/// Creates an |autocommand| event handler, defined by `callback` (Lua function or Vimscript +/// function _name_ string) or `command` (Ex command string). /// +/// Example using Lua callback: +/// <pre>lua /// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { /// pattern = {"*.c", "*.h"}, -/// callback = myluafun, -- Or myvimfun +/// callback = function(ev) +/// print(string.format('event fired: %s', vim.inspect(ev))) +/// end /// }) /// </pre> /// -/// Lua functions receive a table with information about the autocmd event as an argument. To use -/// a function which itself accepts another (optional) parameter, wrap the function -/// in a lambda: -/// -/// <pre> -/// -- Lua function with an optional parameter. -/// -- The autocmd callback would pass a table as argument but this -/// -- function expects number|nil -/// local myluafun = function(bufnr) bufnr = bufnr or vim.api.nvim_get_current_buf() end -/// -/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { -/// pattern = {"*.c", "*.h"}, -/// callback = function() myluafun() end, -/// }) -/// </pre> -/// -/// Example using command: -/// <pre> +/// Example using an Ex command as the handler: +/// <pre>lua /// 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> -/// -/// Example values for event: -/// <pre> -/// "BufWritePre" -/// {"CursorHold", "BufWritePre", "BufWritePost"} +/// Note: `pattern` is NOT automatically expanded (unlike with |:autocmd|), thus names like "$HOME" +/// and "~" must be expanded explicitly: +/// <pre>lua +/// pattern = vim.fn.expand("~") .. "/some/path/*.py" /// </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 +/// @param event (string|array) Event(s) that will trigger the handler (`callback` or `command`). +/// @param opts Options dict: +/// - group (string|integer) optional: autocommand group name or id to match against. +/// - pattern (string|array) optional: pattern(s) to match literally |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>| +/// - desc (string) optional: description (for documentation and troubleshooting). +/// - callback (function|string) optional: Lua function (or Vimscript function name, if +/// string) called when the event(s) is triggered. Lua callback can return true to +/// delete the autocommand, and receives a table argument with these keys: +/// - id: (number) autocommand id +/// - event: (string) name of the triggered event |autocmd-events| +/// - group: (number|nil) autocommand group id, if any +/// - match: (string) expanded value of |<amatch>| +/// - buf: (number) expanded value of |<abuf>| +/// - file: (string) expanded value of |<afile>| /// - data: (any) arbitrary data passed to |nvim_exec_autocmds()| /// - command (string) optional: Vim command to execute on event. Cannot be used with /// {callback} @@ -436,7 +412,7 @@ cleanup: /// - nested (boolean) optional: defaults to false. Run nested /// autocommands |autocmd-nested|. /// -/// @return Integer id of the created autocommand. +/// @return Autocommand id (number) /// @see |autocommand| /// @see |nvim_del_autocmd()| Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autocmd) *opts, @@ -457,8 +433,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc } if (opts->callback.type != kObjectTypeNil && opts->command.type != kObjectTypeNil) { - api_set_error(err, kErrorTypeValidation, - "cannot pass both: 'callback' and 'command' for the same autocmd"); + api_set_error(err, kErrorTypeValidation, "specify either 'callback' or 'command', not both"); goto cleanup; } else if (opts->callback.type != kObjectTypeNil) { // TODO(tjdevries): It's possible we could accept callable tables, @@ -687,7 +662,7 @@ cleanup: /// Create or get an autocommand group |autocmd-groups|. /// /// To get an existing group id, do: -/// <pre> +/// <pre>lua /// local id = vim.api.nvim_create_augroup("MyGroup", { /// clear = false /// }) @@ -965,7 +940,7 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob patlen = aucmd_pattern_length(pat); } } else if (v->type == kObjectTypeArray) { - if (!check_autocmd_string_array(*patterns, "pattern", err)) { + if (!check_autocmd_string_array(v->data.array, "pattern", err)) { return false; } diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 51fedb302a..fe9e6077d6 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -3,25 +3,30 @@ // Some of this code was adapted from 'if_py_both.h' from the original // vim source + +#include <assert.h> #include <lauxlib.h> -#include <limits.h> #include <stdbool.h> +#include <stddef.h> #include <stdint.h> -#include <stdlib.h> +#include <string.h> +#include "klib/kvec.h" +#include "lua.h" #include "nvim/api/buffer.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/ascii.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" #include "nvim/cursor.h" -#include "nvim/decoration.h" #include "nvim/drawscreen.h" #include "nvim/ex_cmds.h" -#include "nvim/ex_docmd.h" #include "nvim/extmark.h" +#include "nvim/globals.h" #include "nvim/lua/executor.h" #include "nvim/mapping.h" #include "nvim/mark.h" @@ -29,9 +34,10 @@ #include "nvim/memory.h" #include "nvim/move.h" #include "nvim/ops.h" +#include "nvim/pos.h" +#include "nvim/types.h" #include "nvim/undo.h" #include "nvim/vim.h" -#include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/buffer.c.generated.h" @@ -78,7 +84,7 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err) /// /// Example (Lua): capture buffer updates in a global `events` variable /// (use "print(vim.inspect(events))" to see its contents): -/// <pre> +/// <pre>lua /// events = {} /// vim.api.nvim_buf_attach(0, false, { /// on_lines=function(...) table.insert(events, {...}) end}) @@ -271,6 +277,7 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, Integer start, Integer end, Boolean strict_indexing, + lua_State *lstate, Error *err) FUNC_API_SINCE(1) { @@ -300,21 +307,18 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, return rv; } - rv.size = (size_t)(end - start); - rv.items = xcalloc(rv.size, sizeof(Object)); + size_t size = (size_t)(end - start); + + init_line_array(lstate, &rv, size); - if (!buf_collect_lines(buf, rv.size, start, - (channel_id != VIML_INTERNAL_CALL), &rv, err)) { + if (!buf_collect_lines(buf, size, (linenr_T)start, 0, (channel_id != VIML_INTERNAL_CALL), &rv, + lstate, err)) { goto end; } end: if (ERROR_SET(err)) { - for (size_t i = 0; i < rv.size; i++) { - xfree(rv.items[i].data.string.data); - } - - xfree(rv.items); + api_free_array(rv); rv.items = NULL; } @@ -790,7 +794,8 @@ early_end: 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) + Dictionary opts, lua_State *lstate, + Error *err) FUNC_API_SINCE(9) { Array rv = ARRAY_DICT_INIT; @@ -830,33 +835,37 @@ ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer, bool replace_nl = (channel_id != VIML_INTERNAL_CALL); + size_t size = (size_t)(end_row - start_row) + 1; + + init_line_array(lstate, &rv, size); + if (start_row == end_row) { - String line = buf_get_text(buf, start_row, start_col, end_col, replace_nl, err); + String line = buf_get_text(buf, start_row, start_col, end_col, err); if (ERROR_SET(err)) { - return rv; + goto end; } - - ADD(rv, STRING_OBJ(line)); + push_linestr(lstate, &rv, line.data, line.size, 0, replace_nl); return rv; } - rv.size = (size_t)(end_row - start_row) + 1; - rv.items = xcalloc(rv.size, sizeof(Object)); + String str = buf_get_text(buf, start_row, start_col, MAXCOL - 1, err); + + push_linestr(lstate, &rv, str.data, str.size, 0, replace_nl); - 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)) { + if (size > 2) { + if (!buf_collect_lines(buf, size - 2, (linenr_T)start_row + 1, 1, replace_nl, &rv, lstate, + err)) { goto end; } } - rv.items[rv.size - 1] = STRING_OBJ(buf_get_text(buf, end_row, 0, end_col, replace_nl, err)); + str = buf_get_text(buf, end_row, 0, end_col, err); + push_linestr(lstate, &rv, str.data, str.size, (int)(size - 1), replace_nl); + if (ERROR_SET(err)) { goto end; } @@ -1390,3 +1399,91 @@ static int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bo index++; return index; } + +/// Initialise a string array either: +/// - on the Lua stack (as a table) (if lstate is not NULL) +/// - as an API array object (if lstate is NULL). +/// +/// @param lstate Lua state. When NULL the Array is initialized instead. +/// @param a Array to initialize +/// @param size Size of array +static inline void init_line_array(lua_State *lstate, Array *a, size_t size) +{ + if (lstate) { + lua_createtable(lstate, (int)size, 0); + } else { + a->size = size; + a->items = xcalloc(a->size, sizeof(Object)); + } +} + +/// Push a string onto either the Lua stack (as a table element) or an API array object. +/// +/// For Lua, a table of the correct size must be created first. +/// API array objects must be pre allocated. +/// +/// @param lstate Lua state. When NULL the Array is pushed to instead. +/// @param a Array to push onto when not using Lua +/// @param s String to push +/// @param len Size of string +/// @param idx 0-based index to place s +/// @param replace_nl Replace newlines ('\n') with null ('\0') +static void push_linestr(lua_State *lstate, Array *a, const char *s, size_t len, int idx, + bool replace_nl) +{ + if (lstate) { + // Vim represents NULs as NLs + if (s && replace_nl && strchr(s, '\n')) { + char *tmp = xmemdupz(s, len); + strchrsub(tmp, '\n', '\0'); + lua_pushlstring(lstate, tmp, len); + xfree(tmp); + } else { + lua_pushlstring(lstate, s, len); + } + lua_rawseti(lstate, -2, idx + 1); + } else { + String str = STRING_INIT; + if (s) { + str = cbuf_to_string(s, len); + if (replace_nl) { + // Vim represents NULs as NLs, but this may confuse clients. + strchrsub(str.data, '\n', '\0'); + } + } + + a->items[idx] = STRING_OBJ(str); + } +} + +/// Collects `n` buffer lines into array `l` and/or lua_State `lstate`, optionally replacing +/// newlines with NUL. +/// +/// @param buf Buffer to get lines from +/// @param n Number of lines to collect +/// @param replace_nl Replace newlines ("\n") with NUL +/// @param start Line number to start from +/// @param start_idx First index to push to +/// @param[out] l If not NULL, Lines are copied here +/// @param[out] lstate If not NULL, Lines are pushed into a table onto the stack +/// @param err[out] Error, if any +/// @return true unless `err` was set +bool buf_collect_lines(buf_T *buf, size_t n, linenr_T start, int start_idx, bool replace_nl, + Array *l, lua_State *lstate, Error *err) +{ + for (size_t i = 0; i < n; i++) { + linenr_T lnum = start + (linenr_T)i; + + if (lnum >= MAXLNUM) { + if (err != NULL) { + api_set_error(err, kErrorTypeValidation, "Line index is too high"); + } + return false; + } + + char *bufstr = ml_get_buf(buf, lnum, false); + push_linestr(lstate, l, bufstr, strlen(bufstr), start_idx + (int)i, replace_nl); + } + + return true; +} diff --git a/src/nvim/api/buffer.h b/src/nvim/api/buffer.h index 1c4a93a587..0814da63cd 100644 --- a/src/nvim/api/buffer.h +++ b/src/nvim/api/buffer.h @@ -1,7 +1,10 @@ #ifndef NVIM_API_BUFFER_H #define NVIM_API_BUFFER_H +#include <lauxlib.h> + #include "nvim/api/private/defs.h" +#include "nvim/buffer_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/buffer.h.generated.h" diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index 8cd2c0f8b8..abd265f2cf 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -1,21 +1,36 @@ // 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 <inttypes.h> #include <stdbool.h> -#include <stdint.h> -#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "klib/kvec.h" +#include "lauxlib.h" #include "nvim/api/command.h" -#include "nvim/api/private/converter.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/ascii.h" #include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" +#include "nvim/decoration.h" +#include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" +#include "nvim/garray.h" +#include "nvim/globals.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/memory.h" #include "nvim/ops.h" +#include "nvim/pos.h" #include "nvim/regexp.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/usercmd.h" +#include "nvim/vim.h" #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -42,7 +57,7 @@ /// Omitted if command cannot take a register. /// - bang: (boolean) Whether command contains a |<bang>| (!) modifier. /// - args: (array) Command arguments. -/// - addr: (string) Value of |:command-addr|. Uses short name. +/// - addr: (string) Value of |:command-addr|. Uses short name or "line" for -addr=lines. /// - nargs: (string) Value of |:command-nargs|. /// - nextcmd: (string) Next command if there are multiple commands separated by a |:bar|. /// Empty if there isn't a next command. @@ -645,6 +660,12 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error OBJ_TO_CMOD_FLAG(CMOD_LOCKMARKS, mods.lockmarks, false, "'mods.lockmarks'"); OBJ_TO_CMOD_FLAG(CMOD_NOSWAPFILE, mods.noswapfile, false, "'mods.noswapfile'"); + if (cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT) { + // CMOD_ERRSILENT must imply CMOD_SILENT, otherwise apply_cmdmod() and undo_cmdmod() won't + // work properly. + cmdinfo.cmdmod.cmod_flags |= CMOD_SILENT; + } + if ((cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX) && !(ea.argt & EX_SBOXOK)) { VALIDATION_ERROR("Command cannot be run in sandbox"); } @@ -874,7 +895,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin /// {command} is the replacement text or Lua function to execute. /// /// Example: -/// <pre> +/// <pre>vim /// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {}) /// :SayHello /// Hello world! @@ -884,6 +905,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin /// @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: +/// - name: (string) Command name /// - 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>| diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index abaac07755..332e2b5fc3 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -1,7 +1,6 @@ // 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 <limits.h> #include <stdbool.h> #include <stdint.h> #include <stdlib.h> @@ -11,10 +10,15 @@ #include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" #include "nvim/api/vimscript.h" +#include "nvim/buffer_defs.h" +#include "nvim/decoration.h" #include "nvim/extmark.h" +#include "nvim/globals.h" #include "nvim/lua/executor.h" +#include "nvim/memory.h" +#include "nvim/pos.h" +#include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/deprecated.c.generated.h" @@ -190,7 +194,7 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) String rv = { .size = 0 }; index = convert_index(index); - Array slice = nvim_buf_get_lines(0, buffer, index, index + 1, true, err); + Array slice = nvim_buf_get_lines(0, buffer, index, index + 1, true, NULL, err); if (!ERROR_SET(err) && slice.size) { rv = slice.items[0].data.string; @@ -263,7 +267,7 @@ ArrayOf(String) buffer_get_line_slice(Buffer buffer, { start = convert_index(start) + !include_start; end = convert_index(end) + include_end; - return nvim_buf_get_lines(0, buffer, start, end, false, err); + return nvim_buf_get_lines(0, buffer, start, end, false, NULL, err); } /// Replaces a line range on the buffer diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 3b1b470629..ab3b3485e4 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -1,20 +1,29 @@ // 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 <assert.h> #include <stdbool.h> #include <stdint.h> -#include <stdlib.h> +#include <string.h> +#include "klib/kvec.h" +#include "lauxlib.h" #include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" +#include "nvim/decoration.h" #include "nvim/decoration_provider.h" #include "nvim/drawscreen.h" #include "nvim/extmark.h" #include "nvim/highlight_group.h" -#include "nvim/lua/executor.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/pos.h" +#include "nvim/strings.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/extmark.c.generated.h" @@ -31,7 +40,7 @@ void api_extmark_free_all_mem(void) map_destroy(String, handle_T)(&namespace_ids); } -/// Creates a new \*namespace\* or gets an existing one. +/// Creates a new namespace or gets an existing one. \*namespace\* /// /// Namespaces are used for buffer highlights and virtual text, see /// |nvim_buf_add_highlight()| and |nvim_buf_set_extmark()|. @@ -57,7 +66,7 @@ Integer nvim_create_namespace(String name) return (Integer)id; } -/// Gets existing, non-anonymous namespaces. +/// Gets existing, non-anonymous |namespace|s. /// /// @return dict that maps from names to namespace ids. Dictionary nvim_get_namespaces(void) @@ -186,7 +195,7 @@ static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict return rv; } -/// Gets the position (0-indexed) of an extmark. +/// Gets the position (0-indexed) of an |extmark|. /// /// @param buffer Buffer handle, or 0 for current buffer /// @param ns_id Namespace id from |nvim_create_namespace()| @@ -240,31 +249,29 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, return extmark_to_array(&extmark, false, details); } -/// Gets extmarks in "traversal order" from a |charwise| region defined by +/// Gets |extmarks| in "traversal order" from a |charwise| region defined by /// buffer positions (inclusive, 0-indexed |api-indexing|). /// /// Region can be given as (row,col) tuples, or valid extmark ids (whose /// positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1) /// respectively, thus the following are equivalent: -/// -/// <pre> -/// nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) -/// nvim_buf_get_extmarks(0, my_ns, [0,0], [-1,-1], {}) +/// <pre>lua +/// vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) +/// vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {}) /// </pre> /// /// If `end` is less than `start`, traversal works backwards. (Useful /// with `limit`, to get the first marks prior to a given position.) /// /// Example: -/// -/// <pre> +/// <pre>lua /// local a = vim.api /// 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, {}) /// -- Create new extmark at line 3, column 1. -/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, {}) +/// local m2 = a.nvim_buf_set_extmark(0, ns, 2, 0, {}) /// -- 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. @@ -361,7 +368,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e return rv; } -/// Creates or updates an extmark. +/// Creates or updates an |extmark|. /// /// By default a new extmark is created when no id is passed in, but it is also /// possible to create a new mark by passing in a previously unused id or move @@ -691,7 +698,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer } if (opts->sign_text.type == kObjectTypeString) { - if (!init_sign_text((char **)&decor.sign_text, + if (!init_sign_text(&decor.sign_text, opts->sign_text.data.string.data)) { api_set_error(err, kErrorTypeValidation, "sign_text is not a valid value"); goto error; @@ -721,8 +728,12 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer bool ephemeral = false; OPTION_TO_BOOL(ephemeral, ephemeral, false); - OPTION_TO_BOOL(decor.spell, spell, false); - if (decor.spell) { + if (opts->spell.type == kObjectTypeNil) { + decor.spell = kNone; + } else { + bool spell = false; + OPTION_TO_BOOL(spell, spell, false); + decor.spell = spell ? kTrue : kFalse; has_decor = true; } @@ -803,7 +814,7 @@ error: return 0; } -/// Removes an extmark. +/// Removes an |extmark|. /// /// @param buffer Buffer handle, or 0 for current buffer /// @param ns_id Namespace id from |nvim_create_namespace()| @@ -833,9 +844,8 @@ uint32_t src2ns(Integer *src_id) } if (*src_id < 0) { return (((uint32_t)1) << 31) - 1; - } else { - return (uint32_t)(*src_id); } + return (uint32_t)(*src_id); } /// Adds a highlight to buffer. @@ -919,7 +929,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In return ns_id; } -/// Clears namespaced objects (highlights, extmarks, virtual text) from +/// Clears |namespace|d objects (highlights, |extmarks|, virtual text) from /// a region. /// /// Lines are 0-indexed. |api-indexing| To clear the namespace in the entire @@ -952,12 +962,12 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, (int)line_end - 1, MAXCOL); } -/// Set or change decoration provider for a namespace +/// Set or change decoration provider for a |namespace| /// /// This is a very general purpose interface for having lua callbacks /// being triggered during the redraw code. /// -/// The expected usage is to set extmarks for the currently +/// The expected usage is to set |extmarks| for the currently /// redrawn buffer. |nvim_buf_set_extmark()| can be called to add marks /// on a per-window or per-lines basis. Use the `ephemeral` key to only /// use the mark for the current screen redraw (the callback will be called @@ -1041,7 +1051,7 @@ error: decor_provider_clear(p); } -/// Gets the line and column of an extmark. +/// Gets the line and column of an |extmark|. /// /// Extmarks may be queried by position, name or even special names /// in the future such as "cursor". diff --git a/src/nvim/api/extmark.h b/src/nvim/api/extmark.h index 74802c6efb..0a627a889c 100644 --- a/src/nvim/api/extmark.h +++ b/src/nvim/api/extmark.h @@ -3,7 +3,10 @@ #include "nvim/api/private/defs.h" #include "nvim/decoration.h" +#include "nvim/macros.h" #include "nvim/map.h" +#include "nvim/map_defs.h" +#include "nvim/types.h" EXTERN Map(String, handle_T) namespace_ids INIT(= MAP_INIT); EXTERN handle_T next_namespace_id INIT(= 1); diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index ea8949bd2c..30dcef6127 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -1,8 +1,8 @@ return { - context = { + { 'context', { "types"; - }; - set_decoration_provider = { + }}; + { 'set_decoration_provider', { "on_start"; "on_buf"; "on_win"; @@ -10,8 +10,8 @@ return { "on_end"; "_on_hl_def"; "_on_spell_nav"; - }; - set_extmark = { + }}; + { 'set_extmark', { "id"; "end_line"; "end_row"; @@ -39,8 +39,8 @@ return { "conceal"; "spell"; "ui_watched"; - }; - keymap = { + }}; + { 'keymap', { "noremap"; "nowait"; "silent"; @@ -50,11 +50,11 @@ return { "callback"; "desc"; "replace_keycodes"; - }; - get_commands = { + }}; + { 'get_commands', { "builtin"; - }; - user_command = { + }}; + { 'user_command', { "addr"; "bang"; "bar"; @@ -67,8 +67,8 @@ return { "preview"; "range"; "register"; - }; - float_config = { + }}; + { 'float_config', { "row"; "col"; "width"; @@ -81,27 +81,29 @@ return { "focusable"; "zindex"; "border"; + "title"; + "title_pos"; "style"; "noautocmd"; - }; - runtime = { + }}; + { 'runtime', { "is_lua"; "do_source"; - }; - eval_statusline = { + }}; + { 'eval_statusline', { "winid"; "maxwidth"; "fillchar"; "highlights"; "use_winbar"; "use_tabline"; - }; - option = { + }}; + { 'option', { "scope"; "win"; "buf"; - }; - highlight = { + }}; + { 'highlight', { "bold"; "standout"; "strikethrough"; @@ -112,6 +114,7 @@ return { "underdashed"; "italic"; "reverse"; + "altfont"; "nocombine"; "default"; "cterm"; @@ -124,8 +127,10 @@ return { "global_link"; "fallback"; "blend"; - }; - highlight_cterm = { + "fg_indexed"; + "bg_indexed"; + }}; + { 'highlight_cterm', { "bold"; "standout"; "strikethrough"; @@ -136,16 +141,17 @@ return { "underdashed"; "italic"; "reverse"; + "altfont"; "nocombine"; - }; + }}; -- Autocmds - clear_autocmds = { + { 'clear_autocmds', { "buffer"; "event"; "group"; "pattern"; - }; - create_autocmd = { + }}; + { 'create_autocmd', { "buffer"; "callback"; "command"; @@ -154,24 +160,24 @@ return { "nested"; "once"; "pattern"; - }; - exec_autocmds = { + }}; + { 'exec_autocmds', { "buffer"; "group"; "modeline"; "pattern"; "data"; - }; - get_autocmds = { + }}; + { 'get_autocmds', { "event"; "group"; "pattern"; "buffer"; - }; - create_augroup = { + }}; + { 'create_augroup', { "clear"; - }; - cmd = { + }}; + { 'cmd', { "cmd"; "range"; "count"; @@ -183,12 +189,12 @@ return { "nargs"; "addr"; "nextcmd"; - }; - cmd_magic = { + }}; + { 'cmd_magic', { "file"; "bar"; - }; - cmd_mods = { + }}; + { 'cmd_mods', { "silent"; "emsg_silent"; "unsilent"; @@ -209,13 +215,15 @@ return { "verbose"; "vertical"; "split"; - }; - cmd_mods_filter = { + }}; + { 'cmd_mods_filter', { "pattern"; "force"; - }; - cmd_opts = { + }}; + { 'cmd_opts', { "output"; - }; + }}; + { 'echo_opts', { + "verbose"; + }}; } - diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index ec1f19cf6a..bfcb99754f 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -1,20 +1,21 @@ // 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 <assert.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> -#include <stddef.h> -#include <stdlib.h> #include <string.h> #include "nvim/api/options.h" -#include "nvim/api/private/converter.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/autocmd.h" -#include "nvim/buffer.h" +#include "nvim/buffer_defs.h" +#include "nvim/eval/window.h" +#include "nvim/globals.h" +#include "nvim/memory.h" #include "nvim/option.h" -#include "nvim/option_defs.h" +#include "nvim/vim.h" #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -256,7 +257,7 @@ void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err) /// @param name Option name /// @param[out] err Error details, if any /// @return Option value (global) -Object nvim_get_option(String name, Error *err) +Object nvim_get_option(String name, Arena *arena, Error *err) FUNC_API_SINCE(1) { return get_option_from(NULL, SREQ_GLOBAL, name, err); @@ -268,7 +269,7 @@ Object nvim_get_option(String name, Error *err) /// @param name Option name /// @param[out] err Error details, if any /// @return Option value -Object nvim_buf_get_option(Buffer buffer, String name, Error *err) +Object nvim_buf_get_option(Buffer buffer, String name, Arena *arena, Error *err) FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -306,7 +307,7 @@ void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, String name, Object /// @param name Option name /// @param[out] err Error details, if any /// @return Option value -Object nvim_win_get_option(Window window, String name, Error *err) +Object nvim_win_get_option(Window window, String name, Arena *arena, Error *err) FUNC_API_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -346,7 +347,7 @@ void nvim_win_set_option(uint64_t channel_id, Window window, String name, Object /// @param name The option name /// @param[out] err Details of an error that may have occurred /// @return the option value -Object get_option_from(void *from, int type, String name, Error *err) +static Object get_option_from(void *from, int type, String name, Error *err) { Object rv = OBJECT_INIT; @@ -358,8 +359,7 @@ Object get_option_from(void *from, int type, String name, Error *err) // Return values int64_t numval; char *stringval = NULL; - int flags = get_option_value_strict(name.data, &numval, &stringval, - type, from); + int flags = get_option_value_strict(name.data, &numval, &stringval, type, from); if (!flags) { api_set_error(err, kErrorTypeValidation, "Invalid option name: '%s'", @@ -487,7 +487,7 @@ static getoption_T access_option_value(char *key, long *numval, char **stringval bool get, Error *err) { if (get) { - return get_option_value(key, numval, stringval, opt_flags); + return get_option_value(key, numval, stringval, NULL, opt_flags); } else { char *errmsg; if ((errmsg = set_option_value(key, *numval, *stringval, opt_flags))) { diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c index b6b3c83f3c..58ff552ab7 100644 --- a/src/nvim/api/private/converter.c +++ b/src/nvim/api/private/converter.c @@ -2,17 +2,24 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> +#include <stdbool.h> #include <stddef.h> +#include <stdint.h> #include <stdlib.h> +#include "klib/kvec.h" #include "nvim/api/private/converter.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/assert.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" -#include "nvim/lua/converter.h" +#include "nvim/garray.h" #include "nvim/lua/executor.h" +#include "nvim/memory.h" +#include "nvim/types.h" +#include "nvim/vim.h" /// Helper structure for vim_to_object typedef struct { @@ -351,7 +358,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) } case kObjectTypeLuaRef: { - char *name = (char *)register_luafunc(api_new_luaref(obj.data.luaref)); + char *name = register_luafunc(api_new_luaref(obj.data.luaref)); tv->v_type = VAR_FUNC; tv->vval.v_string = xstrdup(name); break; diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index 2ae3ee6c7c..8acbf0d9de 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -12,7 +12,7 @@ #define ARRAY_DICT_INIT KV_INITIAL_VALUE #define STRING_INIT { .data = NULL, .size = 0 } #define OBJECT_INIT { .type = kObjectTypeNil } -#define ERROR_INIT { .type = kErrorTypeNone, .msg = NULL } +#define ERROR_INIT ((Error) { .type = kErrorTypeNone, .msg = NULL }) #define REMOTE_TYPE(type) typedef handle_T type #define ERROR_SET(e) ((e)->type != kErrorTypeNone) @@ -48,7 +48,7 @@ typedef enum { /// Internal call from lua code #define LUA_INTERNAL_CALL (VIML_INTERNAL_CALL + 1) -static inline bool is_internal_call(const uint64_t channel_id) +static inline bool is_internal_call(uint64_t channel_id) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_CONST; /// Check whether call is internal diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c index d6a6fc1219..f427bba00e 100644 --- a/src/nvim/api/private/dispatch.c +++ b/src/nvim/api/private/dispatch.c @@ -1,38 +1,11 @@ // 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 <assert.h> -#include <inttypes.h> -#include <msgpack.h> -#include <stdbool.h> +#include <stddef.h> -#include "nvim/api/deprecated.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/command.h" -#include "nvim/api/extmark.h" -#include "nvim/api/options.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/ui_client.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/dispatch_wrappers.generated.h" diff --git a/src/nvim/api/private/dispatch.h b/src/nvim/api/private/dispatch.h index f92b205531..4ae61b2bfb 100644 --- a/src/nvim/api/private/dispatch.h +++ b/src/nvim/api/private/dispatch.h @@ -1,12 +1,14 @@ #ifndef NVIM_API_PRIVATE_DISPATCH_H #define NVIM_API_PRIVATE_DISPATCH_H +#include <stdbool.h> +#include <stdint.h> + #include "nvim/api/private/defs.h" +#include "nvim/memory.h" +#include "nvim/types.h" -typedef Object (*ApiDispatchWrapper)(uint64_t channel_id, - Array args, - Arena *arena, - Error *error); +typedef Object (*ApiDispatchWrapper)(uint64_t channel_id, Array args, Arena *arena, Error *error); /// The rpc_method_handlers table, used in msgpack_rpc_dispatch(), stores /// functions of this type. diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 73b5489d5c..519f2cc5bf 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -3,8 +3,12 @@ #include <assert.h> #include <inttypes.h> +#include <limits.h> +#include <msgpack/unpack.h> +#include <stdarg.h> #include <stdbool.h> #include <stddef.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> @@ -12,28 +16,23 @@ #include "nvim/api/private/converter.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" #include "nvim/ascii.h" -#include "nvim/assert.h" -#include "nvim/buffer.h" -#include "nvim/charset.h" -#include "nvim/eval.h" +#include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" -#include "nvim/ex_cmds_defs.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_eval.h" -#include "nvim/extmark.h" +#include "nvim/garray.h" #include "nvim/highlight_group.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/message.h" #include "nvim/msgpack_rpc/helpers.h" +#include "nvim/pos.h" #include "nvim/ui.h" #include "nvim/version.h" -#include "nvim/vim.h" -#include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/funcs_metadata.generated.h" @@ -151,7 +150,18 @@ bool try_end(Error *err) xfree(msg); } } else if (did_throw) { - api_set_error(err, kErrorTypeException, "%s", current_exception->value); + if (*current_exception->throw_name != NUL) { + if (current_exception->throw_lnum != 0) { + api_set_error(err, kErrorTypeException, "%s, line %" PRIdLINENR ": %s", + current_exception->throw_name, current_exception->throw_lnum, + current_exception->value); + } else { + api_set_error(err, kErrorTypeException, "%s: %s", + current_exception->throw_name, current_exception->value); + } + } else { + api_set_error(err, kErrorTypeException, "%s", current_exception->value); + } discard_current_exception(); } @@ -391,7 +401,13 @@ 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)); +} + +String cstrn_as_string(char *str, size_t maxsize) + FUNC_ATTR_NONNULL_ALL +{ + return cbuf_as_string(str, strnlen(str, maxsize)); } /// Creates a String using the given C string. Unlike @@ -462,53 +478,15 @@ Array string_to_array(const String input, bool crlf) return ret; } -/// Collects `n` buffer lines into array `l`, optionally replacing newlines -/// with NUL. -/// -/// @param buf Buffer to get lines from -/// @param n Number of lines to collect -/// @param replace_nl Replace newlines ("\n") with NUL -/// @param start Line number to start from -/// @param[out] l Lines are copied here -/// @param err[out] Error, if any -/// @return true unless `err` was set -bool buf_collect_lines(buf_T *buf, size_t n, int64_t start, bool replace_nl, Array *l, Error *err) -{ - for (size_t i = 0; i < n; i++) { - int64_t lnum = start + (int64_t)i; - - if (lnum >= MAXLNUM) { - if (err != NULL) { - api_set_error(err, kErrorTypeValidation, "Line index is too high"); - } - return false; - } - - const char *bufstr = ml_get_buf(buf, (linenr_T)lnum, false); - Object str = STRING_OBJ(cstr_to_string(bufstr)); - - if (replace_nl) { - // Vim represents NULs as NLs, but this may confuse clients. - strchrsub(str.data.string.data, '\n', '\0'); - } - - l->items[i] = str; - } - - 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 buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col, Error *err) { String rv = STRING_INIT; @@ -517,7 +495,7 @@ String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col return rv; } - const char *bufstr = ml_get_buf(buf, (linenr_T)lnum, false); + char *bufstr = 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; @@ -537,12 +515,7 @@ String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col 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; + return cstrn_as_string(&bufstr[start_col], (size_t)(end_col - start_col)); } void api_free_string(String value) diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 65215fa8c8..ec97ba9ec6 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -1,11 +1,17 @@ #ifndef NVIM_API_PRIVATE_HELPERS_H #define NVIM_API_PRIVATE_HELPERS_H +#include <stdbool.h> +#include <stddef.h> + #include "klib/kvec.h" #include "nvim/api/private/defs.h" #include "nvim/decoration.h" #include "nvim/ex_eval_defs.h" #include "nvim/getchar.h" +#include "nvim/globals.h" +#include "nvim/macros.h" +#include "nvim/map.h" #include "nvim/memory.h" #include "nvim/vim.h" diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c index b81fc3b7d7..21eb326c3b 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -2,13 +2,14 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <stdbool.h> -#include <stdint.h> #include <stdlib.h> #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/tabpage.h" #include "nvim/api/vim.h" +#include "nvim/buffer_defs.h" +#include "nvim/globals.h" #include "nvim/memory.h" #include "nvim/window.h" @@ -110,15 +111,14 @@ Window nvim_tabpage_get_win(Tabpage tabpage, Error *err) if (tab == curtab) { return nvim_get_current_win(); - } else { - FOR_ALL_WINDOWS_IN_TAB(wp, tab) { - if (wp == tab->tp_curwin) { - return wp->handle; - } + } + FOR_ALL_WINDOWS_IN_TAB(wp, tab) { + if (wp == tab->tp_curwin) { + return wp->handle; } - // There should always be a current window for a tabpage - abort(); } + // There should always be a current window for a tabpage + abort(); } /// Gets the tabpage number diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index e6d8cb2fdb..e67607a7e4 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -2,67 +2,41 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> +#include <inttypes.h> +#include <msgpack/pack.h> #include <stdbool.h> -#include <stddef.h> #include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include "klib/kvec.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/ui.h" +#include "nvim/autocmd.h" #include "nvim/channel.h" -#include "nvim/cursor_shape.h" +#include "nvim/event/loop.h" +#include "nvim/event/wstream.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" +#include "nvim/main.h" #include "nvim/map.h" +#include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/option.h" -#include "nvim/popupmenu.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" -#include "nvim/window.h" - -typedef struct { - uint64_t channel_id; - -#define UI_BUF_SIZE 4096 ///< total buffer size for pending msgpack data. - /// guaranteed size available for each new event (so packing of simple events - /// and the header of grid_line will never fail) -#define EVENT_BUF_SIZE 256 - char buf[UI_BUF_SIZE]; ///< buffer of packed but not yet sent msgpack data - char *buf_wptr; ///< write head of buffer - const char *cur_event; ///< name of current event (might get multiple arglists) - Array call_buf; ///< buffer for constructing a single arg list (max 16 elements!) - - // state for write_cb, while packing a single arglist to msgpack. This - // might fail due to buffer overflow. - size_t pack_totlen; - bool buf_overflow; - char *temp_buf; - - // We start packing the two outermost msgpack arrays before knowing the total - // number of elements. Thus track the location where array size will need - // to be written in the msgpack buffer, once the specific array is finished. - char *nevents_pos; - char *ncalls_pos; - uint32_t nevents; ///< number of distinct events (top-level args to "redraw" - uint32_t ncalls; ///< number of calls made to the current event (plus one for the name!) - bool flushed_events; ///< events where sent to client without "flush" event - - int hl_id; // Current highlight for legacy put event. - Integer cursor_row, cursor_col; // Intended visible cursor position. - - // Position of legacy cursor, used both for drawing and visible user cursor. - Integer client_row, client_col; - bool wildmenu_active; -} UIData; #define BUF_POS(data) ((size_t)((data)->buf_wptr - (data)->buf)) #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/ui.c.generated.h" -# include "ui_events_remote.generated.h" +# include "ui_events_remote.generated.h" // IWYU pragma: export #endif static PMap(uint64_t) connected_uis = MAP_INIT; @@ -137,8 +111,6 @@ void remote_ui_disconnect(uint64_t channel_id) UIData *data = ui->data; kv_destroy(data->call_buf); pmap_del(uint64_t)(&connected_uis, channel_id); - xfree(data); - ui->data = NULL; // Flag UI as "stopped". ui_detach_impl(ui, channel_id); xfree(ui); } @@ -198,32 +170,6 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona ui->pum_col = -1.0; ui->rgb = true; ui->override = false; - ui->grid_resize = remote_ui_grid_resize; - ui->grid_clear = remote_ui_grid_clear; - ui->grid_cursor_goto = remote_ui_grid_cursor_goto; - ui->mode_info_set = remote_ui_mode_info_set; - ui->update_menu = remote_ui_update_menu; - ui->busy_start = remote_ui_busy_start; - ui->busy_stop = remote_ui_busy_stop; - ui->mouse_on = remote_ui_mouse_on; - ui->mouse_off = remote_ui_mouse_off; - ui->mode_change = remote_ui_mode_change; - ui->grid_scroll = remote_ui_grid_scroll; - ui->hl_attr_define = remote_ui_hl_attr_define; - ui->hl_group_set = remote_ui_hl_group_set; - ui->raw_line = remote_ui_raw_line; - ui->bell = remote_ui_bell; - ui->visual_bell = remote_ui_visual_bell; - ui->default_colors_set = remote_ui_default_colors_set; - ui->flush = remote_ui_flush; - ui->suspend = remote_ui_suspend; - ui->set_title = remote_ui_set_title; - ui->set_icon = remote_ui_set_icon; - ui->option_set = remote_ui_option_set; - ui->msg_set_pos = remote_ui_msg_set_pos; - ui->event = remote_ui_event; - ui->inspect = remote_ui_inspect; - ui->win_viewport = remote_ui_win_viewport; CLEAR_FIELD(ui->ui_ext); @@ -246,7 +192,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona ui->ui_ext[kUICmdline] = true; } - UIData *data = xmalloc(sizeof(UIData)); + UIData *data = ui->data; data->channel_id = channel_id; data->cur_event = NULL; data->hl_id = 0; @@ -261,7 +207,6 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona data->wildmenu_active = false; data->call_buf = (Array)ARRAY_DICT_INIT; kv_ensure_space(data->call_buf, 16); - ui->data = data; pmap_put(uint64_t)(&connected_uis, channel_id, ui); ui_attach_impl(ui, channel_id); @@ -277,6 +222,19 @@ void ui_attach(uint64_t channel_id, Integer width, Integer height, Boolean enabl api_free_dictionary(opts); } +/// Tells the nvim server if focus was gained or lost by the GUI +void nvim_ui_set_focus(uint64_t channel_id, Boolean gained, Error *error) + FUNC_API_SINCE(11) FUNC_API_REMOTE_ONLY +{ + if (!pmap_has(uint64_t)(&connected_uis, channel_id)) { + api_set_error(error, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); + return; + } + + do_autocmd_focusgained((bool)gained); +} + /// Deactivates UI events on the channel. /// /// Removes the client from the list of UIs. |nvim_list_uis()| @@ -294,6 +252,10 @@ void nvim_ui_detach(uint64_t channel_id, Error *err) remote_ui_disconnect(channel_id); } +// TODO(bfredl): use me to detach a specifc ui from the server +void remote_ui_stop(UI *ui) +{} + void nvim_ui_try_resize(uint64_t channel_id, Integer width, Integer height, Error *err) FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { @@ -396,6 +358,24 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e return; } + if (strequal(name.data, "stdin_tty")) { + if (value.type != kObjectTypeBoolean) { + api_set_error(error, kErrorTypeValidation, "stdin_tty must be a Boolean"); + return; + } + stdin_isatty = value.data.boolean; + return; + } + + if (strequal(name.data, "stdout_tty")) { + if (value.type != kObjectTypeBoolean) { + api_set_error(error, kErrorTypeValidation, "stdout_tty must be a Boolean"); + return; + } + stdout_isatty = value.data.boolean; + return; + } + // LEGACY: Deprecated option, use `ext_cmdline` instead. bool is_popupmenu = strequal(name.data, "popupmenu_external"); @@ -647,7 +627,7 @@ static void push_call(UI *ui, const char *name, Array args) data->ncalls++; } -static void remote_ui_grid_clear(UI *ui, Integer grid) +void remote_ui_grid_clear(UI *ui, Integer grid) { UIData *data = ui->data; Array args = data->call_buf; @@ -658,7 +638,7 @@ static void remote_ui_grid_clear(UI *ui, Integer grid) push_call(ui, name, args); } -static void remote_ui_grid_resize(UI *ui, Integer grid, Integer width, Integer height) +void remote_ui_grid_resize(UI *ui, Integer grid, Integer width, Integer height) { UIData *data = ui->data; Array args = data->call_buf; @@ -671,8 +651,8 @@ static void remote_ui_grid_resize(UI *ui, Integer grid, Integer width, Integer h push_call(ui, name, args); } -static void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot, Integer left, - Integer right, Integer rows, Integer cols) +void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot, Integer left, + Integer right, Integer rows, Integer cols) { UIData *data = ui->data; if (ui->ui_ext[kUILinegrid]) { @@ -708,8 +688,8 @@ static void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot } } -static void remote_ui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, - Integer cterm_fg, Integer cterm_bg) +void remote_ui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, + Integer cterm_fg, Integer cterm_bg) { if (!ui->ui_ext[kUITermColors]) { HL_SET_DEFAULT_COLORS(rgb_fg, rgb_bg, rgb_sp); @@ -739,8 +719,8 @@ static void remote_ui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg, } } -static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, - Array info) +void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, + Array info) { if (!ui->ui_ext[kUILinegrid]) { return; @@ -765,7 +745,7 @@ static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAt push_call(ui, "hl_attr_define", args); } -static void remote_ui_highlight_set(UI *ui, int id) +void remote_ui_highlight_set(UI *ui, int id) { UIData *data = ui->data; Array args = data->call_buf; @@ -781,7 +761,7 @@ static void remote_ui_highlight_set(UI *ui, int id) } /// "true" cursor used only for input focus -static void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row, Integer col) +void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row, Integer col) { if (ui->ui_ext[kUILinegrid]) { UIData *data = ui->data; @@ -799,7 +779,7 @@ static void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row, Intege } /// emulated cursor used both for drawing and for input focus -static void remote_ui_cursor_goto(UI *ui, Integer row, Integer col) +void remote_ui_cursor_goto(UI *ui, Integer row, Integer col) { UIData *data = ui->data; if (data->client_row == row && data->client_col == col) { @@ -813,7 +793,7 @@ static void remote_ui_cursor_goto(UI *ui, Integer row, Integer col) push_call(ui, "cursor_goto", args); } -static void remote_ui_put(UI *ui, const char *cell) +void remote_ui_put(UI *ui, const char *cell) { UIData *data = ui->data; data->client_col++; @@ -822,9 +802,9 @@ static void remote_ui_put(UI *ui, const char *cell) push_call(ui, "put", args); } -static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol, - Integer clearcol, Integer clearattr, LineFlags flags, - const schar_T *chunk, const sattr_T *attrs) +void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol, + Integer clearcol, Integer clearattr, LineFlags flags, const schar_T *chunk, + const sattr_T *attrs) { UIData *data = ui->data; if (ui->ui_ext[kUILinegrid]) { @@ -845,7 +825,7 @@ static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startc for (size_t i = 0; i < ncells; i++) { repeat++; if (i == ncells - 1 || attrs[i] != attrs[i + 1] - || strcmp(chunk[i], chunk[i + 1])) { + || strcmp(chunk[i], chunk[i + 1]) != 0) { if (UI_BUF_SIZE - BUF_POS(data) < 2 * (1 + 2 + sizeof(schar_T) + 5 + 5)) { // close to overflowing the redraw buffer. finish this event, // flush, and start a new "grid_line" event at the current position. @@ -916,7 +896,7 @@ static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startc /// /// This might happen multiple times before the actual ui_flush, if the /// total redraw size is large! -static void remote_ui_flush_buf(UI *ui) +void remote_ui_flush_buf(UI *ui) { UIData *data = ui->data; if (!data->nevents_pos) { @@ -943,7 +923,7 @@ static void remote_ui_flush_buf(UI *ui) /// /// Clients can know this happened by a final "flush" event at the end of the /// "redraw" batch. -static void remote_ui_flush(UI *ui) +void remote_ui_flush(UI *ui) { UIData *data = ui->data; if (data->nevents > 0 || data->flushed_events) { @@ -988,7 +968,7 @@ static Array translate_firstarg(UI *ui, Array args, Arena *arena) return new_args; } -static void remote_ui_event(UI *ui, char *name, Array args) +void remote_ui_event(UI *ui, char *name, Array args) { Arena arena = ARENA_EMPTY; UIData *data = ui->data; @@ -1055,7 +1035,7 @@ free_ret: arena_mem_free(arena_finish(&arena)); } -static void remote_ui_inspect(UI *ui, Dictionary *info) +void remote_ui_inspect(UI *ui, Dictionary *info) { UIData *data = ui->data; PUT(*info, "chan", INTEGER_OBJ((Integer)data->channel_id)); diff --git a/src/nvim/api/ui.h b/src/nvim/api/ui.h index bc70406acb..b3fe0fa2bb 100644 --- a/src/nvim/api/ui.h +++ b/src/nvim/api/ui.h @@ -5,8 +5,10 @@ #include "nvim/api/private/defs.h" #include "nvim/map.h" +#include "nvim/ui.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/ui.h.generated.h" +# include "ui_events_remote.h.generated.h" #endif #endif // NVIM_API_UI_H diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 17930dca85..a08e8dbfeb 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -31,15 +31,15 @@ void visual_bell(void) void flush(void) FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL; void suspend(void) - FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(3); void set_title(String title) FUNC_API_SINCE(3); void set_icon(String icon) FUNC_API_SINCE(3); void screenshot(String path) - FUNC_API_SINCE(7) FUNC_API_REMOTE_IMPL; + FUNC_API_SINCE(7); void option_set(String name, Object value) - FUNC_API_SINCE(4) FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(4); // Stop event is not exported as such, represented by EOF in the msgpack stream. void stop(void) FUNC_API_NOEXPORT; @@ -69,14 +69,13 @@ void scroll(Integer count) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; // Second revision of the grid protocol, used with ext_linegrid ui option -void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, - Integer cterm_fg, Integer cterm_bg) +void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, Integer cterm_fg, + Integer cterm_bg) FUNC_API_SINCE(4) FUNC_API_REMOTE_IMPL; -void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, - Array info) - FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; +void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, Array info) + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; void hl_group_set(String name, Integer id) - FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(6) FUNC_API_CLIENT_IGNORE; void grid_resize(Integer grid, Integer width, Integer height) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL FUNC_API_CLIENT_IMPL; void grid_clear(Integer grid) @@ -85,8 +84,8 @@ void grid_cursor_goto(Integer grid, Integer row, Integer col) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; void grid_line(Integer grid, Integer row, Integer col_start, Array data) FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY FUNC_API_CLIENT_IMPL; -void grid_scroll(Integer grid, Integer top, Integer bot, - Integer left, Integer right, Integer rows, Integer cols) +void grid_scroll(Integer grid, Integer top, Integer bot, Integer left, Integer right, Integer rows, + Integer cols) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; void grid_destroy(Integer grid) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; @@ -94,20 +93,15 @@ void grid_destroy(Integer grid) // For performance and simplicity, we use the dense screen representation // in internal code, such as compositor and TUI. The remote_ui module will // translate this in to the public grid_line format. -void raw_line(Integer grid, Integer row, Integer startcol, - Integer endcol, Integer clearcol, Integer clearattr, - LineFlags flags, const schar_T *chunk, const sattr_T *attrs) - FUNC_API_NOEXPORT FUNC_API_COMPOSITOR_IMPL; - -void event(char *name, Array args) +void raw_line(Integer grid, Integer row, Integer startcol, Integer endcol, Integer clearcol, + Integer clearattr, LineFlags flags, const schar_T *chunk, const sattr_T *attrs) FUNC_API_NOEXPORT FUNC_API_COMPOSITOR_IMPL; -void win_pos(Integer grid, Window win, Integer startrow, - Integer startcol, Integer width, Integer height) +void win_pos(Integer grid, Window win, Integer startrow, Integer startcol, Integer width, + Integer height) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; -void win_float_pos(Integer grid, Window win, String anchor, Integer anchor_grid, - Float anchor_row, Float anchor_col, Boolean focusable, - Integer zindex) +void win_float_pos(Integer grid, Window win, String anchor, Integer anchor_grid, Float anchor_row, + Float anchor_col, Boolean focusable, Integer zindex) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; void win_external_pos(Integer grid, Window win) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; @@ -115,32 +109,29 @@ void win_hide(Integer grid) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; void win_close(Integer grid) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; + void msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char) - FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL; + FUNC_API_SINCE(6) FUNC_API_COMPOSITOR_IMPL FUNC_API_CLIENT_IGNORE; -void win_viewport(Integer grid, Window win, Integer topline, - Integer botline, Integer curline, Integer curcol, - Integer line_count) - FUNC_API_SINCE(7) FUNC_API_BRIDGE_IMPL; +void win_viewport(Integer grid, Window win, Integer topline, Integer botline, Integer curline, + Integer curcol, Integer line_count) + FUNC_API_SINCE(7) FUNC_API_CLIENT_IGNORE; -void win_extmark(Integer grid, Window win, Integer ns_id, Integer mark_id, - Integer row, Integer col) +void win_extmark(Integer grid, Window win, Integer ns_id, Integer mark_id, Integer row, Integer col) FUNC_API_SINCE(10) FUNC_API_REMOTE_ONLY; -void popupmenu_show(Array items, Integer selected, - Integer row, Integer col, Integer grid) +void popupmenu_show(Array items, Integer selected, Integer row, Integer col, Integer grid) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void popupmenu_hide(void) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void popupmenu_select(Integer selected) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; -void tabline_update(Tabpage current, Array tabs, - Buffer current_buffer, Array buffers) +void tabline_update(Tabpage current, Array tabs, Buffer current_buffer, Array buffers) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; -void cmdline_show(Array content, Integer pos, String firstc, String prompt, - Integer indent, Integer level) +void cmdline_show(Array content, Integer pos, String firstc, String prompt, Integer indent, + Integer level) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void cmdline_pos(Integer pos, Integer level) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; @@ -156,11 +147,11 @@ void cmdline_block_hide(void) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void wildmenu_show(Array items) - FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void wildmenu_select(Integer selected) - FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void wildmenu_hide(void) - FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void msg_show(String kind, Array content, Boolean replace_last) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index fa8d26914a..a53b30dd8a 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -5,9 +5,13 @@ #include <inttypes.h> #include <limits.h> #include <stdbool.h> +#include <stddef.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> +#include "klib/kvec.h" +#include "lauxlib.h" #include "nvim/api/buffer.h" #include "nvim/api/deprecated.h" #include "nvim/api/private/converter.h" @@ -15,55 +19,52 @@ #include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" -#include "nvim/api/window.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" -#include "nvim/buffer_defs.h" -#include "nvim/charset.h" +#include "nvim/channel.h" #include "nvim/context.h" -#include "nvim/decoration.h" -#include "nvim/decoration_provider.h" #include "nvim/drawscreen.h" -#include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" -#include "nvim/eval/userfunc.h" -#include "nvim/ex_cmds_defs.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" -#include "nvim/file_search.h" -#include "nvim/fileio.h" #include "nvim/getchar.h" #include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" -#include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" -#include "nvim/insexpand.h" +#include "nvim/keycodes.h" +#include "nvim/log.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" #include "nvim/mapping.h" #include "nvim/mark.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/move.h" #include "nvim/msgpack_rpc/channel.h" -#include "nvim/msgpack_rpc/helpers.h" +#include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/msgpack_rpc/unpacker.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/optionstr.h" #include "nvim/os/input.h" +#include "nvim/os/os_defs.h" #include "nvim/os/process.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" #include "nvim/runtime.h" #include "nvim/state.h" #include "nvim/statusline.h" +#include "nvim/strings.h" +#include "nvim/terminal.h" #include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" -#include "nvim/viml/parser/expressions.h" -#include "nvim/viml/parser/parser.h" #include "nvim/window.h" #define LINE_BUFFER_MIN_SIZE 4096 @@ -228,14 +229,14 @@ void nvim_set_hl_ns_fast(Integer ns_id, Error *err) /// nvim_feedkeys(). /// /// Example: -/// <pre> +/// <pre>vim /// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true) /// :call nvim_feedkeys(key, 'n', v:false) /// </pre> /// /// @param keys to be typed /// @param mode behavior flags, see |feedkeys()| -/// @param escape_ks If true, escape K_SPECIAL 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() @@ -725,8 +726,11 @@ void nvim_set_vvar(String name, Object value, Error *err) /// text chunk with specified highlight. `hl_group` element /// can be omitted for no highlight. /// @param history if true, add to |message-history|. -/// @param opts Optional parameters. Reserved for future use. -void nvim_echo(Array chunks, Boolean history, Dictionary opts, Error *err) +/// @param opts Optional parameters. +/// - verbose: Message was printed as a result of 'verbose' option +/// if Nvim was invoked with -V3log_file, the message will be +/// redirected to the log_file and suppressed from direct output. +void nvim_echo(Array chunks, Boolean history, Dict(echo_opts) *opts, Error *err) FUNC_API_SINCE(7) { HlMessage hl_msg = parse_hl_msg(chunks, err); @@ -734,13 +738,19 @@ void nvim_echo(Array chunks, Boolean history, Dictionary opts, Error *err) goto error; } - if (opts.size > 0) { - api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); - goto error; + bool verbose = api_object_to_bool(opts->verbose, "verbose", false, err); + + if (verbose) { + verbose_enter(); } msg_multiattr(hl_msg, history ? "echomsg" : "echo", history); + if (verbose) { + verbose_leave(); + verbose_stop(); // flush now + } + if (history) { // history takes ownership return; @@ -1012,7 +1022,7 @@ Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err) return (Integer)chan->id; } -static void term_write(char *buf, size_t size, void *data) +static void term_write(char *buf, size_t size, void *data) // NOLINT(readability-non-const-parameter) { Channel *chan = data; LuaRef cb = chan->stream.internal.cb; @@ -1294,7 +1304,7 @@ void nvim_unsubscribe(uint64_t channel_id, String event) /// "#rrggbb" hexadecimal string. /// /// Example: -/// <pre> +/// <pre>vim /// :echo nvim_get_color_by_name("Pink") /// :echo nvim_get_color_by_name("#cbcbcb") /// </pre> @@ -1436,12 +1446,12 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode) /// Empty {rhs} is |<Nop>|. |keycodes| are replaced as usual. /// /// Example: -/// <pre> +/// <pre>vim /// call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true}) /// </pre> /// /// is equivalent to: -/// <pre> +/// <pre>vim /// nmap <nowait> <Space><NL> <Nop> /// </pre> /// @@ -1685,7 +1695,7 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Arena *arena, Error *er // error handled after loop break; } - // TODO(bfredl): wastefull copy. It could be avoided to encoding to msgpack + // TODO(bfredl): wasteful copy. It could be avoided to encoding to msgpack // directly here. But `result` might become invalid when next api function // is called in the loop. ADD_C(results, copy_object(result, arena)); @@ -1824,7 +1834,7 @@ Dictionary nvim__stats(void) /// - "width" Requested width of the UI /// - "rgb" true if the UI uses RGB colors (false implies |cterm-colors|) /// - "ext_..." Requested UI extensions, see |ui-option| -/// - "chan" Channel id of remote UI (not present for TUI) +/// - "chan" |channel-id| of remote UI Array nvim_list_uis(void) FUNC_API_SINCE(4) { @@ -1908,19 +1918,20 @@ Object nvim_get_proc(Integer pid, Error *err) return rvobj; } -/// Selects an item in the completion popupmenu. +/// Selects an item in the completion popup menu. /// -/// If |ins-completion| is not active this API call is silently ignored. -/// Useful for an external UI using |ui-popupmenu| to control the popupmenu -/// with the mouse. Can also be used in a mapping; use <cmd> |:map-cmd| to -/// ensure the mapping doesn't end completion mode. +/// If neither |ins-completion| nor |cmdline-completion| popup menu is active +/// this API call is silently ignored. +/// Useful for an external UI using |ui-popupmenu| to control the popup menu with the mouse. +/// Can also be used in a mapping; use <Cmd> |:map-cmd| or a Lua mapping to ensure the mapping +/// doesn't end completion mode. /// -/// @param item Index (zero-based) of the item to select. Value of -1 selects -/// nothing and restores the original text. -/// @param insert Whether the selection should be inserted in the buffer. -/// @param finish Finish the completion and dismiss the popupmenu. Implies -/// `insert`. -/// @param opts Optional parameters. Reserved for future use. +/// @param item Index (zero-based) of the item to select. Value of -1 selects nothing +/// and restores the original text. +/// @param insert For |ins-completion|, whether the selection should be inserted in the buffer. +/// Ignored for |cmdline-completion|. +/// @param finish Finish the completion and dismiss the popup menu. Implies {insert}. +/// @param opts Optional parameters. Reserved for future use. /// @param[out] err Error details, if any void nvim_select_popupmenu_item(Integer item, Boolean insert, Boolean finish, Dictionary opts, Error *err) @@ -2124,7 +2135,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * bool use_tabline = false; bool highlights = false; - if (str.size < 2 || memcmp(str.data, "%!", 2)) { + if (str.size < 2 || memcmp(str.data, "%!", 2) != 0) { const char *const errmsg = check_stl_option(str.data); if (errmsg) { api_set_error(err, kErrorTypeValidation, "%s", errmsg); @@ -2222,10 +2233,12 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * buf, sizeof(buf), str.data, - false, + NULL, + 0, fillchar, maxwidth, hltab_ptr, + NULL, NULL); PUT(result, "width", INTEGER_OBJ(width)); @@ -2258,7 +2271,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * if (sp->userhl == 0) { grpname = get_default_stl_hl(wp, use_winbar); } else if (sp->userhl < 0) { - grpname = (char *)syn_id2name(-sp->userhl); + grpname = syn_id2name(-sp->userhl); } else { snprintf(user_group, sizeof(user_group), "User%d", sp->userhl); grpname = user_group; diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 71209c9ab6..af1b23b712 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -2,26 +2,31 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> -#include <limits.h> -#include <stdlib.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> +#include "klib/kvec.h" #include "nvim/api/private/converter.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/vimscript.h" #include "nvim/ascii.h" -#include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" #include "nvim/ex_docmd.h" -#include "nvim/ops.h" +#include "nvim/garray.h" +#include "nvim/globals.h" +#include "nvim/memory.h" +#include "nvim/pos.h" #include "nvim/runtime.h" -#include "nvim/strings.h" #include "nvim/vim.h" #include "nvim/viml/parser/expressions.h" #include "nvim/viml/parser/parser.h" -#include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/vimscript.c.generated.h" @@ -532,7 +537,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E kv_drop(ast_conv_stack, 1); } else { if (cur_item.ret_node_p->type == kObjectTypeNil) { - size_t items_size = (size_t)(3 // "type", "start" and "len" + size_t items_size = (size_t)(3 // "type", "start" and "len" // NOLINT(bugprone-misplaced-widening-cast) + (node->children != NULL) // "children" + (node->type == kExprNodeOption || node->type == kExprNodePlainIdentifier) // "scope" diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 636b9566ce..0ffeac1bff 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -1,19 +1,27 @@ // 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 <assert.h> #include <stdbool.h> -#include <stddef.h> -#include <stdint.h> +#include <string.h> +#include "klib/kvec.h" +#include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/win_config.h" #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" +#include "nvim/decoration.h" #include "nvim/drawscreen.h" +#include "nvim/extmark_defs.h" +#include "nvim/globals.h" +#include "nvim/grid_defs.h" #include "nvim/highlight_group.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/memory.h" #include "nvim/option.h" -#include "nvim/strings.h" +#include "nvim/pos.h" #include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/window.h" @@ -47,13 +55,13 @@ /// this should not be used to specify arbitrary WM screen positions. /// /// Example (Lua): window-relative float -/// <pre> +/// <pre>lua /// vim.api.nvim_open_win(0, false, /// {relative='win', row=3, col=3, width=12, height=3}) /// </pre> /// /// Example (Lua): buffer-relative float (travels as buffer is scrolled) -/// <pre> +/// <pre>lua /// vim.api.nvim_open_win(0, false, /// {relative='win', width=12, height=3, bufpos={100,10}}) /// </pre> @@ -66,6 +74,7 @@ /// - "editor" The global editor grid /// - "win" Window given by the `win` field, or current window. /// - "cursor" Cursor position in current window. +/// - "mouse" Mouse position /// - win: |window-ID| for relative="win". /// - anchor: Decides which corner of the float to place at (row,col): /// - "NW" northwest (default) @@ -106,8 +115,9 @@ /// float where the text should not be edited. Disables /// 'number', 'relativenumber', 'cursorline', 'cursorcolumn', /// 'foldcolumn', 'spell' and 'list' options. 'signcolumn' -/// is changed to `auto` and 'colorcolumn' is cleared. The -/// end-of-buffer region is hidden by setting `eob` flag of +/// is changed to `auto` and 'colorcolumn' is cleared. +/// 'statuscolumn' is changed to empty. The end-of-buffer +/// region is hidden by setting `eob` flag of /// 'fillchars' to a space char, and clearing the /// |hl-EndOfBuffer| region in 'winhighlight'. /// - border: Style of (optional) window border. This can either be a string @@ -133,7 +143,12 @@ /// will only make vertical borders but not horizontal ones. /// By default, `FloatBorder` highlight is used, which links to `WinSeparator` /// when not defined. It could also be specified by character: -/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ]. +/// [ ["+", "MyCorner"], ["x", "MyBorder"] ]. +/// - title: Title (optional) in window border, String or list. +/// List is [text, highlight] tuples. if is string the default +/// highlight group is `FloatTitle`. +/// - title_pos: Title position must set with title option. +/// value can be of `left` `center` `right` default is left. /// - noautocmd: If true then no buffer-related autocommand events such as /// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from /// calling this function. @@ -263,7 +278,7 @@ Dictionary nvim_win_get_config(Window window, Error *err) String s = cstrn_to_string((const char *)config->border_chars[i], sizeof(schar_T)); int hi_id = config->border_hl_ids[i]; - char *hi_name = (char *)syn_id2name(hi_id); + char *hi_name = syn_id2name(hi_id); if (hi_name[0]) { ADD(tuple, STRING_OBJ(s)); ADD(tuple, STRING_OBJ(cstr_to_string((const char *)hi_name))); @@ -273,6 +288,29 @@ Dictionary nvim_win_get_config(Window window, Error *err) } } PUT(rv, "border", ARRAY_OBJ(border)); + if (config->title) { + Array titles = ARRAY_DICT_INIT; + VirtText title_datas = config->title_chunks; + for (size_t i = 0; i < title_datas.size; i++) { + Array tuple = ARRAY_DICT_INIT; + ADD(tuple, CSTR_TO_OBJ((const char *)title_datas.items[i].text)); + if (title_datas.items[i].hl_id > 0) { + ADD(tuple, + STRING_OBJ(cstr_to_string((const char *)syn_id2name(title_datas.items[i].hl_id)))); + } + ADD(titles, ARRAY_OBJ(tuple)); + } + PUT(rv, "title", ARRAY_OBJ(titles)); + char *title_pos; + if (config->title_pos == kAlignLeft) { + title_pos = "left"; + } else if (config->title_pos == kAlignCenter) { + title_pos = "center"; + } else { + title_pos = "right"; + } + PUT(rv, "title_pos", CSTR_TO_OBJ(title_pos)); + } } } @@ -312,6 +350,8 @@ static bool parse_float_relative(String relative, FloatRelative *out) *out = kFloatRelativeWindow; } else if (striequal(str, "cursor")) { *out = kFloatRelativeCursor; + } else if (striequal(str, "mouse")) { + *out = kFloatRelativeMouse; } else { return false; } @@ -330,7 +370,74 @@ static bool parse_float_bufpos(Array bufpos, lpos_T *out) return true; } -static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) +static void parse_border_title(Object title, Object title_pos, FloatConfig *fconfig, Error *err) +{ + if (!parse_title_pos(title_pos, fconfig, err)) { + return; + } + + if (title.type == kObjectTypeString) { + if (title.data.string.size == 0) { + fconfig->title = false; + return; + } + int hl_id = syn_check_group(S_LEN("FloatTitle")); + kv_push(fconfig->title_chunks, ((VirtTextChunk){ .text = xstrdup(title.data.string.data), + .hl_id = hl_id })); + fconfig->title_width = (int)mb_string2cells(title.data.string.data); + fconfig->title = true; + return; + } + + if (title.type != kObjectTypeArray) { + api_set_error(err, kErrorTypeValidation, "title must be string or array"); + return; + } + + if (title.data.array.size == 0) { + api_set_error(err, kErrorTypeValidation, "title cannot be an empty array"); + return; + } + + fconfig->title_width = 0; + fconfig->title_chunks = parse_virt_text(title.data.array, err, &fconfig->title_width); + + fconfig->title = true; +} + +static bool parse_title_pos(Object title_pos, FloatConfig *fconfig, Error *err) +{ + if (!HAS_KEY(title_pos)) { + fconfig->title_pos = kAlignLeft; + return true; + } + + if (title_pos.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "title_pos must be string"); + return false; + } + + if (title_pos.data.string.size == 0) { + fconfig->title_pos = kAlignLeft; + return true; + } + + char *pos = title_pos.data.string.data; + + if (strequal(pos, "left")) { + fconfig->title_pos = kAlignLeft; + } else if (strequal(pos, "center")) { + fconfig->title_pos = kAlignCenter; + } else if (strequal(pos, "right")) { + fconfig->title_pos = kAlignRight; + } else { + api_set_error(err, kErrorTypeValidation, "invalid title_pos value"); + return false; + } + return true; +} + +static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) { struct { const char *name; @@ -414,6 +521,8 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) String str = style.data.string; if (str.size == 0 || strequal(str.data, "none")) { fconfig->border = false; + // title does not work with border equal none + fconfig->title = false; return; } for (size_t i = 0; defaults[i].name; i++) { @@ -603,6 +712,29 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, return false; } + if (HAS_KEY(config->title_pos)) { + if (!HAS_KEY(config->title)) { + api_set_error(err, kErrorTypeException, "title_pos requires title to be set"); + return false; + } + } + + if (HAS_KEY(config->title)) { + // title only work with border + if (!HAS_KEY(config->border) && !fconfig->border) { + api_set_error(err, kErrorTypeException, "title requires border to be set"); + return false; + } + + if (fconfig->title) { + clear_virttext(&fconfig->title_chunks); + } + parse_border_title(config->title, config->title_pos, fconfig, err); + if (ERROR_SET(err)) { + return false; + } + } + if (HAS_KEY(config->border)) { parse_border_style(config->border, fconfig, err); if (ERROR_SET(err)) { diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 08dcc113da..e2c234ab29 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -10,16 +10,18 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/window.h" #include "nvim/ascii.h" -#include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" +#include "nvim/eval/window.h" #include "nvim/ex_docmd.h" +#include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/lua/executor.h" +#include "nvim/memline_defs.h" #include "nvim/move.h" -#include "nvim/option.h" -#include "nvim/syntax.h" -#include "nvim/vim.h" +#include "nvim/pos.h" +#include "nvim/types.h" #include "nvim/window.h" /// Gets the current buffer in a window @@ -117,8 +119,13 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) // Make sure we stick in this column. win->w_set_curswant = true; - // make sure cursor is in visible range even if win != curwin - update_topline_win(win); + // make sure cursor is in visible range and + // cursorcolumn and cursorline are updated even if win != curwin + switchwin_T switchwin; + switch_win(&switchwin, win, NULL, true); + update_topline(curwin); + validate_cursor(); + restore_win(&switchwin, true); redraw_later(win, UPD_VALID); win->w_redr_status = true; @@ -162,9 +169,11 @@ void nvim_win_set_height(Window window, Integer height, Error *err) win_T *savewin = curwin; curwin = win; + curbuf = curwin->w_buffer; try_start(); win_setheight((int)height); curwin = savewin; + curbuf = curwin->w_buffer; try_end(err); } @@ -207,9 +216,11 @@ void nvim_win_set_width(Window window, Integer width, Error *err) win_T *savewin = curwin; curwin = win; + curbuf = curwin->w_buffer; try_start(); win_setwidth((int)width); curwin = savewin; + curbuf = curwin->w_buffer; try_end(err); } @@ -358,11 +369,16 @@ void nvim_win_hide(Window window, Error *err) tabpage_T *tabpage = win_find_tabpage(win); TryState tstate; try_enter(&tstate); - if (tabpage == curtab) { + + // Never close the autocommand window. + if (is_aucmd_win(win)) { + emsg(_(e_autocmd_close)); + } else if (tabpage == curtab) { win_close(win, false, false); } else { win_close_othertab(win, false, tabpage); } + vim_ignored = try_leave(&tstate, err); } diff --git a/src/nvim/arabic.c b/src/nvim/arabic.c index b57019286f..41024cafda 100644 --- a/src/nvim/arabic.c +++ b/src/nvim/arabic.c @@ -21,76 +21,83 @@ /// Stand-Alone - unicode form-B isolated char denoted with a_s_* (NOT USED) #include <stdbool.h> +#include <stddef.h> +#include <stdint.h> #include "nvim/arabic.h" #include "nvim/ascii.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/option_defs.h" #include "nvim/vim.h" // Unicode values for Arabic characters. -#define a_HAMZA 0x0621 -#define a_ALEF_MADDA 0x0622 -#define a_ALEF_HAMZA_ABOVE 0x0623 -#define a_WAW_HAMZA 0x0624 -#define a_ALEF_HAMZA_BELOW 0x0625 -#define a_YEH_HAMZA 0x0626 -#define a_ALEF 0x0627 -#define a_BEH 0x0628 -#define a_TEH_MARBUTA 0x0629 -#define a_TEH 0x062a -#define a_THEH 0x062b -#define a_JEEM 0x062c -#define a_HAH 0x062d -#define a_KHAH 0x062e -#define a_DAL 0x062f -#define a_THAL 0x0630 -#define a_REH 0x0631 -#define a_ZAIN 0x0632 -#define a_SEEN 0x0633 -#define a_SHEEN 0x0634 -#define a_SAD 0x0635 -#define a_DAD 0x0636 -#define a_TAH 0x0637 -#define a_ZAH 0x0638 -#define a_AIN 0x0639 -#define a_GHAIN 0x063a -#define a_TATWEEL 0x0640 -#define a_FEH 0x0641 -#define a_QAF 0x0642 -#define a_KAF 0x0643 -#define a_LAM 0x0644 -#define a_MEEM 0x0645 -#define a_NOON 0x0646 -#define a_HEH 0x0647 -#define a_WAW 0x0648 -#define a_ALEF_MAKSURA 0x0649 -#define a_YEH 0x064a -#define a_FATHATAN 0x064b -#define a_DAMMATAN 0x064c -#define a_KASRATAN 0x064d -#define a_FATHA 0x064e -#define a_DAMMA 0x064f -#define a_KASRA 0x0650 -#define a_SHADDA 0x0651 -#define a_SUKUN 0x0652 -#define a_MADDA_ABOVE 0x0653 -#define a_HAMZA_ABOVE 0x0654 -#define a_HAMZA_BELOW 0x0655 - -#define a_PEH 0x067e -#define a_TCHEH 0x0686 -#define a_JEH 0x0698 -#define a_FKAF 0x06a9 -#define a_GAF 0x06af -#define a_FYEH 0x06cc - -#define a_s_LAM_ALEF_MADDA_ABOVE 0xfef5 -#define a_f_LAM_ALEF_MADDA_ABOVE 0xfef6 -#define a_s_LAM_ALEF_HAMZA_ABOVE 0xfef7 -#define a_f_LAM_ALEF_HAMZA_ABOVE 0xfef8 -#define a_s_LAM_ALEF_HAMZA_BELOW 0xfef9 -#define a_f_LAM_ALEF_HAMZA_BELOW 0xfefa -#define a_s_LAM_ALEF 0xfefb -#define a_f_LAM_ALEF 0xfefc +enum { + a_HAMZA = 0x0621, + a_ALEF_MADDA = 0x0622, + a_ALEF_HAMZA_ABOVE = 0x0623, + a_WAW_HAMZA = 0x0624, + a_ALEF_HAMZA_BELOW = 0x0625, + a_YEH_HAMZA = 0x0626, + a_ALEF = 0x0627, + a_BEH = 0x0628, + a_TEH_MARBUTA = 0x0629, + a_TEH = 0x062a, + a_THEH = 0x062b, + a_JEEM = 0x062c, + a_HAH = 0x062d, + a_KHAH = 0x062e, + a_DAL = 0x062f, + a_THAL = 0x0630, + a_REH = 0x0631, + a_ZAIN = 0x0632, + a_SEEN = 0x0633, + a_SHEEN = 0x0634, + a_SAD = 0x0635, + a_DAD = 0x0636, + a_TAH = 0x0637, + a_ZAH = 0x0638, + a_AIN = 0x0639, + a_GHAIN = 0x063a, + a_TATWEEL = 0x0640, + a_FEH = 0x0641, + a_QAF = 0x0642, + a_KAF = 0x0643, + a_LAM = 0x0644, + a_MEEM = 0x0645, + a_NOON = 0x0646, + a_HEH = 0x0647, + a_WAW = 0x0648, + a_ALEF_MAKSURA = 0x0649, + a_YEH = 0x064a, + a_FATHATAN = 0x064b, + a_DAMMATAN = 0x064c, + a_KASRATAN = 0x064d, + a_FATHA = 0x064e, + a_DAMMA = 0x064f, + a_KASRA = 0x0650, + a_SHADDA = 0x0651, + a_SUKUN = 0x0652, + a_MADDA_ABOVE = 0x0653, + a_HAMZA_ABOVE = 0x0654, + a_HAMZA_BELOW = 0x0655, + + a_PEH = 0x067e, + a_TCHEH = 0x0686, + a_JEH = 0x0698, + a_FKAF = 0x06a9, + a_GAF = 0x06af, + a_FYEH = 0x06cc, + + a_s_LAM_ALEF_MADDA_ABOVE = 0xfef5, + a_f_LAM_ALEF_MADDA_ABOVE = 0xfef6, + a_s_LAM_ALEF_HAMZA_ABOVE = 0xfef7, + a_f_LAM_ALEF_HAMZA_ABOVE = 0xfef8, + a_s_LAM_ALEF_HAMZA_BELOW = 0xfef9, + a_f_LAM_ALEF_HAMZA_BELOW = 0xfefa, + a_s_LAM_ALEF = 0xfefb, + a_f_LAM_ALEF = 0xfefc, +}; static struct achar { unsigned c; diff --git a/src/nvim/arglist.c b/src/nvim/arglist.c index c7af1a71be..c6a4be7e13 100644 --- a/src/nvim/arglist.c +++ b/src/nvim/arglist.c @@ -5,23 +5,36 @@ #include <assert.h> #include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include "auto/config.h" #include "nvim/arglist.h" +#include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" -#include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/eval/window.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/garray.h" +#include "nvim/gettext.h" #include "nvim/globals.h" +#include "nvim/macros.h" #include "nvim/mark.h" +#include "nvim/memline_defs.h" #include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/regexp.h" -#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/undo.h" #include "nvim/version.h" #include "nvim/vim.h" @@ -367,7 +380,7 @@ static void arglist_del_files(garray_T *alist_ga) if (p == NULL) { break; } - regmatch.regprog = vim_regcomp(p, p_magic ? RE_MAGIC : 0); + regmatch.regprog = vim_regcomp(p, magic_isset() ? RE_MAGIC : 0); if (regmatch.regprog == NULL) { xfree(p); break; @@ -618,51 +631,53 @@ void do_argfile(exarg_T *eap, int argn) } else { emsg(_("E165: Cannot go beyond last file")); } - } else { - setpcmark(); - // split window or create new tab page first - if (*eap->cmd == 's' || cmdmod.cmod_tab != 0) { - if (win_split(0, 0) == FAIL) { - return; - } - RESET_BINDING(curwin); - } else { - // if 'hidden' set, only check for changed file when re-editing - // the same buffer - other = true; - if (buf_hide(curbuf)) { - p = fix_fname(alist_name(&ARGLIST[argn])); - other = otherfile(p); - xfree(p); - } - if ((!buf_hide(curbuf) || !other) - && check_changed(curbuf, CCGD_AW - | (other ? 0 : CCGD_MULTWIN) - | (eap->forceit ? CCGD_FORCEIT : 0) - | CCGD_EXCMD)) { - return; - } - } + return; + } - curwin->w_arg_idx = argn; - if (argn == ARGCOUNT - 1 && curwin->w_alist == &global_alist) { - arg_had_last = true; - } + setpcmark(); - // Edit the file; always use the last known line number. - // When it fails (e.g. Abort for already edited file) restore the - // argument index. - if (do_ecmd(0, alist_name(&ARGLIST[curwin->w_arg_idx]), NULL, - eap, ECMD_LAST, - (buf_hide(curwin->w_buffer) ? ECMD_HIDE : 0) - + (eap->forceit ? ECMD_FORCEIT : 0), curwin) == FAIL) { - curwin->w_arg_idx = old_arg_idx; - } else if (eap->cmdidx != CMD_argdo) { - // like Vi: set the mark where the cursor is in the file. - setmark('\''); + // split window or create new tab page first + if (*eap->cmd == 's' || cmdmod.cmod_tab != 0) { + if (win_split(0, 0) == FAIL) { + return; + } + RESET_BINDING(curwin); + } else { + // if 'hidden' set, only check for changed file when re-editing + // the same buffer + other = true; + if (buf_hide(curbuf)) { + p = fix_fname(alist_name(&ARGLIST[argn])); + other = otherfile(p); + xfree(p); + } + if ((!buf_hide(curbuf) || !other) + && check_changed(curbuf, CCGD_AW + | (other ? 0 : CCGD_MULTWIN) + | (eap->forceit ? CCGD_FORCEIT : 0) + | CCGD_EXCMD)) { + return; } } + + curwin->w_arg_idx = argn; + if (argn == ARGCOUNT - 1 && curwin->w_alist == &global_alist) { + arg_had_last = true; + } + + // Edit the file; always use the last known line number. + // When it fails (e.g. Abort for already edited file) restore the + // argument index. + if (do_ecmd(0, alist_name(&ARGLIST[curwin->w_arg_idx]), NULL, + eap, ECMD_LAST, + (buf_hide(curwin->w_buffer) ? ECMD_HIDE : 0) + + (eap->forceit ? ECMD_FORCEIT : 0), curwin) == FAIL) { + curwin->w_arg_idx = old_arg_idx; + } else if (eap->cmdidx != CMD_argdo) { + // like Vi: set the mark where the cursor is in the file. + setmark('\''); + } } /// ":next", and commands that behave like it. @@ -693,8 +708,17 @@ void ex_next(exarg_T *eap) void ex_argdedupe(exarg_T *eap FUNC_ATTR_UNUSED) { for (int i = 0; i < ARGCOUNT; i++) { + // Expand each argument to a full path to catch different paths leading + // to the same file. + char *firstFullname = FullName_save(ARGLIST[i].ae_fname, false); + for (int j = i + 1; j < ARGCOUNT; j++) { - if (path_fnamecmp(ARGLIST[i].ae_fname, ARGLIST[j].ae_fname) == 0) { + char *secondFullname = FullName_save(ARGLIST[j].ae_fname, false); + bool areNamesDuplicate = path_fnamecmp(firstFullname, secondFullname) == 0; + xfree(secondFullname); + + if (areNamesDuplicate) { + // remove one duplicate argument xfree(ARGLIST[j].ae_fname); memmove(ARGLIST + j, ARGLIST + j + 1, (size_t)(ARGCOUNT - j - 1) * sizeof(aentry_T)); @@ -709,6 +733,8 @@ void ex_argdedupe(exarg_T *eap FUNC_ATTR_UNUSED) j--; } } + + xfree(firstFullname); } } @@ -1068,7 +1094,7 @@ static void do_arg_all(int count, int forceit, int keep_tabs) last_curtab = curtab; win_enter(lastwin, false); - // Open upto "count" windows. + // Open up to "count" windows. arg_all_open_windows(&aall, count); // Remove the "lock" on the argument list. diff --git a/src/nvim/ascii.h b/src/nvim/ascii.h index b1241166bf..96d6fe214a 100644 --- a/src/nvim/ascii.h +++ b/src/nvim/ascii.h @@ -84,31 +84,31 @@ # define PATHSEPSTR "/" #endif -static inline bool ascii_iswhite(int) +static inline bool ascii_iswhite(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ascii_iswhite_or_nul(int) +static inline bool ascii_iswhite_or_nul(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ascii_isdigit(int) +static inline bool ascii_isdigit(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ascii_isxdigit(int) +static inline bool ascii_isxdigit(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ascii_isident(int) +static inline bool ascii_isident(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ascii_isbdigit(int) +static inline bool ascii_isbdigit(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ascii_isspace(int) +static inline bool ascii_isspace(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; diff --git a/src/nvim/assert.h b/src/nvim/assert.h index ad92d9a2af..1941f4c33c 100644 --- a/src/nvim/assert.h +++ b/src/nvim/assert.h @@ -61,7 +61,7 @@ # define STATIC_ASSERT_STATEMENT(cond, msg) _Static_assert(cond, msg) // if we're dealing with gcc >= 4.6 in C99 mode, we can still use // _Static_assert but we need to suppress warnings, this is pretty ugly. -#elif (!defined(__clang__) && !defined(__INTEL_COMPILER)) && \ +#elif (!defined(__clang__) && !defined(__INTEL_COMPILER)) && /* NOLINT(whitespace/parens)*/ \ (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) # define STATIC_ASSERT_STATEMENT(cond, msg) _Static_assert(cond, msg) diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 93a870fe04..a75ee3bbd5 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -108,6 +108,7 @@ return { 'TextChanged', -- text was modified 'TextChangedI', -- text was modified in Insert mode(no popup) 'TextChangedP', -- text was modified in Insert mode(popup) + 'TextChangedT', -- text was modified in Terminal mode 'TextYankPost', -- after a yank or delete was done (y, d, c) 'UIEnter', -- after UI attaches 'UILeave', -- after UI detaches @@ -122,13 +123,26 @@ return { 'WinEnter', -- after entering a window 'WinLeave', -- before leaving a window 'WinNew', -- when entering a new window - 'WinScrolled', -- after scrolling a window + 'WinResized', -- after a window was resized + 'WinScrolled', -- after a window was scrolled or resized }, aliases = { - BufCreate = 'BufAdd', - BufRead = 'BufReadPost', - BufWrite = 'BufWritePre', - FileEncoding = 'EncodingChanged', + { + 'BufCreate', + 'BufAdd' + }, + { + 'BufRead', + 'BufReadPost' + }, + { + 'BufWrite', + 'BufWritePre' + }, + { + 'FileEncoding', + 'EncodingChanged' + }, }, -- List of nvim-specific events or aliases for the purpose of generating -- syntax file diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 5b5ea43d86..01ebdfdafe 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -2,9 +2,13 @@ // 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 <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" @@ -12,26 +16,43 @@ #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" -#include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" +#include "nvim/event/defs.h" +#include "nvim/event/loop.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" +#include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/grid.h" +#include "nvim/hashtab.h" +#include "nvim/highlight_defs.h" #include "nvim/insexpand.h" #include "nvim/lua/executor.h" +#include "nvim/main.h" #include "nvim/map.h" +#include "nvim/memline_defs.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option_defs.h" #include "nvim/optionstr.h" #include "nvim/os/input.h" +#include "nvim/os/os.h" +#include "nvim/os/time.h" +#include "nvim/path.h" #include "nvim/profile.h" #include "nvim/regexp.h" #include "nvim/runtime.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/state.h" +#include "nvim/strings.h" +#include "nvim/ui.h" #include "nvim/ui_compositor.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -283,7 +304,7 @@ static void au_show_for_event(int group, event_T event, char *pat) // all buffer-local patterns. if ((group == AUGROUP_ALL || ap->group == group) && ap->patlen == patlen - && STRNCMP(pat, ap->pat, patlen) == 0) { + && strncmp(pat, ap->pat, (size_t)patlen) == 0) { // Show autocmd's for this autopat, or buflocals <buffer=X> aupat_show(ap, event, previous_group); previous_group = ap->group; @@ -638,9 +659,22 @@ void free_all_autocmds(void) api_free_string(name); }) map_destroy(int, String)(&map_augroup_id_to_name); + + // aucmd_win[] is freed in win_free_all() } #endif +/// Return true if "win" is an active entry in aucmd_win[]. +bool is_aucmd_win(win_T *win) +{ + for (int i = 0; i < AUCMD_WIN_COUNT; i++) { + if (aucmd_win[i].auc_win_used && aucmd_win[i].auc_win == win) { + return true; + } + } + return false; +} + // 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". @@ -779,7 +813,7 @@ void au_event_restore(char *old_ei) // :autocmd * *.c show all autocommands for *.c files. // // Mostly a {group} argument can optionally appear before <event>. -void do_autocmd(char *arg_in, int forceit) +void do_autocmd(exarg_T *eap, char *arg_in, int forceit) { char *arg = arg_in; char *envpat = NULL; @@ -790,6 +824,7 @@ void do_autocmd(char *arg_in, int forceit) int group; if (*arg == '|') { + eap->nextcmd = arg + 1; arg = ""; group = AUGROUP_ALL; // no argument, use all groups } else { @@ -806,6 +841,7 @@ void do_autocmd(char *arg_in, int forceit) pat = skipwhite(pat); if (*pat == '|') { + eap->nextcmd = pat + 1; pat = ""; cmd = ""; } else { @@ -979,7 +1015,7 @@ int do_autocmd_event(event_T event, char *pat, bool once, int nested, char *cmd, // all buffer-local patterns. if (ap->group == findgroup && ap->patlen == patlen - && STRNCMP(pat, ap->pat, patlen) == 0) { + && strncmp(pat, ap->pat, (size_t)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 @@ -1063,7 +1099,7 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group // all buffer-local patterns. if (ap->group == findgroup && ap->patlen == patlen - && STRNCMP(pat, ap->pat, patlen) == 0) { + && strncmp(pat, ap->pat, (size_t)patlen) == 0) { if (ap->next == NULL) { // Add autocmd to this autopat, if it's the last one. break; @@ -1118,14 +1154,19 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group 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_skipcol = curwin->w_skipcol; - curwin->w_last_width = curwin->w_width; - curwin->w_last_height = curwin->w_height; + // Initialize the fields checked by the WinScrolled and + // WinResized trigger to prevent them from firing right after + // the first autocmd is defined. + if ((event == EVENT_WINSCROLLED || event == EVENT_WINRESIZED) + && !(has_event(EVENT_WINSCROLLED) || has_event(EVENT_WINRESIZED))) { + tabpage_T *save_curtab = curtab; + FOR_ALL_TABS(tp) { + unuse_tabpage(curtab); + use_tabpage(tp); + snapshot_windows_scroll_size(); + } + unuse_tabpage(curtab); + use_tabpage(save_curtab); } ap->cmds = NULL; @@ -1291,7 +1332,7 @@ void ex_doautoall(exarg_T *eap) // Execute the modeline settings, but don't set window-local // options if we are using the current window for another // buffer. - do_modelines(curwin == aucmd_win ? OPT_NOWIN : 0); + do_modelines(is_aucmd_win(curwin) ? OPT_NOWIN : 0); } // restore the current window @@ -1321,7 +1362,7 @@ void ex_doautoall(exarg_T *eap) bool check_nomodeline(char **argp) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - if (STRNCMP(*argp, "<nomodeline>", 12) == 0) { + if (strncmp(*argp, "<nomodeline>", 12) == 0) { *argp = skipwhite(*argp + 12); return false; } @@ -1330,7 +1371,7 @@ bool check_nomodeline(char **argp) /// Prepare for executing autocommands for (hidden) buffer `buf`. /// If the current buffer is not in any visible window, put it in a temporary -/// floating window `aucmd_win`. +/// floating window using an entry in `aucmd_win[]`. /// Set `curbuf` and `curwin` to match `buf`. /// /// @param aco structure to save values in @@ -1353,15 +1394,29 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) } } - // Allocate the `aucmd_win` dummy floating window. - if (win == NULL && aucmd_win == NULL) { - win_alloc_aucmd_win(); - need_append = false; - } - if (win == NULL && aucmd_win_used) { - // Strange recursive autocommand, fall back to using the current - // window. Expect a few side effects... - win = curwin; + // Allocate a window when needed. + win_T *auc_win = NULL; + int auc_idx = AUCMD_WIN_COUNT; + if (win == NULL) { + for (auc_idx = 0; auc_idx < AUCMD_WIN_COUNT; auc_idx++) { + if (!aucmd_win[auc_idx].auc_win_used) { + break; + } + } + + if (auc_idx == AUCMD_WIN_COUNT) { + kv_push(aucmd_win_vec, ((aucmdwin_T){ + .auc_win = NULL, + .auc_win_used = false, + })); + } + + if (aucmd_win[auc_idx].auc_win == NULL) { + win_alloc_aucmd_win(auc_idx); + need_append = false; + } + auc_win = aucmd_win[auc_idx].auc_win; + aucmd_win[auc_idx].auc_win_used = true; } aco->save_curwin_handle = curwin->handle; @@ -1371,42 +1426,41 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) // There is a window for "buf" in the current tab page, make it the // curwin. This is preferred, it has the least side effects (esp. if // "buf" is curbuf). - aco->use_aucmd_win = false; + aco->use_aucmd_win_idx = -1; curwin = win; } else { - // There is no window for "buf", use "aucmd_win". To minimize the side + // There is no window for "buf", use "auc_win". To minimize the side // effects, insert it in the current tab page. // Anything related to a window (e.g., setting folds) may have // unexpected results. - aco->use_aucmd_win = true; - aucmd_win_used = true; - aucmd_win->w_buffer = buf; - aucmd_win->w_s = &buf->b_s; + aco->use_aucmd_win_idx = auc_idx; + auc_win->w_buffer = buf; + auc_win->w_s = &buf->b_s; buf->b_nwindows++; - win_init_empty(aucmd_win); // set cursor and topline to safe values + win_init_empty(auc_win); // set cursor and topline to safe values // Make sure w_localdir and globaldir are NULL to avoid a chdir() in // win_enter_ext(). - XFREE_CLEAR(aucmd_win->w_localdir); + XFREE_CLEAR(auc_win->w_localdir); aco->globaldir = globaldir; globaldir = NULL; block_autocmds(); // We don't want BufEnter/WinEnter autocommands. if (need_append) { - win_append(lastwin, aucmd_win); - pmap_put(handle_T)(&window_handles, aucmd_win->handle, aucmd_win); - win_config_float(aucmd_win, aucmd_win->w_float_config); + win_append(lastwin, auc_win); + pmap_put(handle_T)(&window_handles, auc_win->handle, auc_win); + win_config_float(auc_win, auc_win->w_float_config); } // 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); + win_enter(auc_win, false); RedrawingDisabled--; p_acd = save_acd; unblock_autocmds(); - curwin = aucmd_win; + curwin = auc_win; } curbuf = buf; aco->new_curwin_handle = curwin->handle; @@ -1423,18 +1477,20 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) /// @param aco structure holding saved values void aucmd_restbuf(aco_save_T *aco) { - if (aco->use_aucmd_win) { + if (aco->use_aucmd_win_idx >= 0) { + win_T *awp = aucmd_win[aco->use_aucmd_win_idx].auc_win; + curbuf->b_nwindows--; - // Find "aucmd_win", it can't be closed, but it may be in another tab page. + // Find "awp", it can't be closed, but it may be in another tab page. // Do not trigger autocommands here. block_autocmds(); - if (curwin != aucmd_win) { + if (curwin != awp) { FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp == aucmd_win) { + if (wp == awp) { if (tp != curtab) { goto_tabpage_tp(tp, true, true); } - win_goto(aucmd_win); + win_goto(awp); goto win_found; } } @@ -1449,7 +1505,9 @@ win_found: grid_free(&curwin->w_grid_alloc); } - aucmd_win_used = false; + // The window is marked as not used, but it is not freed, it can be + // used again. + aucmd_win[aco->use_aucmd_win_idx].auc_win_used = false; if (!valid_tabpage_win(curtab)) { // no valid window in current tabpage @@ -1470,8 +1528,8 @@ win_found: 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 + vars_clear(&awp->w_vars->dv_hashtab); // free all w: variables + hash_init(&awp->w_vars->dv_hashtab); // re-use the hashtab xfree(globaldir); globaldir = aco->globaldir; @@ -1754,7 +1812,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force || event == EVENT_SIGNAL || event == EVENT_SPELLFILEMISSING || event == EVENT_SYNTAX || event == EVENT_TABCLOSED || event == EVENT_USER || event == EVENT_WINCLOSED - || event == EVENT_WINSCROLLED) { + || event == EVENT_WINRESIZED || event == EVENT_WINSCROLLED) { fname = xstrdup(fname); } else { fname = FullName_save(fname, false); @@ -2431,7 +2489,7 @@ bool aupat_is_buflocal(char *pat, int patlen) FUNC_ATTR_PURE { return patlen >= 8 - && STRNCMP(pat, "<buffer", 7) == 0 + && strncmp(pat, "<buffer", 7) == 0 && (pat)[patlen - 1] == '>'; } @@ -2618,26 +2676,27 @@ static int arg_augroup_get(char **argp) { char *p; char *arg = *argp; - int group = AUGROUP_ALL; for (p = arg; *p && !ascii_iswhite(*p) && *p != '|'; p++) {} - if (p > arg) { - char *group_name = xstrnsave(arg, (size_t)(p - arg)); - group = augroup_find(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); + if (p <= arg) { + return AUGROUP_ALL; + } + + char *group_name = xstrnsave(arg, (size_t)(p - arg)); + int group = augroup_find(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 **cmd_ptr, char *pattern, int len) { - if (STRNCMP(*cmd_ptr, pattern, len) == 0 && ascii_iswhite((*cmd_ptr)[len])) { + if (strncmp(*cmd_ptr, pattern, (size_t)len) == 0 && ascii_iswhite((*cmd_ptr)[len])) { if (*flag) { semsg(_(e_duparg2), pattern); return true; @@ -2674,22 +2733,7 @@ void do_autocmd_uienter(uint64_t chanid, bool attached) // 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) +void do_autocmd_focusgained(bool gained) { static bool recursive = false; static Timestamp last_time = (time_t)0; diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h index 75a8a7aaa1..791b589167 100644 --- a/src/nvim/autocmd.h +++ b/src/nvim/autocmd.h @@ -1,8 +1,20 @@ #ifndef NVIM_AUTOCMD_H #define NVIM_AUTOCMD_H +#include <stdbool.h> +#include <stdint.h> + +#include "nvim/api/private/defs.h" #include "nvim/buffer_defs.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" +#include "nvim/macros.h" +#include "nvim/regexp_defs.h" +#include "nvim/types.h" + +struct AutoCmd_S; +struct AutoPatCmd_S; +struct AutoPat_S; // event_T definition #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -13,7 +25,7 @@ // not the current buffer. typedef struct { buf_T *save_curbuf; ///< saved curbuf - bool use_aucmd_win; ///< using aucmd_win + int use_aucmd_win_idx; ///< index in aucmd_win[] if >= 0 handle_T save_curwin_handle; ///< ID of saved curwin handle_T new_curwin_handle; ///< ID of new curwin handle_T save_prevwin_handle; ///< ID of saved prevwin diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 85f79deb2f..5dcb10751f 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -20,10 +20,16 @@ // #include <assert.h> +#include <ctype.h> #include <inttypes.h> #include <stdbool.h> +#include <stdlib.h> #include <string.h> +#include <sys/stat.h> +#include <time.h> +#include "auto/config.h" +#include "klib/kvec.h" #include "nvim/api/private/helpers.h" #include "nvim/arglist.h" #include "nvim/ascii.h" @@ -44,6 +50,7 @@ #include "nvim/eval/vars.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" @@ -53,21 +60,25 @@ #include "nvim/fold.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/hashtab.h" #include "nvim/help.h" -#include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/main.h" +#include "nvim/map.h" #include "nvim/mapping.h" #include "nvim/mark.h" -#include "nvim/mark_defs.h" #include "nvim/mbyte.h" +#include "nvim/memline_defs.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/move.h" +#include "nvim/normal.h" #include "nvim/option.h" #include "nvim/optionstr.h" +#include "nvim/os/fs_defs.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/time.h" @@ -76,11 +87,15 @@ #include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/runtime.h" +#include "nvim/screen.h" +#include "nvim/search.h" #include "nvim/sign.h" #include "nvim/spell.h" #include "nvim/statusline.h" #include "nvim/strings.h" #include "nvim/syntax.h" +#include "nvim/terminal.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/usercmd.h" @@ -93,7 +108,8 @@ #endif static char *e_auabort = N_("E855: Autocommands caused command to abort"); -static char *e_buflocked = N_("E937: Attempt to delete a buffer that is in use"); +static char e_attempt_to_delete_buffer_that_is_in_use_str[] + = N_("E937: Attempt to delete a buffer that is in use: %s"); // Number of times free_buffer() was called. static int buf_free_count = 0; @@ -160,6 +176,22 @@ static int read_buffer(int read_stdin, exarg_T *eap, int flags) return retval; } +/// Ensure buffer "buf" is loaded. Does not trigger the swap-exists action. +void buffer_ensure_loaded(buf_T *buf) +{ + if (buf->b_ml.ml_mfp != NULL) { + return; + } + + aco_save_T aco; + + // Make sure the buffer is in a window. + aucmd_prepbuf(&aco, buf); + swap_exists_action = SEA_NONE; + open_buffer(false, NULL, 0); + aucmd_restbuf(&aco); +} + /// Open current buffer, that is: open the memfile and read the file into /// memory. /// @@ -328,8 +360,9 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg) } apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, false, curbuf, &retval); + // if (retval != OK) { if (retval == FAIL) { - return FAIL; + return retval; } // The autocommands may have changed the current buffer. Apply the @@ -337,7 +370,7 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg) if (bufref_valid(&old_curbuf) && old_curbuf.br_buf->b_ml.ml_mfp != NULL) { aco_save_T aco; - // Go to the buffer that was opened. + // Go to the buffer that was opened, make sure it is in a window. aucmd_prepbuf(&aco, old_curbuf.br_buf); do_modelines(0); curbuf->b_flags &= ~(BF_CHECK_RO | BF_NEVERLOADED); @@ -401,6 +434,29 @@ bool buf_valid(buf_T *buf) return false; } +/// Return true when buffer "buf" can be unloaded. +/// Give an error message and return false when the buffer is locked or the +/// screen is being redrawn and the buffer is in a window. +static bool can_unload_buffer(buf_T *buf) +{ + bool can_unload = !buf->b_locked; + + if (can_unload && updating_screen) { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf) { + can_unload = false; + break; + } + } + } + if (!can_unload) { + char *fname = buf->b_fname != NULL ? buf->b_fname : buf->b_ffname; + semsg(_(e_attempt_to_delete_buffer_that_is_in_use_str), + fname != NULL ? fname : "[No Name]"); + } + return can_unload; +} + /// Close the link to a buffer. /// /// @param win If not NULL, set b_last_cursor. @@ -458,8 +514,7 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i // Disallow deleting the buffer when it is locked (already being closed or // halfway a command that relies on it). Unloading is allowed. - if (buf->b_locked > 0 && (del_buf || wipe_buf)) { - emsg(_(e_buflocked)); + if ((del_buf || wipe_buf) && !can_unload_buffer(buf)) { return false; } @@ -666,6 +721,8 @@ void buf_clear_file(buf_T *buf) { buf->b_ml.ml_line_count = 1; unchanged(buf, true, true); + buf->b_p_eof = false; + buf->b_start_eof = false; buf->b_p_eol = true; buf->b_start_eol = true; buf->b_p_bomb = false; @@ -1034,16 +1091,16 @@ char *do_bufdel(int command, char *arg, int addr_count, int start_bnr, int end_b } else { STRCPY(IObuff, _("E517: No buffers were wiped out")); } - errormsg = (char *)IObuff; + errormsg = IObuff; } else if (deleted >= p_report) { if (command == DOBUF_UNLOAD) { - smsg(NGETTEXT("%d buffer unloaded", "%d buffers unloaded", (unsigned long)deleted), + smsg(NGETTEXT("%d buffer unloaded", "%d buffers unloaded", deleted), deleted); } else if (command == DOBUF_DEL) { - smsg(NGETTEXT("%d buffer deleted", "%d buffers deleted", (unsigned long)deleted), + smsg(NGETTEXT("%d buffer deleted", "%d buffers deleted", deleted), deleted); } else { - smsg(NGETTEXT("%d buffer wiped out", "%d buffers wiped out", (unsigned long)deleted), + smsg(NGETTEXT("%d buffer wiped out", "%d buffers wiped out", deleted), deleted); } } @@ -1201,13 +1258,17 @@ int do_buffer(int action, int start, int dir, int count, int forceit) } return FAIL; } + if ((action == DOBUF_GOTO || action == DOBUF_SPLIT) && (buf->b_flags & BF_DUMMY)) { + // disallow navigating to the dummy buffer + semsg(_(e_nobufnr), count); + return FAIL; + } // delete buffer "buf" from memory and/or the list if (unload) { int forward; bufref_T bufref; - if (buf->b_locked) { - emsg(_(e_buflocked)); + if (!can_unload_buffer(buf)) { return FAIL; } set_bufref(&bufref, buf); @@ -1273,7 +1334,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit) // 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) - && (lastwin == aucmd_win || !last_window(curwin))) { + && (is_aucmd_win(lastwin) || !last_window(curwin))) { if (win_close(curwin, false, false) == FAIL) { break; } @@ -1813,7 +1874,7 @@ buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags) pmap_put(handle_T)(&buffer_handles, buf->b_fnum, buf); if (top_file_num < 0) { // wrap around (may cause duplicates) emsg(_("W14: Warning: List of file names overflow")); - if (emsg_silent == 0) { + if (emsg_silent == 0 && !in_assert_fails) { ui_flush(); os_delay(3001L, true); // make sure it is noticed } @@ -1937,12 +1998,16 @@ void free_buf_options(buf_T *buf, int free_p_ff) clear_string_option(&buf->b_p_ft); 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_lop); clear_string_option(&buf->b_p_cinsd); + clear_string_option(&buf->b_p_cinw); clear_string_option(&buf->b_p_cpt); clear_string_option(&buf->b_p_cfu); + callback_free(&buf->b_cfu_cb); clear_string_option(&buf->b_p_ofu); + callback_free(&buf->b_ofu_cb); clear_string_option(&buf->b_p_tsrfu); + callback_free(&buf->b_tsrfu_cb); clear_string_option(&buf->b_p_gp); clear_string_option(&buf->b_p_mp); clear_string_option(&buf->b_p_efm); @@ -1951,6 +2016,7 @@ void free_buf_options(buf_T *buf, int free_p_ff) clear_string_option(&buf->b_p_tags); clear_string_option(&buf->b_p_tc); clear_string_option(&buf->b_p_tfu); + callback_free(&buf->b_tfu_cb); clear_string_option(&buf->b_p_dict); clear_string_option(&buf->b_p_tsr); clear_string_option(&buf->b_p_qe); @@ -2188,13 +2254,14 @@ int buflist_findpat(const char *pattern, const char *pattern_end, bool unlisted, } regmatch_T regmatch; - regmatch.regprog = vim_regcomp(p, p_magic ? RE_MAGIC : 0); - if (regmatch.regprog == NULL) { - xfree(pat); - return -1; - } + regmatch.regprog = vim_regcomp(p, magic_isset() ? RE_MAGIC : 0); FOR_ALL_BUFFERS_BACKWARDS(buf) { + if (regmatch.regprog == NULL) { + // invalid pattern, possibly after switching engine + xfree(pat); + return -1; + } if (buf->b_p_bl == find_listed && (!diffmode || diff_mode_buf(buf)) && buflist_match(®match, buf, false) != NULL) { @@ -2272,7 +2339,6 @@ int ExpandBufnames(char *pat, int *num_file, char ***file, int options) int round; char *p; int attempt; - char *patc; bufmatch_T *matches = NULL; *num_file = 0; // return values in case of FAIL @@ -2282,31 +2348,34 @@ int ExpandBufnames(char *pat, int *num_file, char ***file, int options) return FAIL; } - // Make a copy of "pat" and change "^" to "\(^\|[\/]\)". - if (*pat == '^') { - patc = xmalloc(strlen(pat) + 11); - STRCPY(patc, "\\(^\\|[\\/]\\)"); - STRCPY(patc + 11, pat + 1); - } else { - patc = pat; + const bool fuzzy = cmdline_fuzzy_complete(pat); + + char *patc = NULL; + // Make a copy of "pat" and change "^" to "\(^\|[\/]\)" (if doing regular + // expression matching) + if (!fuzzy) { + if (*pat == '^') { + patc = xmalloc(strlen(pat) + 11); + STRCPY(patc, "\\(^\\|[\\/]\\)"); + STRCPY(patc + 11, pat + 1); + } else { + patc = pat; + } } + fuzmatch_str_T *fuzmatch = NULL; // attempt == 0: try match with '\<', match at start of word // attempt == 1: try match without '\<', match anywhere - for (attempt = 0; attempt <= 1; attempt++) { - if (attempt > 0 && patc == pat) { - break; // there was no anchor, no need to try again - } - + for (attempt = 0; attempt <= (fuzzy ? 0 : 1); attempt++) { regmatch_T regmatch; - regmatch.regprog = vim_regcomp(patc + attempt * 11, RE_MAGIC); - if (regmatch.regprog == NULL) { - if (patc != pat) { - xfree(patc); + if (!fuzzy) { + if (attempt > 0 && patc == pat) { + break; // there was no anchor, no need to try again } - return FAIL; + regmatch.regprog = vim_regcomp(patc + attempt * 11, RE_MAGIC); } + int score = 0; // round == 1: Count the matches. // round == 2: Build the array to keep the matches. for (round = 1; round <= 2; round++) { @@ -2322,64 +2391,108 @@ int ExpandBufnames(char *pat, int *num_file, char ***file, int options) continue; } } - p = buflist_match(®match, buf, p_wic); - if (p != NULL) { - if (round == 1) { - count++; - } else { - if (options & WILD_HOME_REPLACE) { - p = home_replace_save(buf, p); - } else { - p = xstrdup(p); + + if (!fuzzy) { + if (regmatch.regprog == NULL) { + // invalid pattern, possibly after recompiling + if (patc != pat) { + xfree(patc); } - if (matches != NULL) { - matches[count].buf = buf; - matches[count].match = p; - count++; - } else { - (*file)[count++] = p; + return FAIL; + } + p = buflist_match(®match, buf, p_wic); + } else { + p = NULL; + // first try matching with the short file name + if ((score = fuzzy_match_str(buf->b_sfname, pat)) != 0) { + p = buf->b_sfname; + } + if (p == NULL) { + // next try matching with the full path file name + if ((score = fuzzy_match_str(buf->b_ffname, pat)) != 0) { + p = buf->b_ffname; } } } + + if (p == NULL) { + continue; + } + + if (round == 1) { + count++; + continue; + } + + if (options & WILD_HOME_REPLACE) { + p = home_replace_save(buf, p); + } else { + p = xstrdup(p); + } + + if (!fuzzy) { + if (matches != NULL) { + matches[count].buf = buf; + matches[count].match = p; + count++; + } else { + (*file)[count++] = p; + } + } else { + fuzmatch[count].idx = count; + fuzmatch[count].str = p; + fuzmatch[count].score = score; + count++; + } } if (count == 0) { // no match found, break here break; } if (round == 1) { - *file = xmalloc((size_t)count * sizeof(**file)); - - if (options & WILD_BUFLASTUSED) { - matches = xmalloc((size_t)count * sizeof(*matches)); + if (!fuzzy) { + *file = xmalloc((size_t)count * sizeof(**file)); + if (options & WILD_BUFLASTUSED) { + matches = xmalloc((size_t)count * sizeof(*matches)); + } + } else { + fuzmatch = xmalloc((size_t)count * sizeof(fuzmatch_str_T)); } } } - vim_regfree(regmatch.regprog); - if (count) { // match(es) found, break here - break; + + if (!fuzzy) { + vim_regfree(regmatch.regprog); + if (count) { // match(es) found, break here + break; + } } } - if (patc != pat) { + if (!fuzzy && patc != pat) { xfree(patc); } - if (matches != NULL) { - if (count > 1) { - qsort(matches, (size_t)count, sizeof(bufmatch_T), buf_time_compare); - } - - // if the current buffer is first in the list, place it at the end - if (matches[0].buf == curbuf) { - for (int i = 1; i < count; i++) { - (*file)[i - 1] = matches[i].match; + if (!fuzzy) { + if (matches != NULL) { + if (count > 1) { + qsort(matches, (size_t)count, sizeof(bufmatch_T), buf_time_compare); } - (*file)[count - 1] = matches[0].match; - } else { - for (int i = 0; i < count; i++) { - (*file)[i] = matches[i].match; + + // if the current buffer is first in the list, place it at the end + if (matches[0].buf == curbuf) { + for (int i = 1; i < count; i++) { + (*file)[i - 1] = matches[i].match; + } + (*file)[count - 1] = matches[0].match; + } else { + for (int i = 0; i < count; i++) { + (*file)[i] = matches[i].match; + } } + xfree(matches); } - xfree(matches); + } else { + fuzzymatches_to_strmatches(fuzmatch, file, count, false); } *num_file = count; @@ -2387,6 +2500,7 @@ int ExpandBufnames(char *pat, int *num_file, char ***file, int options) } /// Check for a match on the file name for buffer "buf" with regprog "prog". +/// Note that rmp->regprog may become NULL when switching regexp engine. /// /// @param ignore_case When true, ignore case. Use 'fic' otherwise. static char *buflist_match(regmatch_T *rmp, buf_T *buf, bool ignore_case) @@ -2399,7 +2513,8 @@ static char *buflist_match(regmatch_T *rmp, buf_T *buf, bool ignore_case) return match; } -/// Try matching the regexp in "prog" with file name "name". +/// Try matching the regexp in "rmp->regprog" with file name "name". +/// Note that rmp->regprog may become NULL when switching regexp engine. /// /// @param ignore_case When true, ignore case. Use 'fileignorecase' otherwise. /// @@ -2409,19 +2524,22 @@ static char *fname_match(regmatch_T *rmp, char *name, bool ignore_case) char *match = NULL; char *p; - if (name != NULL) { - // Ignore case when 'fileignorecase' or the argument is set. - rmp->rm_ic = p_fic || ignore_case; - if (vim_regexec(rmp, name, (colnr_T)0)) { + // extra check for valid arguments + if (name == NULL || rmp->regprog == NULL) { + return NULL; + } + + // Ignore case when 'fileignorecase' or the argument is set. + rmp->rm_ic = p_fic || ignore_case; + if (vim_regexec(rmp, name, (colnr_T)0)) { + match = name; + } else if (rmp->regprog != NULL) { + // Replace $(HOME) with '~' and try matching again. + p = home_replace_save(NULL, name); + if (vim_regexec(rmp, p, (colnr_T)0)) { match = name; - } else if (rmp->regprog != NULL) { - // Replace $(HOME) with '~' and try matching again. - p = home_replace_save(NULL, name); - if (vim_regexec(rmp, p, (colnr_T)0)) { - match = name; - } - xfree(p); } + xfree(p); } return match; @@ -2528,17 +2646,18 @@ void buflist_setfpos(buf_T *const buf, win_T *const win, linenr_T lnum, colnr_T static bool wininfo_other_tab_diff(wininfo_T *wip) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - if (wip->wi_opt.wo_diff) { - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - // return false when it's a window in the current tab page, thus - // the buffer was in diff mode here - if (wip->wi_win == wp) { - return false; - } + if (!wip->wi_opt.wo_diff) { + return false; + } + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + // return false when it's a window in the current tab page, thus + // the buffer was in diff mode here + if (wip->wi_win == wp) { + return false; } - return true; } - return false; + return true; } /// Find info for the current window in buffer "buf". @@ -2561,25 +2680,27 @@ static wininfo_T *find_wininfo(buf_T *buf, bool need_options, bool skip_diff_buf } } + if (wip != NULL) { + return wip; + } + // If no wininfo for curwin, use the first in the list (that doesn't have // 'diff' set and is in another tab page). // If "need_options" is true skip entries that don't have options set, // unless the window is editing "buf", so we can copy from the window // itself. - if (wip == NULL) { - if (skip_diff_buffer) { - for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) { - if (!wininfo_other_tab_diff(wip) - && (!need_options - || wip->wi_optset - || (wip->wi_win != NULL - && wip->wi_win->w_buffer == buf))) { - break; - } + if (skip_diff_buffer) { + for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) { + if (!wininfo_other_tab_diff(wip) + && (!need_options + || wip->wi_optset + || (wip->wi_win != NULL + && wip->wi_win->w_buffer == buf))) { + break; } - } else { - wip = buf->b_wininfo; } + } else { + wip = buf->b_wininfo; } return wip; } @@ -2696,9 +2817,9 @@ void buflist_list(exarg_T *eap) continue; } if (buf_spname(buf) != NULL) { - STRLCPY(NameBuff, buf_spname(buf), MAXPATHL); + xstrlcpy(NameBuff, buf_spname(buf), MAXPATHL); } else { - home_replace(buf, buf->b_fname, (char *)NameBuff, MAXPATHL, true); + home_replace(buf, buf->b_fname, NameBuff, MAXPATHL, true); } if (message_filtered(NameBuff)) { @@ -2714,7 +2835,7 @@ void buflist_list(exarg_T *eap) } msg_putchar('\n'); - len = vim_snprintf((char *)IObuff, IOSIZE - 20, "%3d%c%c%c%c%c \"%s\"", + len = vim_snprintf(IObuff, IOSIZE - 20, "%3d%c%c%c%c%c \"%s\"", buf->b_fnum, buf->b_p_bl ? ' ' : 'u', buf == curbuf ? '%' : (curwin->w_alt_fnum == buf->b_fnum ? '#' : ' '), @@ -2728,18 +2849,18 @@ void buflist_list(exarg_T *eap) } // put "line 999" in column 40 or after the file name - i = 40 - vim_strsize((char *)IObuff); + i = 40 - vim_strsize(IObuff); do { IObuff[len++] = ' '; } while (--i > 0 && len < IOSIZE - 18); if (vim_strchr(eap->arg, 't') && buf->b_last_used) { - undo_fmt_time((char_u *)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), + vim_snprintf(IObuff + len, (size_t)(IOSIZE - len), _("line %" PRId64), buf == curbuf ? (int64_t)curwin->w_cursor.lnum : (int64_t)buflist_findlnum(buf)); } - msg_outtrans((char *)IObuff); + msg_outtrans(IObuff); line_breakcheck(); } @@ -2841,18 +2962,20 @@ int setfname(buf_T *buf, char *ffname_arg, char *sfname_arg, bool message) void buf_set_name(int fnum, char *name) { buf_T *buf = buflist_findnr(fnum); - if (buf != NULL) { - if (buf->b_sfname != buf->b_ffname) { - xfree(buf->b_sfname); - } - xfree(buf->b_ffname); - buf->b_ffname = xstrdup(name); - buf->b_sfname = NULL; - // Allocate ffname and expand into full path. Also resolves .lnk - // files on Win32. - fname_expand(buf, &buf->b_ffname, &buf->b_sfname); - buf->b_fname = buf->b_sfname; + if (buf == NULL) { + return; + } + + if (buf->b_sfname != buf->b_ffname) { + xfree(buf->b_sfname); } + xfree(buf->b_ffname); + buf->b_ffname = xstrdup(name); + buf->b_sfname = NULL; + // Allocate ffname and expand into full path. Also resolves .lnk + // files on Win32. + fname_expand(buf, &buf->b_ffname, &buf->b_sfname); + buf->b_fname = buf->b_sfname; } /// Take care of what needs to be done when the name of buffer "buf" has changed. @@ -3044,7 +3167,7 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate) *p++ = '"'; if (buf_spname(curbuf) != NULL) { - STRLCPY(p, buf_spname(curbuf), IOSIZE - (p - buffer)); + xstrlcpy(p, buf_spname(curbuf), (size_t)(IOSIZE - (p - buffer))); } else { if (!fullname && curbuf->b_fname != NULL) { name = curbuf->b_fname; @@ -3087,7 +3210,7 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate) vim_snprintf_add(buffer, IOSIZE, NGETTEXT("%" PRId64 " line --%d%%--", "%" PRId64 " lines --%d%%--", - (unsigned long)curbuf->b_ml.ml_line_count), + curbuf->b_ml.ml_line_count), (int64_t)curbuf->b_ml.ml_line_count, n); } else { vim_snprintf_add(buffer, IOSIZE, @@ -3169,17 +3292,9 @@ void maketitle(void) if (*p_titlestring != NUL) { if (stl_syntax & STL_IN_TITLE) { - int use_sandbox = false; - const int called_emsg_before = called_emsg; - - use_sandbox = was_set_insecurely(curwin, "titlestring", 0); - build_stl_str_hl(curwin, buf, sizeof(buf), - p_titlestring, use_sandbox, - 0, maxlen, NULL, NULL); + build_stl_str_hl(curwin, buf, sizeof(buf), p_titlestring, + "titlestring", 0, 0, maxlen, NULL, NULL, NULL); title_str = buf; - if (called_emsg > called_emsg_before) { - set_string_option_direct("titlestring", -1, "", OPT_FREE, SID_ERROR); - } } else { title_str = p_titlestring; } @@ -3228,7 +3343,7 @@ void maketitle(void) (SPACE_FOR_DIR - (size_t)(buf_p - buf)), true); #ifdef BACKSLASH_IN_FILENAME // Avoid "c:/name" to be reduced to "c". - if (isalpha((uint8_t)buf_p) && *(buf_p + 1) == ':') { + if (isalpha((uint8_t)(*buf_p)) && *(buf_p + 1) == ':') { buf_p += 2; } #endif @@ -3283,16 +3398,8 @@ void maketitle(void) icon_str = buf; if (*p_iconstring != NUL) { if (stl_syntax & STL_IN_ICON) { - int use_sandbox = false; - const int called_emsg_before = called_emsg; - - use_sandbox = was_set_insecurely(curwin, "iconstring", 0); - build_stl_str_hl(curwin, icon_str, sizeof(buf), - p_iconstring, use_sandbox, - 0, 0, NULL, NULL); - if (called_emsg > called_emsg_before) { - set_string_option_direct("iconstring", -1, "", OPT_FREE, SID_ERROR); - } + build_stl_str_hl(curwin, icon_str, sizeof(buf), p_iconstring, + "iconstring", 0, 0, 0, NULL, NULL, NULL); } else { icon_str = p_iconstring; } @@ -3386,9 +3493,9 @@ void get_rel_pos(win_T *wp, char *buf, int buflen) } below = wp->w_buffer->b_ml.ml_line_count - wp->w_botline + 1; if (below <= 0) { - STRLCPY(buf, (above == 0 ? _("All") : _("Bot")), buflen); + xstrlcpy(buf, (above == 0 ? _("All") : _("Bot")), (size_t)buflen); } else if (above <= 0) { - STRLCPY(buf, _("Top"), buflen); + xstrlcpy(buf, _("Top"), (size_t)buflen); } else { vim_snprintf(buf, (size_t)buflen, "%2d%%", above > 1000000L ? (int)(above / ((above + below) / 100L)) @@ -3703,8 +3810,8 @@ static int chk_modeline(linenr_T lnum, int flags) prev = -1; for (s = ml_get(lnum); *s != NUL; s++) { if (prev == -1 || ascii_isspace(prev)) { - if ((prev != -1 && STRNCMP(s, "ex:", (size_t)3) == 0) - || STRNCMP(s, "vi:", (size_t)3) == 0) { + if ((prev != -1 && strncmp(s, "ex:", (size_t)3) == 0) + || strncmp(s, "vi:", (size_t)3) == 0) { break; } // Accept both "vim" and "Vim". @@ -3720,9 +3827,9 @@ static int chk_modeline(linenr_T lnum, int flags) if (*e == ':' && (s[0] != 'V' - || STRNCMP(skipwhite((char *)e + 1), "set", 3) == 0) + || strncmp(skipwhite(e + 1), "set", 3) == 0) && (s[3] == ':' - || (VIM_VERSION_100 >= vers && isdigit(s[3])) + || (VIM_VERSION_100 >= vers && isdigit((uint8_t)s[3])) || (VIM_VERSION_100 < vers && s[3] == '<') || (VIM_VERSION_100 > vers && s[3] == '>') || (VIM_VERSION_100 == vers && s[3] == '='))) { @@ -3769,8 +3876,8 @@ static int chk_modeline(linenr_T lnum, int flags) // "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 (strncmp(s, "set ", (size_t)4) == 0 + || strncmp(s, "se ", (size_t)3) == 0) { if (*e != ':') { // no terminating ':'? break; } @@ -3929,30 +4036,6 @@ char *buf_spname(buf_T *buf) return NULL; } -/// Find a window for buffer "buf". -/// If found true is returned and "wp" and "tp" are set to -/// the window and tabpage. -/// If not found, false is returned. -/// -/// @param buf buffer to find a window for -/// @param[out] wp stores the found window -/// @param[out] tp stores the found tabpage -/// -/// @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; - *tp = NULL; - FOR_ALL_TAB_WINDOWS(tp2, wp2) { - if (wp2->w_buffer == buf) { - *tp = tp2; - *wp = wp2; - return true; - } - } - return false; -} - static int buf_signcols_inner(buf_T *buf, int maximum) { sign_entry_T *sign; // a sign in the sign list @@ -4116,13 +4199,15 @@ char *buf_get_fname(const buf_T *buf) /// Set 'buflisted' for curbuf to "on" and trigger autocommands if it changed. void set_buflisted(int on) { - if (on != curbuf->b_p_bl) { - curbuf->b_p_bl = on; - if (on) { - apply_autocmds(EVENT_BUFADD, NULL, NULL, false, curbuf); - } else { - apply_autocmds(EVENT_BUFDELETE, NULL, NULL, false, curbuf); - } + if (on == curbuf->b_p_bl) { + return; + } + + curbuf->b_p_bl = on; + if (on) { + apply_autocmds(EVENT_BUFADD, NULL, NULL, false, curbuf); + } else { + apply_autocmds(EVENT_BUFDELETE, NULL, NULL, false, curbuf); } } @@ -4147,7 +4232,7 @@ bool buf_contents_changed(buf_T *buf) exarg_T ea; prep_exarg(&ea, buf); - // set curwin/curbuf to buf and save a few things + // Set curwin/curbuf to buf and save a few things. aco_save_T aco; aucmd_prepbuf(&aco, newbuf); diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h index d56a70dc7e..610d9e37ec 100644 --- a/src/nvim/buffer.h +++ b/src/nvim/buffer.h @@ -1,13 +1,20 @@ #ifndef NVIM_BUFFER_H #define NVIM_BUFFER_H +#include <assert.h> +#include <stdbool.h> +#include <stddef.h> + +#include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/func_attr.h" #include "nvim/grid_defs.h" // for StlClickRecord #include "nvim/macros.h" #include "nvim/memline.h" -#include "nvim/pos.h" // for linenr_T +#include "nvim/memline_defs.h" +#include "nvim/pos.h" // Values for buflist_getfile() enum getf_values { @@ -69,8 +76,7 @@ EXTERN char *msg_qflist INIT(= N_("[Quickfix List]")); # include "buffer.h.generated.h" #endif -static inline void buf_set_changedtick(buf_T *const buf, - const varnumber_T changedtick) +static inline void buf_set_changedtick(buf_T *buf, varnumber_T changedtick) REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE; /// Set b:changedtick, also checking b: for consistency in debug build @@ -102,7 +108,7 @@ static inline void buf_set_changedtick(buf_T *const buf, const varnumber_T chang } } -static inline varnumber_T buf_get_changedtick(const buf_T *const buf) +static inline varnumber_T buf_get_changedtick(const buf_T *buf) REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; @@ -116,7 +122,7 @@ static inline varnumber_T buf_get_changedtick(const buf_T *const buf) return buf->changedtick_di.di_tv.vval.v_number; } -static inline void buf_inc_changedtick(buf_T *const buf) +static inline void buf_inc_changedtick(buf_T *buf) REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE; /// Increment b:changedtick value diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 0e629afb87..78ac10a7d6 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -3,11 +3,8 @@ #include <stdbool.h> #include <stdint.h> -// for FILE #include <stdio.h> -#include "grid_defs.h" - typedef struct file_buffer buf_T; // Forward declaration // Reference to a buffer that stores the value of buf_free_count. @@ -18,32 +15,23 @@ typedef struct { int br_buf_free_count; } bufref_T; -// for garray_T +#include "klib/kvec.h" +#include "nvim/api/private/defs.h" +#include "nvim/eval/typval.h" #include "nvim/garray.h" -// for ScreenGrid #include "nvim/grid_defs.h" -// for HLF_COUNT -#include "nvim/highlight_defs.h" -// for pos_T, lpos_T and linenr_T -#include "nvim/pos.h" -// for the number window-local and buffer-local options -#include "nvim/option_defs.h" -// for jump list and tag stack sizes in a buffer and mark types -#include "nvim/mark_defs.h" -// for u_header_T -#include "nvim/undo_defs.h" -// for hashtab_T #include "nvim/hashtab.h" -// for dict_T -#include "nvim/eval/typval.h" -// for String -#include "nvim/api/private/defs.h" -// for Map(K, V) +#include "nvim/highlight_defs.h" #include "nvim/map.h" -// for kvec -#include "klib/kvec.h" -// for marktree +#include "nvim/mark_defs.h" #include "nvim/marktree.h" +// for float window title +#include "nvim/extmark_defs.h" +// for click definitions +#include "nvim/option_defs.h" +#include "nvim/pos.h" +#include "nvim/statusline_defs.h" +#include "nvim/undo_defs.h" #define GETFILE_SUCCESS(x) ((x) <= 0) #define MODIFIABLE(buf) (buf->b_p_ma) @@ -98,17 +86,12 @@ typedef struct wininfo_S wininfo_T; typedef struct frame_S frame_T; typedef uint64_t disptick_T; // display tick type -// for struct memline (it needs memfile_T) #include "nvim/memline_defs.h" - -// for regprog_T. Needs win_T and buf_T. +#include "nvim/os/fs_defs.h" #include "nvim/regexp_defs.h" -// for synstate_T (needs reg_extmatch_T, win_T, buf_T) -#include "nvim/syntax_defs.h" -// for sign_entry_T -#include "nvim/os/fs_defs.h" // for FileID #include "nvim/sign_defs.h" -#include "nvim/terminal.h" // for Terminal +#include "nvim/syntax_defs.h" +#include "nvim/terminal.h" // The taggy struct is used to store the information about a :tag command. typedef struct taggy { @@ -221,6 +204,8 @@ typedef struct { #define w_p_cc w_onebuf_opt.wo_cc // 'colorcolumn' char *wo_sbr; #define w_p_sbr w_onebuf_opt.wo_sbr // 'showbreak' + char *wo_stc; +#define w_p_stc w_onebuf_opt.wo_stc // 'statuscolumn' char *wo_stl; #define w_p_stl w_onebuf_opt.wo_stl // 'statusline' char *wo_wbr; @@ -335,7 +320,7 @@ typedef struct { typedef struct mapblock mapblock_T; struct mapblock { mapblock_T *m_next; // next mapblock in list - uint8_t *m_keys; // mapped from, lhs + char *m_keys; // mapped from, lhs char *m_str; // mapped to, rhs char *m_orig_str; // rhs as entered by the user LuaRef m_luaref; // lua function reference as rhs @@ -673,8 +658,12 @@ struct file_buffer { char *b_p_csl; ///< 'completeslash' #endif char *b_p_cfu; ///< 'completefunc' + Callback b_cfu_cb; ///< 'completefunc' callback char *b_p_ofu; ///< 'omnifunc' + Callback b_ofu_cb; ///< 'omnifunc' callback char *b_p_tfu; ///< 'tagfunc' + Callback b_tfu_cb; ///< 'tagfunc' callback + int b_p_eof; ///< 'endoffile' int b_p_eol; ///< 'endofline' int b_p_fixeol; ///< 'fixendofline' int b_p_et; ///< 'expandtab' @@ -699,6 +688,7 @@ struct file_buffer { uint32_t b_p_fex_flags; ///< flags for 'formatexpr' char *b_p_kp; ///< 'keywordprg' int b_p_lisp; ///< 'lisp' + char *b_p_lop; ///< 'lispoptions' char *b_p_menc; ///< 'makeencoding' char *b_p_mps; ///< 'matchpairs' int b_p_ml; ///< 'modeline' @@ -744,6 +734,7 @@ struct file_buffer { char *b_p_dict; ///< 'dictionary' local value char *b_p_tsr; ///< 'thesaurus' local value char *b_p_tsrfu; ///< 'thesaurusfunc' local value + Callback b_tsrfu_cb; ///< 'thesaurusfunc' callback long b_p_ul; ///< 'undolevels' local value int b_p_udf; ///< 'undofile' char *b_p_lw; ///< 'lispwords' local value @@ -792,6 +783,7 @@ struct file_buffer { linenr_T b_no_eol_lnum; // non-zero lnum when last line of next binary // write should not have an end-of-line + int b_start_eof; // last line had eof (CTRL-Z) when it was read int b_start_eol; // last line had eol when it was read int b_start_ffc; // first char of 'ff' when edit started char *b_start_fenc; // 'fileencoding' when edit started or NULL @@ -891,6 +883,8 @@ struct diffblock_S { diff_T *df_next; linenr_T df_lnum[DB_COUNT]; // line number in buffer linenr_T df_count[DB_COUNT]; // nr of inserted/changed lines + bool is_linematched; // has the linematch algorithm ran on this diff hunk to divide it into + // smaller diff hunks? }; #define SNAP_HELP_IDX 0 @@ -911,7 +905,8 @@ struct tabpage_S { win_T *tp_firstwin; ///< first window in this Tab page win_T *tp_lastwin; ///< last window in this Tab page long tp_old_Rows_avail; ///< ROWS_AVAIL when Tab page was left - long tp_old_Columns; ///< Columns when Tab page was left + long tp_old_Columns; ///< Columns when Tab page was left, -1 when + ///< calling win_new_screen_cols() postponed long tp_ch_used; ///< value of 'cmdheight' when frame size was set diff_T *tp_first_diff; @@ -958,7 +953,8 @@ struct frame_S { // for first // fr_child and fr_win are mutually exclusive frame_T *fr_child; // first contained frame - win_T *fr_win; // window that fills this frame + win_T *fr_win; // window that fills this frame; for a snapshot + // set to the current window }; #define FR_LEAF 0 // frame is a leaf @@ -1032,16 +1028,23 @@ typedef enum { kFloatRelativeEditor = 0, kFloatRelativeWindow = 1, kFloatRelativeCursor = 2, + kFloatRelativeMouse = 3, } FloatRelative; EXTERN const char *const float_relative_str[] INIT(= { "editor", "win", - "cursor" }); + "cursor", "mouse" }); typedef enum { kWinStyleUnused = 0, kWinStyleMinimal, /// Minimal UI: no number column, eob markers, etc } WinStyle; +typedef enum { + kAlignLeft = 0, + kAlignCenter = 1, + kAlignRight = 2, +} AlignTextPos; + typedef struct { Window window; lpos_T bufpos; @@ -1054,10 +1057,14 @@ typedef struct { int zindex; WinStyle style; bool border; + bool title; bool shadow; schar_T border_chars[8]; int border_hl_ids[8]; int border_attr[8]; + AlignTextPos title_pos; + VirtText title_chunks; + int title_width; bool noautocmd; } FloatConfig; @@ -1189,8 +1196,9 @@ struct window_S { colnr_T w_skipcol; // starting column when a single line // doesn't fit in the window - // five fields that are only used when there is a WinScrolled autocommand + // six fields that are only used when there is a WinScrolled autocommand linenr_T w_last_topline; ///< last known value for w_topline + int w_last_topfill; ///< last known value for w_topfill colnr_T w_last_leftcol; ///< last known value for w_leftcol colnr_T w_last_skipcol; ///< last known value for w_skipcol int w_last_width; ///< last known value for w_width @@ -1296,6 +1304,7 @@ struct window_S { linenr_T w_redraw_bot; // when != 0: last line needing redraw bool w_redr_status; // if true statusline/winbar must be redrawn bool w_redr_border; // if true border must be redrawn + bool w_redr_statuscol; // if true 'statuscolumn' must be redrawn // remember what is shown in the ruler for this window (if 'ruler' set) pos_T w_ru_cursor; // cursor position shown in ruler @@ -1336,6 +1345,7 @@ struct window_S { int w_briopt_shift; // additional shift for breakindent bool w_briopt_sbr; // sbr in 'briopt' int w_briopt_list; // additional indent for lists + int w_briopt_vcol; // indent for specific column // transform a pointer to a "onebuf" option into a "allbuf" option #define GLOBAL_WO(p) ((char *)(p) + sizeof(winopt_T)) @@ -1399,10 +1409,37 @@ struct window_S { StlClickDefinition *w_winbar_click_defs; // Size of the w_winbar_click_defs array size_t w_winbar_click_defs_size; + + // Status column click definitions + StlClickDefinition *w_statuscol_click_defs; + // Size of the w_statuscol_click_defs array + size_t w_statuscol_click_defs_size; +}; + +/// Struct to hold info for 'statuscolumn' +typedef struct statuscol statuscol_T; + +struct statuscol { + int width; ///< width of the status column + int cur_attr; ///< current attributes in text + int num_attr; ///< attributes used for line number + int fold_attr; ///< attributes used for fold column + int sign_attr[SIGN_SHOW_MAX + 1]; ///< attributes used for signs + int truncate; ///< truncated width + bool draw; ///< draw statuscolumn or not + char fold_text[9 * 4 + 1]; ///< text in fold column (%C) + char *sign_text[SIGN_SHOW_MAX + 1]; ///< text in sign column (%s) + char text[MAXPATHL]; ///< text in status column + char *textp; ///< current position in text + char *text_end; ///< end of text (the NUL byte) + stl_hlrec_t *hlrec; ///< highlight groups + stl_hlrec_t *hlrecp; ///< current highlight group }; /// Macros defined in Vim, but not in Neovim +// uncrustify:off #define CHANGEDTICK(buf) \ (=== Include buffer.h & use buf_(get|set|inc) _changedtick ===) +// uncrustify:on #endif // NVIM_BUFFER_DEFS_H diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c index 1b3c0bc28f..075ac2adbf 100644 --- a/src/nvim/buffer_updates.c +++ b/src/nvim/buffer_updates.c @@ -1,17 +1,31 @@ // 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 <inttypes.h> +#include <stdbool.h> +#include <stddef.h> + +#include "klib/kvec.h" +#include "lauxlib.h" +#include "nvim/api/buffer.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/assert.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/buffer_updates.h" #include "nvim/extmark.h" +#include "nvim/globals.h" +#include "nvim/log.h" #include "nvim/lua/executor.h" #include "nvim/memline.h" +#include "nvim/memory.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/pos.h" +#include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "buffer_updates.c.generated.h" +# include "buffer_updates.c.generated.h" // IWYU pragma: export #endif // Register a channel. Return True if the channel was added, or already added. @@ -34,12 +48,10 @@ bool buf_updates_register(buf_T *buf, uint64_t channel_id, BufUpdateCallbacks cb // count how many channels are currently watching the buffer size_t size = kv_size(buf->update_channels); - if (size) { - for (size_t i = 0; i < size; i++) { - if (kv_A(buf->update_channels, i) == channel_id) { - // buffer is already registered ... nothing to do - return true; - } + for (size_t i = 0; i < size; i++) { + if (kv_A(buf->update_channels, i) == channel_id) { + // buffer is already registered ... nothing to do + return true; } } @@ -69,13 +81,14 @@ bool buf_updates_register(buf_T *buf, uint64_t channel_id, BufUpdateCallbacks cb linedata.size = line_count; linedata.items = xcalloc(line_count, sizeof(Object)); - buf_collect_lines(buf, line_count, 1, true, &linedata, NULL); + buf_collect_lines(buf, line_count, 1, 0, true, &linedata, NULL, NULL); } args.items[4] = ARRAY_OBJ(linedata); args.items[5] = BOOLEAN_OBJ(false); rpc_send_event(channel_id, "nvim_buf_lines_event", args); + api_free_array(args); // TODO(bfredl): no } else { buf_updates_changedtick_single(buf, channel_id); } @@ -91,10 +104,8 @@ bool buf_updates_active(buf_T *buf) void buf_updates_send_end(buf_T *buf, uint64_t channelid) { - Array args = ARRAY_DICT_INIT; - args.size = 1; - args.items = xcalloc(args.size, sizeof(Object)); - args.items[0] = BUFFER_OBJ(buf->handle); + MAXSIZE_TEMP_ARRAY(args, 1); + ADD_C(args, BUFFER_OBJ(buf->handle)); rpc_send_event(channelid, "nvim_buf_detach_event", args); } @@ -135,6 +146,15 @@ void buf_updates_unregister(buf_T *buf, uint64_t channelid) } } +void buf_free_callbacks(buf_T *buf) +{ + kv_destroy(buf->update_channels); + for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) { + buffer_update_callbacks_free(kv_A(buf->update_callbacks, i)); + } + kv_destroy(buf->update_callbacks); +} + void buf_updates_unload(buf_T *buf, bool can_reload) { size_t size = kv_size(buf->update_channels); @@ -230,8 +250,8 @@ void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added, STATIC_ASSERT(SIZE_MAX >= MAXLNUM, "size_t smaller than MAXLNUM"); linedata.size = (size_t)num_added; linedata.items = xcalloc((size_t)num_added, sizeof(Object)); - buf_collect_lines(buf, (size_t)num_added, firstline, true, &linedata, - NULL); + buf_collect_lines(buf, (size_t)num_added, firstline, 0, true, &linedata, + NULL, NULL); } args.items[4] = ARRAY_OBJ(linedata); args.items[5] = BOOLEAN_OBJ(false); @@ -241,6 +261,7 @@ void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added, // the end. badchannelid = channelid; } + api_free_array(args); // TODO(bfredl): no } // We can only ever remove one dead channel at a time. This is OK because the @@ -387,15 +408,13 @@ void buf_updates_changedtick(buf_T *buf) void buf_updates_changedtick_single(buf_T *buf, uint64_t channel_id) { - Array args = ARRAY_DICT_INIT; - args.size = 2; - args.items = xcalloc(args.size, sizeof(Object)); + MAXSIZE_TEMP_ARRAY(args, 2); // the first argument is always the buffer handle - args.items[0] = BUFFER_OBJ(buf->handle); + ADD_C(args, BUFFER_OBJ(buf->handle)); // next argument is b:changedtick - args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf)); + ADD_C(args, INTEGER_OBJ(buf_get_changedtick(buf))); // don't try and clean up dead channels here rpc_send_event(channel_id, "nvim_buf_changedtick_event", args); diff --git a/src/nvim/buffer_updates.h b/src/nvim/buffer_updates.h index 3c2635be71..961fec879b 100644 --- a/src/nvim/buffer_updates.h +++ b/src/nvim/buffer_updates.h @@ -1,8 +1,8 @@ #ifndef NVIM_BUFFER_UPDATES_H #define NVIM_BUFFER_UPDATES_H -#include "nvim/buffer_defs.h" // for buf_T -#include "nvim/extmark.h" // for bcount_t +#include "nvim/buffer_defs.h" +#include "nvim/extmark.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "buffer_updates.h.generated.h" diff --git a/src/nvim/change.c b/src/nvim/change.c index c9e57ab88f..06696610b0 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -3,8 +3,16 @@ /// change.c: functions related to changing text +#include <assert.h> +#include <stdbool.h> +#include <stdint.h> +#include <string.h> + +#include "nvim/ascii.h" #include "nvim/assert.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" #include "nvim/charset.h" @@ -13,22 +21,35 @@ #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/extmark.h" -#include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/grid_defs.h" +#include "nvim/highlight_defs.h" #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/insexpand.h" +#include "nvim/macros.h" #include "nvim/mark.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/move.h" #include "nvim/option.h" +#include "nvim/os/time.h" #include "nvim/plines.h" +#include "nvim/pos.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/state.h" +#include "nvim/strings.h" #include "nvim/textformat.h" #include "nvim/ui.h" #include "nvim/undo.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "change.c.generated.h" @@ -107,7 +128,7 @@ void changed(void) // Wait two seconds, to make sure the user reads this unexpected // message. Since we could be anywhere, call wait_return() now, // and don't let the emsg() set msg_scroll. - if (need_wait_return && emsg_silent == 0) { + if (need_wait_return && emsg_silent == 0 && !in_assert_fails) { ui_flush(); os_delay(2002L, true); wait_return(true); @@ -397,14 +418,7 @@ void appended_lines(linenr_T lnum, linenr_T count) /// Like appended_lines(), but adjust marks first. void appended_lines_mark(linenr_T lnum, long count) { - // Skip mark_adjust when adding a line after the last one, there can't - // be marks there. But it's still needed in diff mode. - if (lnum + count < curbuf->b_ml.ml_line_count || curwin->w_p_diff) { - mark_adjust(lnum + 1, (linenr_T)MAXLNUM, (linenr_T)count, 0L, kExtmarkUndo); - } else { - extmark_adjust(curbuf, lnum + 1, (linenr_T)MAXLNUM, (linenr_T)count, 0L, - kExtmarkUndo); - } + mark_adjust(lnum + 1, (linenr_T)MAXLNUM, (linenr_T)count, 0L, kExtmarkUndo); changed_lines(lnum + 1, 0, lnum + 1, (linenr_T)count, true); } @@ -539,6 +553,7 @@ void unchanged(buf_T *buf, int ff, bool always_inc_changedtick) void save_file_ff(buf_T *buf) { buf->b_start_ffc = (unsigned char)(*buf->b_p_ff); + buf->b_start_eof = buf->b_p_eof; buf->b_start_eol = buf->b_p_eol; buf->b_start_bomb = buf->b_p_bomb; @@ -573,7 +588,8 @@ bool file_ff_differs(buf_T *buf, bool ignore_empty) if (buf->b_start_ffc != *buf->b_p_ff) { return true; } - if ((buf->b_p_bin || !buf->b_p_fixeol) && buf->b_start_eol != buf->b_p_eol) { + if ((buf->b_p_bin || !buf->b_p_fixeol) + && (buf->b_start_eof != buf->b_p_eof || buf->b_start_eol != buf->b_p_eol)) { return true; } if (!buf->b_p_bin && buf->b_start_bomb != buf->b_p_bomb) { @@ -854,7 +870,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) bool was_alloced = ml_line_alloced(); // check if oldp was allocated char *newp; if (was_alloced) { - ml_add_deleted_len((char *)curbuf->b_ml.ml_line_ptr, oldlen); + ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, oldlen); newp = oldp; // use same allocated memory } else { // need to allocate a new line newp = xmalloc((size_t)(oldlen + 1 - count)); @@ -1319,7 +1335,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // the comment leader. if (dir == FORWARD) { for (p = saved_line + lead_len; *p; p++) { - if (STRNCMP(p, lead_end, n) == 0) { + if (strncmp(p, lead_end, n) == 0) { comment_end = p; lead_len = 0; break; @@ -1410,7 +1426,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) leader = xmalloc((size_t)bytes); allocated = leader; // remember to free it later - STRLCPY(leader, saved_line, lead_len + 1); + xstrlcpy(leader, saved_line, (size_t)lead_len + 1); // TODO(vim): handle multi-byte and double width chars for (int li = 0; li < comment_start; li++) { @@ -1671,13 +1687,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) } // Postpone calling changed_lines(), because it would mess up folding // with markers. - // Skip mark_adjust when adding a line after the last one, there can't - // be marks there. But still needed in diff mode. - if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count - || curwin->w_p_diff) { - mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, - kExtmarkNOOP); - } + mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, kExtmarkNOOP); did_append = true; } else { // In MODE_VREPLACE state we are starting to replace the next line. @@ -1814,19 +1824,19 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) vreplace_mode = 0; } - // May do lisp indenting. - if (!p_paste - && leader == NULL - && curbuf->b_p_lisp - && curbuf->b_p_ai) { - fixthisline(get_lisp_indent); - ai_col = (colnr_T)getwhitecols_curline(); - } - - // May do indenting after opening a new line. - if (do_cindent) { - do_c_expr_indent(); - ai_col = (colnr_T)getwhitecols_curline(); + if (!p_paste) { + if (leader == NULL + && !use_indentexpr_for_lisp() + && curbuf->b_p_lisp + && curbuf->b_p_ai) { + // do lisp indenting + fixthisline(get_lisp_indent); + ai_col = (colnr_T)getwhitecols_curline(); + } else if (do_cindent || (curbuf->b_p_ai && use_indentexpr_for_lisp())) { + // do 'cindent' or 'indentexpr' indenting + do_c_expr_indent(); + ai_col = (colnr_T)getwhitecols_curline(); + } } if (vreplace_mode != 0) { @@ -2194,7 +2204,7 @@ int get_last_leader_offset(char *line, char **flags) // 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 (!strncmp(string + off, com_leader, (size_t)(len2 - off))) { if (i - off < lower_check_bound) { lower_check_bound = i - off; } diff --git a/src/nvim/change.h b/src/nvim/change.h index fdfa8a29ec..d1d016c630 100644 --- a/src/nvim/change.h +++ b/src/nvim/change.h @@ -1,8 +1,8 @@ #ifndef NVIM_CHANGE_H #define NVIM_CHANGE_H -#include "nvim/buffer_defs.h" // for buf_T -#include "nvim/pos.h" // for linenr_T +#include "nvim/buffer_defs.h" +#include "nvim/pos.h" // flags for open_line() #define OPENLINE_DELSPACES 0x01 // delete spaces after cursor diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 7c5bc1c410..65bb87bc2c 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -1,24 +1,42 @@ // 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 <assert.h> +#include <inttypes.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> + +#include "lauxlib.h" #include "nvim/api/private/converter.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/ui.h" #include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/channel.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" +#include "nvim/eval/typval.h" +#include "nvim/event/loop.h" +#include "nvim/event/rstream.h" #include "nvim/event/socket.h" +#include "nvim/event/wstream.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/log.h" #include "nvim/lua/executor.h" +#include "nvim/main.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/server.h" -#include "nvim/os/fs.h" +#include "nvim/os/os_defs.h" #include "nvim/os/shell.h" +#include "nvim/rbuffer.h" #ifdef MSWIN # include "nvim/os/os_win_console.h" # include "nvim/os/pty_conpty_win.h" #endif -#include "nvim/ascii.h" #include "nvim/path.h" static bool did_stdio = false; @@ -197,7 +215,7 @@ void channel_create_event(Channel *chan, const char *ext_source) // external events should be included. source = ext_source; } else { - eval_fmt_source_name_line((char *)IObuff, sizeof(IObuff)); + eval_fmt_source_name_line(IObuff, sizeof(IObuff)); source = (const char *)IObuff; } @@ -359,6 +377,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, CallbackReader } else { has_out = rpc || callback_reader_set(chan->on_data); has_err = callback_reader_set(chan->on_stderr); + proc->fwd_err = chan->on_stderr.fwd_err; } switch (stdin_mode) { @@ -501,6 +520,13 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output, const char **err stdout_dup_fd = os_dup(STDOUT_FILENO); os_replace_stdout_and_stderr_to_conout(); } +#else + if (embedded_mode) { + stdin_dup_fd = dup(STDIN_FILENO); + stdout_dup_fd = dup(STDOUT_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + dup2(STDERR_FILENO, STDIN_FILENO); + } #endif rstream_init_fd(&main_loop, &channel->stream.stdio.in, stdin_dup_fd, 0); wstream_init_fd(&main_loop, &channel->stream.stdio.out, stdout_dup_fd, 0); diff --git a/src/nvim/channel.h b/src/nvim/channel.h index 0f1b481792..5743eaead5 100644 --- a/src/nvim/channel.h +++ b/src/nvim/channel.h @@ -1,13 +1,26 @@ #ifndef NVIM_CHANNEL_H #define NVIM_CHANNEL_H +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/event/libuv_process.h" +#include "nvim/event/multiqueue.h" #include "nvim/event/process.h" #include "nvim/event/socket.h" +#include "nvim/event/stream.h" +#include "nvim/garray.h" +#include "nvim/macros.h" #include "nvim/main.h" +#include "nvim/map.h" +#include "nvim/map_defs.h" #include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/os/pty_process.h" +#include "nvim/terminal.h" +#include "nvim/types.h" #define CHAN_STDIO 1 #define CHAN_STDERR 2 @@ -53,6 +66,7 @@ typedef struct { garray_T buffer; bool eof; bool buffered; + bool fwd_err; const char *type; } CallbackReader; @@ -60,6 +74,7 @@ typedef struct { .self = NULL, \ .buffer = GA_EMPTY_INIT_VALUE, \ .buffered = false, \ + .fwd_err = false, \ .type = NULL }) static inline bool callback_reader_set(CallbackReader reader) { diff --git a/src/nvim/charset.c b/src/nvim/charset.c index f5db03b0b4..5aec9ccf9d 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -6,26 +6,35 @@ /// Code related to character sets. #include <assert.h> +#include <errno.h> #include <inttypes.h> +#include <limits.h> +#include <stdlib.h> #include <string.h> -#include <wctype.h> +#include "auto/config.h" +#include "klib/kvec.h" #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" -#include "nvim/func_attr.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/garray.h" +#include "nvim/globals.h" +#include "nvim/grid_defs.h" #include "nvim/indent.h" -#include "nvim/main.h" +#include "nvim/keycodes.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/move.h" #include "nvim/option.h" -#include "nvim/os_unix.h" #include "nvim/path.h" #include "nvim/plines.h" +#include "nvim/pos.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/vim.h" @@ -137,19 +146,19 @@ int buf_init_chartab(buf_T *buf, int global) // options Each option is a list of characters, character numbers or // ranges, separated by commas, e.g.: "200-210,x,#-178,-" for (i = global ? 0 : 3; i <= 3; i++) { - const char_u *p; + const char *p; if (i == 0) { // first round: 'isident' - p = (char_u *)p_isi; + p = p_isi; } else if (i == 1) { // second round: 'isprint' - p = (char_u *)p_isp; + p = p_isp; } else if (i == 2) { // third round: 'isfname' - p = (char_u *)p_isf; + p = p_isf; } else { // i == 3 // fourth round: 'iskeyword' - p = (char_u *)buf->b_p_isk; + p = buf->b_p_isk; } while (*p) { @@ -245,8 +254,8 @@ int buf_init_chartab(buf_T *buf, int global) c++; } - c = *p; - p = (char_u *)skip_to_option_part((char *)p); + c = (uint8_t)(*p); + p = skip_to_option_part(p); if ((c == ',') && (*p == NUL)) { // Trailing comma is not allowed. @@ -267,7 +276,7 @@ int buf_init_chartab(buf_T *buf, int global) /// @param bufsize void trans_characters(char *buf, int bufsize) { - char_u *trs; // translated character + char *trs; // translated character int len = (int)strlen(buf); // length of string needing translation int room = bufsize - len; // room in buffer after string @@ -277,8 +286,8 @@ void trans_characters(char *buf, int bufsize) if ((trs_len = utfc_ptr2len(buf)) > 1) { len -= trs_len; } else { - trs = transchar_byte((uint8_t)(*buf)); - trs_len = (int)STRLEN(trs); + trs = (char *)transchar_byte((uint8_t)(*buf)); + trs_len = (int)strlen(trs); if (trs_len > 1) { room -= trs_len - 1; @@ -411,12 +420,28 @@ char *transstr(const char *const s, bool untab) return buf; } +size_t kv_transstr(StringBuilder *str, const char *const s, bool untab) + FUNC_ATTR_NONNULL_ARG(1) +{ + if (!s) { + return 0; + } + + // Compute the length of the result, taking account of unprintable + // multi-byte characters. + const size_t len = transstr_len(s, untab); + kv_ensure_space(*str, len + 1); + transstr_buf(s, str->items + str->size, len + 1, untab); + str->size += len; // do not include NUL byte + return len; +} + /// Convert the string "str[orglen]" to do ignore-case comparing. /// Use the current locale. /// /// When "buf" is NULL, return an allocated string. /// Otherwise, put the result in buf, limited by buflen, and return buf. -char_u *str_foldcase(char_u *str, int orglen, char_u *buf, int buflen) +char *str_foldcase(char *str, int orglen, char *buf, int buflen) FUNC_ATTR_NONNULL_RET { garray_T ga; @@ -424,7 +449,7 @@ char_u *str_foldcase(char_u *str, int orglen, char_u *buf, int buflen) int len = orglen; #define GA_CHAR(i) ((char *)ga.ga_data)[i] -#define GA_PTR(i) ((char_u *)ga.ga_data + (i)) +#define GA_PTR(i) ((char *)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)) @@ -452,8 +477,8 @@ char_u *str_foldcase(char_u *str, int orglen, char_u *buf, int buflen) // Make each character lower case. i = 0; while (STR_CHAR(i) != NUL) { - int c = utf_ptr2char((char *)STR_PTR(i)); - int olen = utf_ptr2len((char *)STR_PTR(i)); + int c = utf_ptr2char(STR_PTR(i)); + int olen = utf_ptr2len(STR_PTR(i)); int lc = mb_tolower(c); // Only replace the character when it is not an invalid @@ -487,15 +512,15 @@ char_u *str_foldcase(char_u *str, int orglen, char_u *buf, int buflen) } } } - (void)utf_char2bytes(lc, (char *)STR_PTR(i)); + (void)utf_char2bytes(lc, STR_PTR(i)); } // skip to next multi-byte char - i += utfc_ptr2len((char *)STR_PTR(i)); + i += utfc_ptr2len(STR_PTR(i)); } if (buf == NULL) { - return (char_u *)ga.ga_data; + return ga.ga_data; } return buf; } @@ -515,9 +540,9 @@ static char_u transchar_charbuf[11]; /// @param[in] c Character to translate. /// /// @return translated character into a static buffer. -char_u *transchar(int c) +char *transchar(int c) { - return transchar_buf(curbuf, c); + return (char *)transchar_buf(curbuf, c); } char_u *transchar_buf(const buf_T *buf, int c) @@ -559,7 +584,7 @@ char_u *transchar_byte(const int c) transchar_nonprint(curbuf, transchar_charbuf, c); return transchar_charbuf; } - return transchar(c); + return (char_u *)transchar(c); } /// Convert non-printable characters to 2..4 printable ones @@ -786,10 +811,10 @@ bool vim_iswordc_buf(const int c, buf_T *const buf) /// @param p pointer to the multi-byte character /// /// @return true if "p" points to a keyword character. -bool vim_iswordp(const char_u *const p) +bool vim_iswordp(const char *const p) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - return vim_iswordp_buf((char *)p, curbuf); + return vim_iswordp_buf(p, curbuf); } /// Just like vim_iswordc_buf() but uses a pointer to the (multi-byte) @@ -1698,7 +1723,7 @@ bool rem_backslash(const char *str) || (str[1] != NUL && str[1] != '*' && str[1] != '?' - && !vim_isfilec(str[1]))); + && !vim_isfilec((uint8_t)str[1]))); #else // ifdef BACKSLASH_IN_FILENAME return str[0] == '\\' && str[1] != NUL; diff --git a/src/nvim/charset.h b/src/nvim/charset.h index c4e5d9522b..e1ef06ef1d 100644 --- a/src/nvim/charset.h +++ b/src/nvim/charset.h @@ -1,10 +1,13 @@ #ifndef NVIM_CHARSET_H #define NVIM_CHARSET_H +#include <stdbool.h> + #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" #include "nvim/option_defs.h" #include "nvim/pos.h" +#include "nvim/strings.h" #include "nvim/types.h" /// Return the folded-case equivalent of the given character diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index a29d022606..be815151ef 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -3,9 +3,20 @@ // cmdexpand.c: functions for command-line completion +#include <assert.h> +#include <limits.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "auto/config.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/arglist.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" @@ -13,28 +24,42 @@ #include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/eval/funcs.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" #include "nvim/ex_cmds.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/grid.h" +#include "nvim/hashtab.h" #include "nvim/help.h" +#include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" -#include "nvim/if_cscope.h" +#include "nvim/keycodes.h" #include "nvim/locale.h" +#include "nvim/log.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" #include "nvim/mapping.h" +#include "nvim/mbyte.h" +#include "nvim/memory.h" #include "nvim/menu.h" +#include "nvim/message.h" #include "nvim/option.h" #include "nvim/os/os.h" +#include "nvim/path.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/regexp.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" #include "nvim/search.h" #include "nvim/sign.h" +#include "nvim/statusline.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/tag.h" @@ -48,7 +73,7 @@ typedef char *(*CompleteListItemGetter)(expand_T *, int); /// Type used by call_user_expand_func -typedef void *(*user_expand_func_T)(const char_u *, int, typval_T *); +typedef void *(*user_expand_func_T)(const char *, int, typval_T *); #ifdef INCLUDE_GENERATED_DECLARATIONS # include "cmdexpand.c.generated.h" @@ -64,6 +89,44 @@ static int compl_match_arraysize; static int compl_startcol; static int compl_selected; +#define SHOW_MATCH(m) (showtail ? showmatches_gettail(matches[m], false) : matches[m]) + +/// Returns true if fuzzy completion is supported for a given cmdline completion +/// context. +static bool cmdline_fuzzy_completion_supported(const expand_T *const xp) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE +{ + return (wop_flags & WOP_FUZZY) + && xp->xp_context != EXPAND_BOOL_SETTINGS + && xp->xp_context != EXPAND_COLORS + && xp->xp_context != EXPAND_COMPILER + && xp->xp_context != EXPAND_DIRECTORIES + && xp->xp_context != EXPAND_FILES + && xp->xp_context != EXPAND_FILES_IN_PATH + && xp->xp_context != EXPAND_FILETYPE + && xp->xp_context != EXPAND_HELP + && xp->xp_context != EXPAND_LUA + && xp->xp_context != EXPAND_OLD_SETTING + && xp->xp_context != EXPAND_OWNSYNTAX + && xp->xp_context != EXPAND_PACKADD + && xp->xp_context != EXPAND_SHELLCMD + && xp->xp_context != EXPAND_TAGS + && xp->xp_context != EXPAND_TAGS_LISTFILES + && xp->xp_context != EXPAND_USER_LIST + && xp->xp_context != EXPAND_USER_LUA; +} + +/// Returns true if fuzzy completion for cmdline completion is enabled and +/// "fuzzystr" is not empty. If search pattern is empty, then don't use fuzzy +/// matching. +bool cmdline_fuzzy_complete(const char *const fuzzystr) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE +{ + return (wop_flags & WOP_FUZZY) && *fuzzystr != NUL; +} + +/// Sort function for the completion matches. +/// <SNR> functions should be sorted to the end. static int sort_func_compare(const void *s1, const void *s2) { char *p1 = *(char **)s1; @@ -78,71 +141,76 @@ static int sort_func_compare(const void *s1, const void *s2) return strcmp(p1, p2); } -static void ExpandEscape(expand_T *xp, char_u *str, int numfiles, char **files, int options) +/// Escape special characters in the cmdline completion matches. +static void wildescape(expand_T *xp, const char *str, int numfiles, char **files) { - int i; - char_u *p; + char *p; const int vse_what = xp->xp_context == EXPAND_BUFFERS ? VSE_BUFFER : VSE_NONE; - // May change home directory back to "~" - if (options & WILD_HOME_REPLACE) { - tilde_replace(str, numfiles, files); - } - - if (options & WILD_ESCAPE) { - if (xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_FILES_IN_PATH - || xp->xp_context == EXPAND_SHELLCMD - || xp->xp_context == EXPAND_BUFFERS - || xp->xp_context == EXPAND_DIRECTORIES) { - // Insert a backslash into a file name before a space, \, %, # - // and wildmatch characters, except '~'. - for (i = 0; i < numfiles; i++) { - // for ":set path=" we need to escape spaces twice - if (xp->xp_backslash == XP_BS_THREE) { - p = vim_strsave_escaped((char_u *)files[i], (char_u *)" "); - xfree(files[i]); - files[i] = (char *)p; + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_FILES_IN_PATH + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS + || xp->xp_context == EXPAND_DIRECTORIES) { + // Insert a backslash into a file name before a space, \, %, # + // and wildmatch characters, except '~'. + for (int i = 0; i < numfiles; i++) { + // for ":set path=" we need to escape spaces twice + if (xp->xp_backslash == XP_BS_THREE) { + p = vim_strsave_escaped(files[i], " "); + xfree(files[i]); + files[i] = p; #if defined(BACKSLASH_IN_FILENAME) - p = vim_strsave_escaped(files[i], (char_u *)" "); - xfree(files[i]); - files[i] = p; + p = vim_strsave_escaped(files[i], " "); + xfree(files[i]); + files[i] = p; #endif - } + } #ifdef BACKSLASH_IN_FILENAME - p = (char_u *)vim_strsave_fnameescape((const char *)files[i], vse_what); + p = vim_strsave_fnameescape(files[i], vse_what); #else - p = (char_u *)vim_strsave_fnameescape((const char *)files[i], - xp->xp_shell ? VSE_SHELL : vse_what); + p = vim_strsave_fnameescape(files[i], xp->xp_shell ? VSE_SHELL : vse_what); #endif - xfree(files[i]); - files[i] = (char *)p; + xfree(files[i]); + files[i] = p; - // If 'str' starts with "\~", replace "~" at start of - // files[i] with "\~". - if (str[0] == '\\' && str[1] == '~' && files[i][0] == '~') { - escape_fname(&files[i]); - } + // If 'str' starts with "\~", replace "~" at start of + // files[i] with "\~". + if (str[0] == '\\' && str[1] == '~' && files[i][0] == '~') { + escape_fname(&files[i]); } - xp->xp_backslash = XP_BS_NONE; + } + xp->xp_backslash = XP_BS_NONE; - // If the first file starts with a '+' escape it. Otherwise it - // could be seen as "+cmd". - if (*files[0] == '+') { - escape_fname(&files[0]); - } - } else if (xp->xp_context == EXPAND_TAGS) { - // Insert a backslash before characters in a tag name that - // would terminate the ":tag" command. - for (i = 0; i < numfiles; i++) { - p = vim_strsave_escaped((char_u *)files[i], (char_u *)"\\|\""); - xfree(files[i]); - files[i] = (char *)p; - } + // If the first file starts with a '+' escape it. Otherwise it + // could be seen as "+cmd". + if (*files[0] == '+') { + escape_fname(&files[0]); + } + } else if (xp->xp_context == EXPAND_TAGS) { + // Insert a backslash before characters in a tag name that + // would terminate the ":tag" command. + for (int i = 0; i < numfiles; i++) { + p = vim_strsave_escaped(files[i], "\\|\""); + xfree(files[i]); + files[i] = p; } } } +/// Escape special characters in the cmdline completion matches. +static void ExpandEscape(expand_T *xp, char *str, int numfiles, char **files, int options) +{ + // May change home directory back to "~" + if (options & WILD_HOME_REPLACE) { + tilde_replace(str, numfiles, files); + } + + if (options & WILD_ESCAPE) { + wildescape(xp, str, numfiles, files); + } +} + /// Return FAIL if this is not an appropriate context in which to do /// completion of anything, return OK if it is (even if there are no matches). /// For the caller, this means that the character is just passed through like a @@ -154,8 +222,8 @@ int nextwild(expand_T *xp, int type, int options, bool escape) { CmdlineInfo *const ccline = get_cmdline_info(); int i, j; - char_u *p1; - char_u *p2; + char *p1; + char *p2; int difflen; if (xp->xp_numfiles == -1) { @@ -172,7 +240,9 @@ int nextwild(expand_T *xp, int type, int options, bool escape) return FAIL; } - if (!(ui_has(kUICmdline) || ui_has(kUIWildmenu))) { + // If cmd_silent is set then don't show the dots, because redrawcmd() below + // won't remove them. + if (!cmd_silent && !(ui_has(kUICmdline) || ui_has(kUIWildmenu))) { msg_puts("..."); // show that we are busy ui_flush(); } @@ -181,20 +251,27 @@ int nextwild(expand_T *xp, int type, int options, bool escape) assert(ccline->cmdpos >= i); xp->xp_pattern_len = (size_t)ccline->cmdpos - (size_t)i; - if (type == WILD_NEXT || type == WILD_PREV) { + if (type == WILD_NEXT || type == WILD_PREV + || type == WILD_PAGEUP || type == WILD_PAGEDOWN + || type == WILD_PUM_WANT) { // Get next/previous match for a previous expanded pattern. - p2 = (char_u *)ExpandOne(xp, NULL, NULL, 0, type); + p2 = ExpandOne(xp, NULL, NULL, 0, type); } else { + if (cmdline_fuzzy_completion_supported(xp)) { + // If fuzzy matching, don't modify the search string + p1 = xstrdup(xp->xp_pattern); + } else { + p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); + } // Translate string into pattern and expand it. - p1 = (char_u *)addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); const int use_options = (options | WILD_HOME_REPLACE | WILD_ADD_SLASH | WILD_SILENT | (escape ? WILD_ESCAPE : 0) | (p_wic ? WILD_ICASE : 0)); - p2 = (char_u *)ExpandOne(xp, (char *)p1, xstrnsave(&ccline->cmdbuff[i], xp->xp_pattern_len), - use_options, type); + p2 = ExpandOne(xp, p1, xstrnsave(&ccline->cmdbuff[i], xp->xp_pattern_len), + use_options, type); xfree(p1); // xp->xp_pattern might have been modified by ExpandOne (for example, @@ -210,14 +287,14 @@ int nextwild(expand_T *xp, int type, int options, bool escape) break; } } - if ((int)STRLEN(p2) < j) { + if ((int)strlen(p2) < j) { XFREE_CLEAR(p2); } } } if (p2 != NULL && !got_int) { - difflen = (int)STRLEN(p2) - (int)(xp->xp_pattern_len); + difflen = (int)strlen(p2) - (int)(xp->xp_pattern_len); if (ccline->cmdlen + difflen + 4 > ccline->cmdbufflen) { realloc_cmdbuff(ccline->cmdlen + difflen + 4); xp->xp_pattern = ccline->cmdbuff + i; @@ -226,7 +303,7 @@ int nextwild(expand_T *xp, int type, int options, bool escape) memmove(&ccline->cmdbuff[ccline->cmdpos + difflen], &ccline->cmdbuff[ccline->cmdpos], (size_t)ccline->cmdlen - (size_t)ccline->cmdpos + 1); - memmove(&ccline->cmdbuff[i], p2, STRLEN(p2)); + memmove(&ccline->cmdbuff[i], p2, strlen(p2)); ccline->cmdlen += difflen; ccline->cmdpos += difflen; } @@ -251,19 +328,54 @@ int nextwild(expand_T *xp, int type, int options, bool escape) return OK; } +/// Create and display a cmdline completion popup menu with items from +/// "matches". +static int cmdline_pum_create(CmdlineInfo *ccline, expand_T *xp, char **matches, int numMatches, + int showtail) +{ + assert(numMatches >= 0); + // Add all the completion matches + compl_match_arraysize = numMatches; + compl_match_array = xmalloc(sizeof(pumitem_T) * (size_t)compl_match_arraysize); + for (int i = 0; i < numMatches; i++) { + compl_match_array[i] = (pumitem_T){ + .pum_text = SHOW_MATCH(i), + .pum_info = NULL, + .pum_extra = NULL, + .pum_kind = NULL, + }; + } + + // Compute the popup menu starting column + char *endpos = showtail ? showmatches_gettail(xp->xp_pattern, true) : xp->xp_pattern; + if (ui_has(kUICmdline)) { + compl_startcol = (int)(endpos - ccline->cmdbuff); + } else { + compl_startcol = cmd_screencol((int)(endpos - ccline->cmdbuff)); + } + + // no default selection + compl_selected = -1; + + cmdline_pum_display(true); + + return EXPAND_OK; +} + void cmdline_pum_display(bool changed_array) { pum_display(compl_match_array, compl_match_arraysize, compl_selected, changed_array, compl_startcol); } +/// Returns true if the cmdline completion popup menu is being displayed. bool cmdline_pum_active(void) { // compl_match_array != NULL should already imply pum_visible() in Nvim. return compl_match_array != NULL; } -/// Remove the cmdline completion popup menu +/// Remove the cmdline completion popup menu (if present), free the list of items. void cmdline_pum_remove(void) { pum_undisplay(true); @@ -276,6 +388,235 @@ void cmdline_pum_cleanup(CmdlineInfo *cclp) wildmenu_cleanup(cclp); } +/// Return the number of characters that should be skipped in the wildmenu +/// These are backslashes used for escaping. Do show backslashes in help tags. +static int skip_wildmenu_char(expand_T *xp, char *s) +{ + if ((rem_backslash(s) && xp->xp_context != EXPAND_HELP) + || ((xp->xp_context == EXPAND_MENUS || xp->xp_context == EXPAND_MENUNAMES) + && (s[0] == '\t' || (s[0] == '\\' && s[1] != NUL)))) { +#ifndef BACKSLASH_IN_FILENAME + // TODO(bfredl): Why in the actual fuck are we special casing the + // shell variety deep in the redraw logic? Shell special snowflakiness + // should already be eliminated multiple layers before reaching the + // screen infracstructure. + if (xp->xp_shell && csh_like_shell() && s[1] == '\\' && s[2] == '!') { + return 2; + } +#endif + return 1; + } + return 0; +} + +/// Get the length of an item as it will be shown in the status line. +static int wildmenu_match_len(expand_T *xp, char *s) +{ + int len = 0; + + int emenu = (xp->xp_context == EXPAND_MENUS + || xp->xp_context == EXPAND_MENUNAMES); + + // Check for menu separators - replace with '|'. + if (emenu && menu_is_separator(s)) { + return 1; + } + + while (*s != NUL) { + s += skip_wildmenu_char(xp, s); + len += ptr2cells(s); + MB_PTR_ADV(s); + } + + return len; +} + +/// Show wildchar matches in the status line. +/// Show at least the "match" item. +/// We start at item "first_match" in the list and show all matches that fit. +/// +/// If inversion is possible we use it. Else '=' characters are used. +/// +/// @param matches list of matches +static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int match, int showtail) +{ + int row; + char *buf; + int len; + int clen; // length in screen cells + int fillchar; + int attr; + int i; + bool highlight = true; + char *selstart = NULL; + int selstart_col = 0; + char *selend = NULL; + static int first_match = 0; + bool add_left = false; + char *s; + int emenu; + int l; + + if (matches == NULL) { // interrupted completion? + return; + } + + buf = xmalloc((size_t)Columns * MB_MAXBYTES + 1); + + if (match == -1) { // don't show match but original text + match = 0; + highlight = false; + } + // count 1 for the ending ">" + clen = wildmenu_match_len(xp, SHOW_MATCH(match)) + 3; + if (match == 0) { + first_match = 0; + } else if (match < first_match) { + // jumping left, as far as we can go + first_match = match; + add_left = true; + } else { + // check if match fits on the screen + for (i = first_match; i < match; i++) { + clen += wildmenu_match_len(xp, SHOW_MATCH(i)) + 2; + } + if (first_match > 0) { + clen += 2; + } + // jumping right, put match at the left + if ((long)clen > Columns) { + first_match = match; + // if showing the last match, we can add some on the left + clen = 2; + for (i = match; i < num_matches; i++) { + clen += wildmenu_match_len(xp, SHOW_MATCH(i)) + 2; + if ((long)clen >= Columns) { + break; + } + } + if (i == num_matches) { + add_left = true; + } + } + } + if (add_left) { + while (first_match > 0) { + clen += wildmenu_match_len(xp, SHOW_MATCH(first_match - 1)) + 2; + if ((long)clen >= Columns) { + break; + } + first_match--; + } + } + + fillchar = fillchar_status(&attr, curwin); + + if (first_match == 0) { + *buf = NUL; + len = 0; + } else { + STRCPY(buf, "< "); + len = 2; + } + clen = len; + + i = first_match; + while (clen + wildmenu_match_len(xp, SHOW_MATCH(i)) + 2 < Columns) { + if (i == match) { + selstart = buf + len; + selstart_col = clen; + } + + s = SHOW_MATCH(i); + // Check for menu separators - replace with '|' + emenu = (xp->xp_context == EXPAND_MENUS + || xp->xp_context == EXPAND_MENUNAMES); + if (emenu && menu_is_separator(s)) { + STRCPY(buf + len, transchar('|')); + l = (int)strlen(buf + len); + len += l; + clen += l; + } else { + for (; *s != NUL; s++) { + s += skip_wildmenu_char(xp, s); + clen += ptr2cells(s); + if ((l = utfc_ptr2len(s)) > 1) { + strncpy(buf + len, s, (size_t)l); // NOLINT(runtime/printf) + s += l - 1; + len += l; + } else { + STRCPY(buf + len, transchar_byte((uint8_t)(*s))); + len += (int)strlen(buf + len); + } + } + } + if (i == match) { + selend = buf + len; + } + + *(buf + len++) = ' '; + *(buf + len++) = ' '; + clen += 2; + if (++i == num_matches) { + break; + } + } + + if (i != num_matches) { + *(buf + len++) = '>'; + clen++; + } + + buf[len] = NUL; + + row = cmdline_row - 1; + if (row >= 0) { + if (wild_menu_showing == 0 || wild_menu_showing == WM_LIST) { + if (msg_scrolled > 0) { + // Put the wildmenu just above the command line. If there is + // no room, scroll the screen one line up. + if (cmdline_row == Rows - 1) { + msg_scroll_up(false, false); + msg_scrolled++; + } else { + cmdline_row++; + row++; + } + wild_menu_showing = WM_SCROLLED; + } else { + // 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 && global_stl_height() == 0) { + save_p_ls = (int)p_ls; + save_p_wmh = (int)p_wmh; + p_ls = 2; + p_wmh = 0; + last_status(false); + } + wild_menu_showing = WM_SHOWN; + } + } + + // Tricky: wildmenu can be drawn either over a status line, or at empty + // scrolled space in the message output + ScreenGrid *grid = (wild_menu_showing == WM_SCROLLED) + ? &msg_grid_adj : &default_grid; + + grid_puts(grid, buf, row, 0, attr); + if (selstart != NULL && highlight) { + *selend = NUL; + grid_puts(grid, selstart, row, selstart_col, HL_ATTR(HLF_WM)); + } + + grid_fill(grid, row, row + 1, clen, Columns, + fillchar, fillchar, attr); + } + + win_redraw_last_status(topframe); + xfree(buf); +} + /// Get the next or prev cmdline completion match. The index of the match is set /// in "p_findex" static char *get_next_or_prev_match(int mode, expand_T *xp, int *p_findex, char *orig_save) @@ -291,8 +632,49 @@ static char *get_next_or_prev_match(int mode, expand_T *xp, int *p_findex, char findex = xp->xp_numfiles; } findex--; - } else { // mode == WILD_NEXT + } else if (mode == WILD_NEXT) { findex++; + } else if (mode == WILD_PAGEUP) { + if (findex == 0) { + // at the first entry, don't select any entries + findex = -1; + } else if (findex == -1) { + // no entry is selected. select the last entry + findex = xp->xp_numfiles - 1; + } else { + // go up by the pum height + int ht = pum_get_height(); + if (ht > 3) { + ht -= 2; + } + findex -= ht; + if (findex < 0) { + // few entries left, select the first entry + findex = 0; + } + } + } else if (mode == WILD_PAGEDOWN) { + if (findex == xp->xp_numfiles - 1) { + // at the last entry, don't select any entries + findex = -1; + } else if (findex == -1) { + // no entry is selected. select the first entry + findex = 0; + } else { + // go down by the pum height + int ht = pum_get_height(); + if (ht > 3) { + ht -= 2; + } + findex += ht; + if (findex >= xp->xp_numfiles) { + // few entries left, select the last entry + findex = xp->xp_numfiles - 1; + } + } + } else { // mode == WILD_PUM_WANT + assert(pum_want.active); + findex = pum_want.item; } // When wrapping around, return the original string, set findex to -1. @@ -328,7 +710,7 @@ static char *ExpandOne_start(int mode, expand_T *xp, char *str, int options) char *ss = NULL; // Do the expansion. - if (ExpandFromContext(xp, (char_u *)str, &xp->xp_numfiles, &xp->xp_files, options) == FAIL) { + if (ExpandFromContext(xp, str, &xp->xp_files, &xp->xp_numfiles, options) == FAIL) { #ifdef FNAME_ILLEGAL // Illegal file name has been silently skipped. But when there // are wildcards, the real problem is that there was no match, @@ -343,11 +725,10 @@ static char *ExpandOne_start(int mode, expand_T *xp, char *str, int options) } } else { // Escape the matches for use on the command line. - ExpandEscape(xp, (char_u *)str, xp->xp_numfiles, xp->xp_files, options); + ExpandEscape(xp, str, xp->xp_numfiles, xp->xp_files, options); // Check for matching suffixes in file names. - if (mode != WILD_ALL && mode != WILD_ALL_KEEP - && mode != WILD_LONGEST) { + if (mode != WILD_ALL && mode != WILD_ALL_KEEP && mode != WILD_LONGEST) { if (xp->xp_numfiles) { non_suf_match = xp->xp_numfiles; } else { @@ -420,7 +801,7 @@ static char *find_longest_match(expand_T *xp, int options) return xstrndup(xp->xp_files[0], len); } -/// Do wildcard expansion on the string 'str'. +/// Do wildcard expansion on the string "str". /// Chars that should not be expanded must be preceded with a backslash. /// Return a pointer to allocated memory containing the new string. /// Return NULL for failure. @@ -444,6 +825,7 @@ static char *find_longest_match(expand_T *xp, int options) /// popup menu and close the menu. /// mode = WILD_CANCEL: cancel and close the cmdline completion popup and /// use the original text. +/// mode = WILD_PUM_WANT: use the match at index pum_want.item /// /// options = WILD_LIST_NOTFOUND: list entries without a match /// options = WILD_HOME_REPLACE: do home_replace() for buffer names @@ -467,7 +849,9 @@ char *ExpandOne(expand_T *xp, char *str, char *orig, int options, int mode) int i; // first handle the case of using an old match - if (mode == WILD_NEXT || mode == WILD_PREV) { + if (mode == WILD_NEXT || mode == WILD_PREV + || mode == WILD_PAGEUP || mode == WILD_PAGEDOWN + || mode == WILD_PUM_WANT) { return get_next_or_prev_match(mode, xp, &findex, orig_save); } @@ -484,6 +868,11 @@ char *ExpandOne(expand_T *xp, char *str, char *orig, int options, int mode) FreeWild(xp->xp_numfiles, xp->xp_files); xp->xp_numfiles = -1; XFREE_CLEAR(orig_save); + + // The entries from xp_files may be used in the PUM, remove it. + if (compl_match_array != NULL) { + cmdline_pum_remove(); + } } findex = 0; @@ -553,36 +942,99 @@ void ExpandCleanup(expand_T *xp) } } +/// Display one line of completion matches. Multiple matches are displayed in +/// each line (used by wildmode=list and CTRL-D) +/// +/// @param matches list of completion match names +/// @param numMatches number of completion matches in "matches" +/// @param lines number of output lines +/// @param linenr line number of matches to display +/// @param maxlen maximum number of characters in each line +/// @param showtail display only the tail of the full path of a file name +/// @param dir_attr highlight attribute to use for directory names +static void showmatches_oneline(expand_T *xp, char **matches, int numMatches, int lines, int linenr, + int maxlen, int showtail, int dir_attr) +{ + char *p; + int lastlen = 999; + for (int j = linenr; j < numMatches; j += lines) { + if (xp->xp_context == EXPAND_TAGS_LISTFILES) { + msg_outtrans_attr(matches[j], HL_ATTR(HLF_D)); + p = matches[j] + strlen(matches[j]) + 1; + msg_advance(maxlen + 1); + msg_puts((const char *)p); + msg_advance(maxlen + 3); + msg_outtrans_long_attr(p + 2, HL_ATTR(HLF_D)); + break; + } + for (int i = maxlen - lastlen; --i >= 0;) { + msg_putchar(' '); + } + bool isdir; + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS) { + // highlight directories + if (xp->xp_numfiles != -1) { + // Expansion was done before and special characters + // were escaped, need to halve backslashes. Also + // $HOME has been replaced with ~/. + char *exp_path = expand_env_save_opt(matches[j], true); + char *path = exp_path != NULL ? exp_path : matches[j]; + char *halved_slash = backslash_halve_save(path); + isdir = os_isdir(halved_slash); + xfree(exp_path); + if (halved_slash != path) { + xfree(halved_slash); + } + } else { + // Expansion was done here, file names are literal. + isdir = os_isdir(matches[j]); + } + if (showtail) { + p = SHOW_MATCH(j); + } else { + home_replace(NULL, matches[j], NameBuff, MAXPATHL, true); + p = NameBuff; + } + } else { + isdir = false; + p = SHOW_MATCH(j); + } + lastlen = msg_outtrans_attr(p, isdir ? dir_attr : 0); + } + if (msg_col > 0) { // when not wrapped around + msg_clr_eos(); + msg_putchar('\n'); + } +} + /// Show all matches for completion on the command line. /// Returns EXPAND_NOTHING when the character that triggered expansion should /// be inserted like a normal character. int showmatches(expand_T *xp, int wildmenu) { CmdlineInfo *const ccline = get_cmdline_info(); -#define L_SHOWFILE(m) (showtail \ - ? sm_gettail(files_found[m], false) : files_found[m]) - int num_files; - char **files_found; - int i, j, k; + int numMatches; + char **matches; + int i, j; int maxlen; int lines; int columns; - char_u *p; - int lastlen; int attr; int showtail; if (xp->xp_numfiles == -1) { set_expand_context(xp); - i = expand_cmdline(xp, (char_u *)ccline->cmdbuff, ccline->cmdpos, - &num_files, &files_found); + i = expand_cmdline(xp, ccline->cmdbuff, ccline->cmdpos, + &numMatches, &matches); showtail = expand_showtail(xp); if (i != EXPAND_OK) { return i; } } else { - num_files = xp->xp_numfiles; - files_found = xp->xp_files; + numMatches = xp->xp_numfiles; + matches = xp->xp_files; showtail = cmd_showtail; } @@ -592,26 +1044,8 @@ int showmatches(expand_T *xp, int wildmenu) || ui_has(kUIWildmenu); if (compl_use_pum) { - assert(num_files >= 0); - compl_match_arraysize = num_files; - compl_match_array = xmalloc(sizeof(pumitem_T) * (size_t)compl_match_arraysize); - for (i = 0; i < num_files; i++) { - compl_match_array[i] = (pumitem_T){ - .pum_text = (char_u *)L_SHOWFILE(i), - .pum_info = NULL, - .pum_extra = NULL, - .pum_kind = NULL, - }; - } - char *endpos = (showtail ? sm_gettail(xp->xp_pattern, true) : xp->xp_pattern); - if (ui_has(kUICmdline)) { - compl_startcol = (int)(endpos - ccline->cmdbuff); - } else { - compl_startcol = cmd_screencol((int)(endpos - ccline->cmdbuff)); - } - compl_selected = -1; - cmdline_pum_display(true); - return EXPAND_OK; + // cmdline completion popup menu (with wildoptions=pum) + return cmdline_pum_create(ccline, xp, matches, numMatches, showtail); } if (!wildmenu) { @@ -627,18 +1061,18 @@ int showmatches(expand_T *xp, int wildmenu) if (got_int) { got_int = false; // only int. the completion, not the cmd line } else if (wildmenu) { - redraw_wildmenu(xp, num_files, files_found, -1, showtail); + redraw_wildmenu(xp, numMatches, matches, -1, showtail); } else { // find the length of the longest file name maxlen = 0; - for (i = 0; i < num_files; i++) { + for (i = 0; i < numMatches; i++) { if (!showtail && (xp->xp_context == EXPAND_FILES || xp->xp_context == EXPAND_SHELLCMD || xp->xp_context == EXPAND_BUFFERS)) { - home_replace(NULL, files_found[i], (char *)NameBuff, MAXPATHL, true); - j = vim_strsize((char *)NameBuff); + home_replace(NULL, matches[i], NameBuff, MAXPATHL, true); + j = vim_strsize(NameBuff); } else { - j = vim_strsize(L_SHOWFILE(i)); + j = vim_strsize(SHOW_MATCH(i)); } if (j > maxlen) { maxlen = j; @@ -646,7 +1080,7 @@ int showmatches(expand_T *xp, int wildmenu) } if (xp->xp_context == EXPAND_TAGS_LISTFILES) { - lines = num_files; + lines = numMatches; } else { // compute the number of columns and lines for the listing maxlen += 2; // two spaces between file names @@ -654,7 +1088,7 @@ int showmatches(expand_T *xp, int wildmenu) if (columns < 1) { columns = 1; } - lines = (num_files + columns - 1) / columns; + lines = (numMatches + columns - 1) / columns; } attr = HL_ATTR(HLF_D); // find out highlighting for directories @@ -668,56 +1102,7 @@ int showmatches(expand_T *xp, int wildmenu) // list the files line by line for (i = 0; i < lines; i++) { - lastlen = 999; - for (k = i; k < num_files; k += lines) { - if (xp->xp_context == EXPAND_TAGS_LISTFILES) { - msg_outtrans_attr(files_found[k], HL_ATTR(HLF_D)); - p = (char_u *)files_found[k] + strlen(files_found[k]) + 1; - msg_advance(maxlen + 1); - msg_puts((const char *)p); - msg_advance(maxlen + 3); - msg_outtrans_long_attr((char *)p + 2, HL_ATTR(HLF_D)); - break; - } - for (j = maxlen - lastlen; --j >= 0;) { - msg_putchar(' '); - } - if (xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_SHELLCMD - || xp->xp_context == EXPAND_BUFFERS) { - // highlight directories - if (xp->xp_numfiles != -1) { - // Expansion was done before and special characters - // were escaped, need to halve backslashes. Also - // $HOME has been replaced with ~/. - char_u *exp_path = expand_env_save_opt((char_u *)files_found[k], true); - char_u *path = exp_path != NULL ? exp_path : (char_u *)files_found[k]; - char_u *halved_slash = (char_u *)backslash_halve_save((char *)path); - j = os_isdir((char *)halved_slash); - xfree(exp_path); - if (halved_slash != path) { - xfree(halved_slash); - } - } else { - // Expansion was done here, file names are literal. - j = os_isdir(files_found[k]); - } - if (showtail) { - p = (char_u *)L_SHOWFILE(k); - } else { - home_replace(NULL, files_found[k], (char *)NameBuff, MAXPATHL, true); - p = (char_u *)NameBuff; - } - } else { - j = false; - p = (char_u *)L_SHOWFILE(k); - } - lastlen = msg_outtrans_attr((char *)p, j ? attr : 0); - } - if (msg_col > 0) { // when not wrapped around - msg_clr_eos(); - msg_putchar('\n'); - } + showmatches_oneline(xp, matches, numMatches, lines, i, maxlen, showtail, attr); if (got_int) { got_int = false; break; @@ -730,21 +1115,21 @@ int showmatches(expand_T *xp, int wildmenu) } if (xp->xp_numfiles == -1) { - FreeWild(num_files, files_found); + FreeWild(numMatches, matches); } return EXPAND_OK; } -/// Private path_tail for showmatches() (and redraw_wildmenu()): -/// Find tail of file name path, but ignore trailing "/". -char *sm_gettail(char *s, bool eager) +/// path_tail() version for showmatches() and redraw_wildmenu(): +/// Return the tail of file name path "s", ignoring a trailing "/". +static char *showmatches_gettail(char *s, bool eager) { - char_u *p; - char_u *t = (char_u *)s; + char *p; + char *t = s; bool had_sep = false; - for (p = (char_u *)s; *p != NUL;) { + for (p = s; *p != NUL;) { if (vim_ispathsep(*p) #ifdef BACKSLASH_IN_FILENAME && !rem_backslash(p) @@ -761,7 +1146,7 @@ char *sm_gettail(char *s, bool eager) } MB_PTR_ADV(p); } - return (char *)t; + return t; } /// Return true if we only need to show the tail of completion matches. @@ -769,8 +1154,8 @@ char *sm_gettail(char *s, bool eager) /// returned. static bool expand_showtail(expand_T *xp) { - char_u *s; - char_u *end; + char *s; + char *end; // When not completing file names a "/" may mean something different. if (xp->xp_context != EXPAND_FILES @@ -779,17 +1164,17 @@ static bool expand_showtail(expand_T *xp) return false; } - end = (char_u *)path_tail(xp->xp_pattern); - if (end == (char_u *)xp->xp_pattern) { // there is no path separator + end = path_tail(xp->xp_pattern); + if (end == xp->xp_pattern) { // there is no path separator return false; } - for (s = (char_u *)xp->xp_pattern; s < end; s++) { + for (s = xp->xp_pattern; s < end; s++) { // Skip escaped wildcards. Only when the backslash is not a path // separator, on DOS the '*' "path\*\file" must not be skipped. - if (rem_backslash((char *)s)) { + if (rem_backslash(s)) { s++; - } else if (vim_strchr("*?[", *s) != NULL) { + } else if (vim_strchr("*?[", (uint8_t)(*s)) != NULL) { return false; } } @@ -894,7 +1279,7 @@ char *addstar(char *fname, size_t len, int context) } } else { retval = xmalloc(len + 4); - STRLCPY(retval, fname, len + 1); + xstrlcpy(retval, fname, len + 1); // Don't add a star to *, ~, ~user, $var or `cmd`. // * would become **, which walks the whole tree. @@ -977,7 +1362,7 @@ void set_expand_context(expand_T *xp) xp->xp_context = EXPAND_NOTHING; return; } - set_cmd_context(xp, (char_u *)ccline->cmdbuff, ccline->cmdlen, ccline->cmdpos, true); + set_cmd_context(xp, ccline->cmdbuff, ccline->cmdlen, ccline->cmdpos, true); } /// Sets the index of a built-in or user defined command "cmd" in eap->cmdidx. @@ -989,13 +1374,16 @@ static const char *set_cmd_index(const char *cmd, exarg_T *eap, expand_T *xp, in { const char *p = NULL; size_t len = 0; + const bool fuzzy = cmdline_fuzzy_complete(cmd); // Isolate the command and search for it in the command table. // Exceptions: - // - the 'k' command can directly be followed by any character, but - // do accept "keepmarks", "keepalt" and "keepjumps". + // - the 'k' command can directly be followed by any character, but do + // accept "keepmarks", "keepalt" and "keepjumps". As fuzzy matching can + // find matches anywhere in the command name, do this only for command + // expansion based on regular expression and not for fuzzy matching. // - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' - if (*cmd == 'k' && cmd[1] != 'e') { + if (!fuzzy && (*cmd == 'k' && cmd[1] != 'e')) { eap->cmdidx = CMD_k; p = cmd + 1; } else { @@ -1017,7 +1405,7 @@ static const char *set_cmd_index(const char *cmd, exarg_T *eap, expand_T *xp, in } } // check for non-alpha command - if (p == cmd && vim_strchr("@*!=><&~#", *p) != NULL) { + if (p == cmd && vim_strchr("@*!=><&~#", (uint8_t)(*p)) != NULL) { p++; } len = (size_t)(p - cmd); @@ -1029,7 +1417,11 @@ static const char *set_cmd_index(const char *cmd, exarg_T *eap, expand_T *xp, in eap->cmdidx = excmd_get_cmdidx(cmd, len); - if (cmd[0] >= 'A' && cmd[0] <= 'Z') { + // User defined commands support alphanumeric characters. + // Also when doing fuzzy expansion for non-shell commands, support + // alphanumeric characters. + if ((cmd[0] >= 'A' && cmd[0] <= 'Z') + || (fuzzy && eap->cmdidx != CMD_bang && *p != NUL)) { while (ASCII_ISALNUM(*p) || *p == '*') { // Allow * wild card p++; } @@ -1043,7 +1435,7 @@ static const char *set_cmd_index(const char *cmd, exarg_T *eap, expand_T *xp, in } if (eap->cmdidx == CMD_SIZE) { - if (*cmd == 's' && vim_strchr("cgriI", cmd[1]) != NULL) { + if (*cmd == 's' && vim_strchr("cgriI", (uint8_t)cmd[1]) != NULL) { eap->cmdidx = CMD_substitute; p = cmd + 1; } else if (cmd[0] >= 'A' && cmd[0] <= 'Z') { @@ -1155,17 +1547,244 @@ static void set_context_for_wildcard_arg(exarg_T *eap, const char *arg, bool use } } +/// Set the completion context for the :filter command. Returns a pointer to the +/// next command after the :filter command. +static const char *set_context_in_filter_cmd(expand_T *xp, const char *arg) +{ + if (*arg != NUL) { + arg = (const char *)skip_vimgrep_pat((char *)arg, NULL, NULL); + } + if (arg == NULL || *arg == NUL) { + xp->xp_context = EXPAND_NOTHING; + return NULL; + } + return (const char *)skipwhite(arg); +} + +/// Set the completion context for the :match command. Returns a pointer to the +/// next command after the :match command. +static const char *set_context_in_match_cmd(expand_T *xp, const char *arg) +{ + if (*arg == NUL || !ends_excmd(*arg)) { + // also complete "None" + set_context_in_echohl_cmd(xp, arg); + arg = (const char *)skipwhite(skiptowhite(arg)); + if (*arg != NUL) { + xp->xp_context = EXPAND_NOTHING; + arg = (const char *)skip_regexp((char *)arg + 1, (uint8_t)(*arg), magic_isset()); + } + } + return (const char *)find_nextcmd(arg); +} + +/// Returns a pointer to the next command after a :global or a :v command. +/// Returns NULL if there is no next command. +static const char *find_cmd_after_global_cmd(const char *arg) +{ + const int delim = (uint8_t)(*arg); // Get the delimiter. + if (delim) { + arg++; // Skip delimiter if there is one. + } + + while (arg[0] != NUL && (uint8_t)arg[0] != delim) { + if (arg[0] == '\\' && arg[1] != NUL) { + arg++; + } + arg++; + } + if (arg[0] != NUL) { + return arg + 1; + } + + return NULL; +} + +/// Returns a pointer to the next command after a :substitute or a :& command. +/// Returns NULL if there is no next command. +static const char *find_cmd_after_substitute_cmd(const char *arg) +{ + const int delim = (uint8_t)(*arg); + if (delim) { + // Skip "from" part. + arg++; + arg = (const char *)skip_regexp((char *)arg, delim, magic_isset()); + + if (arg[0] != NUL && arg[0] == delim) { + // Skip "to" part. + arg++; + while (arg[0] != NUL && (uint8_t)arg[0] != delim) { + if (arg[0] == '\\' && arg[1] != NUL) { + arg++; + } + arg++; + } + if (arg[0] != NUL) { // Skip delimiter. + arg++; + } + } + } + while (arg[0] && strchr("|\"#", arg[0]) == NULL) { + arg++; + } + if (arg[0] != NUL) { + return arg; + } + + return NULL; +} + +/// Returns a pointer to the next command after a :isearch/:dsearch/:ilist +/// :dlist/:ijump/:psearch/:djump/:isplit/:dsplit command. +/// Returns NULL if there is no next command. +static const char *find_cmd_after_isearch_cmd(expand_T *xp, const char *arg) +{ + // Skip count. + arg = (const char *)skipwhite(skipdigits(arg)); + if (*arg != '/') { + return NULL; + } + + // Match regexp, not just whole words. + for (++arg; *arg && *arg != '/'; arg++) { + if (*arg == '\\' && arg[1] != NUL) { + arg++; + } + } + if (*arg) { + arg = (const char *)skipwhite(arg + 1); + + // Check for trailing illegal characters. + if (*arg == NUL || strchr("|\"\n", *arg) == NULL) { + xp->xp_context = EXPAND_NOTHING; + } else { + return arg; + } + } + + return NULL; +} + +/// Set the completion context for the :unlet command. Always returns NULL. +static const char *set_context_in_unlet_cmd(expand_T *xp, const char *arg) +{ + while ((xp->xp_pattern = strchr(arg, ' ')) != NULL) { + arg = (const char *)xp->xp_pattern + 1; + } + + xp->xp_context = EXPAND_USER_VARS; + xp->xp_pattern = (char *)arg; + + if (*xp->xp_pattern == '$') { + xp->xp_context = EXPAND_ENV_VARS; + xp->xp_pattern++; + } + + return NULL; +} + +/// Set the completion context for the :language command. Always returns NULL. +static const char *set_context_in_lang_cmd(expand_T *xp, const char *arg) +{ + const char *p = (const char *)skiptowhite(arg); + if (*p == NUL) { + xp->xp_context = EXPAND_LANGUAGE; + xp->xp_pattern = (char *)arg; + } else { + if (strncmp(arg, "messages", (size_t)(p - arg)) == 0 + || strncmp(arg, "ctype", (size_t)(p - arg)) == 0 + || strncmp(arg, "time", (size_t)(p - arg)) == 0 + || strncmp(arg, "collate", (size_t)(p - arg)) == 0) { + xp->xp_context = EXPAND_LOCALES; + xp->xp_pattern = skipwhite(p); + } else { + xp->xp_context = EXPAND_NOTHING; + } + } + + return NULL; +} + +static enum { + EXP_BREAKPT_ADD, ///< expand ":breakadd" sub-commands + EXP_BREAKPT_DEL, ///< expand ":breakdel" sub-commands + EXP_PROFDEL, ///< expand ":profdel" sub-commands +} breakpt_expand_what; + +/// Set the completion context for the :breakadd command. Always returns NULL. +static const char *set_context_in_breakadd_cmd(expand_T *xp, const char *arg, cmdidx_T cmdidx) +{ + xp->xp_context = EXPAND_BREAKPOINT; + xp->xp_pattern = (char *)arg; + + if (cmdidx == CMD_breakadd) { + breakpt_expand_what = EXP_BREAKPT_ADD; + } else if (cmdidx == CMD_breakdel) { + breakpt_expand_what = EXP_BREAKPT_DEL; + } else { + breakpt_expand_what = EXP_PROFDEL; + } + + const char *p = skipwhite(arg); + if (*p == NUL) { + return NULL; + } + const char *subcmd_start = p; + + if (strncmp("file ", p, 5) == 0 || strncmp("func ", p, 5) == 0) { + // :breakadd file [lnum] <filename> + // :breakadd func [lnum] <funcname> + p += 4; + p = skipwhite(p); + + // skip line number (if specified) + if (ascii_isdigit(*p)) { + p = skipdigits(p); + if (*p != ' ') { + xp->xp_context = EXPAND_NOTHING; + return NULL; + } + p = skipwhite(p); + } + if (strncmp("file", subcmd_start, 4) == 0) { + xp->xp_context = EXPAND_FILES; + } else { + xp->xp_context = EXPAND_USER_FUNC; + } + xp->xp_pattern = (char *)p; + } else if (strncmp("expr ", p, 5) == 0) { + // :breakadd expr <expression> + xp->xp_context = EXPAND_EXPRESSION; + xp->xp_pattern = skipwhite(p + 5); + } + + return NULL; +} + +static const char *set_context_in_scriptnames_cmd(expand_T *xp, const char *arg) +{ + xp->xp_context = EXPAND_NOTHING; + xp->xp_pattern = NULL; + + char *p = skipwhite(arg); + if (ascii_isdigit(*p)) { + return NULL; + } + + xp->xp_context = EXPAND_SCRIPTNAMES; + xp->xp_pattern = p; + + return NULL; +} + /// Set the completion context in "xp" for command "cmd" with index "cmdidx". /// The argument to the command is "arg" and the argument flags is "argt". /// For user-defined commands and for environment variables, "context" has the /// completion type. /// /// @return a pointer to the next command, or NULL if there is no next command. -static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, const char *arg, - uint32_t argt, int context, expand_T *xp, bool forceit) +static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expand_T *xp, + const char *arg, uint32_t argt, int context, bool forceit) { - const char *p; - switch (cmdidx) { case CMD_find: case CMD_sfind: @@ -1227,26 +1846,10 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, cons return arg; case CMD_filter: - if (*arg != NUL) { - arg = (const char *)skip_vimgrep_pat((char *)arg, NULL, NULL); - } - if (arg == NULL || *arg == NUL) { - xp->xp_context = EXPAND_NOTHING; - return NULL; - } - return (const char *)skipwhite(arg); + return set_context_in_filter_cmd(xp, arg); case CMD_match: - if (*arg == NUL || !ends_excmd(*arg)) { - // also complete "None" - set_context_in_echohl_cmd(xp, arg); - arg = (const char *)skipwhite(skiptowhite(arg)); - if (*arg != NUL) { - xp->xp_context = EXPAND_NOTHING; - arg = (const char *)skip_regexp((char *)arg + 1, (uint8_t)(*arg), p_magic, NULL); - } - } - return (const char *)find_nextcmd(arg); + return set_context_in_match_cmd(xp, arg); // All completion for the +cmdline_compl feature goes here. @@ -1259,49 +1862,11 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, cons break; case CMD_global: - case CMD_vglobal: { - const int delim = (uint8_t)(*arg); // Get the delimiter. - if (delim) { - arg++; // Skip delimiter if there is one. - } - - while (arg[0] != NUL && (uint8_t)arg[0] != delim) { - if (arg[0] == '\\' && arg[1] != NUL) { - arg++; - } - arg++; - } - if (arg[0] != NUL) { - return arg + 1; - } - break; - } + case CMD_vglobal: + return find_cmd_after_global_cmd(arg); case CMD_and: - case CMD_substitute: { - const int delim = (uint8_t)(*arg); - if (delim) { - // Skip "from" part. - arg++; - arg = (const char *)skip_regexp((char *)arg, delim, p_magic, NULL); - } - // Skip "to" part. - while (arg[0] != NUL && (uint8_t)arg[0] != delim) { - if (arg[0] == '\\' && arg[1] != NUL) { - arg++; - } - arg++; - } - if (arg[0] != NUL) { // Skip delimiter. - arg++; - } - while (arg[0] && strchr("|\"#", arg[0]) == NULL) { - arg++; - } - if (arg[0] != NUL) { - return arg; - } - break; - } + case CMD_substitute: + return find_cmd_after_substitute_cmd(arg); case CMD_isearch: case CMD_dsearch: case CMD_ilist: @@ -1311,26 +1876,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, cons case CMD_djump: case CMD_isplit: case CMD_dsplit: - // Skip count. - arg = (const char *)skipwhite(skipdigits(arg)); - if (*arg == '/') { // Match regexp, not just whole words. - for (++arg; *arg && *arg != '/'; arg++) { - if (*arg == '\\' && arg[1] != NUL) { - arg++; - } - } - if (*arg) { - arg = (const char *)skipwhite(arg + 1); - - // Check for trailing illegal characters. - if (*arg && strchr("|\"\n", *arg) == NULL) { - xp->xp_context = EXPAND_NOTHING; - } else { - return arg; - } - } - } - break; + return find_cmd_after_isearch_cmd(xp, arg); case CMD_autocmd: return (const char *)set_context_in_autocmd(xp, (char *)arg, false); @@ -1338,13 +1884,13 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, cons case CMD_doautoall: return (const char *)set_context_in_autocmd(xp, (char *)arg, true); case CMD_set: - set_context_in_set_cmd(xp, (char_u *)arg, 0); + set_context_in_set_cmd(xp, (char *)arg, 0); break; case CMD_setglobal: - set_context_in_set_cmd(xp, (char_u *)arg, OPT_GLOBAL); + set_context_in_set_cmd(xp, (char *)arg, OPT_GLOBAL); break; case CMD_setlocal: - set_context_in_set_cmd(xp, (char_u *)arg, OPT_LOCAL); + set_context_in_set_cmd(xp, (char *)arg, OPT_LOCAL); break; case CMD_tag: case CMD_stag: @@ -1393,20 +1939,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, cons break; case CMD_unlet: - while ((xp->xp_pattern = strchr(arg, ' ')) != NULL) { - arg = (const char *)xp->xp_pattern + 1; - } - - xp->xp_context = EXPAND_USER_VARS; - xp->xp_pattern = (char *)arg; - - if (*xp->xp_pattern == '$') { - xp->xp_context = EXPAND_ENV_VARS; - xp->xp_pattern++; - } - - break; - + return set_context_in_unlet_cmd(xp, arg); case CMD_function: case CMD_delfunction: xp->xp_context = EXPAND_USER_FUNC; @@ -1419,11 +1952,6 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, cons case CMD_highlight: set_context_in_highlight_cmd(xp, arg); break; - case CMD_cscope: - case CMD_lcscope: - case CMD_scscope: - set_context_in_cscope_cmd(xp, arg, cmdidx); - break; case CMD_sign: set_context_in_sign_cmd(xp, (char *)arg); break; @@ -1450,34 +1978,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, cons case CMD_USER: case CMD_USER_BUF: - if (context != EXPAND_NOTHING) { - // EX_XFILE: file names are handled above. - if (!(argt & EX_XFILE)) { - if (context == EXPAND_MENUS) { - return (const char *)set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit); - } else if (context == EXPAND_COMMANDS) { - return arg; - } else if (context == EXPAND_MAPPINGS) { - return (const char *)set_context_in_map_cmd(xp, "map", (char_u *)arg, forceit, - false, false, - CMD_map); - } - // Find start of last argument. - p = arg; - while (*p) { - if (*p == ' ') { - // argument starts after a space - arg = p + 1; - } else if (*p == '\\' && *(p + 1) != NUL) { - p++; // skip over escaped character - } - MB_PTR_ADV(p); - } - xp->xp_pattern = (char *)arg; - } - xp->xp_context = context; - } - break; + return set_context_in_user_cmdarg(cmd, arg, argt, context, xp, forceit); case CMD_map: case CMD_noremap: @@ -1497,7 +1998,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, cons case CMD_snoremap: case CMD_xmap: case CMD_xnoremap: - return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, false, + return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, false, false, cmdidx); case CMD_unmap: case CMD_nunmap: @@ -1508,7 +2009,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, cons case CMD_lunmap: case CMD_sunmap: case CMD_xunmap: - return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, false, + return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, false, true, cmdidx); case CMD_mapclear: case CMD_nmapclear: @@ -1529,12 +2030,12 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, cons case CMD_cnoreabbrev: case CMD_iabbrev: case CMD_inoreabbrev: - return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, true, + return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, true, false, cmdidx); case CMD_unabbreviate: case CMD_cunabbrev: case CMD_iunabbrev: - return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, true, + return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, true, true, cmdidx); case CMD_menu: case CMD_noremenu: @@ -1593,22 +2094,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, cons #ifdef HAVE_WORKING_LIBINTL case CMD_language: - p = (const char *)skiptowhite(arg); - if (*p == NUL) { - xp->xp_context = EXPAND_LANGUAGE; - xp->xp_pattern = (char *)arg; - } else { - if (strncmp(arg, "messages", (size_t)(p - arg)) == 0 - || strncmp(arg, "ctype", (size_t)(p - arg)) == 0 - || strncmp(arg, "time", (size_t)(p - arg)) == 0 - || strncmp(arg, "collate", (size_t)(p - arg)) == 0) { - xp->xp_context = EXPAND_LOCALES; - xp->xp_pattern = skipwhite(p); - } else { - xp->xp_context = EXPAND_NOTHING; - } - } - break; + return set_context_in_lang_cmd(xp, arg); #endif case CMD_profile: set_context_in_profile_cmd(xp, arg); @@ -1644,6 +2130,14 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, cons xp->xp_pattern = (char *)arg; break; + case CMD_breakadd: + case CMD_profdel: + case CMD_breakdel: + return set_context_in_breakadd_cmd(xp, arg, cmdidx); + + case CMD_scriptnames: + return set_context_in_scriptnames_cmd(xp, arg); + case CMD_lua: xp->xp_context = EXPAND_LUA; break; @@ -1677,7 +2171,7 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) // 1. skip comment lines and leading space, colons or bars const char *cmd; - for (cmd = buff; vim_strchr(" \t:|", *cmd) != NULL; cmd++) {} + for (cmd = buff; vim_strchr(" \t:|", (uint8_t)(*cmd)) != NULL; cmd++) {} xp->xp_pattern = (char *)cmd; if (*cmd == NUL) { @@ -1827,17 +2321,19 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) } // Switch on command name. - return set_context_by_cmdname(cmd, ea.cmdidx, arg, ea.argt, context, xp, forceit); + return set_context_by_cmdname(cmd, ea.cmdidx, xp, arg, ea.argt, context, forceit); } +/// Set the completion context in "xp" for command "str" +/// /// @param str start of command line /// @param len length of command line (excl. NUL) /// @param col position of cursor /// @param use_ccline use ccline for info -void set_cmd_context(expand_T *xp, char_u *str, int len, int col, int use_ccline) +void set_cmd_context(expand_T *xp, char *str, int len, int col, int use_ccline) { CmdlineInfo *const ccline = get_cmdline_info(); - char_u old_char = NUL; + char old_char = NUL; // Avoid a UMR warning from Purify, only save the character if it has been // written before. @@ -1849,11 +2345,11 @@ void set_cmd_context(expand_T *xp, char_u *str, int len, int col, int use_ccline if (use_ccline && ccline->cmdfirstc == '=') { // pass CMD_SIZE because there is no real command - set_context_for_expression(xp, (char *)str, CMD_SIZE); + set_context_for_expression(xp, str, CMD_SIZE); } else if (use_ccline && ccline->input_fn) { xp->xp_context = ccline->xp_context; xp->xp_pattern = ccline->cmdbuff; - xp->xp_arg = (char *)ccline->xp_arg; + xp->xp_arg = ccline->xp_arg; } else { while (nextcomm != NULL) { nextcomm = set_one_cmd_context(xp, nextcomm); @@ -1862,7 +2358,7 @@ void set_cmd_context(expand_T *xp, char_u *str, int len, int col, int use_ccline // Store the string here so that call_user_expand_func() can get to them // easily. - xp->xp_line = (char *)str; + xp->xp_line = str; xp->xp_col = col; str[col] = old_char; @@ -1882,9 +2378,9 @@ void set_cmd_context(expand_T *xp, char_u *str, int len, int col, int use_ccline /// @param col position of cursor /// @param matchcount return: nr of matches /// @param matches return: array of pointers to matches -int expand_cmdline(expand_T *xp, char_u *str, int col, int *matchcount, char ***matches) +int expand_cmdline(expand_T *xp, const char *str, int col, int *matchcount, char ***matches) { - char_u *file_str = NULL; + char *file_str = NULL; int options = WILD_ADD_SLASH|WILD_SILENT; if (xp->xp_context == EXPAND_UNSUCCESSFUL) { @@ -1897,16 +2393,21 @@ int expand_cmdline(expand_T *xp, char_u *str, int col, int *matchcount, char *** } // add star to file name, or convert to regexp if not exp. files. - assert((str + col) - (char_u *)xp->xp_pattern >= 0); - xp->xp_pattern_len = (size_t)((str + col) - (char_u *)xp->xp_pattern); - file_str = (char_u *)addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); + assert((str + col) - xp->xp_pattern >= 0); + xp->xp_pattern_len = (size_t)((str + col) - xp->xp_pattern); + if (cmdline_fuzzy_completion_supported(xp)) { + // If fuzzy matching, don't modify the search string + file_str = xstrdup(xp->xp_pattern); + } else { + file_str = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); + } if (p_wic) { options += WILD_ICASE; } // find all files that match the description - if (ExpandFromContext(xp, file_str, matchcount, matches, options) == FAIL) { + if (ExpandFromContext(xp, file_str, matches, matchcount, options) == FAIL) { *matchcount = 0; *matches = NULL; } @@ -1916,8 +2417,8 @@ int expand_cmdline(expand_T *xp, char_u *str, int col, int *matchcount, char *** } /// Expand file or directory names. -static int expand_files_and_dirs(expand_T *xp, char *pat, char ***file, int *num_file, int flags, - int options) +static int expand_files_and_dirs(expand_T *xp, char *pat, char ***matches, int *numMatches, + int flags, int options) { bool free_pat = false; @@ -1953,14 +2454,14 @@ static int expand_files_and_dirs(expand_T *xp, char *pat, char ***file, int *num } // Expand wildcards, supporting %:h and the like. - int ret = expand_wildcards_eval(&pat, num_file, file, flags); + int ret = expand_wildcards_eval(&pat, numMatches, matches, flags); if (free_pat) { xfree(pat); } #ifdef BACKSLASH_IN_FILENAME if (p_csl[0] != NUL && (options & WILD_IGNORE_COMPLETESLASH) == 0) { - for (int j = 0; j < *num_file; j++) { - char *ptr = (*file)[j]; + for (int j = 0; j < *numMatches; j++) { + char *ptr = (*matches)[j]; while (*ptr != NUL) { if (p_csl[0] == 's' && *ptr == '\\') { *ptr = '/'; @@ -1989,6 +2490,45 @@ static char *get_behave_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) } /// Function given to ExpandGeneric() to obtain the possible arguments of the +/// ":breakadd {expr, file, func, here}" command. +/// ":breakdel {func, file, here}" command. +static char *get_breakadd_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + char *opts[] = { "expr", "file", "func", "here" }; + + if (idx >= 0 && idx <= 3) { + // breakadd {expr, file, func, here} + if (breakpt_expand_what == EXP_BREAKPT_ADD) { + return opts[idx]; + } else if (breakpt_expand_what == EXP_BREAKPT_DEL) { + // breakdel {func, file, here} + if (idx <= 2) { + return opts[idx + 1]; + } + } else { + // profdel {func, file} + if (idx <= 1) { + return opts[idx + 1]; + } + } + } + return NULL; +} + +/// Function given to ExpandGeneric() to obtain the possible arguments for the +/// ":scriptnames" command. +static char *get_scriptnames_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + if (!SCRIPT_ID_VALID(idx + 1)) { + return NULL; + } + + scriptitem_T *si = &SCRIPT_ITEM(idx + 1); + home_replace(NULL, si->sn_name, NameBuff, MAXPATHL, true); + return NameBuff; +} + +/// Function given to ExpandGeneric() to obtain the possible arguments of the /// ":messages {clear}" command. static char *get_messages_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) { @@ -2033,7 +2573,7 @@ static char *get_healthcheck_names(expand_T *xp FUNC_ATTR_UNUSED, int idx) } /// Do the expansion based on xp->xp_context and "rmp". -static int ExpandOther(expand_T *xp, regmatch_T *rmp, int *num_file, char ***file) +static int ExpandOther(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches, int *numMatches) { typedef CompleteListItemGetter ExpandFunc; static struct expgen { @@ -2063,7 +2603,6 @@ static int ExpandOther(expand_T *xp, regmatch_T *rmp, int *num_file, char ***fil { EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, false }, { EXPAND_EVENTS, expand_get_event_name, true, false }, { EXPAND_AUGROUP, expand_get_augroup_name, true, false }, - { EXPAND_CSCOPE, get_cscope_name, true, true }, { EXPAND_SIGN, get_sign_name, true, true }, { EXPAND_PROFILE, get_profile_name, true, true }, #ifdef HAVE_WORKING_LIBINTL @@ -2073,6 +2612,8 @@ static int ExpandOther(expand_T *xp, regmatch_T *rmp, int *num_file, char ***fil { EXPAND_ENV_VARS, get_env_name, true, true }, { EXPAND_USER, get_users, true, false }, { EXPAND_ARGLIST, get_arglist_name, true, false }, + { EXPAND_BREAKPOINT, get_breakadd_arg, true, true }, + { EXPAND_SCRIPTNAMES, get_scriptnames_arg, true, false }, { EXPAND_CHECKHEALTH, get_healthcheck_names, true, false }, }; int ret = FAIL; @@ -2084,7 +2625,7 @@ static int ExpandOther(expand_T *xp, regmatch_T *rmp, int *num_file, char ***fil if (tab[i].ic) { rmp->rm_ic = true; } - ExpandGeneric(xp, rmp, num_file, file, tab[i].func, tab[i].escaped); + ExpandGeneric(pat, xp, rmp, matches, numMatches, tab[i].func, tab[i].escaped); ret = OK; break; } @@ -2093,16 +2634,10 @@ static int ExpandOther(expand_T *xp, regmatch_T *rmp, int *num_file, char ***fil return ret; } -/// Do the expansion based on xp->xp_context and "pat". -/// -/// @param options WILD_ flags -static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char ***file, int options) +/// Map wild expand options to flags for expand_wildcards() +static int map_wildopts_to_ewflags(int options) { - regmatch_T regmatch; - int ret; - int flags; - - flags = EW_DIR; // include directories + int flags = EW_DIR; // include directories if (options & WILD_LIST_NOTFOUND) { flags |= EW_NOTFOUND; } @@ -2122,106 +2657,123 @@ static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char ***f flags |= EW_ALLLINKS; } + return flags; +} + +/// Do the expansion based on xp->xp_context and "pat". +/// +/// @param options WILD_ flags +static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numMatches, int options) +{ + regmatch_T regmatch = { .rm_ic = false }; + int ret; + int flags = map_wildopts_to_ewflags(options); + const bool fuzzy = cmdline_fuzzy_complete(pat) + && cmdline_fuzzy_completion_supported(xp); + if (xp->xp_context == EXPAND_FILES || xp->xp_context == EXPAND_DIRECTORIES || xp->xp_context == EXPAND_FILES_IN_PATH) { - return expand_files_and_dirs(xp, (char *)pat, file, num_file, flags, options); + return expand_files_and_dirs(xp, pat, matches, numMatches, flags, options); } - *file = NULL; - *num_file = 0; + *matches = NULL; + *numMatches = 0; if (xp->xp_context == EXPAND_HELP) { // With an empty argument we would get all the help tags, which is // very slow. Get matches for "help" instead. - if (find_help_tags(*pat == NUL ? "help" : (char *)pat, - num_file, file, false) == OK) { - cleanup_help_tags(*num_file, *file); + if (find_help_tags(*pat == NUL ? "help" : pat, + numMatches, matches, false) == OK) { + cleanup_help_tags(*numMatches, *matches); return OK; } return FAIL; } if (xp->xp_context == EXPAND_SHELLCMD) { - *file = NULL; - expand_shellcmd((char *)pat, num_file, file, flags); + expand_shellcmd(pat, matches, numMatches, flags); return OK; } if (xp->xp_context == EXPAND_OLD_SETTING) { - ExpandOldSetting(num_file, file); + ExpandOldSetting(numMatches, matches); return OK; } if (xp->xp_context == EXPAND_BUFFERS) { - return ExpandBufnames((char *)pat, num_file, file, options); + return ExpandBufnames(pat, numMatches, matches, options); } if (xp->xp_context == EXPAND_DIFF_BUFFERS) { - return ExpandBufnames((char *)pat, num_file, file, options | BUF_DIFF_FILTER); + return ExpandBufnames(pat, numMatches, matches, options | BUF_DIFF_FILTER); } if (xp->xp_context == EXPAND_TAGS || xp->xp_context == EXPAND_TAGS_LISTFILES) { - return expand_tags(xp->xp_context == EXPAND_TAGS, pat, num_file, file); + return expand_tags(xp->xp_context == EXPAND_TAGS, pat, numMatches, matches); } if (xp->xp_context == EXPAND_COLORS) { char *directories[] = { "colors", NULL }; - return ExpandRTDir((char *)pat, DIP_START + DIP_OPT + DIP_LUA, num_file, file, directories); + return ExpandRTDir(pat, DIP_START + DIP_OPT, numMatches, matches, directories); } if (xp->xp_context == EXPAND_COMPILER) { char *directories[] = { "compiler", NULL }; - return ExpandRTDir((char *)pat, DIP_LUA, num_file, file, directories); + return ExpandRTDir(pat, 0, numMatches, matches, directories); } if (xp->xp_context == EXPAND_OWNSYNTAX) { char *directories[] = { "syntax", NULL }; - return ExpandRTDir((char *)pat, 0, num_file, file, directories); + return ExpandRTDir(pat, 0, numMatches, matches, directories); } if (xp->xp_context == EXPAND_FILETYPE) { char *directories[] = { "syntax", "indent", "ftplugin", NULL }; - return ExpandRTDir((char *)pat, DIP_LUA, num_file, file, directories); + return ExpandRTDir(pat, 0, numMatches, matches, directories); } if (xp->xp_context == EXPAND_USER_LIST) { - return ExpandUserList(xp, num_file, file); + return ExpandUserList(xp, matches, numMatches); } if (xp->xp_context == EXPAND_USER_LUA) { - return ExpandUserLua(xp, num_file, file); + return ExpandUserLua(xp, numMatches, matches); } if (xp->xp_context == EXPAND_PACKADD) { - return ExpandPackAddDir((char *)pat, num_file, file); + return ExpandPackAddDir(pat, numMatches, matches); } // When expanding a function name starting with s:, match the <SNR>nr_ // prefix. char *tofree = NULL; - if (xp->xp_context == EXPAND_USER_FUNC && STRNCMP(pat, "^s:", 3) == 0) { - const size_t len = STRLEN(pat) + 20; + if (xp->xp_context == EXPAND_USER_FUNC && strncmp(pat, "^s:", 3) == 0) { + const size_t len = strlen(pat) + 20; tofree = xmalloc(len); snprintf(tofree, len, "^<SNR>\\d\\+_%s", pat + 3); - pat = (char_u *)tofree; + pat = tofree; } if (xp->xp_context == EXPAND_LUA) { ILOG("PAT %s", pat); - return nlua_expand_pat(xp, pat, num_file, file); + return nlua_expand_pat(xp, pat, numMatches, matches); } - regmatch.regprog = vim_regcomp((char *)pat, p_magic ? RE_MAGIC : 0); - if (regmatch.regprog == NULL) { - return FAIL; - } + if (!fuzzy) { + regmatch.regprog = vim_regcomp(pat, magic_isset() ? RE_MAGIC : 0); + if (regmatch.regprog == NULL) { + return FAIL; + } - // set ignore-case according to p_ic, p_scs and pat - regmatch.rm_ic = ignorecase(pat); + // set ignore-case according to p_ic, p_scs and pat + regmatch.rm_ic = ignorecase(pat); + } if (xp->xp_context == EXPAND_SETTINGS || xp->xp_context == EXPAND_BOOL_SETTINGS) { - ret = ExpandSettings(xp, ®match, num_file, file); + ret = ExpandSettings(xp, ®match, pat, numMatches, matches, fuzzy); } else if (xp->xp_context == EXPAND_MAPPINGS) { - ret = ExpandMappings(®match, num_file, file); + ret = ExpandMappings(pat, ®match, numMatches, matches); } else if (xp->xp_context == EXPAND_USER_DEFINED) { - ret = ExpandUserDefined(xp, ®match, num_file, file); + ret = ExpandUserDefined(pat, xp, ®match, matches, numMatches); } else { - ret = ExpandOther(xp, ®match, num_file, file); + ret = ExpandOther(pat, xp, ®match, matches, numMatches); } - vim_regfree(regmatch.regprog); + if (!fuzzy) { + vim_regfree(regmatch.regprog); + } xfree(tofree); return ret; @@ -2234,86 +2786,159 @@ static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char ***f /// program. Matching strings are copied into an array, which is returned. /// /// @param func returns a string from the list -static void ExpandGeneric(expand_T *xp, regmatch_T *regmatch, int *num_file, char ***file, - CompleteListItemGetter func, int escaped) +static void ExpandGeneric(const char *const pat, expand_T *xp, regmatch_T *regmatch, + char ***matches, int *numMatches, CompleteListItemGetter func, + int escaped) { - int i; - size_t count = 0; - char *str; + const bool fuzzy = cmdline_fuzzy_complete(pat); + *matches = NULL; + *numMatches = 0; - // count the number of matching names - for (i = 0;; i++) { - str = (*func)(xp, i); - if (str == NULL) { // end of list - break; - } - if (*str == NUL) { // skip empty strings - continue; - } - if (vim_regexec(regmatch, str, (colnr_T)0)) { - count++; - } - } - if (count == 0) { - return; + garray_T ga; + if (!fuzzy) { + ga_init(&ga, sizeof(char *), 30); + } else { + ga_init(&ga, sizeof(fuzmatch_str_T), 30); } - assert(count < INT_MAX); - *num_file = (int)count; - *file = xmalloc(count * sizeof(char_u *)); - // copy the matching names into allocated memory - count = 0; - for (i = 0;; i++) { - str = (*func)(xp, i); + for (int i = 0;; i++) { + char *str = (*func)(xp, i); if (str == NULL) { // End of list. break; } if (*str == NUL) { // Skip empty strings. continue; } - if (vim_regexec(regmatch, str, (colnr_T)0)) { - if (escaped) { - str = (char *)vim_strsave_escaped((char_u *)str, (char_u *)" \t\\."); + + bool match; + int score = 0; + if (xp->xp_pattern[0] != NUL) { + if (!fuzzy) { + match = vim_regexec(regmatch, str, (colnr_T)0); } else { - str = xstrdup(str); + score = fuzzy_match_str(str, pat); + match = (score != 0); } - (*file)[count++] = str; - if (func == get_menu_names) { - // Test for separator added by get_menu_names(). - str += strlen(str) - 1; - if (*str == '\001') { - *str = '.'; - } + } else { + match = true; + } + + if (!match) { + continue; + } + + if (escaped) { + str = vim_strsave_escaped(str, " \t\\."); + } else { + str = xstrdup(str); + } + + if (fuzzy) { + GA_APPEND(fuzmatch_str_T, &ga, ((fuzmatch_str_T){ + .idx = ga.ga_len, + .str = str, + .score = score, + })); + } else { + GA_APPEND(char *, &ga, str); + } + + if (func == get_menu_names) { + // Test for separator added by get_menu_names(). + str += strlen(str) - 1; + if (*str == '\001') { + *str = '.'; } } } - // Sort the results. Keep menu's in the specified order. - if (xp->xp_context != EXPAND_MENUNAMES && xp->xp_context != EXPAND_MENUS) { - if (xp->xp_context == EXPAND_EXPRESSION - || xp->xp_context == EXPAND_FUNCTIONS - || xp->xp_context == EXPAND_USER_FUNC) { + if (ga.ga_len == 0) { + return; + } + + // Sort the matches when using regular expression matching and sorting + // applies to the completion context. Menus and scriptnames should be kept + // in the specified order. + const bool sort_matches = !fuzzy + && xp->xp_context != EXPAND_MENUNAMES + && xp->xp_context != EXPAND_MENUS + && xp->xp_context != EXPAND_SCRIPTNAMES; + + // <SNR> functions should be sorted to the end. + const bool funcsort = xp->xp_context == EXPAND_EXPRESSION + || xp->xp_context == EXPAND_FUNCTIONS + || xp->xp_context == EXPAND_USER_FUNC; + + // Sort the matches. + if (sort_matches) { + if (funcsort) { // <SNR> functions should be sorted to the end. - qsort((void *)(*file), (size_t)(*num_file), sizeof(char *), sort_func_compare); + qsort(ga.ga_data, (size_t)ga.ga_len, sizeof(char *), sort_func_compare); } else { - sort_strings(*file, *num_file); + sort_strings(ga.ga_data, ga.ga_len); } } + if (!fuzzy) { + *matches = ga.ga_data; + *numMatches = ga.ga_len; + } else { + fuzzymatches_to_strmatches(ga.ga_data, matches, ga.ga_len, funcsort); + *numMatches = ga.ga_len; + } + // Reset the variables used for special highlight names expansion, so that // they don't show up when getting normal highlight names by ID. reset_expand_highlight(); } +/// Expand shell command matches in one directory of $PATH. +static void expand_shellcmd_onedir(char *buf, char *s, size_t l, char *pat, char ***matches, + int *numMatches, int flags, hashtab_T *ht, garray_T *gap) +{ + xstrlcpy(buf, s, l + 1); + add_pathsep(buf); + l = strlen(buf); + xstrlcpy(buf + l, pat, MAXPATHL - l); + + // Expand matches in one directory of $PATH. + int ret = expand_wildcards(1, &buf, numMatches, matches, flags); + if (ret != OK) { + return; + } + + ga_grow(gap, *numMatches); + + for (int i = 0; i < *numMatches; i++) { + char *name = (*matches)[i]; + + if (strlen(name) > l) { + // Check if this name was already found. + hash_T hash = hash_hash(name + l); + hashitem_T *hi = + hash_lookup(ht, (const char *)(name + l), strlen(name + l), hash); + if (HASHITEM_EMPTY(hi)) { + // Remove the path that was prepended. + STRMOVE(name, name + l); + ((char **)gap->ga_data)[gap->ga_len++] = name; + hash_add_item(ht, hi, name, hash); + name = NULL; + } + } + xfree(name); + } + xfree(*matches); +} + /// Complete a shell command. /// -/// @param filepat is a pattern to match with command names. -/// @param[out] num_file is pointer to number of matches. -/// @param[out] file is pointer to array of pointers to matches. -/// *file will either be set to NULL or point to -/// allocated memory. -/// @param flagsarg is a combination of EW_* flags. -static void expand_shellcmd(char *filepat, int *num_file, char ***file, int flagsarg) +/// @param filepat is a pattern to match with command names. +/// @param[out] matches is pointer to array of pointers to matches. +/// *matches will either be set to NULL or point to +/// allocated memory. +/// @param[out] numMatches is pointer to number of matches. +/// @param flagsarg is a combination of EW_* flags. +static void expand_shellcmd(char *filepat, char ***matches, int *numMatches, int flagsarg) FUNC_ATTR_NONNULL_ALL { char *pat; @@ -2324,7 +2949,6 @@ static void expand_shellcmd(char *filepat, int *num_file, char ***file, int flag size_t l; char *s, *e; int flags = flagsarg; - int ret; bool did_curdir = false; // for ":set path=" and ":set tags=" halve backslashes for escaped space @@ -2343,7 +2967,7 @@ static void expand_shellcmd(char *filepat, int *num_file, char ***file, int flag path = "."; } else { // For an absolute name we don't use $PATH. - if (!path_is_absolute((char_u *)pat)) { + if (!path_is_absolute(pat)) { path = vim_getenv("PATH"); } if (path == NULL) { @@ -2354,7 +2978,7 @@ static void expand_shellcmd(char *filepat, int *num_file, char ***file, int flag } // Go over all directories in $PATH. Expand matches in that directory and - // collect them in "ga". When "." is not in $PATH also expaned for the + // collect them in "ga". When "." is not in $PATH also expand for the // current directory, to find "subdir/cmd". ga_init(&ga, (int)sizeof(char *), 10); hashtab_T found_ht; @@ -2372,7 +2996,7 @@ static void expand_shellcmd(char *filepat, int *num_file, char ***file, int flag // Find directories in the current directory, path is empty. did_curdir = true; flags |= EW_DIR; - } else if (STRNCMP(s, ".", e - s) == 0) { + } else if (strncmp(s, ".", (size_t)(e - s)) == 0) { did_curdir = true; flags |= EW_DIR; } else { @@ -2384,44 +3008,13 @@ static void expand_shellcmd(char *filepat, int *num_file, char ***file, int flag if (l > MAXPATHL - 5) { break; } - STRLCPY(buf, s, l + 1); - add_pathsep(buf); - l = strlen(buf); - STRLCPY(buf + l, pat, MAXPATHL - l); - - // Expand matches in one directory of $PATH. - ret = expand_wildcards(1, &buf, num_file, file, flags); - if (ret == OK) { - ga_grow(&ga, *num_file); - { - for (i = 0; i < *num_file; i++) { - char_u *name = (char_u *)(*file)[i]; - - if (STRLEN(name) > l) { - // Check if this name was already found. - hash_T hash = hash_hash(name + l); - hashitem_T *hi = - hash_lookup(&found_ht, (const char *)(name + l), - STRLEN(name + l), hash); - if (HASHITEM_EMPTY(hi)) { - // Remove the path that was prepended. - STRMOVE(name, name + l); - ((char_u **)ga.ga_data)[ga.ga_len++] = name; - hash_add_item(&found_ht, hi, name, hash); - name = NULL; - } - } - xfree(name); - } - xfree(*file); - } - } + expand_shellcmd_onedir(buf, s, l, pat, matches, numMatches, flags, &found_ht, &ga); if (*e != NUL) { e++; } } - *file = ga.ga_data; - *num_file = ga.ga_len; + *matches = ga.ga_data; + *numMatches = ga.ga_len; xfree(buf); xfree(pat); @@ -2433,8 +3026,7 @@ static void expand_shellcmd(char *filepat, int *num_file, char ***file, int flag /// Call "user_expand_func()" to invoke a user defined Vim script function and /// return the result (either a string, a List or NULL). -static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T *xp, int *num_file, - char ***file) +static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T *xp) FUNC_ATTR_NONNULL_ALL { CmdlineInfo *const ccline = get_cmdline_info(); @@ -2446,8 +3038,6 @@ static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T if (xp->xp_arg == NULL || xp->xp_arg[0] == '\0' || xp->xp_line == NULL) { return NULL; } - *num_file = 0; - *file = NULL; if (ccline->cmdbuff != NULL) { keep = ccline->cmdbuff[ccline->cmdlen]; @@ -2465,7 +3055,7 @@ static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T current_sctx = xp->xp_script_ctx; - void *const ret = user_expand_func((char_u *)xp->xp_arg, 3, args); + void *const ret = user_expand_func(xp->xp_arg, 3, args); current_sctx = save_current_sctx; if (ccline->cmdbuff != NULL) { @@ -2476,21 +3066,28 @@ static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T return ret; } -/// Expand names with a function defined by the user. -static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, char ***file) +/// Expand names with a function defined by the user (EXPAND_USER_DEFINED and +/// EXPAND_USER_LIST). +static int ExpandUserDefined(const char *const pat, expand_T *xp, regmatch_T *regmatch, + char ***matches, int *numMatches) { - char *e; - garray_T ga; - - char *const retstr = call_user_expand_func((user_expand_func_T)call_func_retstr, xp, num_file, - file); + const bool fuzzy = cmdline_fuzzy_complete(pat); + *matches = NULL; + *numMatches = 0; + char *const retstr = call_user_expand_func((user_expand_func_T)call_func_retstr, xp); if (retstr == NULL) { return FAIL; } - ga_init(&ga, (int)sizeof(char *), 3); - for (char *s = retstr; *s != NUL; s = e) { + garray_T ga; + if (!fuzzy) { + ga_init(&ga, (int)sizeof(char *), 3); + } else { + ga_init(&ga, (int)sizeof(fuzmatch_str_T), 3); + } + + for (char *s = retstr, *e; *s != NUL; s = e) { e = vim_strchr(s, '\n'); if (e == NULL) { e = s + strlen(s); @@ -2498,10 +3095,31 @@ static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, const char keep = *e; *e = NUL; - const bool skip = xp->xp_pattern[0] && vim_regexec(regmatch, s, (colnr_T)0) == 0; + bool match; + int score = 0; + if (xp->xp_pattern[0] != NUL) { + if (!fuzzy) { + match = vim_regexec(regmatch, s, (colnr_T)0); + } else { + score = fuzzy_match_str(s, pat); + match = (score != 0); + } + } else { + match = true; // match everything + } + *e = keep; - if (!skip) { - GA_APPEND(char *, &ga, xstrnsave(s, (size_t)(e - s))); + + if (match) { + if (!fuzzy) { + GA_APPEND(char *, &ga, xstrnsave(s, (size_t)(e - s))); + } else { + GA_APPEND(fuzmatch_str_T, &ga, ((fuzmatch_str_T){ + .idx = ga.ga_len, + .str = xstrnsave(s, (size_t)(e - s)), + .score = score, + })); + } } if (*e != NUL) { @@ -2509,16 +3127,27 @@ static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, } } xfree(retstr); - *file = ga.ga_data; - *num_file = ga.ga_len; + + if (ga.ga_len == 0) { + return OK; + } + + if (!fuzzy) { + *matches = ga.ga_data; + *numMatches = ga.ga_len; + } else { + fuzzymatches_to_strmatches(ga.ga_data, matches, ga.ga_len, false); + *numMatches = ga.ga_len; + } return OK; } /// Expand names with a list returned by a function defined by the user. -static int ExpandUserList(expand_T *xp, int *num_file, char ***file) +static int ExpandUserList(expand_T *xp, char ***matches, int *numMatches) { - list_T *const retlist = call_user_expand_func((user_expand_func_T)call_func_retlist, xp, num_file, - file); + *matches = NULL; + *numMatches = 0; + list_T *const retlist = call_user_expand_func((user_expand_func_T)call_func_retlist, xp); if (retlist == NULL) { return FAIL; } @@ -2536,8 +3165,8 @@ static int ExpandUserList(expand_T *xp, int *num_file, char ***file) }); tv_list_unref(retlist); - *file = ga.ga_data; - *num_file = ga.ga_len; + *matches = ga.ga_data; + *numMatches = ga.ga_len; return OK; } @@ -2578,19 +3207,19 @@ void globpath(char *path, char *file, garray_T *ga, int expand_options) ExpandInit(&xpc); xpc.xp_context = EXPAND_FILES; - char_u *buf = xmalloc(MAXPATHL); + char *buf = xmalloc(MAXPATHL); // Loop over all entries in {path}. while (*path != NUL) { // Copy one item of the path to buf[] and concatenate the file name. - copy_option_part(&path, (char *)buf, MAXPATHL, ","); - if (STRLEN(buf) + strlen(file) + 2 < MAXPATHL) { - add_pathsep((char *)buf); + copy_option_part(&path, buf, MAXPATHL, ","); + if (strlen(buf) + strlen(file) + 2 < MAXPATHL) { + add_pathsep(buf); STRCAT(buf, file); // NOLINT char **p; int num_p = 0; - (void)ExpandFromContext(&xpc, buf, &num_p, &p, + (void)ExpandFromContext(&xpc, buf, &p, &num_p, WILD_SILENT | expand_options); if (num_p > 0) { ExpandEscape(&xpc, buf, num_p, p, WILD_SILENT | expand_options); @@ -2645,145 +3274,158 @@ static void cmdline_del(CmdlineInfo *cclp, int from) cclp->cmdpos = from; } -/// Handle a key pressed when wild menu is displayed -int wildmenu_process_key(CmdlineInfo *cclp, int key, expand_T *xp) +/// Handle a key pressed when the wild menu for the menu names +/// (EXPAND_MENUNAMES) is displayed. +static int wildmenu_process_key_menunames(CmdlineInfo *cclp, int key, expand_T *xp) { - int c = key; - - // Special translations for 'wildmenu' - if (xp->xp_context == EXPAND_MENUNAMES) { - // Hitting <Down> after "emenu Name.": complete submenu - if (c == K_DOWN && cclp->cmdpos > 0 - && cclp->cmdbuff[cclp->cmdpos - 1] == '.') { - c = (int)p_wc; - KeyTyped = true; // in case the key was mapped - } else if (c == K_UP) { - // Hitting <Up>: Remove one submenu name in front of the - // cursor - int found = false; - - int j = (int)(xp->xp_pattern - cclp->cmdbuff); - int i = 0; - while (--j > 0) { - // check for start of menu name - if (cclp->cmdbuff[j] == ' ' - && cclp->cmdbuff[j - 1] != '\\') { + // Hitting <Down> after "emenu Name.": complete submenu + if (key == K_DOWN && cclp->cmdpos > 0 + && cclp->cmdbuff[cclp->cmdpos - 1] == '.') { + key = (int)p_wc; + KeyTyped = true; // in case the key was mapped + } else if (key == K_UP) { + // Hitting <Up>: Remove one submenu name in front of the + // cursor + int found = false; + + int j = (int)(xp->xp_pattern - cclp->cmdbuff); + int i = 0; + while (--j > 0) { + // check for start of menu name + if (cclp->cmdbuff[j] == ' ' + && cclp->cmdbuff[j - 1] != '\\') { + i = j + 1; + break; + } + // check for start of submenu name + if (cclp->cmdbuff[j] == '.' + && cclp->cmdbuff[j - 1] != '\\') { + if (found) { i = j + 1; break; - } - - // check for start of submenu name - if (cclp->cmdbuff[j] == '.' - && cclp->cmdbuff[j - 1] != '\\') { - if (found) { - i = j + 1; - break; - } else { - found = true; - } + } else { + found = true; } } - if (i > 0) { - cmdline_del(cclp, i); - } - c = (int)p_wc; - KeyTyped = true; // in case the key was mapped - xp->xp_context = EXPAND_NOTHING; } + if (i > 0) { + cmdline_del(cclp, i); + } + key = (int)p_wc; + KeyTyped = true; // in case the key was mapped + xp->xp_context = EXPAND_NOTHING; } - if (xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_DIRECTORIES - || xp->xp_context == EXPAND_SHELLCMD) { - char_u upseg[5]; - - upseg[0] = PATHSEP; - upseg[1] = '.'; - upseg[2] = '.'; - upseg[3] = PATHSEP; - upseg[4] = NUL; - - if (c == K_DOWN - && cclp->cmdpos > 0 - && cclp->cmdbuff[cclp->cmdpos - 1] == PATHSEP - && (cclp->cmdpos < 3 - || cclp->cmdbuff[cclp->cmdpos - 2] != '.' - || cclp->cmdbuff[cclp->cmdpos - 3] != '.')) { - // go down a directory - c = (int)p_wc; - KeyTyped = true; // in case the key was mapped - } else if (STRNCMP(xp->xp_pattern, upseg + 1, 3) == 0 - && c == K_DOWN) { - // If in a direct ancestor, strip off one ../ to go down - int found = false; - - int j = cclp->cmdpos; - int i = (int)(xp->xp_pattern - cclp->cmdbuff); - while (--j > i) { - j -= utf_head_off(cclp->cmdbuff, cclp->cmdbuff + j); - if (vim_ispathsep(cclp->cmdbuff[j])) { - found = true; - break; - } - } - if (found - && cclp->cmdbuff[j - 1] == '.' - && cclp->cmdbuff[j - 2] == '.' - && (vim_ispathsep(cclp->cmdbuff[j - 3]) || j == i + 2)) { - cmdline_del(cclp, j - 2); - c = (int)p_wc; - KeyTyped = true; // in case the key was mapped + + return key; +} + +/// Handle a key pressed when the wild menu for file names (EXPAND_FILES) or +/// directory names (EXPAND_DIRECTORIES) or shell command names +/// (EXPAND_SHELLCMD) is displayed. +static int wildmenu_process_key_filenames(CmdlineInfo *cclp, int key, expand_T *xp) +{ + char upseg[5]; + upseg[0] = PATHSEP; + upseg[1] = '.'; + upseg[2] = '.'; + upseg[3] = PATHSEP; + upseg[4] = NUL; + + if (key == K_DOWN + && cclp->cmdpos > 0 + && cclp->cmdbuff[cclp->cmdpos - 1] == PATHSEP + && (cclp->cmdpos < 3 + || cclp->cmdbuff[cclp->cmdpos - 2] != '.' + || cclp->cmdbuff[cclp->cmdpos - 3] != '.')) { + // go down a directory + key = (int)p_wc; + KeyTyped = true; // in case the key was mapped + } else if (strncmp(xp->xp_pattern, upseg + 1, 3) == 0 && key == K_DOWN) { + // If in a direct ancestor, strip off one ../ to go down + int found = false; + + int j = cclp->cmdpos; + int i = (int)(xp->xp_pattern - cclp->cmdbuff); + while (--j > i) { + j -= utf_head_off(cclp->cmdbuff, cclp->cmdbuff + j); + if (vim_ispathsep(cclp->cmdbuff[j])) { + found = true; + break; } - } else if (c == K_UP) { - // go up a directory - int found = false; - - int j = cclp->cmdpos - 1; - int i = (int)(xp->xp_pattern - cclp->cmdbuff); - while (--j > i) { - j -= utf_head_off(cclp->cmdbuff, cclp->cmdbuff + j); - if (vim_ispathsep(cclp->cmdbuff[j]) + } + if (found + && cclp->cmdbuff[j - 1] == '.' + && cclp->cmdbuff[j - 2] == '.' + && (vim_ispathsep(cclp->cmdbuff[j - 3]) || j == i + 2)) { + cmdline_del(cclp, j - 2); + key = (int)p_wc; + KeyTyped = true; // in case the key was mapped + } + } else if (key == K_UP) { + // go up a directory + int found = false; + + int j = cclp->cmdpos - 1; + int i = (int)(xp->xp_pattern - cclp->cmdbuff); + while (--j > i) { + j -= utf_head_off(cclp->cmdbuff, cclp->cmdbuff + j); + if (vim_ispathsep(cclp->cmdbuff[j]) #ifdef BACKSLASH_IN_FILENAME - && vim_strchr((const char_u *)" *?[{`$%#", cclp->cmdbuff[j + 1]) - == NULL + && vim_strchr(" *?[{`$%#", (uint8_t)cclp->cmdbuff[j + 1]) == NULL #endif - ) { - if (found) { - i = j + 1; - break; - } else { - found = true; - } + ) { + if (found) { + i = j + 1; + break; + } else { + found = true; } } + } - if (!found) { - j = i; - } else if (STRNCMP(cclp->cmdbuff + j, upseg, 4) == 0) { - j += 4; - } else if (STRNCMP(cclp->cmdbuff + j, upseg + 1, 3) == 0 - && j == i) { - j += 3; - } else { - j = 0; - } - - if (j > 0) { - // TODO(tarruda): this is only for DOS/Unix systems - need to put in - // machine-specific stuff here and in upseg init - cmdline_del(cclp, j); - put_on_cmdline(upseg + 1, 3, false); - } else if (cclp->cmdpos > i) { - cmdline_del(cclp, i); - } + if (!found) { + j = i; + } else if (strncmp(cclp->cmdbuff + j, upseg, 4) == 0) { + j += 4; + } else if (strncmp(cclp->cmdbuff + j, upseg + 1, 3) == 0 + && j == i) { + j += 3; + } else { + j = 0; + } - // Now complete in the new directory. Set KeyTyped in case the - // Up key came from a mapping. - c = (int)p_wc; - KeyTyped = true; + if (j > 0) { + // TODO(tarruda): this is only for DOS/Unix systems - need to put in + // machine-specific stuff here and in upseg init + cmdline_del(cclp, j); + put_on_cmdline(upseg + 1, 3, false); + } else if (cclp->cmdpos > i) { + cmdline_del(cclp, i); } + + // Now complete in the new directory. Set KeyTyped in case the + // Up key came from a mapping. + key = (int)p_wc; + KeyTyped = true; } - return c; + return key; +} + +/// Handle a key pressed when wild menu is displayed +int wildmenu_process_key(CmdlineInfo *cclp, int key, expand_T *xp) +{ + // Special translations for 'wildmenu' + if (xp->xp_context == EXPAND_MENUNAMES) { + return wildmenu_process_key_menunames(cclp, key, xp); + } + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_DIRECTORIES + || xp->xp_context == EXPAND_SHELLCMD) { + return wildmenu_process_key_filenames(cclp, key, xp); + } + + return key; } /// Free expanded names when finished walking through the matches @@ -2831,7 +3473,7 @@ void wildmenu_cleanup(CmdlineInfo *cclp) /// "getcompletion()" function void f_getcompletion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - char_u *pat; + char *pat; expand_T xpc; bool filtered = false; int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH @@ -2883,19 +3525,20 @@ void f_getcompletion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) xpc.xp_pattern_len = strlen(xpc.xp_pattern); } - if (xpc.xp_context == EXPAND_CSCOPE) { - set_context_in_cscope_cmd(&xpc, (const char *)xpc.xp_pattern, CMD_cscope); - xpc.xp_pattern_len = strlen(xpc.xp_pattern); - } - if (xpc.xp_context == EXPAND_SIGN) { set_context_in_sign_cmd(&xpc, xpc.xp_pattern); xpc.xp_pattern_len = strlen(xpc.xp_pattern); } theend: - pat = (char_u *)addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); - ExpandOne(&xpc, (char *)pat, NULL, options, WILD_ALL_KEEP); + if (cmdline_fuzzy_completion_supported(&xpc)) { + // when fuzzy matching, don't modify the search string + pat = xstrdup(xpc.xp_pattern); + } else { + pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); + } + + ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); tv_list_alloc_ret(rettv, xpc.xp_numfiles); for (int i = 0; i < xpc.xp_numfiles; i++) { diff --git a/src/nvim/cmdexpand.h b/src/nvim/cmdexpand.h index 93e91af169..810e289f7c 100644 --- a/src/nvim/cmdexpand.h +++ b/src/nvim/cmdexpand.h @@ -19,6 +19,9 @@ enum { WILD_ALL_KEEP = 8, WILD_CANCEL = 9, WILD_APPLY = 10, + WILD_PAGEUP = 11, + WILD_PAGEDOWN = 12, + WILD_PUM_WANT = 13, }; enum { diff --git a/src/nvim/cmdhist.c b/src/nvim/cmdhist.c index 5a59f09113..2df82d9355 100644 --- a/src/nvim/cmdhist.c +++ b/src/nvim/cmdhist.c @@ -3,14 +3,30 @@ // cmdhist.c: Functions for the history of the command-line. +#include <assert.h> +#include <limits.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + #include "nvim/ascii.h" #include "nvim/charset.h" #include "nvim/cmdhist.h" +#include "nvim/eval/typval.h" #include "nvim/ex_cmds.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_getln.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/macros.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option_defs.h" +#include "nvim/pos.h" #include "nvim/regexp.h" #include "nvim/strings.h" -#include "nvim/ui.h" +#include "nvim/types.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -116,56 +132,58 @@ void init_history(void) int newlen = (int)p_hi; int oldlen = hislen; + if (newlen == oldlen) { // history length didn't change + return; + } + // If history tables size changed, reallocate them. // Tables are circular arrays (current position marked by hisidx[type]). // On copying them to the new arrays, we take the chance to reorder them. - if (newlen != oldlen) { - for (int type = 0; type < HIST_COUNT; type++) { - histentry_T *temp = (newlen - ? xmalloc((size_t)newlen * sizeof(*temp)) - : NULL); - - int j = hisidx[type]; - if (j >= 0) { - // old array gets partitioned this way: - // [0 , i1 ) --> newest entries to be deleted - // [i1 , i1 + l1) --> newest entries to be copied - // [i1 + l1 , i2 ) --> oldest entries to be deleted - // [i2 , i2 + l2) --> oldest entries to be copied - int l1 = MIN(j + 1, newlen); // how many newest to copy - int l2 = MIN(newlen, oldlen) - l1; // how many oldest to copy - int i1 = j + 1 - l1; // copy newest from here - int i2 = MAX(l1, oldlen - newlen + l1); // copy oldest from here - - // copy as much entries as they fit to new table, reordering them - if (newlen) { - // copy oldest entries - memcpy(&temp[0], &history[type][i2], (size_t)l2 * sizeof(*temp)); - // copy newest entries - memcpy(&temp[l2], &history[type][i1], (size_t)l1 * sizeof(*temp)); - } - - // delete entries that don't fit in newlen, if any - for (int i = 0; i < i1; i++) { - hist_free_entry(history[type] + i); - } - for (int i = i1 + l1; i < i2; i++) { - hist_free_entry(history[type] + i); - } + for (int type = 0; type < HIST_COUNT; type++) { + histentry_T *temp = (newlen > 0 + ? xmalloc((size_t)newlen * sizeof(*temp)) + : NULL); + + int j = hisidx[type]; + if (j >= 0) { + // old array gets partitioned this way: + // [0 , i1 ) --> newest entries to be deleted + // [i1 , i1 + l1) --> newest entries to be copied + // [i1 + l1 , i2 ) --> oldest entries to be deleted + // [i2 , i2 + l2) --> oldest entries to be copied + int l1 = MIN(j + 1, newlen); // how many newest to copy + int l2 = MIN(newlen, oldlen) - l1; // how many oldest to copy + int i1 = j + 1 - l1; // copy newest from here + int i2 = MAX(l1, oldlen - newlen + l1); // copy oldest from here + + // copy as much entries as they fit to new table, reordering them + if (newlen) { + // copy oldest entries + memcpy(&temp[0], &history[type][i2], (size_t)l2 * sizeof(*temp)); + // copy newest entries + memcpy(&temp[l2], &history[type][i1], (size_t)l1 * sizeof(*temp)); } - // clear remaining space, if any - int l3 = j < 0 ? 0 : MIN(newlen, oldlen); // number of copied entries - if (newlen) { - memset(temp + l3, 0, (size_t)(newlen - l3) * sizeof(*temp)); + // delete entries that don't fit in newlen, if any + for (int i = 0; i < i1; i++) { + hist_free_entry(history[type] + i); + } + for (int i = i1 + l1; i < i2; i++) { + hist_free_entry(history[type] + i); } + } - hisidx[type] = l3 - 1; - xfree(history[type]); - history[type] = temp; + // clear remaining space, if any + int l3 = j < 0 ? 0 : MIN(newlen, oldlen); // number of copied entries + if (newlen > 0) { + memset(temp + l3, 0, (size_t)(newlen - l3) * sizeof(*temp)); } - hislen = newlen; + + hisidx[type] = l3 - 1; + xfree(history[type]); + history[type] = temp; } + hislen = newlen; } static inline void hist_free_entry(histentry_T *hisptr) @@ -203,7 +221,7 @@ static int in_history(int type, char *str, int move_to_front, int sep) // well. char *p = history[type][i].hisstr; if (strcmp(str, p) == 0 - && (type != HIST_SEARCH || sep == p[STRLEN(p) + 1])) { + && (type != HIST_SEARCH || sep == p[strlen(p) + 1])) { if (!move_to_front) { return true; } @@ -215,24 +233,25 @@ static int in_history(int type, char *str, int move_to_front, int sep) } } while (i != hisidx[type]); - if (last_i >= 0) { - list_T *const list = history[type][i].additional_elements; - str = history[type][i].hisstr; - while (i != hisidx[type]) { - if (++i >= hislen) { - i = 0; - } - history[type][last_i] = history[type][i]; - last_i = i; + if (last_i < 0) { + return false; + } + + list_T *const list = history[type][i].additional_elements; + str = history[type][i].hisstr; + while (i != hisidx[type]) { + if (++i >= hislen) { + i = 0; } - tv_list_unref(list); - history[type][i].hisnum = ++hisnum[type]; - history[type][i].hisstr = str; - history[type][i].timestamp = os_time(); - history[type][i].additional_elements = NULL; - return true; - } - return false; + history[type][last_i] = history[type][i]; + last_i = i; + } + tv_list_unref(list); + history[type][i].hisnum = ++hisnum[type]; + history[type][i].hisstr = str; + history[type][i].timestamp = os_time(); + history[type][i].additional_elements = NULL; + return true; } /// Convert history name to its HIST_ equivalent @@ -261,7 +280,7 @@ static HistoryType get_histtype(const char *const name, const size_t len, const } } - if (vim_strchr(":=@>?/", name[0]) != NULL && len == 1) { + if (vim_strchr(":=@>?/", (uint8_t)name[0]) != NULL && len == 1) { return hist_char2type(name[0]); } @@ -304,24 +323,27 @@ void add_to_history(int histype, char *new_entry, int in_map, int sep) } last_maptick = -1; } - if (!in_history(histype, new_entry, true, sep)) { - if (++hisidx[histype] == hislen) { - hisidx[histype] = 0; - } - hisptr = &history[histype][hisidx[histype]]; - hist_free_entry(hisptr); - - // Store the separator after the NUL of the string. - size_t len = strlen(new_entry); - hisptr->hisstr = xstrnsave(new_entry, len + 2); - hisptr->timestamp = os_time(); - hisptr->additional_elements = NULL; - hisptr->hisstr[len + 1] = (char)sep; - - hisptr->hisnum = ++hisnum[histype]; - if (histype == HIST_SEARCH && in_map) { - last_maptick = maptick; - } + + if (in_history(histype, new_entry, true, sep)) { + return; + } + + if (++hisidx[histype] == hislen) { + hisidx[histype] = 0; + } + hisptr = &history[histype][hisidx[histype]]; + hist_free_entry(hisptr); + + // Store the separator after the NUL of the string. + size_t len = strlen(new_entry); + hisptr->hisstr = xstrnsave(new_entry, len + 2); + hisptr->timestamp = os_time(); + hisptr->additional_elements = NULL; + hisptr->hisstr[len + 1] = (char)sep; + + hisptr->hisnum = ++hisnum[histype]; + if (histype == HIST_SEARCH && in_map) { + last_maptick = maptick; } } @@ -415,49 +437,50 @@ int clr_history(const int histype) /// Remove all entries matching {str} from a history. /// /// @param histype may be one of the HIST_ values. -static int del_history_entry(int histype, char_u *str) +static int del_history_entry(int histype, char *str) { + if (hislen == 0 || histype < 0 || histype >= HIST_COUNT || *str == NUL + || hisidx[histype] < 0) { + return false; + } + + const int idx = hisidx[histype]; regmatch_T regmatch; - histentry_T *hisptr; - int idx; - int i; - int last; - bool found = false; + regmatch.regprog = vim_regcomp(str, RE_MAGIC + RE_STRING); + if (regmatch.regprog == NULL) { + return false; + } - regmatch.regprog = NULL; regmatch.rm_ic = false; // always match case - if (hislen != 0 - && histype >= 0 - && histype < HIST_COUNT - && *str != NUL - && (idx = hisidx[histype]) >= 0 - && (regmatch.regprog = vim_regcomp((char *)str, RE_MAGIC + RE_STRING)) != NULL) { - i = last = idx; - do { - hisptr = &history[histype][i]; - if (hisptr->hisstr == NULL) { - break; - } - if (vim_regexec(®match, hisptr->hisstr, (colnr_T)0)) { - found = true; - hist_free_entry(hisptr); - } else { - if (i != last) { - history[histype][last] = *hisptr; - clear_hist_entry(hisptr); - } - if (--last < 0) { - last += hislen; - } + + bool found = false; + int i = idx, last = idx; + do { + histentry_T *hisptr = &history[histype][i]; + if (hisptr->hisstr == NULL) { + break; + } + if (vim_regexec(®match, hisptr->hisstr, (colnr_T)0)) { + found = true; + hist_free_entry(hisptr); + } else { + if (i != last) { + history[histype][last] = *hisptr; + clear_hist_entry(hisptr); } - if (--i < 0) { - i += hislen; + if (--last < 0) { + last += hislen; } - } while (i != idx); - if (history[histype][idx].hisstr == NULL) { - hisidx[histype] = -1; } + if (--i < 0) { + i += hislen; + } + } while (i != idx); + + if (history[histype][idx].hisstr == NULL) { + hisidx[histype] = -1; } + vim_regfree(regmatch.regprog); return found; } @@ -504,16 +527,19 @@ void f_histadd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID; - if (histype != HIST_INVALID) { - char buf[NUMBUFLEN]; - str = tv_get_string_buf(&argvars[1], buf); - if (*str != NUL) { - init_history(); - add_to_history(histype, (char *)str, false, NUL); - rettv->vval.v_number = true; - return; - } + if (histype == HIST_INVALID) { + return; + } + + char buf[NUMBUFLEN]; + str = tv_get_string_buf(&argvars[1], buf); + if (*str == NUL) { + return; } + + init_history(); + add_to_history(histype, (char *)str, false, NUL); + rettv->vval.v_number = true; } /// "histdel()" function @@ -534,7 +560,7 @@ void f_histdel(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // string given: remove all matching entries char buf[NUMBUFLEN]; n = del_history_entry(get_histtype(str, strlen(str), false), - (char_u *)tv_get_string_buf(&argvars[1], buf)); + (char *)tv_get_string_buf(&argvars[1], buf)); } rettv->vval.v_number = n; } @@ -585,7 +611,7 @@ void ex_history(exarg_T *eap) int idx; int i, j, k; char *end; - char_u *arg = (char_u *)eap->arg; + char *arg = eap->arg; if (hislen == 0) { msg(_("'history' option is zero")); @@ -593,12 +619,12 @@ void ex_history(exarg_T *eap) } if (!(ascii_isdigit(*arg) || *arg == '-' || *arg == ',')) { - end = (char *)arg; + end = arg; while (ASCII_ISALPHA(*end) - || vim_strchr(":=@>/?", *end) != NULL) { + || vim_strchr(":=@>/?", (uint8_t)(*end)) != NULL) { end++; } - histype1 = get_histtype((const char *)arg, (size_t)(end - (char *)arg), false); + histype1 = get_histtype(arg, (size_t)(end - arg), false); if (histype1 == HIST_INVALID) { if (STRNICMP(arg, "all", end - (char *)arg) == 0) { histype1 = 0; @@ -611,7 +637,7 @@ void ex_history(exarg_T *eap) histype2 = histype1; } } else { - end = (char *)arg; + end = arg; } if (!get_list_range(&end, &hisidx1, &hisidx2) || *end != NUL) { semsg(_(e_trailing_arg), end); @@ -622,7 +648,7 @@ void ex_history(exarg_T *eap) STRCPY(IObuff, "\n # "); assert(history_names[histype1] != NULL); STRCAT(STRCAT(IObuff, history_names[histype1]), " history"); - msg_puts_title((char *)IObuff); + msg_puts_title(IObuff); idx = hisidx[histype1]; hist = history[histype1]; j = hisidx1; @@ -641,15 +667,15 @@ void ex_history(exarg_T *eap) if (hist[i].hisstr != NULL && hist[i].hisnum >= j && hist[i].hisnum <= k) { msg_putchar('\n'); - snprintf((char *)IObuff, IOSIZE, "%c%6d ", i == idx ? '>' : ' ', + snprintf(IObuff, IOSIZE, "%c%6d ", i == idx ? '>' : ' ', hist[i].hisnum); if (vim_strsize(hist[i].hisstr) > Columns - 10) { - trunc_string(hist[i].hisstr, (char *)IObuff + strlen(IObuff), + trunc_string(hist[i].hisstr, IObuff + strlen(IObuff), Columns - 10, IOSIZE - (int)strlen(IObuff)); } else { STRCAT(IObuff, hist[i].hisstr); } - msg_outtrans((char *)IObuff); + msg_outtrans(IObuff); } if (i == idx) { break; diff --git a/src/nvim/cmdhist.h b/src/nvim/cmdhist.h index 8b7bb7ac24..f86a2f855c 100644 --- a/src/nvim/cmdhist.h +++ b/src/nvim/cmdhist.h @@ -2,6 +2,7 @@ #define NVIM_CMDHIST_H #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/os/time.h" diff --git a/src/nvim/context.c b/src/nvim/context.c index 34692cdf64..9de6c16536 100644 --- a/src/nvim/context.c +++ b/src/nvim/context.c @@ -3,14 +3,26 @@ // Context: snapshot of the entire editor state as one big object/map +#include <assert.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + #include "nvim/api/private/converter.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" #include "nvim/api/vimscript.h" #include "nvim/context.h" #include "nvim/eval/encode.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" #include "nvim/ex_docmd.h" +#include "nvim/gettext.h" +#include "nvim/hashtab.h" +#include "nvim/keycodes.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/option.h" #include "nvim/shada.h" @@ -131,7 +143,7 @@ bool ctx_restore(Context *ctx, const int flags) } char *op_shada; - get_option_value("shada", NULL, &op_shada, OPT_GLOBAL); + get_option_value("shada", NULL, &op_shada, NULL, OPT_GLOBAL); set_option_value("shada", 0L, "!,'100,%", OPT_GLOBAL); if (flags & kCtxRegs) { @@ -251,12 +263,12 @@ static inline void ctx_save_funcs(Context *ctx, bool scriptonly) Error err = ERROR_INIT; HASHTAB_ITER(func_tbl_get(), hi, { - const char_u *const name = hi->hi_key; - bool islambda = (STRNCMP(name, "<lambda>", 8) == 0); - bool isscript = (name[0] == K_SPECIAL); + const char *const name = hi->hi_key; + bool islambda = (strncmp(name, "<lambda>", 8) == 0); + bool isscript = ((uint8_t)name[0] == K_SPECIAL); if (!islambda && (!scriptonly || isscript)) { - size_t cmd_len = sizeof("func! ") + STRLEN(name); + 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(VIML_INTERNAL_CALL, cstr_as_string(cmd), diff --git a/src/nvim/context.h b/src/nvim/context.h index ae77e66516..7a1224d876 100644 --- a/src/nvim/context.h +++ b/src/nvim/context.h @@ -2,6 +2,8 @@ #define NVIM_CONTEXT_H #include <msgpack.h> +#include <msgpack/sbuffer.h> +#include <stddef.h> #include "klib/kvec.h" #include "nvim/api/private/defs.h" diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index 6c0475822b..b1dbc68ea3 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -1,24 +1,31 @@ // 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 <assert.h> #include <inttypes.h> #include <stdbool.h> +#include <string.h> #include "nvim/ascii.h" #include "nvim/assert.h" +#include "nvim/buffer_defs.h" #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" -#include "nvim/extmark.h" #include "nvim/fold.h" +#include "nvim/globals.h" +#include "nvim/macros.h" #include "nvim/mark.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" +#include "nvim/pos.h" #include "nvim/state.h" +#include "nvim/types.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -101,10 +108,10 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a || (VIsual_active && *p_sel != 'o') || ((get_ve_flags() & VE_ONEMORE) && wcol < MAXCOL); - char_u *line = (char_u *)ml_get_buf(curbuf, pos->lnum, false); + char *line = ml_get_buf(curbuf, pos->lnum, false); if (wcol >= MAXCOL) { - idx = (int)STRLEN(line) - 1 + one_more; + idx = (int)strlen(line) - 1 + one_more; col = wcol; if ((addspaces || finetune) && !VIsual_active) { @@ -138,7 +145,7 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a } chartabsize_T cts; - init_chartabsize_arg(&cts, curwin, pos->lnum, 0, (char *)line, (char *)line); + init_chartabsize_arg(&cts, curwin, pos->lnum, 0, line, line); while (cts.cts_vcol <= wcol && *cts.cts_ptr != NUL) { // Count a tab for what it's worth (if list mode not on) csize = win_lbr_chartabsize(&cts, &head); @@ -146,7 +153,7 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a cts.cts_vcol += csize; } col = cts.cts_vcol; - idx = (int)(cts.cts_ptr - (char *)line); + idx = (int)(cts.cts_ptr - line); clear_chartabsize_arg(&cts); // Handle all the special cases. The virtual_active() check @@ -171,19 +178,19 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a int correct = wcol - col; size_t newline_size; STRICT_ADD(idx, correct, &newline_size, size_t); - char_u *newline = xmallocz(newline_size); + char *newline = xmallocz(newline_size); memcpy(newline, line, (size_t)idx); memset(newline + idx, ' ', (size_t)correct); - ml_replace(pos->lnum, (char *)newline, false); + ml_replace(pos->lnum, newline, false); inserted_bytes(pos->lnum, (colnr_T)idx, 0, correct); idx += correct; col = wcol; } else { // Break a tab - int linelen = (int)STRLEN(line); + int linelen = (int)strlen(line); int correct = wcol - col - csize + 1; // negative!! - char_u *newline; + char *newline; if (-correct > csize) { return FAIL; @@ -201,7 +208,7 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a STRICT_SUB(n, 1, &n, size_t); memcpy(newline + idx + csize, line + idx + 1, n); - ml_replace(pos->lnum, (char *)newline, false); + ml_replace(pos->lnum, newline, false); inserted_bytes(pos->lnum, idx, 1, csize); idx += (csize - 1 + correct); col += correct; @@ -308,8 +315,8 @@ void check_pos(buf_T *buf, pos_T *pos) } if (pos->col > 0) { - char_u *line = (char_u *)ml_get_buf(buf, pos->lnum, false); - colnr_T len = (colnr_T)STRLEN(line); + char *line = ml_get_buf(buf, pos->lnum, false); + colnr_T len = (colnr_T)strlen(line); if (pos->col > len) { pos->col = len; } @@ -449,12 +456,13 @@ bool leftcol_changed(void) // 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)) { + long siso = get_sidescrolloff_value(curwin); + if (curwin->w_virtcol > (colnr_T)(lastcol - siso)) { retval = true; - coladvance((colnr_T)(lastcol - p_siso)); - } else if (curwin->w_virtcol < curwin->w_leftcol + p_siso) { + coladvance((colnr_T)(lastcol - siso)); + } else if (curwin->w_virtcol < curwin->w_leftcol + siso) { retval = true; - coladvance((colnr_T)(curwin->w_leftcol + p_siso)); + coladvance((colnr_T)(curwin->w_leftcol + siso)); } // If the start of the character under the cursor is not on the screen, diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c index 6f4f17659a..f21e632036 100644 --- a/src/nvim/cursor_shape.c +++ b/src/nvim/cursor_shape.c @@ -1,15 +1,23 @@ // 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 <assert.h> +#include <stdbool.h> #include <stdint.h> +#include <string.h> +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/charset.h" #include "nvim/cursor_shape.h" #include "nvim/ex_getln.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/highlight_group.h" +#include "nvim/log.h" +#include "nvim/macros.h" +#include "nvim/memory.h" +#include "nvim/option_defs.h" #include "nvim/strings.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -19,8 +27,7 @@ #endif /// Handling of cursor and mouse pointer shapes in various modes. -cursorentry_T shape_table[SHAPE_IDX_COUNT] = -{ +cursorentry_T shape_table[SHAPE_IDX_COUNT] = { // Values are set by 'guicursor' and 'mouseshape'. // Adjust the SHAPE_IDX_ defines when changing this! { "normal", 0, 0, 0, 700L, 400L, 250L, 0, 0, "n", SHAPE_CURSOR + SHAPE_MOUSE }, diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c index 3e0cf82740..f7e70a78ce 100644 --- a/src/nvim/debugger.c +++ b/src/nvim/debugger.c @@ -5,17 +5,33 @@ /// /// Vim script debugger functions +#include <inttypes.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/debugger.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" +#include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/globals.h" +#include "nvim/keycodes.h" +#include "nvim/macros.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/os/os.h" +#include "nvim/path.h" #include "nvim/pos.h" #include "nvim/regexp.h" #include "nvim/runtime.h" @@ -114,6 +130,7 @@ void do_debug(char *cmd) for (;;) { msg_scroll = true; need_wait_return = false; + // Save the current typeahead buffer and replace it with an empty one. // This makes sure we get input from the user here and don't interfere // with the commands being executed. Reset "ex_normal_busy" to avoid @@ -300,13 +317,15 @@ static int get_maxbacktrace_level(char *sname) { int maxbacktrace = 0; - if (sname != NULL) { - char *p = sname; - char *q; - while ((q = strstr(p, "..")) != NULL) { - p = q + 2; - maxbacktrace++; - } + if (sname == NULL) { + return 0; + } + + char *p = sname; + char *q; + while ((q = strstr(p, "..")) != NULL) { + p = q + 2; + maxbacktrace++; } return maxbacktrace; } @@ -384,7 +403,7 @@ void ex_debug(exarg_T *eap) debug_break_level = debug_break_level_save; } -static char_u *debug_breakpoint_name = NULL; +static char *debug_breakpoint_name = NULL; static linenr_T debug_breakpoint_lnum; /// When debugging or a breakpoint is set on a skipped command, no debug prompt @@ -393,7 +412,7 @@ static linenr_T debug_breakpoint_lnum; /// a skipped command decides itself that a debug prompt should be displayed, it /// can do so by calling dbg_check_skipped(). static int debug_skipped; -static char_u *debug_skipped_name; +static char *debug_skipped_name; /// Go to debug mode when a breakpoint was encountered or "ex_nesting_level" is /// at or below the break level. But only when the line is actually @@ -407,8 +426,8 @@ void dbg_check_breakpoint(exarg_T *eap) if (!eap->skip) { char *p; // replace K_SNR with "<SNR>" - if (debug_breakpoint_name[0] == K_SPECIAL - && debug_breakpoint_name[1] == KS_EXTRA + if ((uint8_t)debug_breakpoint_name[0] == K_SPECIAL + && (uint8_t)debug_breakpoint_name[1] == KS_EXTRA && debug_breakpoint_name[2] == KE_SNR) { p = "<SNR>"; } else { @@ -441,20 +460,21 @@ void dbg_check_breakpoint(exarg_T *eap) /// @return true when the debug mode is entered this time. bool dbg_check_skipped(exarg_T *eap) { - if (debug_skipped) { - // Save the value of got_int and reset it. We don't want a previous - // interruption cause flushing the input buffer. - int prev_got_int = got_int; - got_int = false; - debug_breakpoint_name = debug_skipped_name; - // eap->skip is true - eap->skip = false; - dbg_check_breakpoint(eap); - eap->skip = true; - got_int |= prev_got_int; - return true; - } - return false; + if (!debug_skipped) { + return false; + } + + // Save the value of got_int and reset it. We don't want a previous + // interruption cause flushing the input buffer. + int prev_got_int = got_int; + got_int = false; + debug_breakpoint_name = debug_skipped_name; + // eap->skip is true + eap->skip = false; + dbg_check_breakpoint(eap); + eap->skip = true; + got_int |= prev_got_int; + return true; } static garray_T dbg_breakp = { 0, 0, sizeof(struct debuggy), 4, NULL }; @@ -498,18 +518,18 @@ static int dbg_parsearg(char *arg, garray_T *gap) struct debuggy *bp = &DEBUGGY(gap, gap->ga_len); // Find "func" or "file". - if (STRNCMP(p, "func", 4) == 0) { + if (strncmp(p, "func", 4) == 0) { bp->dbg_type = DBG_FUNC; - } else if (STRNCMP(p, "file", 4) == 0) { + } else if (strncmp(p, "file", 4) == 0) { bp->dbg_type = DBG_FILE; - } else if (gap != &prof_ga && STRNCMP(p, "here", 4) == 0) { + } else if (gap != &prof_ga && strncmp(p, "here", 4) == 0) { if (curbuf->b_ffname == NULL) { emsg(_(e_noname)); return FAIL; } bp->dbg_type = DBG_FILE; here = true; - } else if (gap != &prof_ga && STRNCMP(p, "expr", 4) == 0) { + } else if (gap != &prof_ga && strncmp(p, "expr", 4) == 0) { bp->dbg_type = DBG_EXPR; } else { semsg(_(e_invarg2), p); @@ -577,33 +597,35 @@ void ex_breakadd(exarg_T *eap) gap = &prof_ga; } - if (dbg_parsearg(eap->arg, gap) == OK) { - struct debuggy *bp = &DEBUGGY(gap, gap->ga_len); - bp->dbg_forceit = eap->forceit; + if (dbg_parsearg(eap->arg, gap) != OK) { + return; + } - if (bp->dbg_type != DBG_EXPR) { - char *pat = file_pat_to_reg_pat(bp->dbg_name, NULL, NULL, false); - if (pat != NULL) { - bp->dbg_prog = vim_regcomp(pat, RE_MAGIC + RE_STRING); - xfree(pat); + struct debuggy *bp = &DEBUGGY(gap, gap->ga_len); + bp->dbg_forceit = eap->forceit; + + if (bp->dbg_type != DBG_EXPR) { + char *pat = file_pat_to_reg_pat(bp->dbg_name, NULL, NULL, false); + if (pat != NULL) { + bp->dbg_prog = vim_regcomp(pat, RE_MAGIC + RE_STRING); + xfree(pat); + } + if (pat == NULL || bp->dbg_prog == NULL) { + xfree(bp->dbg_name); + } else { + if (bp->dbg_lnum == 0) { // default line number is 1 + bp->dbg_lnum = 1; } - if (pat == NULL || bp->dbg_prog == NULL) { - xfree(bp->dbg_name); - } else { - if (bp->dbg_lnum == 0) { // default line number is 1 - bp->dbg_lnum = 1; - } - if (eap->cmdidx != CMD_profile) { - DEBUGGY(gap, gap->ga_len).dbg_nr = ++last_breakp; - debug_tick++; - } - gap->ga_len++; + if (eap->cmdidx != CMD_profile) { + DEBUGGY(gap, gap->ga_len).dbg_nr = ++last_breakp; + debug_tick++; } - } else { - // DBG_EXPR - DEBUGGY(gap, gap->ga_len++).dbg_nr = ++last_breakp; - debug_tick++; + gap->ga_len++; } + } else { + // DBG_EXPR + DEBUGGY(gap, gap->ga_len++).dbg_nr = ++last_breakp; + debug_tick++; } } @@ -665,32 +687,33 @@ void ex_breakdel(exarg_T *eap) if (todel < 0) { semsg(_("E161: Breakpoint not found: %s"), eap->arg); - } else { - while (!GA_EMPTY(gap)) { - xfree(DEBUGGY(gap, todel).dbg_name); - if (DEBUGGY(gap, todel).dbg_type == DBG_EXPR - && DEBUGGY(gap, todel).dbg_val != NULL) { - tv_free(DEBUGGY(gap, todel).dbg_val); - } - vim_regfree(DEBUGGY(gap, todel).dbg_prog); - gap->ga_len--; - if (todel < gap->ga_len) { - memmove(&DEBUGGY(gap, todel), &DEBUGGY(gap, todel + 1), - (size_t)(gap->ga_len - todel) * sizeof(struct debuggy)); - } - if (eap->cmdidx == CMD_breakdel) { - debug_tick++; - } - if (!del_all) { - break; - } - } + return; + } - // If all breakpoints were removed clear the array. - if (GA_EMPTY(gap)) { - ga_clear(gap); + while (!GA_EMPTY(gap)) { + xfree(DEBUGGY(gap, todel).dbg_name); + if (DEBUGGY(gap, todel).dbg_type == DBG_EXPR + && DEBUGGY(gap, todel).dbg_val != NULL) { + tv_free(DEBUGGY(gap, todel).dbg_val); + } + vim_regfree(DEBUGGY(gap, todel).dbg_prog); + gap->ga_len--; + if (todel < gap->ga_len) { + memmove(&DEBUGGY(gap, todel), &DEBUGGY(gap, todel + 1), + (size_t)(gap->ga_len - todel) * sizeof(struct debuggy)); + } + if (eap->cmdidx == CMD_breakdel) { + debug_tick++; + } + if (!del_all) { + break; } } + + // If all breakpoints were removed clear the array. + if (GA_EMPTY(gap)) { + ga_clear(gap); + } } /// ":breaklist". @@ -698,21 +721,22 @@ void ex_breaklist(exarg_T *eap) { if (GA_EMPTY(&dbg_breakp)) { msg(_("No breakpoints defined")); - } else { - for (int i = 0; i < dbg_breakp.ga_len; i++) { - struct debuggy *bp = &BREAKP(i); - if (bp->dbg_type == DBG_FILE) { - home_replace(NULL, bp->dbg_name, (char *)NameBuff, MAXPATHL, true); - } - if (bp->dbg_type != DBG_EXPR) { - smsg(_("%3d %s %s line %" PRId64), - bp->dbg_nr, - bp->dbg_type == DBG_FUNC ? "func" : "file", - bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff, - (int64_t)bp->dbg_lnum); - } else { - smsg(_("%3d expr %s"), bp->dbg_nr, bp->dbg_name); - } + return; + } + + for (int i = 0; i < dbg_breakp.ga_len; i++) { + struct debuggy *bp = &BREAKP(i); + if (bp->dbg_type == DBG_FILE) { + home_replace(NULL, bp->dbg_name, (char *)NameBuff, MAXPATHL, true); + } + if (bp->dbg_type != DBG_EXPR) { + smsg(_("%3d %s %s line %" PRId64), + bp->dbg_nr, + bp->dbg_type == DBG_FUNC ? "func" : "file", + bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff, + (int64_t)bp->dbg_lnum); + } else { + smsg(_("%3d expr %s"), bp->dbg_nr, bp->dbg_name); } } } @@ -725,7 +749,7 @@ void ex_breaklist(exarg_T *eap) /// @param after after this line number linenr_T dbg_find_breakpoint(bool file, char *fname, linenr_T after) { - return debuggy_find(file, (char_u *)fname, after, &dbg_breakp, NULL); + return debuggy_find(file, fname, after, &dbg_breakp, NULL); } /// @param file true for a file, false for a function @@ -735,7 +759,7 @@ linenr_T dbg_find_breakpoint(bool file, char *fname, linenr_T after) /// @returns true if profiling is on for a function or sourced file. bool has_profiling(bool file, char *fname, bool *fp) { - return debuggy_find(file, (char_u *)fname, (linenr_T)0, &prof_ga, fp) + return debuggy_find(file, fname, (linenr_T)0, &prof_ga, fp) != (linenr_T)0; } @@ -746,11 +770,11 @@ bool has_profiling(bool file, char *fname, bool *fp) /// @param after after this line number /// @param gap either &dbg_breakp or &prof_ga /// @param fp if not NULL: return forceit -static linenr_T debuggy_find(bool file, char_u *fname, linenr_T after, garray_T *gap, bool *fp) +static linenr_T debuggy_find(bool file, char *fname, linenr_T after, garray_T *gap, bool *fp) { struct debuggy *bp; linenr_T lnum = 0; - char_u *name = fname; + char *name = fname; int prev_got_int; // Return quickly when there are no breakpoints. @@ -759,8 +783,8 @@ static linenr_T debuggy_find(bool file, char_u *fname, linenr_T after, garray_T } // Replace K_SNR in function name with "<SNR>". - if (!file && fname[0] == K_SPECIAL) { - name = xmalloc(STRLEN(fname) + 3); + if (!file && (uint8_t)fname[0] == K_SPECIAL) { + name = xmalloc(strlen(fname) + 3); STRCPY(name, "<SNR>"); STRCPY(name + 5, fname + 3); } @@ -791,26 +815,26 @@ static linenr_T debuggy_find(bool file, char_u *fname, linenr_T after, garray_T typval_T *const tv = eval_expr_no_emsg(bp); if (tv != NULL) { if (bp->dbg_val == NULL) { - debug_oldval = typval_tostring(NULL); + debug_oldval = typval_tostring(NULL, true); bp->dbg_val = tv; - debug_newval = typval_tostring(bp->dbg_val); + debug_newval = typval_tostring(bp->dbg_val, true); line = true; } else { if (typval_compare(tv, bp->dbg_val, EXPR_IS, false) == OK && tv->vval.v_number == false) { line = true; - debug_oldval = typval_tostring(bp->dbg_val); + debug_oldval = typval_tostring(bp->dbg_val, true); // Need to evaluate again, typval_compare() overwrites "tv". typval_T *const v = eval_expr_no_emsg(bp); - debug_newval = typval_tostring(v); + debug_newval = typval_tostring(v, true); tv_free(bp->dbg_val); bp->dbg_val = v; } tv_free(tv); } } else if (bp->dbg_val != NULL) { - debug_oldval = typval_tostring(bp->dbg_val); - debug_newval = typval_tostring(NULL); + debug_oldval = typval_tostring(bp->dbg_val, true); + debug_newval = typval_tostring(NULL, true); tv_free(bp->dbg_val); bp->dbg_val = NULL; line = true; @@ -833,6 +857,6 @@ static linenr_T debuggy_find(bool file, char_u *fname, linenr_T after, garray_T void dbg_breakpoint(char *name, linenr_T lnum) { // We need to check if this line is actually executed in do_one_cmd() - debug_breakpoint_name = (char_u *)name; + debug_breakpoint_name = name; debug_breakpoint_lnum = lnum; } diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 5a1708a57c..63c55ec602 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -1,16 +1,18 @@ // 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/ui.h" +#include <assert.h> + #include "nvim/buffer.h" #include "nvim/decoration.h" #include "nvim/drawscreen.h" #include "nvim/extmark.h" +#include "nvim/fold.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" -#include "nvim/lua/executor.h" -#include "nvim/move.h" -#include "nvim/vim.h" +#include "nvim/memory.h" +#include "nvim/pos.h" +#include "nvim/sign_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "decoration.c.generated.h" @@ -69,7 +71,11 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor) { if (row2 >= row1) { - if (!decor || decor->hl_id || decor_has_sign(decor) || decor->conceal || decor->spell) { + if (!decor + || decor->hl_id + || decor_has_sign(decor) + || decor->conceal + || decor->spell != kNone) { redraw_buf_range_later(buf, row1 + 1, row2 + 1); } } @@ -169,13 +175,12 @@ 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; } + 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; } /// @return true if decor has a virtual position (virtual text or ui_watched) @@ -310,7 +315,7 @@ next_mark: bool conceal = 0; int conceal_char = 0; int conceal_attr = 0; - bool spell = false; + TriState spell = kNone; for (size_t i = 0; i < kv_size(state->active); i++) { DecorRange item = kv_A(state->active, i); @@ -344,8 +349,8 @@ next_mark: conceal_attr = item.attr_id; } } - if (active && item.decor.spell) { - spell = true; + if (active && item.decor.spell != kNone) { + spell = item.decor.spell; } if ((item.start_row == state->row && item.start_col <= col) && decor_virt_pos(item.decor) @@ -403,7 +408,7 @@ void decor_redraw_signs(buf_T *buf, int row, int *num_signs, SignTextAttrs sattr } if (j < SIGN_SHOW_MAX) { sattrs[j] = (SignTextAttrs) { - .text = (char *)decor->sign_text, + .text = decor->sign_text, .hl_attr_id = decor->sign_hl_id == 0 ? 0 : syn_id2attr(decor->sign_hl_id), .priority = decor->priority }; @@ -546,7 +551,8 @@ void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col, decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true, ns_id, mark_id); } -int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines) +/// @param has_fold whether line "lnum" has a fold, or kNone when not calculated yet +int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines, TriState has_fold) { buf_T *buf = wp->w_buffer; if (!buf->b_virt_line_blocks) { @@ -560,6 +566,10 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines) int end_row = (int)lnum; MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, row, 0, itr); + bool below_fold = lnum > 1 && hasFoldingWin(wp, lnum - 1, NULL, NULL, true, NULL); + if (has_fold == kNone) { + has_fold = hasFoldingWin(wp, lnum, NULL, NULL, true, NULL); + } while (true) { mtkey_t mark = marktree_itr_current(itr); if (mark.pos.row < 0 || mark.pos.row >= end_row) { @@ -568,8 +578,9 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines) goto next_mark; } bool above = mark.pos.row > (lnum - 2); + bool has_fold_cur = above ? has_fold : below_fold; Decoration *decor = mark.decor_full; - if (decor && decor->virt_lines_above == above) { + if (!has_fold_cur && decor && decor->virt_lines_above == above) { virt_lines += (int)kv_size(decor->virt_lines); if (lines) { kv_splice(*lines, decor->virt_lines); diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index bdbfd72a81..c9ec8ede7f 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -1,9 +1,17 @@ #ifndef NVIM_DECORATION_H #define NVIM_DECORATION_H +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#include "klib/kvec.h" #include "nvim/buffer_defs.h" #include "nvim/extmark_defs.h" +#include "nvim/macros.h" +#include "nvim/marktree.h" #include "nvim/pos.h" +#include "nvim/types.h" // actual Decoration data is in extmark_defs.h @@ -28,7 +36,6 @@ typedef enum { EXTERN const char *const hl_mode_str[] INIT(= { "", "replace", "combine", "blend" }); -typedef kvec_t(VirtTextChunk) VirtText; #define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) typedef kvec_t(struct virt_line { VirtText line; bool left_col; }) VirtLines; @@ -46,12 +53,12 @@ struct Decoration { bool hl_eol; bool virt_lines_above; bool conceal; - bool spell; + TriState spell; // 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; + char *sign_text; int sign_hl_id; int number_hl_id; int line_hl_id; @@ -62,7 +69,7 @@ struct Decoration { bool ui_watched; // watched for win_extmark }; #define DECORATION_INIT { KV_INITIAL_VALUE, KV_INITIAL_VALUE, 0, kVTEndOfLine, \ - kHlModeUnknown, false, false, false, false, false, \ + kHlModeUnknown, false, false, false, false, kNone, \ DECOR_PRIORITY_BASE, 0, 0, NULL, 0, 0, 0, 0, 0, false } typedef struct { @@ -92,7 +99,7 @@ typedef struct { int conceal_char; int conceal_attr; - bool spell; + TriState spell; } DecorState; EXTERN DecorState decor_state INIT(= { 0 }); diff --git a/src/nvim/decoration_provider.c b/src/nvim/decoration_provider.c index 48664421a3..ed21474935 100644 --- a/src/nvim/decoration_provider.c +++ b/src/nvim/decoration_provider.c @@ -1,14 +1,23 @@ // 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 <assert.h> +#include <stdio.h> +#include <string.h> + #include "klib/kvec.h" +#include "lauxlib.h" #include "nvim/api/extmark.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/buffer.h" -#include "nvim/decoration.h" +#include "nvim/buffer_defs.h" #include "nvim/decoration_provider.h" +#include "nvim/globals.h" #include "nvim/highlight.h" +#include "nvim/log.h" #include "nvim/lua/executor.h" +#include "nvim/memory.h" +#include "nvim/pos.h" static kvec_t(DecorProvider) decor_providers = KV_INITIAL_VALUE; @@ -194,6 +203,25 @@ void decor_providers_invoke_end(DecorProviders *providers, char **err) } } +/// Mark all cached state of per-namespace highlights as invalid. Revalidate +/// current namespace. +/// +/// Expensive! Should on be called by an already throttled validity check +/// like highlight_changed() (throttled to the next redraw or mode change) +void decor_provider_invalidate_hl(void) +{ + size_t len = kv_size(decor_providers); + for (size_t i = 0; i < len; i++) { + DecorProvider *item = &kv_A(decor_providers, i); + item->hl_cached = false; + } + + if (ns_hl_active) { + ns_hl_active = -1; + hl_check_ns(); + } +} + DecorProvider *get_decor_provider(NS ns_id, bool force) { assert(ns_id > 0); diff --git a/src/nvim/decoration_provider.h b/src/nvim/decoration_provider.h index 852b1583b9..b91ddabdfd 100644 --- a/src/nvim/decoration_provider.h +++ b/src/nvim/decoration_provider.h @@ -1,7 +1,12 @@ #ifndef NVIM_DECORATION_PROVIDER_H #define NVIM_DECORATION_PROVIDER_H +#include <stdbool.h> + +#include "klib/kvec.h" #include "nvim/buffer_defs.h" +#include "nvim/macros.h" +#include "nvim/types.h" typedef struct { NS ns_id; diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 9446d35630..032de561b3 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -10,9 +10,15 @@ /// - Use the compiled-in xdiff library. /// - Let 'diffexpr' do the work, using files. +#include <assert.h> +#include <ctype.h> #include <inttypes.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "auto/config.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" @@ -23,9 +29,15 @@ #include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/ex_cmds.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" +#include "nvim/extmark_defs.h" #include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/linematch.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" @@ -35,10 +47,13 @@ #include "nvim/normal.h" #include "nvim/option.h" #include "nvim/optionstr.h" +#include "nvim/os/fs_defs.h" #include "nvim/os/os.h" #include "nvim/os/shell.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -61,10 +76,12 @@ static bool diff_need_update = false; // ex_diffupdate needs to be called #define DIFF_INTERNAL 0x200 // use internal xdiff algorithm #define DIFF_CLOSE_OFF 0x400 // diffoff when closing window #define DIFF_FOLLOWWRAP 0x800 // follow the wrap option +#define DIFF_LINEMATCH 0x1000 // match most similar lines within diff #define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL) static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF; static long diff_algorithm = 0; +static int linematch_lines = 0; #define LBUFLEN 50 // length of line in diff file @@ -74,13 +91,13 @@ static TriState diff_a_works = kNone; // used for diff input typedef struct { - char_u *din_fname; // used for external diff + char *din_fname; // used for external diff mmfile_t din_mmfile; // used for internal diff } diffin_T; // used for diff result typedef struct { - char_u *dout_fname; // used for external diff + char *dout_fname; // used for external diff garray_T dout_ga; // used for internal diff } diffout_T; @@ -100,6 +117,12 @@ typedef struct { int dio_internal; // using internal diff } diffio_T; +typedef enum { + DIFF_ED, + DIFF_UNIFIED, + DIFF_NONE, +} diffstyle_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "diff.c.generated.h" #endif @@ -204,13 +227,7 @@ static void diff_buf_clear(void) /// @return Its index or DB_COUNT if not found. static int diff_buf_idx(buf_T *buf) { - int idx; - for (idx = 0; idx < DB_COUNT; idx++) { - if (curtab->tp_diffbuf[idx] == buf) { - break; - } - } - return idx; + return diff_buf_idx_tp(buf, curtab); } /// Find buffer "buf" in the list of diff buffers for tab page "tp". @@ -309,8 +326,6 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T diff_T *dp = tp->tp_first_diff; linenr_T lnum_deleted = line1; // lnum of remaining deletion - linenr_T n; - linenr_T off; for (;;) { // If the change is after the previous diff block and before the next // diff block, thus not touching an existing change, create a new diff @@ -363,7 +378,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T if (last >= line1 - 1) { // 6. change below line2: only adjust for amount_after; also when // "deleted" became zero when deleted all lines between two diffs. - if (dp->df_lnum[idx] - (deleted + inserted != 0) > line2) { + if (dp->df_lnum[idx] - (deleted + inserted != 0) > line2 - dp->is_linematched) { if (amount_after == 0) { // nothing left to change break; @@ -374,7 +389,8 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T // 2. 3. 4. 5.: inserted/deleted lines touching this diff. if (deleted > 0) { - off = 0; + linenr_T n; + linenr_T off = 0; if (dp->df_lnum[idx] >= line1) { if (last <= line2) { // 4. delete all lines of diff @@ -454,7 +470,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T } // check if this block touches the previous one, may merge them. - if ((dprev != NULL) + if ((dprev != NULL) && !dp->is_linematched && (dprev->df_lnum[idx] + dprev->df_count[idx] == dp->df_lnum[idx])) { int i; for (i = 0; i < DB_COUNT; i++) { @@ -462,9 +478,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T dprev->df_count[i] += dp->df_count[i]; } } - dprev->df_next = dp->df_next; - xfree(dp); - dp = dprev->df_next; + dp = diff_free(tp, dprev, dp); } else { // Advance to next entry. dprev = dp; @@ -485,15 +499,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T } if (i == DB_COUNT) { - diff_T *dnext = dp->df_next; - xfree(dp); - dp = dnext; - - if (dprev == NULL) { - tp->tp_first_diff = dnext; - } else { - dprev->df_next = dnext; - } + dp = diff_free(tp, dprev, dp); } else { // Advance to next entry. dprev = dp; @@ -523,6 +529,7 @@ static diff_T *diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp) { diff_T *dnew = xmalloc(sizeof(*dnew)); + dnew->is_linematched = false; dnew->df_next = dp; if (dprev == NULL) { tp->tp_first_diff = dnew; @@ -533,6 +540,20 @@ static diff_T *diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp) return dnew; } +static diff_T *diff_free(tabpage_T *tp, diff_T *dprev, diff_T *dp) +{ + diff_T *ret = dp->df_next; + xfree(dp); + + if (dprev == NULL) { + tp->tp_first_diff = ret; + } else { + dprev->df_next = ret; + } + + return ret; +} + /// Check if the diff block "dp" can be made smaller for lines at the start and /// end that are equal. Called after inserting lines. /// @@ -704,16 +725,16 @@ static void clear_diffin(diffin_T *din) if (din->din_fname == NULL) { XFREE_CLEAR(din->din_mmfile.ptr); } else { - os_remove((char *)din->din_fname); + os_remove(din->din_fname); } } static void clear_diffout(diffout_T *dout) { if (dout->dout_fname == NULL) { - ga_clear_strings(&dout->dout_ga); + ga_clear(&dout->dout_ga); } else { - os_remove((char *)dout->dout_fname); + os_remove(dout->dout_fname); } } @@ -723,15 +744,19 @@ static void clear_diffout(diffout_T *dout) /// @param din /// /// @return FAIL for failure. -static int diff_write_buffer(buf_T *buf, diffin_T *din) +static int diff_write_buffer(buf_T *buf, mmfile_t *m, linenr_T start, linenr_T end) { - long len = 0; + size_t len = 0; + + if (end < 0) { + end = buf->b_ml.ml_line_count; + } // xdiff requires one big block of memory with all the text. - for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { - len += (long)strlen(ml_get_buf(buf, lnum, false)) + 1; + for (linenr_T lnum = start; lnum <= end; lnum++) { + len += strlen(ml_get_buf(buf, lnum, false)) + 1; } - char_u *ptr = try_malloc((size_t)len); + char *ptr = try_malloc(len); if (ptr == NULL) { // Allocating memory failed. This can happen, because we try to read // the whole buffer text into memory. Set the failed flag, the diff @@ -745,37 +770,32 @@ static int diff_write_buffer(buf_T *buf, diffin_T *din) } return FAIL; } - din->din_mmfile.ptr = (char *)ptr; - din->din_mmfile.size = len; + m->ptr = ptr; + m->size = (long)len; len = 0; - for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { - for (char_u *s = (char_u *)ml_get_buf(buf, lnum, false); *s != NUL;) { - if (diff_flags & DIFF_ICASE) { - int c; + for (linenr_T lnum = start; lnum <= end; lnum++) { + char *s = ml_get_buf(buf, lnum, false); + if (diff_flags & DIFF_ICASE) { + while (*s != NUL) { char cbuf[MB_MAXBYTES + 1]; - if (*s == NL) { - c = NUL; - } else { - // xdiff doesn't support ignoring case, fold-case the text. - c = utf_ptr2char((char *)s); - c = utf_fold(c); - } - const int orig_len = utfc_ptr2len((char *)s); - if (utf_char2bytes(c, (char *)cbuf) != orig_len) { - // TODO(Bram): handle byte length difference - memmove(ptr + len, s, (size_t)orig_len); - } else { - memmove(ptr + len, cbuf, (size_t)orig_len); - } + // xdiff doesn't support ignoring case, fold-case the text. + int c = *s == NL ? NUL : utf_fold(utf_ptr2char(s)); + const int orig_len = utfc_ptr2len(s); + // TODO(Bram): handle byte length difference + char *s1 = (utf_char2bytes(c, cbuf) != orig_len) ? s : cbuf; + memmove(ptr + len, s1, (size_t)orig_len); s += orig_len; - len += orig_len; - } else { - ptr[len++] = *s == NL ? NUL : *s; - s++; + len += (size_t)orig_len; } + } else { + size_t slen = strlen(s); + memmove(ptr + len, s, slen); + // NUL is represented as NL; convert + memchrsub(ptr + len, NL, NUL, slen); + len += slen; } ptr[len++] = NL; } @@ -793,7 +813,7 @@ static int diff_write_buffer(buf_T *buf, diffin_T *din) static int diff_write(buf_T *buf, diffin_T *din) { if (din->din_fname == NULL) { - return diff_write_buffer(buf, din); + return diff_write_buffer(buf, &din->din_mmfile, 1, -1); } // Always use 'fileformat' set to "unix". @@ -803,7 +823,7 @@ static int diff_write(buf_T *buf, diffin_T *din) // Writing the buffer is an implementation detail of performing the diff, // so it shouldn't update the '[ and '] marks. cmdmod.cmod_flags |= CMOD_LOCKMARKS; - int r = buf_write(buf, (char *)din->din_fname, NULL, + int r = buf_write(buf, din->din_fname, NULL, (linenr_T)1, buf->b_ml.ml_line_count, NULL, false, false, false, true); cmdmod.cmod_flags = save_cmod_flags; @@ -821,12 +841,12 @@ static int diff_write(buf_T *buf, diffin_T *din) static void diff_try_update(diffio_T *dio, int idx_orig, exarg_T *eap) { if (dio->dio_internal) { - ga_init(&dio->dio_diff.dout_ga, sizeof(char *), 1000); + ga_init(&dio->dio_diff.dout_ga, sizeof(diffhunk_T), 100); } else { // We need three temp file names. - dio->dio_orig.din_fname = (char_u *)vim_tempname(); - dio->dio_new.din_fname = (char_u *)vim_tempname(); - dio->dio_diff.dout_fname = (char_u *)vim_tempname(); + dio->dio_orig.din_fname = vim_tempname(); + dio->dio_new.din_fname = vim_tempname(); + dio->dio_diff.dout_fname = vim_tempname(); if (dio->dio_orig.din_fname == NULL || dio->dio_new.din_fname == NULL || dio->dio_diff.dout_fname == NULL) { @@ -992,7 +1012,7 @@ static int check_external_diff(diffio_T *diffio) TriState ok = kFalse; for (;;) { ok = kFalse; - FILE *fd = os_fopen((char *)diffio->dio_orig.din_fname, "w"); + FILE *fd = os_fopen(diffio->dio_orig.din_fname, "w"); if (fd == NULL) { io_error = true; @@ -1001,7 +1021,7 @@ static int check_external_diff(diffio_T *diffio) io_error = true; } fclose(fd); - fd = os_fopen((char *)diffio->dio_new.din_fname, "w"); + fd = os_fopen(diffio->dio_new.din_fname, "w"); if (fd == NULL) { io_error = true; @@ -1012,13 +1032,13 @@ static int check_external_diff(diffio_T *diffio) fclose(fd); fd = NULL; if (diff_file(diffio) == OK) { - fd = os_fopen((char *)diffio->dio_diff.dout_fname, "r"); + fd = os_fopen(diffio->dio_diff.dout_fname, "r"); } if (fd == NULL) { io_error = true; } else { - char_u linebuf[LBUFLEN]; + char linebuf[LBUFLEN]; for (;;) { // For normal diff there must be a line that contains @@ -1027,17 +1047,17 @@ static int check_external_diff(diffio_T *diffio) break; } - if (STRNCMP(linebuf, "1c1", 3) == 0 - || STRNCMP(linebuf, "@@ -1 +1 @@", 11) == 0) { + if (strncmp(linebuf, "1c1", 3) == 0 + || strncmp(linebuf, "@@ -1 +1 @@", 11) == 0) { ok = kTrue; } } fclose(fd); } - os_remove((char *)diffio->dio_diff.dout_fname); - os_remove((char *)diffio->dio_new.din_fname); + os_remove(diffio->dio_diff.dout_fname); + os_remove(diffio->dio_new.din_fname); } - os_remove((char *)diffio->dio_orig.din_fname); + os_remove(diffio->dio_orig.din_fname); } // When using 'diffexpr' break here. @@ -1115,9 +1135,9 @@ static int diff_file_internal(diffio_T *diffio) /// @return OK or FAIL static int diff_file(diffio_T *dio) { - char *tmp_orig = (char *)dio->dio_orig.din_fname; - char *tmp_new = (char *)dio->dio_new.din_fname; - char *tmp_diff = (char *)dio->dio_diff.dout_fname; + char *tmp_orig = dio->dio_orig.din_fname; + char *tmp_new = dio->dio_new.din_fname; + char *tmp_diff = dio->dio_diff.dout_fname; if (*p_dex != NUL) { // Use 'diffexpr' to generate the diff file. eval_diff(tmp_orig, tmp_new, tmp_diff); @@ -1126,37 +1146,37 @@ static int diff_file(diffio_T *dio) // Use xdiff for generating the diff. if (dio->dio_internal) { return diff_file_internal(dio); - } else { - const size_t len = (strlen(tmp_orig) + strlen(tmp_new) + strlen(tmp_diff) - + strlen(p_srr) + 27); - char *const cmd = xmalloc(len); - - // We don't want $DIFF_OPTIONS to get in the way. - if (os_getenv("DIFF_OPTIONS")) { - os_unsetenv("DIFF_OPTIONS"); - } - - // Build the diff command and execute it. Always use -a, binary - // differences are of no use. Ignore errors, diff returns - // non-zero when differences have been found. - vim_snprintf(cmd, len, "diff %s%s%s%s%s%s%s%s %s", - diff_a_works == kFalse ? "" : "-a ", - "", - (diff_flags & DIFF_IWHITE) ? "-b " : "", - (diff_flags & DIFF_IWHITEALL) ? "-w " : "", - (diff_flags & DIFF_IWHITEEOL) ? "-Z " : "", - (diff_flags & DIFF_IBLANK) ? "-B " : "", - (diff_flags & DIFF_ICASE) ? "-i " : "", - tmp_orig, tmp_new); - append_redir(cmd, len, p_srr, tmp_diff); - block_autocmds(); // Avoid ShellCmdPost stuff - (void)call_shell((char_u *)cmd, - kShellOptFilter | kShellOptSilent | kShellOptDoOut, - NULL); - unblock_autocmds(); - xfree(cmd); - return OK; } + + const size_t len = (strlen(tmp_orig) + strlen(tmp_new) + strlen(tmp_diff) + + strlen(p_srr) + 27); + char *const cmd = xmalloc(len); + + // We don't want $DIFF_OPTIONS to get in the way. + if (os_getenv("DIFF_OPTIONS")) { + os_unsetenv("DIFF_OPTIONS"); + } + + // Build the diff command and execute it. Always use -a, binary + // differences are of no use. Ignore errors, diff returns + // non-zero when differences have been found. + vim_snprintf(cmd, len, "diff %s%s%s%s%s%s%s%s %s", + diff_a_works == kFalse ? "" : "-a ", + "", + (diff_flags & DIFF_IWHITE) ? "-b " : "", + (diff_flags & DIFF_IWHITEALL) ? "-w " : "", + (diff_flags & DIFF_IWHITEEOL) ? "-Z " : "", + (diff_flags & DIFF_IBLANK) ? "-B " : "", + (diff_flags & DIFF_ICASE) ? "-i " : "", + tmp_orig, tmp_new); + append_redir(cmd, len, p_srr, tmp_diff); + block_autocmds(); // Avoid ShellCmdPost stuff + (void)call_shell(cmd, + kShellOptFilter | kShellOptSilent | kShellOptDoOut, + NULL); + unblock_autocmds(); + xfree(cmd); + return OK; } /// Create a new version of a file from the current buffer and a diff file. @@ -1167,10 +1187,10 @@ static int diff_file(diffio_T *dio) /// @param eap void ex_diffpatch(exarg_T *eap) { - char_u *buf = NULL; + char *buf = NULL; win_T *old_curwin = curwin; char *newname = NULL; // name of patched file buffer - char_u *esc_name = NULL; + char *esc_name = NULL; #ifdef UNIX char *fullname = NULL; @@ -1178,16 +1198,16 @@ void ex_diffpatch(exarg_T *eap) // We need two temp file names. // Name of original temp file. - char_u *tmp_orig = (char_u *)vim_tempname(); + char *tmp_orig = vim_tempname(); // Name of patched temp file. - char_u *tmp_new = (char_u *)vim_tempname(); + char *tmp_new = vim_tempname(); if ((tmp_orig == NULL) || (tmp_new == NULL)) { goto theend; } // Write the current buffer to "tmp_orig". - if (buf_write(curbuf, (char *)tmp_orig, NULL, + if (buf_write(curbuf, tmp_orig, NULL, (linenr_T)1, curbuf->b_ml.ml_line_count, NULL, false, false, false, true) == FAIL) { goto theend; @@ -1196,23 +1216,22 @@ void ex_diffpatch(exarg_T *eap) #ifdef UNIX // Get the absolute path of the patchfile, changing directory below. fullname = FullName_save(eap->arg, false); - esc_name = - vim_strsave_shellescape((char_u *)(fullname != NULL ? fullname : eap->arg), true, true); + esc_name = vim_strsave_shellescape(fullname != NULL ? fullname : eap->arg, true, true); #else esc_name = vim_strsave_shellescape(eap->arg, true, true); #endif - size_t buflen = STRLEN(tmp_orig) + STRLEN(esc_name) + STRLEN(tmp_new) + 16; + size_t buflen = strlen(tmp_orig) + strlen(esc_name) + strlen(tmp_new) + 16; buf = xmalloc(buflen); #ifdef UNIX - char_u dirbuf[MAXPATHL]; + char dirbuf[MAXPATHL]; // Temporarily chdir to /tmp, to avoid patching files in the current // directory when the patch file contains more than one patch. When we // have our own temp dir use that instead, it will be cleaned up when we // exit (any .rej files created). Don't change directory if we can't // return to the current. if ((os_dirname(dirbuf, MAXPATHL) != OK) - || (os_chdir((char *)dirbuf) != 0)) { + || (os_chdir(dirbuf) != 0)) { dirbuf[0] = NUL; } else { char *tempdir = vim_gettempdir(); @@ -1227,15 +1246,13 @@ void ex_diffpatch(exarg_T *eap) if (*p_pex != NUL) { // Use 'patchexpr' to generate the new file. #ifdef UNIX - eval_patch((char *)tmp_orig, - (fullname != NULL ? fullname : eap->arg), - (char *)tmp_new); + eval_patch(tmp_orig, (fullname != NULL ? fullname : eap->arg), tmp_new); #else - eval_patch((char *)tmp_orig, (char *)eap->arg, (char *)tmp_new); + eval_patch(tmp_orig, eap->arg, tmp_new); #endif } else { // Build the patch command and execute it. Ignore errors. - vim_snprintf((char *)buf, buflen, "patch -o %s %s < %s", + vim_snprintf(buf, buflen, "patch -o %s %s < %s", tmp_new, tmp_orig, esc_name); block_autocmds(); // Avoid ShellCmdPost stuff (void)call_shell(buf, kShellOptFilter, NULL); @@ -1244,7 +1261,7 @@ void ex_diffpatch(exarg_T *eap) #ifdef UNIX if (dirbuf[0] != NUL) { - if (os_chdir((char *)dirbuf) != 0) { + if (os_chdir(dirbuf) != 0) { emsg(_(e_prev_dir)); } shorten_fnames(true); @@ -1254,14 +1271,14 @@ void ex_diffpatch(exarg_T *eap) // Delete any .orig or .rej file created. STRCPY(buf, tmp_new); STRCAT(buf, ".orig"); - os_remove((char *)buf); + os_remove(buf); STRCPY(buf, tmp_new); STRCAT(buf, ".rej"); - os_remove((char *)buf); + os_remove(buf); // Only continue if the output file was created. FileInfo file_info; - bool info_ok = os_fileinfo((char *)tmp_new, &file_info); + bool info_ok = os_fileinfo(tmp_new, &file_info); uint64_t filesize = os_fileinfo_size(&file_info); if (!info_ok || filesize == 0) { emsg(_("E816: Cannot read patch output")); @@ -1277,7 +1294,7 @@ void ex_diffpatch(exarg_T *eap) if (win_split(0, (diff_flags & DIFF_VERTICAL) ? WSP_VERT : 0) != FAIL) { // Pretend it was a ":split fname" command eap->cmdidx = CMD_split; - eap->arg = (char *)tmp_new; + eap->arg = tmp_new; do_exedit(eap, old_curwin); // check that split worked and editing tmp_new @@ -1302,12 +1319,12 @@ void ex_diffpatch(exarg_T *eap) theend: if (tmp_orig != NULL) { - os_remove((char *)tmp_orig); + os_remove(tmp_orig); } xfree(tmp_orig); if (tmp_new != NULL) { - os_remove((char *)tmp_new); + os_remove(tmp_new); } xfree(tmp_new); xfree(newname); @@ -1334,30 +1351,33 @@ void ex_diffsplit(exarg_T *eap) // don't use a new tab page, each tab page has its own diffs cmdmod.cmod_tab = 0; - if (win_split(0, (diff_flags & DIFF_VERTICAL) ? WSP_VERT : 0) != FAIL) { - // Pretend it was a ":split fname" command - eap->cmdidx = CMD_split; - curwin->w_p_diff = true; - do_exedit(eap, old_curwin); - - // split must have worked - if (curwin != old_curwin) { - // Set 'diff', 'scrollbind' on and 'wrap' off. - diff_win_options(curwin, true); - if (win_valid(old_curwin)) { - diff_win_options(old_curwin, true); + if (win_split(0, (diff_flags & DIFF_VERTICAL) ? WSP_VERT : 0) == FAIL) { + return; + } - if (bufref_valid(&old_curbuf)) { - // Move the cursor position to that of the old window. - curwin->w_cursor.lnum = diff_get_corresponding_line(old_curbuf.br_buf, - old_curwin->w_cursor.lnum); - } - } - // Now that lines are folded scroll to show the cursor at the same - // relative position. - scroll_to_fraction(curwin, curwin->w_height); + // Pretend it was a ":split fname" command + eap->cmdidx = CMD_split; + curwin->w_p_diff = true; + do_exedit(eap, old_curwin); + + if (curwin == old_curwin) { // split didn't work + return; + } + + // Set 'diff', 'scrollbind' on and 'wrap' off. + diff_win_options(curwin, true); + if (win_valid(old_curwin)) { + diff_win_options(old_curwin, true); + + if (bufref_valid(&old_curbuf)) { + // Move the cursor position to that of the old window. + curwin->w_cursor.lnum = diff_get_corresponding_line(old_curbuf.br_buf, + old_curwin->w_cursor.lnum); } } + // Now that lines are folded scroll to show the cursor at the same + // relative position. + scroll_to_fraction(curwin, curwin->w_height); } // Set options to show diffs for the current window. @@ -1482,7 +1502,7 @@ void ex_diffoff(exarg_T *eap) free_string_option(wp->w_p_fdm); wp->w_p_fdm = xstrdup(*wp->w_p_fdm_save ? wp->w_p_fdm_save : "manual"); free_string_option(wp->w_p_fdc); - wp->w_p_fdc = xstrdup(wp->w_p_fdc_save); + wp->w_p_fdc = xstrdup(*wp->w_p_fdc_save ? wp->w_p_fdc_save : "0"); if (wp->w_p_fdl == 0) { wp->w_p_fdl = wp->w_p_fdl_save; @@ -1526,210 +1546,219 @@ void ex_diffoff(exarg_T *eap) } } -/// Read the diff output and add each entry to the diff list. -/// -/// @param idx_orig idx of original file -/// @param idx_new idx of new file -/// @dout diff output -static void diff_read(int idx_orig, int idx_new, diffio_T *dio) +static bool extract_hunk_internal(diffout_T *dout, diffhunk_T *hunk, int *line_idx) { - FILE *fd = NULL; - int line_idx = 0; - diff_T *dprev = NULL; - diff_T *dp = curtab->tp_first_diff; - diff_T *dn, *dpl; - diffout_T *dout = &dio->dio_diff; - char linebuf[LBUFLEN]; // only need to hold the diff line - char *line; - linenr_T off; - int i; - int notset = true; // block "*dp" not set yet - diffhunk_T *hunk = NULL; // init to avoid gcc warning - enum { - DIFF_ED, - DIFF_UNIFIED, - DIFF_NONE, - } diffstyle = DIFF_NONE; - - if (dout->dout_fname == NULL) { - diffstyle = DIFF_UNIFIED; - } else { - fd = os_fopen((char *)dout->dout_fname, "r"); - if (fd == NULL) { - emsg(_("E98: Cannot read diff output")); - return; - } - } - - if (!dio->dio_internal) { - hunk = xmalloc(sizeof(*hunk)); + bool eof = *line_idx >= dout->dout_ga.ga_len; + if (!eof) { + *hunk = ((diffhunk_T *)dout->dout_ga.ga_data)[(*line_idx)++]; } + return eof; +} +// Extract hunk by parsing the diff output from file and calculate the diffstyle. +static bool extract_hunk(FILE *fd, diffhunk_T *hunk, diffstyle_T *diffstyle) +{ for (;;) { - if (dio->dio_internal) { - if (line_idx >= dout->dout_ga.ga_len) { - break; // did last line - } - hunk = ((diffhunk_T **)dout->dout_ga.ga_data)[line_idx++]; - } else { - if (fd == NULL) { - if (line_idx >= dout->dout_ga.ga_len) { - break; // did last line - } - line = ((char **)dout->dout_ga.ga_data)[line_idx++]; + char line[LBUFLEN]; // only need to hold the diff line + if (vim_fgets(line, LBUFLEN, fd)) { + return true; // end of file + } + + if (*diffstyle == DIFF_NONE) { + // Determine diff style. + // ed like diff looks like this: + // {first}[,{last}]c{first}[,{last}] + // {first}a{first}[,{last}] + // {first}[,{last}]d{first} + // + // unified diff looks like this: + // --- file1 2018-03-20 13:23:35.783153140 +0100 + // +++ file2 2018-03-20 13:23:41.183156066 +0100 + // @@ -1,3 +1,5 @@ + if (isdigit((uint8_t)(*line))) { + *diffstyle = DIFF_ED; + } else if ((strncmp(line, "@@ ", 3) == 0)) { + *diffstyle = DIFF_UNIFIED; + } else if ((strncmp(line, "--- ", 4) == 0) // -V501 + && (vim_fgets(line, LBUFLEN, fd) == 0) // -V501 + && (strncmp(line, "+++ ", 4) == 0) + && (vim_fgets(line, LBUFLEN, fd) == 0) // -V501 + && (strncmp(line, "@@ ", 3) == 0)) { + *diffstyle = DIFF_UNIFIED; } else { - if (vim_fgets((char_u *)linebuf, LBUFLEN, fd)) { - break; // end of file - } - line = linebuf; - } - - if (diffstyle == DIFF_NONE) { - // Determine diff style. - // ed like diff looks like this: - // {first}[,{last}]c{first}[,{last}] - // {first}a{first}[,{last}] - // {first}[,{last}]d{first} - // - // unified diff looks like this: - // --- file1 2018-03-20 13:23:35.783153140 +0100 - // +++ file2 2018-03-20 13:23:41.183156066 +0100 - // @@ -1,3 +1,5 @@ - if (isdigit(*line)) { - diffstyle = DIFF_ED; - } else if ((STRNCMP(line, "@@ ", 3) == 0)) { - diffstyle = DIFF_UNIFIED; - } else if ((STRNCMP(line, "--- ", 4) == 0) // -V501 - && (vim_fgets((char_u *)linebuf, LBUFLEN, fd) == 0) // -V501 - && (STRNCMP(line, "+++ ", 4) == 0) - && (vim_fgets((char_u *)linebuf, LBUFLEN, fd) == 0) // -V501 - && (STRNCMP(line, "@@ ", 3) == 0)) { - diffstyle = DIFF_UNIFIED; - } else { - // Format not recognized yet, skip over this line. Cygwin diff - // may put a warning at the start of the file. - continue; - } + // Format not recognized yet, skip over this line. Cygwin diff + // may put a warning at the start of the file. + continue; } + } - if (diffstyle == DIFF_ED) { - if (!isdigit(*line)) { - continue; // not the start of a diff block - } - if (parse_diff_ed((char_u *)line, hunk) == FAIL) { - continue; - } - } else { - assert(diffstyle == DIFF_UNIFIED); - if (STRNCMP(line, "@@ ", 3) != 0) { - continue; // not the start of a diff block - } - if (parse_diff_unified((char_u *)line, hunk) == FAIL) { - continue; - } + if (*diffstyle == DIFF_ED) { + if (!isdigit((uint8_t)(*line))) { + continue; // not the start of a diff block + } + if (parse_diff_ed(line, hunk) == FAIL) { + continue; + } + } else { + assert(*diffstyle == DIFF_UNIFIED); + if (strncmp(line, "@@ ", 3) != 0) { + continue; // not the start of a diff block + } + if (parse_diff_unified(line, hunk) == FAIL) { + continue; } } - // Go over blocks before the change, for which orig and new are equal. - // Copy blocks from orig to new. - while (dp != NULL - && hunk->lnum_orig > dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) { - if (notset) { - diff_copy_entry(dprev, dp, idx_orig, idx_new); - } - dprev = dp; - dp = dp->df_next; - notset = true; + // Successfully parsed diff output, can return + return false; + } +} + +static void process_hunk(diff_T **dpp, diff_T **dprevp, int idx_orig, int idx_new, diffhunk_T *hunk, + bool *notsetp) +{ + diff_T *dp = *dpp; + diff_T *dprev = *dprevp; + + // Go over blocks before the change, for which orig and new are equal. + // Copy blocks from orig to new. + while (dp != NULL + && hunk->lnum_orig > dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) { + if (*notsetp) { + diff_copy_entry(dprev, dp, idx_orig, idx_new); } + dprev = dp; + dp = dp->df_next; + *notsetp = true; + } - if ((dp != NULL) - && (hunk->lnum_orig <= dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) - && (hunk->lnum_orig + hunk->count_orig >= dp->df_lnum[idx_orig])) { - // New block overlaps with existing block(s). - // First find last block that overlaps. - for (dpl = dp; dpl->df_next != NULL; dpl = dpl->df_next) { - if (hunk->lnum_orig + hunk->count_orig < dpl->df_next->df_lnum[idx_orig]) { - break; - } + if ((dp != NULL) + && (hunk->lnum_orig <= dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) + && (hunk->lnum_orig + hunk->count_orig >= dp->df_lnum[idx_orig])) { + // New block overlaps with existing block(s). + // First find last block that overlaps. + diff_T *dpl; + for (dpl = dp; dpl->df_next != NULL; dpl = dpl->df_next) { + if (hunk->lnum_orig + hunk->count_orig < dpl->df_next->df_lnum[idx_orig]) { + break; } + } - // If the newly found block starts before the old one, set the - // start back a number of lines. - off = dp->df_lnum[idx_orig] - hunk->lnum_orig; + // If the newly found block starts before the old one, set the + // start back a number of lines. + linenr_T off = dp->df_lnum[idx_orig] - hunk->lnum_orig; - if (off > 0) { - for (i = idx_orig; i < idx_new; i++) { - if (curtab->tp_diffbuf[i] != NULL) { - dp->df_lnum[i] -= off; - } - } - dp->df_lnum[idx_new] = hunk->lnum_new; - dp->df_count[idx_new] = (linenr_T)hunk->count_new; - } else if (notset) { - // new block inside existing one, adjust new block - dp->df_lnum[idx_new] = hunk->lnum_new + off; - dp->df_count[idx_new] = (linenr_T)hunk->count_new - off; - } else { - // second overlap of new block with existing block - dp->df_count[idx_new] += (linenr_T)hunk->count_new - (linenr_T)hunk->count_orig - + dpl->df_lnum[idx_orig] + - dpl->df_count[idx_orig] - - (dp->df_lnum[idx_orig] + - dp->df_count[idx_orig]); - } - - // Adjust the size of the block to include all the lines to the - // end of the existing block or the new diff, whatever ends last. - off = (hunk->lnum_orig + (linenr_T)hunk->count_orig) - - (dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]); - - if (off < 0) { - // new change ends in existing block, adjust the end if not - // done already - if (notset) { - dp->df_count[idx_new] += -off; + if (off > 0) { + for (int i = idx_orig; i < idx_new; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + dp->df_lnum[i] -= off; } - off = 0; } + dp->df_lnum[idx_new] = hunk->lnum_new; + dp->df_count[idx_new] = (linenr_T)hunk->count_new; + } else if (*notsetp) { + // new block inside existing one, adjust new block + dp->df_lnum[idx_new] = hunk->lnum_new + off; + dp->df_count[idx_new] = (linenr_T)hunk->count_new - off; + } else { + // second overlap of new block with existing block + dp->df_count[idx_new] += (linenr_T)hunk->count_new - (linenr_T)hunk->count_orig + + dpl->df_lnum[idx_orig] + + dpl->df_count[idx_orig] + - (dp->df_lnum[idx_orig] + + dp->df_count[idx_orig]); + } + + // Adjust the size of the block to include all the lines to the + // end of the existing block or the new diff, whatever ends last. + off = (hunk->lnum_orig + (linenr_T)hunk->count_orig) + - (dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]); + + if (off < 0) { + // new change ends in existing block, adjust the end if not + // done already + if (*notsetp) { + dp->df_count[idx_new] += -off; + } + off = 0; + } - for (i = idx_orig; i < idx_new; i++) { - if (curtab->tp_diffbuf[i] != NULL) { - dp->df_count[i] = dpl->df_lnum[i] + dpl->df_count[i] - - dp->df_lnum[i] + off; - } + for (int i = idx_orig; i < idx_new; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + dp->df_count[i] = dpl->df_lnum[i] + dpl->df_count[i] + - dp->df_lnum[i] + off; } + } - // Delete the diff blocks that have been merged into one. - dn = dp->df_next; - dp->df_next = dpl->df_next; + // Delete the diff blocks that have been merged into one. + diff_T *dn = dp->df_next; + dp->df_next = dpl->df_next; - while (dn != dp->df_next) { - dpl = dn->df_next; - xfree(dn); - dn = dpl; + while (dn != dp->df_next) { + dpl = dn->df_next; + xfree(dn); + dn = dpl; + } + } else { + // Allocate a new diffblock. + dp = diff_alloc_new(curtab, dprev, dp); + + dp->df_lnum[idx_orig] = hunk->lnum_orig; + dp->df_count[idx_orig] = (linenr_T)hunk->count_orig; + dp->df_lnum[idx_new] = hunk->lnum_new; + dp->df_count[idx_new] = (linenr_T)hunk->count_new; + + // Set values for other buffers, these must be equal to the + // original buffer, otherwise there would have been a change + // already. + for (int i = idx_orig + 1; i < idx_new; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + diff_copy_entry(dprev, dp, idx_orig, i); } - } else { - // Allocate a new diffblock. - dp = diff_alloc_new(curtab, dprev, dp); + } + } + *notsetp = false; // "*dp" has been set + *dpp = dp; + *dprevp = dprev; +} - dp->df_lnum[idx_orig] = hunk->lnum_orig; - dp->df_count[idx_orig] = (linenr_T)hunk->count_orig; - dp->df_lnum[idx_new] = hunk->lnum_new; - dp->df_count[idx_new] = (linenr_T)hunk->count_new; +/// Read the diff output and add each entry to the diff list. +/// +/// @param idx_orig idx of original file +/// @param idx_new idx of new file +/// @dout diff output +static void diff_read(int idx_orig, int idx_new, diffio_T *dio) +{ + FILE *fd = NULL; + int line_idx = 0; + diff_T *dprev = NULL; + diff_T *dp = curtab->tp_first_diff; + diffout_T *dout = &dio->dio_diff; + bool notset = true; // block "*dp" not set yet + diffstyle_T diffstyle = DIFF_NONE; - // Set values for other buffers, these must be equal to the - // original buffer, otherwise there would have been a change - // already. - for (i = idx_orig + 1; i < idx_new; i++) { - if (curtab->tp_diffbuf[i] != NULL) { - diff_copy_entry(dprev, dp, idx_orig, i); - } - } + if (!dio->dio_internal) { + fd = os_fopen(dout->dout_fname, "r"); + if (fd == NULL) { + emsg(_("E98: Cannot read diff output")); + return; + } + } + + for (;;) { + diffhunk_T hunk = { 0 }; + bool eof = dio->dio_internal + ? extract_hunk_internal(dout, &hunk, &line_idx) + : extract_hunk(fd, &hunk, &diffstyle); + + if (eof) { + break; } - notset = false; // "*dp" has been set + + process_hunk(&dp, &dprev, idx_orig, idx_new, &hunk, ¬set); } -// for remaining diff blocks orig and new are equal + // for remaining diff blocks orig and new are equal while (dp != NULL) { if (notset) { diff_copy_entry(dprev, dp, idx_orig, idx_new); @@ -1739,10 +1768,6 @@ static void diff_read(int idx_orig, int idx_new, diffio_T *dio) notset = true; } - if (!dio->dio_internal) { - xfree(hunk); - } - if (fd != NULL) { fclose(fd); } @@ -1782,6 +1807,293 @@ void diff_clear(tabpage_T *tp) tp->tp_first_diff = NULL; } +/// +/// return true if the options are set to use diff linematch +/// +bool diff_linematch(diff_T *dp) +{ + if (!(diff_flags & DIFF_LINEMATCH)) { + return false; + } + // are there more than three diff buffers? + int tsize = 0; + for (int i = 0; i < DB_COUNT; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + // for the rare case (bug?) that the count of a diff block is negative, do + // not run the algorithm because this will try to allocate a negative + // amount of space and crash + if (dp->df_count[i] < 0) { + return false; + } + tsize += dp->df_count[i]; + } + } + // avoid allocating a huge array because it will lag + return tsize <= linematch_lines; +} + +static int get_max_diff_length(const diff_T *dp) +{ + int maxlength = 0; + for (int k = 0; k < DB_COUNT; k++) { + if (curtab->tp_diffbuf[k] != NULL) { + if (dp->df_count[k] > maxlength) { + maxlength = dp->df_count[k]; + } + } + } + return maxlength; +} + +static void find_top_diff_block(diff_T **thistopdiff, diff_T **nextblockblock, int fromidx, + int topline) +{ + diff_T *topdiff = NULL; + diff_T *localtopdiff = NULL; + int topdiffchange = 0; + + for (topdiff = curtab->tp_first_diff; topdiff != NULL; topdiff = topdiff->df_next) { + // set the top of the current overlapping diff block set as we + // iterate through all of the sets of overlapping diff blocks + if (!localtopdiff || topdiffchange) { + localtopdiff = topdiff; + topdiffchange = 0; + } + + // check if the fromwin topline is matched by the current diff. if so, set it to the top of the diff block + if (topline >= topdiff->df_lnum[fromidx] && topline <= + (topdiff->df_lnum[fromidx] + topdiff->df_count[fromidx])) { + // this line is inside the current diff block, so we will save the + // top block of the set of blocks to refer to later + if ((*thistopdiff) == NULL) { + (*thistopdiff) = localtopdiff; + } + } + + // check if the next set of overlapping diff blocks is next + if (!(topdiff->df_next && (topdiff->df_next->df_lnum[fromidx] == + (topdiff->df_lnum[fromidx] + topdiff->df_count[fromidx])))) { + // mark that the next diff block is belongs to a different set of + // overlapping diff blocks + topdiffchange = 1; + + // if we already have found that the line number is inside a diff block, + // set the marker of the next block and finish the iteration + if (*thistopdiff) { + (*nextblockblock) = topdiff->df_next; + break; + } + } + } +} + +static void count_filler_lines_and_topline(int *curlinenum_to, int *linesfiller, + const diff_T *thistopdiff, const int toidx, + int virtual_lines_passed) +{ + const diff_T *curdif = thistopdiff; + int ch_virtual_lines = 0; + int isfiller = 0; + while (virtual_lines_passed > 0) { + if (ch_virtual_lines) { + virtual_lines_passed--; + ch_virtual_lines--; + if (!isfiller) { + (*curlinenum_to)++; + } else { + (*linesfiller)++; + } + } else { + (*linesfiller) = 0; + ch_virtual_lines = get_max_diff_length(curdif); + isfiller = (curdif->df_count[toidx] ? 0 : 1); + if (isfiller) { + while (curdif && curdif->df_next && curdif->df_lnum[toidx] == + curdif->df_next->df_lnum[toidx] + && curdif->df_next->df_count[toidx] == 0) { + curdif = curdif->df_next; + ch_virtual_lines += get_max_diff_length(curdif); + } + } + if (curdif) { + curdif = curdif->df_next; + } + } + } +} + +static void calculate_topfill_and_topline(const int fromidx, const int toidx, const + int from_topline, const int from_topfill, int *topfill, + linenr_T *topline) +{ + // 1. find the position from the top of the diff block, and the start + // of the next diff block + diff_T *thistopdiff = NULL; + diff_T *nextblockblock = NULL; + int virtual_lines_passed = 0; + + find_top_diff_block(&thistopdiff, &nextblockblock, fromidx, from_topline); + + // count the virtual lines that have been passed + + diff_T *curdif = thistopdiff; + while (curdif && (curdif->df_lnum[fromidx] + curdif->df_count[fromidx]) + <= from_topline) { + virtual_lines_passed += get_max_diff_length(curdif); + + curdif = curdif->df_next; + } + + if (curdif != nextblockblock) { + virtual_lines_passed += from_topline - curdif->df_lnum[fromidx]; + } + virtual_lines_passed -= from_topfill; + + // count the same amount of virtual lines in the toidx buffer + int curlinenum_to = thistopdiff->df_lnum[toidx]; + int linesfiller = 0; + count_filler_lines_and_topline(&curlinenum_to, &linesfiller, + thistopdiff, toidx, virtual_lines_passed); + + // count the number of filler lines that would normally be above this line + int maxfiller = 0; + for (diff_T *dpfillertest = thistopdiff; dpfillertest != NULL; + dpfillertest = dpfillertest->df_next) { + if (dpfillertest->df_lnum[toidx] == curlinenum_to) { + while (dpfillertest && dpfillertest->df_lnum[toidx] == curlinenum_to) { + maxfiller += dpfillertest->df_count[toidx] ? 0 : get_max_diff_length(dpfillertest); + dpfillertest = dpfillertest->df_next; + } + break; + } + } + (*topfill) = maxfiller - linesfiller; + (*topline) = curlinenum_to; +} + +static int linematched_filler_lines(diff_T *dp, int idx, linenr_T lnum, int *linestatus) +{ + int filler_lines_d1 = 0; + while (dp && dp->df_next + && lnum == (dp->df_lnum[idx] + dp->df_count[idx]) + && dp->df_next->df_lnum[idx] == lnum) { + if (dp->df_count[idx] == 0) { + filler_lines_d1 += get_max_diff_length(dp); + } + dp = dp->df_next; + } + + if (dp->df_count[idx] == 0) { + filler_lines_d1 += get_max_diff_length(dp); + } + + if (lnum < dp->df_lnum[idx] + dp->df_count[idx]) { + int j = 0; + for (int i = 0; i < DB_COUNT; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + if (dp->df_count[i]) { + j++; + } + } + // is this an added line or a changed line? + if (linestatus) { + (*linestatus) = (j == 1) ? -2 : -1; + } + } + } + return filler_lines_d1; +} + +// Apply results from the linematch algorithm and apply to 'dp' by splitting it into multiple +// adjacent diff blocks. +static void apply_linematch_results(diff_T *dp, size_t decisions_length, const int *decisions) +{ + // get the start line number here in each diff buffer, and then increment + int line_numbers[DB_COUNT]; + int outputmap[DB_COUNT]; + size_t ndiffs = 0; + for (int i = 0; i < DB_COUNT; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + line_numbers[i] = dp->df_lnum[i]; + dp->df_count[i] = 0; + + // Keep track of the index of the diff buffer we are using here. + // We will use this to write the output of the algorithm to + // diff_T structs at the correct indexes + outputmap[ndiffs] = i; + ndiffs++; + } + } + + // write the diffs starting with the current diff block + diff_T *dp_s = dp; + for (size_t i = 0; i < decisions_length; i++) { + // Don't allocate on first iter since we can reuse the initial diffblock + if (i != 0 && (decisions[i - 1] != decisions[i])) { + // create new sub diff blocks to segment the original diff block which we + // further divided by running the linematch algorithm + dp_s = diff_alloc_new(curtab, dp_s, dp_s->df_next); + dp_s->is_linematched = true; + for (int j = 0; j < DB_COUNT; j++) { + if (curtab->tp_diffbuf[j] != NULL) { + dp_s->df_lnum[j] = line_numbers[j]; + dp_s->df_count[j] = 0; + } + } + } + for (size_t j = 0; j < ndiffs; j++) { + if (decisions[i] & (1 << j)) { + // will need to use the map here + dp_s->df_count[outputmap[j]]++; + line_numbers[outputmap[j]]++; + } + } + } + dp->is_linematched = true; +} + +static void run_linematch_algorithm(diff_T *dp) +{ + // define buffers for diff algorithm + mmfile_t diffbufs_mm[DB_COUNT]; + const char *diffbufs[DB_COUNT]; + int diff_length[DB_COUNT]; + size_t ndiffs = 0; + for (int i = 0; i < DB_COUNT; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + // write the contents of the entire buffer to + // diffbufs_mm[diffbuffers_count] + diff_write_buffer(curtab->tp_diffbuf[i], &diffbufs_mm[ndiffs], + dp->df_lnum[i], dp->df_lnum[i] + dp->df_count[i] - 1); + + // we want to get the char* to the diff buffer that was just written + // we add it to the array of char*, diffbufs + diffbufs[ndiffs] = diffbufs_mm[ndiffs].ptr; + + // keep track of the length of this diff block to pass it to the linematch + // algorithm + diff_length[ndiffs] = dp->df_count[i]; + + // increment the amount of diff buffers we are passing to the algorithm + ndiffs++; + } + } + + // we will get the output of the linematch algorithm in the format of an array + // of integers (*decisions) and the length of that array (decisions_length) + int *decisions = NULL; + const bool iwhite = (diff_flags & (DIFF_IWHITEALL | DIFF_IWHITE)) > 0; + size_t decisions_length = linematch_nbuffers(diffbufs, diff_length, ndiffs, &decisions, iwhite); + + for (size_t i = 0; i < ndiffs; i++) { + XFREE_CLEAR(diffbufs_mm[i].ptr); + } + + apply_linematch_results(dp, decisions_length, decisions); + + xfree(decisions); +} + /// Check diff status for line "lnum" in buffer "buf": /// /// Returns 0 for nothing special @@ -1793,11 +2105,11 @@ void diff_clear(tabpage_T *tp) /// /// @param wp /// @param lnum +/// @param[out] linestatus /// /// @return diff status. -int diff_check(win_T *wp, linenr_T lnum) +int diff_check_with_linestatus(win_T *wp, linenr_T lnum, int *linestatus) { - diff_T *dp; buf_T *buf = wp->w_buffer; if (curtab->tp_diff_invalid) { @@ -1828,6 +2140,7 @@ int diff_check(win_T *wp, linenr_T lnum) } // search for a change that includes "lnum" in the list of diffblocks. + diff_T *dp; for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) { if (lnum <= dp->df_lnum[idx] + dp->df_count[idx]) { break; @@ -1838,6 +2151,14 @@ int diff_check(win_T *wp, linenr_T lnum) return 0; } + if (!dp->is_linematched && diff_linematch(dp)) { + run_linematch_algorithm(dp); + } + + if (dp->is_linematched) { + return linematched_filler_lines(dp, idx, lnum, linestatus); + } + if (lnum < dp->df_lnum[idx] + dp->df_count[idx]) { int zero = false; @@ -1892,15 +2213,16 @@ int diff_check(win_T *wp, linenr_T lnum) // Insert filler lines above the line just below the change. Will return // 0 when this buf had the max count. - linenr_T maxcount = 0; - for (int i = 0; i < DB_COUNT; i++) { - if ((curtab->tp_diffbuf[i] != NULL) && (dp->df_count[i] > maxcount)) { - maxcount = dp->df_count[i]; - } - } + int maxcount = get_max_diff_length(dp); return maxcount - dp->df_count[idx]; } +/// See diff_check_with_linestatus +int diff_check(win_T *wp, linenr_T lnum) +{ + return diff_check_with_linestatus(wp, lnum, NULL); +} + /// Compare two entries in diff "dp" and return true if they are equal. /// /// @param dp diff @@ -1936,24 +2258,24 @@ static bool diff_equal_entry(diff_T *dp, int idx1, int idx2) // Compare the characters at "p1" and "p2". If they are equal (possibly // ignoring case) return true and set "len" to the number of bytes. -static bool diff_equal_char(const char_u *const p1, const char_u *const p2, int *const len) +static bool diff_equal_char(const char *const p1, const char *const p2, int *const len) { - const int l = utfc_ptr2len((char *)p1); + const int l = utfc_ptr2len(p1); - if (l != utfc_ptr2len((char *)p2)) { + if (l != utfc_ptr2len(p2)) { return false; } if (l > 1) { - if (STRNCMP(p1, p2, l) != 0 + if (strncmp(p1, p2, (size_t)l) != 0 && (!(diff_flags & DIFF_ICASE) - || utf_fold(utf_ptr2char((char *)p1)) != utf_fold(utf_ptr2char((char *)p2)))) { + || utf_fold(utf_ptr2char(p1)) != utf_fold(utf_ptr2char(p2)))) { return false; } *len = l; } else { if ((*p1 != *p2) && (!(diff_flags & DIFF_ICASE) - || TOLOWER_LOC(*p1) != TOLOWER_LOC(*p2))) { + || TOLOWER_LOC((uint8_t)(*p1)) != TOLOWER_LOC((uint8_t)(*p2)))) { return false; } *len = 1; @@ -1971,7 +2293,7 @@ static bool diff_equal_char(const char_u *const p1, const char_u *const p2, int static int diff_cmp(char *s1, char *s2) { if ((diff_flags & DIFF_IBLANK) - && (*(char_u *)skipwhite(s1) == NUL || *skipwhite(s2) == NUL)) { + && (*skipwhite(s1) == NUL || *skipwhite(s2) == NUL)) { return 0; } @@ -1980,7 +2302,7 @@ static int diff_cmp(char *s1, char *s2) } if ((diff_flags & DIFF_ICASE) && !(diff_flags & ALL_WHITE_DIFF)) { - return mb_stricmp((const char *)s1, (const char *)s2); + return mb_stricmp(s1, s2); } char *p1 = s1; @@ -1996,7 +2318,7 @@ static int diff_cmp(char *s1, char *s2) p2 = skipwhite(p2); } else { int l; - if (!diff_equal_char((char_u *)p1, (char_u *)p2, &l)) { + if (!diff_equal_char(p1, p2, &l)) { break; } p1 += l; @@ -2023,7 +2345,6 @@ void diff_set_topline(win_T *fromwin, win_T *towin) { buf_T *frombuf = fromwin->w_buffer; linenr_T lnum = fromwin->w_topline; - diff_T *dp; int fromidx = diff_buf_idx(frombuf); if (fromidx == DB_COUNT) { @@ -2038,6 +2359,7 @@ void diff_set_topline(win_T *fromwin, win_T *towin) towin->w_topfill = 0; // search for a change that includes "lnum" in the list of diffblocks. + diff_T *dp; for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) { if (lnum <= dp->df_lnum[fromidx] + dp->df_count[fromidx]) { break; @@ -2060,46 +2382,51 @@ void diff_set_topline(win_T *fromwin, win_T *towin) towin->w_topline = lnum + (dp->df_lnum[toidx] - dp->df_lnum[fromidx]); if (lnum >= dp->df_lnum[fromidx]) { - // Inside a change: compute filler lines. With three or more - // buffers we need to know the largest count. - linenr_T max_count = 0; - - for (int i = 0; i < DB_COUNT; i++) { - if ((curtab->tp_diffbuf[i] != NULL) && (max_count < dp->df_count[i])) { - max_count = dp->df_count[i]; - } - } + if (diff_flags & DIFF_LINEMATCH) { + calculate_topfill_and_topline(fromidx, toidx, fromwin->w_topline, + fromwin->w_topfill, &towin->w_topfill, &towin->w_topline); + } else { + // Inside a change: compute filler lines. With three or more + // buffers we need to know the largest count. + linenr_T max_count = 0; - if (dp->df_count[toidx] == dp->df_count[fromidx]) { - // same number of lines: use same filler count - towin->w_topfill = fromwin->w_topfill; - } else if (dp->df_count[toidx] > dp->df_count[fromidx]) { - if (lnum == dp->df_lnum[fromidx] + dp->df_count[fromidx]) { - // more lines in towin and fromwin doesn't show diff - // lines, only filler lines - if (max_count - fromwin->w_topfill >= dp->df_count[toidx]) { - // towin also only shows filler lines - towin->w_topline = dp->df_lnum[toidx] + dp->df_count[toidx]; - towin->w_topfill = fromwin->w_topfill; - } else { - // towin still has some diff lines to show - towin->w_topline = dp->df_lnum[toidx] - + max_count - fromwin->w_topfill; + for (int i = 0; i < DB_COUNT; i++) { + if ((curtab->tp_diffbuf[i] != NULL) && (max_count < dp->df_count[i])) { + max_count = dp->df_count[i]; } } - } else if (towin->w_topline >= dp->df_lnum[toidx] - + dp->df_count[toidx]) { - // less lines in towin and no diff lines to show: compute - // filler lines - towin->w_topline = dp->df_lnum[toidx] + dp->df_count[toidx]; - if (diff_flags & DIFF_FILLER) { + if (dp->df_count[toidx] == dp->df_count[fromidx]) { + // same number of lines: use same filler count + towin->w_topfill = fromwin->w_topfill; + } else if (dp->df_count[toidx] > dp->df_count[fromidx]) { if (lnum == dp->df_lnum[fromidx] + dp->df_count[fromidx]) { - // fromwin is also out of diff lines - towin->w_topfill = fromwin->w_topfill; - } else { - // fromwin has some diff lines - towin->w_topfill = dp->df_lnum[fromidx] + max_count - lnum; + // more lines in towin and fromwin doesn't show diff + // lines, only filler lines + if (max_count - fromwin->w_topfill >= dp->df_count[toidx]) { + // towin also only shows filler lines + towin->w_topline = dp->df_lnum[toidx] + dp->df_count[toidx]; + towin->w_topfill = fromwin->w_topfill; + } else { + // towin still has some diff lines to show + towin->w_topline = dp->df_lnum[toidx] + + max_count - fromwin->w_topfill; + } + } + } else if (towin->w_topline >= dp->df_lnum[toidx] + + dp->df_count[toidx]) { + // less lines in towin and no diff lines to show: compute + // filler lines + towin->w_topline = dp->df_lnum[toidx] + dp->df_count[toidx]; + + if (diff_flags & DIFF_FILLER) { + if (lnum == dp->df_lnum[fromidx] + dp->df_count[fromidx]) { + // fromwin is also out of diff lines + towin->w_topfill = fromwin->w_topfill; + } else { + // fromwin has some diff lines + towin->w_topfill = dp->df_lnum[fromidx] + max_count - lnum; + } } } } @@ -2134,6 +2461,7 @@ void diff_set_topline(win_T *fromwin, win_T *towin) int diffopt_changed(void) { int diff_context_new = 6; + int linematch_lines_new = 0; int diff_flags_new = 0; int diff_foldcolumn_new = 2; long diff_algorithm_new = 0; @@ -2141,68 +2469,72 @@ int diffopt_changed(void) char *p = p_dip; while (*p != NUL) { - if (STRNCMP(p, "filler", 6) == 0) { + if (strncmp(p, "filler", 6) == 0) { p += 6; diff_flags_new |= DIFF_FILLER; - } else if ((STRNCMP(p, "context:", 8) == 0) && ascii_isdigit(p[8])) { + } else if ((strncmp(p, "context:", 8) == 0) && ascii_isdigit(p[8])) { p += 8; diff_context_new = getdigits_int(&p, false, diff_context_new); - } else if (STRNCMP(p, "iblank", 6) == 0) { + } else if (strncmp(p, "iblank", 6) == 0) { p += 6; diff_flags_new |= DIFF_IBLANK; - } else if (STRNCMP(p, "icase", 5) == 0) { + } else if (strncmp(p, "icase", 5) == 0) { p += 5; diff_flags_new |= DIFF_ICASE; - } else if (STRNCMP(p, "iwhiteall", 9) == 0) { + } else if (strncmp(p, "iwhiteall", 9) == 0) { p += 9; diff_flags_new |= DIFF_IWHITEALL; - } else if (STRNCMP(p, "iwhiteeol", 9) == 0) { + } else if (strncmp(p, "iwhiteeol", 9) == 0) { p += 9; diff_flags_new |= DIFF_IWHITEEOL; - } else if (STRNCMP(p, "iwhite", 6) == 0) { + } else if (strncmp(p, "iwhite", 6) == 0) { p += 6; diff_flags_new |= DIFF_IWHITE; - } else if (STRNCMP(p, "horizontal", 10) == 0) { + } else if (strncmp(p, "horizontal", 10) == 0) { p += 10; diff_flags_new |= DIFF_HORIZONTAL; - } else if (STRNCMP(p, "vertical", 8) == 0) { + } else if (strncmp(p, "vertical", 8) == 0) { p += 8; diff_flags_new |= DIFF_VERTICAL; - } else if ((STRNCMP(p, "foldcolumn:", 11) == 0) && ascii_isdigit(p[11])) { + } else if ((strncmp(p, "foldcolumn:", 11) == 0) && ascii_isdigit(p[11])) { p += 11; diff_foldcolumn_new = getdigits_int(&p, false, diff_foldcolumn_new); - } else if (STRNCMP(p, "hiddenoff", 9) == 0) { + } else if (strncmp(p, "hiddenoff", 9) == 0) { p += 9; diff_flags_new |= DIFF_HIDDEN_OFF; - } else if (STRNCMP(p, "closeoff", 8) == 0) { + } else if (strncmp(p, "closeoff", 8) == 0) { p += 8; diff_flags_new |= DIFF_CLOSE_OFF; - } else if (STRNCMP(p, "followwrap", 10) == 0) { + } else if (strncmp(p, "followwrap", 10) == 0) { p += 10; diff_flags_new |= DIFF_FOLLOWWRAP; - } else if (STRNCMP(p, "indent-heuristic", 16) == 0) { + } else if (strncmp(p, "indent-heuristic", 16) == 0) { p += 16; diff_indent_heuristic = XDF_INDENT_HEURISTIC; - } else if (STRNCMP(p, "internal", 8) == 0) { + } else if (strncmp(p, "internal", 8) == 0) { p += 8; diff_flags_new |= DIFF_INTERNAL; - } else if (STRNCMP(p, "algorithm:", 10) == 0) { + } else if (strncmp(p, "algorithm:", 10) == 0) { p += 10; - if (STRNCMP(p, "myers", 5) == 0) { + if (strncmp(p, "myers", 5) == 0) { p += 5; diff_algorithm_new = 0; - } else if (STRNCMP(p, "minimal", 7) == 0) { + } else if (strncmp(p, "minimal", 7) == 0) { p += 7; diff_algorithm_new = XDF_NEED_MINIMAL; - } else if (STRNCMP(p, "patience", 8) == 0) { + } else if (strncmp(p, "patience", 8) == 0) { p += 8; diff_algorithm_new = XDF_PATIENCE_DIFF; - } else if (STRNCMP(p, "histogram", 9) == 0) { + } else if (strncmp(p, "histogram", 9) == 0) { p += 9; diff_algorithm_new = XDF_HISTOGRAM_DIFF; } else { return FAIL; } + } else if ((strncmp(p, "linematch:", 10) == 0) && ascii_isdigit(p[10])) { + p += 10; + linematch_lines_new = getdigits_int(&p, false, linematch_lines_new); + diff_flags_new |= DIFF_LINEMATCH; } if ((*p != ',') && (*p != NUL)) { @@ -2231,6 +2563,7 @@ int diffopt_changed(void) diff_flags = diff_flags_new; diff_context = diff_context_new == 0 ? 1 : diff_context_new; + linematch_lines = linematch_lines_new; diff_foldcolumn = diff_foldcolumn_new; diff_algorithm = diff_algorithm_new; @@ -2281,14 +2614,6 @@ bool diffopt_filler(void) bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - char *line_new; - int si_org; - int si_new; - int ei_org; - int ei_new; - bool added = true; - int l; - // Make a copy of the line, the next ml_get() will invalidate it. char *line_org = xstrdup(ml_get_buf(wp->w_buffer, lnum, false)); @@ -2306,6 +2631,13 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) break; } } + if (dp != NULL && dp->is_linematched) { + while (dp && dp->df_next + && lnum == dp->df_count[idx] + dp->df_lnum[idx] + && dp->df_next->df_lnum[idx] == lnum) { + dp = dp->df_next; + } + } if ((dp == NULL) || (diff_check_sanity(curtab, dp) == FAIL)) { xfree(line_org); @@ -2313,6 +2645,12 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) return false; } + int si_org; + int si_new; + int ei_org; + int ei_new; + bool added = true; + linenr_T off = lnum - dp->df_lnum[idx]; int i; for (i = 0; i < DB_COUNT; i++) { @@ -2322,7 +2660,7 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) continue; } added = false; - line_new = ml_get_buf(curtab->tp_diffbuf[i], dp->df_lnum[i] + off, false); + char *line_new = ml_get_buf(curtab->tp_diffbuf[i], dp->df_lnum[i] + off, false); // Search for start of difference si_org = si_new = 0; @@ -2337,7 +2675,8 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) si_org = (int)(skipwhite(line_org + si_org) - line_org); si_new = (int)(skipwhite(line_new + si_new) - line_new); } else { - if (!diff_equal_char((char_u *)line_org + si_org, (char_u *)line_new + si_new, &l)) { + int l; + if (!diff_equal_char(line_org + si_org, line_new + si_new, &l)) { break; } si_org += l; @@ -2377,12 +2716,13 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) ei_new--; } } else { - const char_u *p1 = (char_u *)line_org + ei_org; - const char_u *p2 = (char_u *)line_new + ei_new; + const char *p1 = line_org + ei_org; + const char *p2 = line_new + ei_new; - p1 -= utf_head_off(line_org, (char *)p1); - p2 -= utf_head_off(line_new, (char *)p2); + p1 -= utf_head_off(line_org, p1); + p2 -= utf_head_off(line_new, p2); + int l; if (!diff_equal_char(p1, p2, &l)) { break; } @@ -2412,16 +2752,14 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) bool diff_infold(win_T *wp, linenr_T lnum) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1) { - bool other = false; - // Return if 'diff' isn't set. if (!wp->w_p_diff) { return false; } int idx = -1; - int i; - for (i = 0; i < DB_COUNT; i++) { + bool other = false; + for (int i = 0; i < DB_COUNT; i++) { if (curtab->tp_diffbuf[i] == wp->w_buffer) { idx = i; } else if (curtab->tp_diffbuf[i] != NULL) { @@ -2461,13 +2799,13 @@ bool diff_infold(win_T *wp, linenr_T lnum) /// "dp" and "do" commands. void nv_diffgetput(bool put, size_t count) { - exarg_T ea; - char buf[30]; - if (bt_prompt(curbuf)) { vim_beep(BO_OPER); return; } + + exarg_T ea; + char buf[30]; if (count == 0) { ea.arg = ""; } else { @@ -2503,24 +2841,7 @@ static bool valid_diff(diff_T *diff) /// @param eap void ex_diffgetput(exarg_T *eap) { - linenr_T lnum; - linenr_T count; - linenr_T off = 0; - diff_T *dp; - diff_T *dfree; - int i; - int added; - char *p; - aco_save_T aco; - buf_T *buf; - linenr_T start_skip; - linenr_T end_skip; - linenr_T new_count; - int buf_empty; - int found_not_ma = false; int idx_other; - int idx_from; - int idx_to; // Find the current buffer in the list of diff buffers. int idx_cur = diff_buf_idx(curbuf); @@ -2530,6 +2851,7 @@ void ex_diffgetput(exarg_T *eap) } if (*eap->arg == NUL) { + int found_not_ma = false; // No argument: Find the other buffer in the list of diff buffers. for (idx_other = 0; idx_other < DB_COUNT; idx_other++) { if ((curtab->tp_diffbuf[idx_other] != curbuf) @@ -2552,7 +2874,7 @@ void ex_diffgetput(exarg_T *eap) } // Check that there isn't a third buffer in the list - for (i = idx_other + 1; i < DB_COUNT; i++) { + for (int i = idx_other + 1; i < DB_COUNT; i++) { if ((curtab->tp_diffbuf[i] != curbuf) && (curtab->tp_diffbuf[i] != NULL) && ((eap->cmdidx != CMD_diffput) @@ -2564,11 +2886,12 @@ void ex_diffgetput(exarg_T *eap) } } else { // Buffer number or pattern given. Ignore trailing white space. - p = eap->arg + strlen(eap->arg); + char *p = eap->arg + strlen(eap->arg); while (p > eap->arg && ascii_iswhite(p[-1])) { p--; } + int i; for (i = 0; ascii_isdigit(eap->arg[i]) && eap->arg + i < p; i++) {} if (eap->arg + i == p) { @@ -2582,7 +2905,7 @@ void ex_diffgetput(exarg_T *eap) return; } } - buf = buflist_findnr(i); + buf_T *buf = buflist_findnr(i); if (buf == NULL) { semsg(_("E102: Can't find buffer \"%s\""), eap->arg); @@ -2617,20 +2940,19 @@ void ex_diffgetput(exarg_T *eap) } } - if (eap->cmdidx == CMD_diffget) { - idx_from = idx_other; - idx_to = idx_cur; - } else { - idx_from = idx_cur; - idx_to = idx_other; + aco_save_T aco; + if (eap->cmdidx != CMD_diffget) { // Need to make the other buffer the current buffer to be able to make // changes in it. - // set curwin/curbuf to buf and save a few things + // Set curwin/curbuf to buf and save a few things. aucmd_prepbuf(&aco, curtab->tp_diffbuf[idx_other]); } + const int idx_from = eap->cmdidx == CMD_diffget ? idx_other : idx_cur; + const int idx_to = eap->cmdidx == CMD_diffget ? idx_cur : idx_other; + // May give the warning for a changed buffer here, which can trigger the // FileChangedRO autocommand, which may do nasty things and mess // everything up. @@ -2642,26 +2964,83 @@ void ex_diffgetput(exarg_T *eap) } } + diffgetput(eap->addr_count, idx_cur, idx_from, idx_to, eap->line1, eap->line2); + + // restore curwin/curbuf and a few other things + if (eap->cmdidx != CMD_diffget) { + // Syncing undo only works for the current buffer, but we change + // another buffer. Sync undo if the command was typed. This isn't + // 100% right when ":diffput" is used in a function or mapping. + if (KeyTyped) { + u_sync(false); + } + aucmd_restbuf(&aco); + } + +theend: + diff_busy = false; + + if (diff_need_update) { + ex_diffupdate(NULL); + } + + // Check that the cursor is on a valid character and update its + // position. When there were filler lines the topline has become + // invalid. + check_cursor(); + changed_line_abv_curs(); + + if (diff_need_update) { + // redraw already done by ex_diffupdate() + diff_need_update = false; + } else { + // Also need to redraw the other buffers. + diff_redraw(false); + apply_autocmds(EVENT_DIFFUPDATED, NULL, NULL, false, curbuf); + } +} + +/// Apply diffget/diffput to buffers and diffblocks +/// +/// @param idx_cur index of "curbuf" before aucmd_prepbuf() in the list of diff buffers +/// @param idx_from index of the buffer to read from in the list of diff buffers +/// @param idx_to index of the buffer to modify in the list of diff buffers +static void diffgetput(const int addr_count, const int idx_cur, const int idx_from, + const int idx_to, const linenr_T line1, const linenr_T line2) +{ + linenr_T off = 0; diff_T *dprev = NULL; - for (dp = curtab->tp_first_diff; dp != NULL;) { - if (dp->df_lnum[idx_cur] > eap->line2 + off) { + for (diff_T *dp = curtab->tp_first_diff; dp != NULL;) { + if (!addr_count) { + // handle the case with adjacent diff blocks + while (dp->is_linematched + && dp->df_next + && dp->df_next->df_lnum[idx_cur] == dp->df_lnum[idx_cur] + dp->df_count[idx_cur] + && dp->df_next->df_lnum[idx_cur] == line1 + off + 1) { + dprev = dp; + dp = dp->df_next; + } + } + + if (dp->df_lnum[idx_cur] > line2 + off) { // past the range that was specified break; } - dfree = NULL; - lnum = dp->df_lnum[idx_to]; - count = dp->df_count[idx_to]; + diff_T dfree = { 0 }; + bool did_free = false; + linenr_T lnum = dp->df_lnum[idx_to]; + linenr_T count = dp->df_count[idx_to]; - if ((dp->df_lnum[idx_cur] + dp->df_count[idx_cur] > eap->line1 + off) + if ((dp->df_lnum[idx_cur] + dp->df_count[idx_cur] > line1 + off) && (u_save(lnum - 1, lnum + count) != FAIL)) { // Inside the specified range and saving for undo worked. - start_skip = 0; - end_skip = 0; + linenr_T start_skip = 0; + linenr_T end_skip = 0; - if (eap->addr_count > 0) { + if (addr_count > 0) { // A range was specified: check if lines need to be skipped. - start_skip = eap->line1 + off - dp->df_lnum[idx_cur]; + start_skip = line1 + off - dp->df_lnum[idx_cur]; if (start_skip > 0) { // range starts below start of current diff block if (start_skip > count) { @@ -2676,13 +3055,13 @@ void ex_diffgetput(exarg_T *eap) } end_skip = dp->df_lnum[idx_cur] + dp->df_count[idx_cur] - 1 - - (eap->line2 + off); + - (line2 + off); if (end_skip > 0) { // range ends above end of current/from diff block if (idx_cur == idx_from) { // :diffput - i = dp->df_count[idx_cur] - start_skip - end_skip; + int i = dp->df_count[idx_cur] - start_skip - end_skip; if (count > i) { count = i; @@ -2701,10 +3080,10 @@ void ex_diffgetput(exarg_T *eap) } } - buf_empty = buf_is_empty(curbuf); - added = 0; + bool buf_empty = buf_is_empty(curbuf); + int added = 0; - for (i = 0; i < count; i++) { + for (int i = 0; i < count; i++) { // remember deleting the last line of the buffer buf_empty = curbuf->b_ml.ml_line_count == 1; if (ml_delete(lnum, false) == OK) { @@ -2712,12 +3091,12 @@ void ex_diffgetput(exarg_T *eap) } } - for (i = 0; i < dp->df_count[idx_from] - start_skip - end_skip; i++) { + for (int i = 0; i < dp->df_count[idx_from] - start_skip - end_skip; i++) { linenr_T nr = dp->df_lnum[idx_from] + start_skip + i; if (nr > curtab->tp_diffbuf[idx_from]->b_ml.ml_line_count) { break; } - p = xstrdup(ml_get_buf(curtab->tp_diffbuf[idx_from], nr, false)); + char *p = xstrdup(ml_get_buf(curtab->tp_diffbuf[idx_from], nr, false)); ml_append(lnum + i - 1, p, 0, false); xfree(p); added++; @@ -2728,12 +3107,13 @@ void ex_diffgetput(exarg_T *eap) ml_delete((linenr_T)2, false); } } - new_count = dp->df_count[idx_to] + added; + linenr_T new_count = dp->df_count[idx_to] + added; dp->df_count[idx_to] = new_count; if ((start_skip == 0) && (end_skip == 0)) { // Check if there are any other buffers and if the diff is // equal in them. + int i; for (i = 0; i < DB_COUNT; i++) { if ((curtab->tp_diffbuf[i] != NULL) && (i != idx_from) @@ -2745,14 +3125,9 @@ void ex_diffgetput(exarg_T *eap) if (i == DB_COUNT) { // delete the diff entry, the buffers are now equal here - dfree = dp; - dp = dp->df_next; - - if (dprev == NULL) { - curtab->tp_first_diff = dp; - } else { - dprev->df_next = dp; - } + dfree = *dp; + did_free = true; + dp = diff_free(curtab, dprev, dp); } } @@ -2771,10 +3146,9 @@ void ex_diffgetput(exarg_T *eap) } changed_lines(lnum, 0, lnum + count, added, true); - if (dfree != NULL) { + if (did_free) { // Diff is deleted, update folds in other windows. - diff_fold_update(dfree, idx_to); - xfree(dfree); + diff_fold_update(&dfree, idx_to); } // mark_adjust() may have made "dp" invalid. We don't know where @@ -2783,7 +3157,7 @@ void ex_diffgetput(exarg_T *eap) break; } - if (dfree == NULL) { + if (!did_free) { // mark_adjust() may have changed the count in a wrong way dp->df_count[idx_to] = new_count; } @@ -2795,43 +3169,11 @@ void ex_diffgetput(exarg_T *eap) } // If before the range or not deleted, go to next diff. - if (dfree == NULL) { + if (!did_free) { dprev = dp; dp = dp->df_next; } } - - // restore curwin/curbuf and a few other things - if (eap->cmdidx != CMD_diffget) { - // Syncing undo only works for the current buffer, but we change - // another buffer. Sync undo if the command was typed. This isn't - // 100% right when ":diffput" is used in a function or mapping. - if (KeyTyped) { - u_sync(false); - } - aucmd_restbuf(&aco); - } - -theend: - diff_busy = false; - if (diff_need_update) { - ex_diffupdate(NULL); - } - - // Check that the cursor is on a valid character and update its - // position. When there were filler lines the topline has become - // invalid. - check_cursor(); - changed_line_abv_curs(); - - if (diff_need_update) { - // redraw already done by ex_diffupdate() - diff_need_update = false; - } else { - // Also need to redraw the other buffers. - diff_redraw(false); - apply_autocmds(EVENT_DIFFUPDATED, NULL, NULL, false, curbuf); - } } /// Update folds for all diff buffers for entry "dp". @@ -3055,7 +3397,7 @@ linenr_T diff_lnum_win(linenr_T lnum, win_T *wp) /// Handle an ED style diff line. /// Return FAIL if the line does not contain diff info. /// -static int parse_diff_ed(char_u *line, diffhunk_T *hunk) +static int parse_diff_ed(char *line, diffhunk_T *hunk) { long l1, l2; @@ -3063,7 +3405,7 @@ static int parse_diff_ed(char_u *line, diffhunk_T *hunk) // change: {first}[,{last}]c{first}[,{last}] // append: {first}a{first}[,{last}] // delete: {first}[,{last}]d{first} - char *p = (char *)line; + char *p = line; linenr_T f1 = getdigits_int32(&p, true, 0); if (*p == ',') { p++; @@ -3107,11 +3449,11 @@ static int parse_diff_ed(char_u *line, diffhunk_T *hunk) /// Parses unified diff with zero(!) context lines. /// Return FAIL if there is no diff information in "line". /// -static int parse_diff_unified(char_u *line, diffhunk_T *hunk) +static int parse_diff_unified(char *line, diffhunk_T *hunk) { // Parse unified diff hunk header: // @@ -oldline,oldcount +newline,newcount @@ - char *p = (char *)line; + char *p = line; if (*p++ == '@' && *p++ == '@' && *p++ == ' ' && *p++ == '-') { long oldcount; long newline; @@ -3163,13 +3505,11 @@ static int parse_diff_unified(char_u *line, diffhunk_T *hunk) static int xdiff_out(long start_a, long count_a, long start_b, long count_b, void *priv) { diffout_T *dout = (diffout_T *)priv; - diffhunk_T *p = xmalloc(sizeof(*p)); - - ga_grow(&dout->dout_ga, 1); - p->lnum_orig = (linenr_T)start_a + 1; - p->count_orig = count_a; - p->lnum_new = (linenr_T)start_b + 1; - p->count_new = count_b; - ((diffhunk_T **)dout->dout_ga.ga_data)[dout->dout_ga.ga_len++] = p; + GA_APPEND(diffhunk_T, &(dout->dout_ga), ((diffhunk_T){ + .lnum_orig = (linenr_T)start_a + 1, + .count_orig = count_a, + .lnum_new = (linenr_T)start_b + 1, + .count_new = count_b, + })); return 0; } diff --git a/src/nvim/diff.h b/src/nvim/diff.h index 53fc5aa077..1f64465336 100644 --- a/src/nvim/diff.h +++ b/src/nvim/diff.h @@ -1,7 +1,10 @@ #ifndef NVIM_DIFF_H #define NVIM_DIFF_H +#include <stdbool.h> + #include "nvim/ex_cmds_defs.h" +#include "nvim/macros.h" #include "nvim/pos.h" // Value set from 'diffopt'. diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index 1267d49ad1..a057978a5e 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -8,24 +8,34 @@ #include <assert.h> #include <inttypes.h> #include <stdbool.h> +#include <string.h> #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/digraph.h" #include "nvim/drawscreen.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" +#include "nvim/keycodes.h" #include "nvim/mapping.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/normal.h" +#include "nvim/option_defs.h" #include "nvim/os/input.h" #include "nvim/runtime.h" #include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/vim.h" typedef int result_T; @@ -52,7 +62,6 @@ static garray_T user_digraphs = { 0, 0, (int)sizeof(digr_T), 10, NULL }; /// Note: Characters marked with XX are not included literally, because some /// compilers cannot handle them (Amiga SAS/C is the most picky one). static digr_T digraphdefault[] = - // digraphs for Unicode from RFC1345 // (also work for ISO-8859-1 aka latin1) { @@ -1480,7 +1489,7 @@ int do_digraph(int c) /// Find a digraph for "val". If found return the string to display it. /// If not found return NULL. -char_u *get_digraph_for_char(int val_arg) +char *get_digraph_for_char(int val_arg) { const int val = val_arg; const digr_T *dp; @@ -1497,7 +1506,7 @@ char_u *get_digraph_for_char(int val_arg) r[0] = dp->char1; r[1] = dp->char2; r[2] = NUL; - return r; + return (char *)r; } dp++; } @@ -1519,30 +1528,31 @@ int get_digraph(bool cmdline) no_mapping--; allow_keys--; - if (c != ESC) { - // ESC cancels CTRL-K - if (IS_SPECIAL(c)) { - // insert special key code - return c; - } + if (c == ESC) { // ESC cancels CTRL-K + return NUL; + } - if (cmdline) { - if ((char2cells(c) == 1) && c < 128 && (cmdline_star == 0)) { - putcmdline((char)c, true); - } - } else { - add_to_showcmd(c); - } - no_mapping++; - allow_keys++; - int cc = plain_vgetc(); - no_mapping--; - allow_keys--; - - if (cc != ESC) { - // ESC cancels CTRL-K - return digraph_get(c, cc, true); + if (IS_SPECIAL(c)) { + // insert special key code + return c; + } + + if (cmdline) { + if ((char2cells(c) == 1) && c < 128 && (cmdline_star == 0)) { + putcmdline((char)c, true); } + } else { + add_to_showcmd(c); + } + no_mapping++; + allow_keys++; + int cc = plain_vgetc(); + no_mapping--; + allow_keys--; + + if (cc != ESC) { + // ESC cancels CTRL-K + return digraph_get(c, cc, true); } return NUL; } @@ -1646,8 +1656,8 @@ static void registerdigraph(int char1, int char2, int n) bool check_digraph_chars_valid(int char1, int char2) { if (char2 == 0) { - char_u msg[MB_MAXBYTES + 1]; - msg[utf_char2bytes(char1, (char *)msg)] = NUL; + char msg[MB_MAXBYTES + 1]; + msg[utf_char2bytes(char1, msg)] = NUL; semsg(_(e_digraph_must_be_just_two_characters_str), msg); return false; } @@ -1739,16 +1749,16 @@ 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; + char buf[30]; + buf[0] = (char)dp->char1; + buf[1] = (char)dp->char2; buf[2] = NUL; - tv_list_append_string(l2, (char *)buf, -1); + tv_list_append_string(l2, buf, -1); - char_u *p = buf; - p += utf_char2bytes(dp->result, (char *)p); + char *p = buf; + p += utf_char2bytes(dp->result, p); *p = NUL; - tv_list_append_string(l2, (char *)buf, -1); + tv_list_append_string(l2, buf, -1); } void digraph_getlist_common(bool list_all, typval_T *rettv) @@ -1814,57 +1824,59 @@ struct dg_header_entry { static void printdigraph(const digr_T *dp, result_T *previous) FUNC_ATTR_NONNULL_ARG(1) { - char_u buf[30]; + char buf[30]; int list_width = 13; - if (dp->result != 0) { - if (previous != NULL) { - for (int i = 0; header_table[i].dg_header != NULL; i++) { - if (*previous < header_table[i].dg_start - && dp->result >= header_table[i].dg_start - && dp->result < header_table[i + 1].dg_start) { - digraph_header(_(header_table[i].dg_header)); - break; - } + if (dp->result == 0) { + return; + } + + if (previous != NULL) { + for (int i = 0; header_table[i].dg_header != NULL; i++) { + if (*previous < header_table[i].dg_start + && dp->result >= header_table[i].dg_start + && dp->result < header_table[i + 1].dg_start) { + digraph_header(_(header_table[i].dg_header)); + break; } - *previous = dp->result; - } - if (msg_col > Columns - list_width) { - msg_putchar('\n'); } + *previous = dp->result; + } + if (msg_col > Columns - list_width) { + msg_putchar('\n'); + } - // Make msg_col a multiple of list_width by using spaces. - if (msg_col % list_width != 0) { - int spaces = (msg_col / list_width + 1) * list_width - msg_col; - while (spaces--) { - msg_putchar(' '); - } + // Make msg_col a multiple of list_width by using spaces. + if (msg_col % list_width != 0) { + int spaces = (msg_col / list_width + 1) * list_width - msg_col; + while (spaces--) { + msg_putchar(' '); } + } - char_u *p = &buf[0]; - *p++ = dp->char1; - *p++ = dp->char2; - *p++ = ' '; - *p = NUL; - msg_outtrans((char *)buf); - p = buf; + char *p = &buf[0]; + *p++ = (char)dp->char1; + *p++ = (char)dp->char2; + *p++ = ' '; + *p = NUL; + msg_outtrans(buf); + p = buf; - // add a space to draw a composing char on - if (utf_iscomposing(dp->result)) { - *p++ = ' '; - } - p += utf_char2bytes(dp->result, (char *)p); + // add a space to draw a composing char on + if (utf_iscomposing(dp->result)) { + *p++ = ' '; + } + p += utf_char2bytes(dp->result, p); - *p = NUL; - msg_outtrans_attr((char *)buf, HL_ATTR(HLF_8)); - p = buf; - if (char2cells(dp->result) == 1) { - *p++ = ' '; - } - assert(p >= buf); - vim_snprintf((char *)p, sizeof(buf) - (size_t)(p - buf), " %3d", dp->result); - msg_outtrans((char *)buf); + *p = NUL; + msg_outtrans_attr(buf, HL_ATTR(HLF_8)); + p = buf; + if (char2cells(dp->result) == 1) { + *p++ = ' '; } + assert(p >= buf); + vim_snprintf(p, sizeof(buf) - (size_t)(p - buf), " %3d", dp->result); + msg_outtrans(buf); } /// Get the two digraph characters from a typval. @@ -1873,7 +1885,7 @@ 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; + const char *p = chars; if (p != NULL) { if (*p != NUL) { @@ -1905,7 +1917,7 @@ static bool digraph_set_common(const typval_T *argchars, const typval_T *argdigr if (digraph == NULL) { return false; } - const char_u *p = (const char_u *)digraph; + const char *p = digraph; int n = mb_cptr2char_adv(&p); if (*p != NUL) { semsg(_(e_digraph_argument_must_be_one_character_str), digraph); @@ -2121,7 +2133,7 @@ void ex_loadkeymap(exarg_T *eap) vim_snprintf(buf, sizeof(buf), "<buffer> %s %s", ((kmap_T *)curbuf->b_kmap_ga.ga_data)[i].from, ((kmap_T *)curbuf->b_kmap_ga.ga_data)[i].to); - (void)do_map(MAPTYPE_MAP, (char_u *)buf, MODE_LANGMAP, false); + (void)do_map(MAPTYPE_MAP, buf, MODE_LANGMAP, false); } p_cpo = save_cpo; @@ -2158,7 +2170,7 @@ static void keymap_unload(void) for (int i = 0; i < curbuf->b_kmap_ga.ga_len; i++) { vim_snprintf(buf, sizeof(buf), "<buffer> %s", kp[i].from); - (void)do_map(MAPTYPE_UNMAP, (char_u *)buf, MODE_LANGMAP, false); + (void)do_map(MAPTYPE_UNMAP, buf, MODE_LANGMAP, false); } keymap_ga_clear(&curbuf->b_kmap_ga); diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index c6145a6c39..28f19a95e0 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -5,40 +5,69 @@ // This is the middle level, drawscreen.c is the top and grid.c/screen.c the lower level. #include <assert.h> -#include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include "nvim/arabic.h" +#include "nvim/ascii.h" #include "nvim/buffer.h" -#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/cursor_shape.h" #include "nvim/decoration.h" +#include "nvim/decoration_provider.h" #include "nvim/diff.h" #include "nvim/drawline.h" +#include "nvim/eval.h" +#include "nvim/extmark_defs.h" #include "nvim/fold.h" +#include "nvim/garray.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" +#include "nvim/mark.h" #include "nvim/match.h" +#include "nvim/mbyte.h" +#include "nvim/memline.h" +#include "nvim/memory.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" +#include "nvim/pos.h" #include "nvim/quickfix.h" -#include "nvim/search.h" +#include "nvim/screen.h" #include "nvim/sign.h" #include "nvim/spell.h" #include "nvim/state.h" +#include "nvim/statusline.h" +#include "nvim/strings.h" #include "nvim/syntax.h" -#include "nvim/undo.h" -#include "nvim/window.h" +#include "nvim/terminal.h" +#include "nvim/types.h" +#include "nvim/ui.h" +#include "nvim/vim.h" #define MB_FILLER_CHAR '<' // character used when a double-width character // doesn't fit. +/// possible draw states in win_line(), drawn in sequence. +typedef enum { + WL_START = 0, // nothing done yet + WL_CMDLINE, // cmdline window column + WL_FOLD, // 'foldcolumn' + WL_SIGN, // column for signs + WL_NR, // line number + WL_STC, // 'statuscolumn' + WL_BRI, // 'breakindent' + WL_SBR, // 'showbreak' or 'diff' + WL_LINE, // text in the line +} LineDrawState; + /// for line_putchar. Contains the state that needs to be remembered from /// putting one character to the next. typedef struct { @@ -111,39 +140,39 @@ static void margin_columns_win(win_T *wp, int *left_col, int *right_col) /// Handles composing chars and arabic shaping state. static int line_putchar(buf_T *buf, LineState *s, schar_T *dest, int maxcells, bool rl, int vcol) { - const char_u *p = (char_u *)s->p; - int cells = utf_ptr2cells((char *)p); - int c_len = utfc_ptr2len((char *)p); + const char *p = s->p; + int cells = utf_ptr2cells(p); + int c_len = utfc_ptr2len(p); int u8c, u8cc[MAX_MCO]; if (cells > maxcells) { return -1; } - u8c = utfc_ptr2char((char *)p, u8cc); + u8c = utfc_ptr2char(p, u8cc); if (*p == TAB) { cells = MIN(tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array), maxcells); for (int c = 0; c < cells; c++) { schar_from_ascii(dest[c], ' '); } goto done; - } else if (*p < 0x80 && u8cc[0] == 0) { - schar_from_ascii(dest[0], (char)(*p)); + } else if ((uint8_t)(*p) < 0x80 && u8cc[0] == 0) { + schar_from_ascii(dest[0], *p); s->prev_c = u8c; } else { if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) { // Do Arabic shaping. int pc, pc1, nc; int pcc[MAX_MCO]; - int firstbyte = *p; + int firstbyte = (uint8_t)(*p); // The idea of what is the previous and next // character depends on 'rightleft'. if (rl) { pc = s->prev_c; pc1 = s->prev_c1; - nc = utf_ptr2char((char *)p + c_len); + nc = utf_ptr2char(p + c_len); s->prev_c1 = u8cc[0]; } else { - pc = utfc_ptr2char((char *)p + c_len, pcc); + pc = utfc_ptr2char(p + c_len, pcc); nc = s->prev_c; pc1 = pcc[0]; } @@ -287,8 +316,8 @@ static bool use_cursor_line_sign(win_T *wp, linenr_T lnum) // @param sign_idxp Index of the displayed sign static void get_sign_display_info(bool nrcol, win_T *wp, linenr_T lnum, SignTextAttrs 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 sign_idx, + int *c_extrap, int *c_finalp, char *extra, size_t extra_size, + char **pp_extra, int *n_extrap, int *char_attrp, int sign_idx, int cul_attr) { // Draw cells with the sign value or blank. @@ -308,7 +337,7 @@ static void get_sign_display_info(bool nrcol, win_T *wp, linenr_T lnum, SignText if (row == startrow + filler_lines && filler_todo <= 0) { SignTextAttrs *sattr = sign_get_attr(sign_idx, sattrs, wp->w_scwidth); if (sattr != NULL) { - *pp_extra = (char_u *)sattr->text; + *pp_extra = sattr->text; if (*pp_extra != NULL) { *c_extrap = NUL; *c_finalp = NUL; @@ -322,16 +351,16 @@ static void get_sign_display_info(bool nrcol, win_T *wp, linenr_T lnum, SignText STRCAT(extra, *pp_extra); STRCAT(extra, " "); *pp_extra = extra; - *n_extrap = (int)STRLEN(*pp_extra); + *n_extrap = (int)strlen(*pp_extra); } else { - size_t symbol_blen = STRLEN(*pp_extra); + size_t symbol_blen = strlen(*pp_extra); // TODO(oni-link): Is sign text already extended to // full cell width? assert((size_t)win_signcol_width(wp) >= mb_string2cells((char *)(*pp_extra))); // symbol(s) bytes + (filling spaces) (one byte each) *n_extrap = (int)symbol_blen + win_signcol_width(wp) - - (int)mb_string2cells((char *)(*pp_extra)); + (int)mb_string2cells(*pp_extra); assert(extra_size > symbol_blen); memset(extra, ' ', extra_size); @@ -369,6 +398,102 @@ static int get_sign_attrs(buf_T *buf, linenr_T lnum, SignTextAttrs *sattrs, int return num_signs; } +/// Prepare and build the 'statuscolumn' string for line "lnum" in window "wp". +/// Fill "stcp" with the built status column string and attributes. +/// This can be called three times per win_line(), once for virt_lines, once for +/// the start of the buffer line "lnum" and once for the wrapped lines. +/// +/// @param[out] stcp Status column attributes +static void get_statuscol_str(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines, + int cul_attr, int sign_num_attr, int sign_cul_attr, statuscol_T *stcp, + foldinfo_T foldinfo, SignTextAttrs *sattrs) +{ + long relnum = -1; + bool use_cul = use_cursor_line_sign(wp, lnum); + int virtnum = row - startrow - filler_lines; + + set_vim_var_nr(VV_VIRTNUM, virtnum); + // When called the first time for line "lnum" set num_attr + if (stcp->num_attr == 0) { + stcp->num_attr = sign_num_attr ? sign_num_attr + : get_line_number_attr(wp, lnum, row, startrow, filler_lines); + } + // When called for the first non-filler row of line "lnum" set num v:vars and fold column + if (virtnum == 0) { + relnum = labs(get_cursor_rel_lnum(wp, lnum)); + if (compute_foldcolumn(wp, 0)) { + size_t n = fill_foldcolumn(stcp->fold_text, wp, foldinfo, lnum); + stcp->fold_text[n] = NUL; + stcp->fold_attr = win_hl_attr(wp, use_cul ? HLF_CLF : HLF_FC); + } + } + // Make sure to clear->set->clear sign column for filler->first->wrapped lines + int i = 0; + for (; i < wp->w_scwidth; i++) { + SignTextAttrs *sattr = virtnum ? NULL : sign_get_attr(i, sattrs, wp->w_scwidth); + stcp->sign_text[i] = sattr && sattr->text ? sattr->text : " "; + stcp->sign_attr[i] = sattr ? (use_cul && sign_cul_attr ? sign_cul_attr : sattr->hl_attr_id) + : win_hl_attr(wp, use_cul ? HLF_CLS : HLF_SC); + } + stcp->sign_text[i] = NULL; + + int width = build_statuscol_str(wp, lnum, relnum, stcp->width, + ' ', stcp->text, &stcp->hlrec, stcp); + // Force a redraw in case of error or when truncated + if (*wp->w_p_stc == NUL || (stcp->truncate > 0 && wp->w_nrwidth < MAX_NUMBERWIDTH)) { + if (stcp->truncate) { // Avoid truncating 'statuscolumn' + wp->w_nrwidth = MIN(MAX_NUMBERWIDTH, wp->w_nrwidth + stcp->truncate); + wp->w_nrwidth_width = wp->w_nrwidth; + } else { // 'statuscolumn' reset due to error + wp->w_nrwidth_line_count = 0; + wp->w_nrwidth = (wp->w_p_nu || wp->w_p_rnu) * number_width(wp); + } + wp->w_redr_statuscol = true; + return; + } + + // Reset text/highlight pointer and current attr for new line + stcp->textp = stcp->text; + stcp->hlrecp = stcp->hlrec; + stcp->cur_attr = stcp->num_attr; + stcp->text_end = stcp->text + strlen(stcp->text); + + int fill = stcp->width - width; + if (fill > 0) { + // Fill up with ' ' + memset(stcp->text_end, ' ', (size_t)fill); + *(stcp->text_end += fill) = NUL; + } +} + +/// Get information needed to display the next segment in the 'statuscolumn'. +/// If not yet at the end, prepare for next segment and decrement "draw_state". +/// +/// @param stcp Status column attributes +/// @param[out] draw_state Current draw state in win_line() +static void get_statuscol_display_info(statuscol_T *stcp, LineDrawState *draw_state, int *char_attr, + int *n_extrap, int *c_extrap, int *c_finalp, char **pp_extra) +{ + *c_extrap = NUL; + *c_finalp = NUL; + do { + *draw_state = WL_STC; + *char_attr = stcp->cur_attr; + *pp_extra = stcp->textp; + *n_extrap = (int)((stcp->hlrecp->start ? stcp->hlrecp->start : stcp->text_end) - stcp->textp); + // Prepare for next highlight section if not yet at the end + if (stcp->textp + *n_extrap < stcp->text_end) { + int hl = stcp->hlrecp->userhl; + stcp->textp = stcp->hlrecp->start; + stcp->cur_attr = hl < 0 ? syn_id2attr(-stcp->hlrecp->userhl) + : hl > 0 ? hl : stcp->num_attr; + stcp->hlrecp++; + *draw_state = WL_STC - 1; + } + // Skip over empty highlight sections + } while (*n_extrap == 0 && stcp->textp < stcp->text_end); +} + /// Return true if CursorLineNr highlight is to be used for the number column. /// /// - 'cursorline' must be set @@ -388,7 +513,7 @@ static bool use_cursor_line_nr(win_T *wp, linenr_T lnum, int row, int startrow, && (wp->w_p_culopt_flags & CULOPT_LINE))); } -static inline void get_line_number_str(win_T *wp, linenr_T lnum, char_u *buf, size_t buf_len) +static inline void get_line_number_str(win_T *wp, linenr_T lnum, char *buf, size_t buf_len) { long num; char *fmt = "%*ld "; @@ -406,7 +531,7 @@ static inline void get_line_number_str(win_T *wp, linenr_T lnum, char_u *buf, si } } - snprintf((char *)buf, buf_len, fmt, number_width(wp), num); + snprintf(buf, buf_len, fmt, number_width(wp), num); } static int get_line_number_attr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines) @@ -482,29 +607,28 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, long vcol = 0; // virtual column (for tabs) long vcol_sbr = -1; // virtual column after showbreak long vcol_prev = -1; // "vcol" of previous character - char_u *line; // current line - char_u *ptr; // current position in "line" + char *line; // current line + char *ptr; // current position in "line" int row; // row in the window, excl w_winrow ScreenGrid *grid = &wp->w_grid; // grid specific to the window - char_u extra[57]; // sign, line number and 'fdc' must + char extra[57]; // sign, line number and 'fdc' must // fit in here int n_extra = 0; // number of extra chars - char_u *p_extra = NULL; // string of extra chars, plus NUL - char_u *p_extra_free = NULL; // p_extra needs to be freed + char *p_extra = NULL; // string of extra chars, plus NUL + char *p_extra_free = NULL; // p_extra needs to be freed int c_extra = NUL; // extra chars, all the same int c_final = NUL; // final char, mandatory if set int extra_attr = 0; // attributes when n_extra != 0 - static char_u *at_end_str = (char_u *)""; // used for p_extra when displaying - // curwin->w_p_lcs_chars.eol at - // end-of-line + static char *at_end_str = ""; // used for p_extra when displaying curwin->w_p_lcs_chars.eol + // at end-of-line int lcs_eol_one = wp->w_p_lcs_chars.eol; // 'eol' until it's been used int lcs_prec_todo = wp->w_p_lcs_chars.prec; // 'prec' until it's been used bool has_fold = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0; // saved "extra" items for when draw_state becomes WL_LINE (again) int saved_n_extra = 0; - char_u *saved_p_extra = NULL; + char *saved_p_extra = NULL; int saved_c_extra = 0; int saved_c_final = 0; int saved_char_attr = 0; @@ -527,7 +651,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, int char_attr = 0; // attributes for next character bool attr_pri = false; // char_attr has priority bool area_highlighting = false; // Visual or incsearch highlighting in this line - int attr = 0; // attributes for area highlighting + int vi_attr = 0; // attributes for Visual and incsearch highlighting int area_attr = 0; // attributes desired by highlighting int search_attr = 0; // attributes desired by 'hlsearch' int vcol_save_attr = 0; // saved attr for 'cursorcolumn' @@ -539,7 +663,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, int *color_cols = NULL; // pointer to according columns array bool has_spell = false; // this buffer has spell checking #define SPWORDLEN 150 - char_u nextline[SPWORDLEN * 2]; // text with start of the next line + char nextline[SPWORDLEN * 2]; // text with start of the next line int nextlinecol = 0; // column where nextline[] starts int nextline_idx = 0; // index in nextline[] where next line // starts @@ -578,7 +702,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, bool has_decor = false; // this buffer has decoration int win_col_offset = 0; // offset for window columns - char_u buf_fold[FOLD_TEXT_LEN]; // Hold value returned by get_foldtext + char buf_fold[FOLD_TEXT_LEN]; // Hold value returned by get_foldtext bool area_active = false; @@ -590,17 +714,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, int left_curline_col = 0; int right_curline_col = 0; - // 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 - int draw_state = WL_START; // what to draw next + LineDrawState draw_state = WL_START; // what to draw next + int match_conc = 0; ///< cchar for match functions + bool on_last_col = false; int syntax_flags = 0; int syntax_seqnr = 0; int prev_syntax_id = 0; @@ -610,7 +727,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, ///< force wrapping int vcol_off = 0; ///< offset for concealed characters int did_wcol = false; - int match_conc = 0; ///< cchar for match functions int old_boguscols = 0; #define VCOL_HLC (vcol - vcol_off) #define FIX_FOR_BOGUSCOLS \ @@ -689,7 +805,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // Trick: skip a few chars for C/shell/Vim comments nextline[SPWORDLEN] = NUL; if (lnum < wp->w_buffer->b_ml.ml_line_count) { - line = (char_u *)ml_get_buf(wp->w_buffer, lnum + 1, false); + line = ml_get_buf(wp->w_buffer, lnum + 1, false); spell_cat_line(nextline + SPWORDLEN, line, SPWORDLEN); } @@ -774,7 +890,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // if inverting in this line set area_highlighting if (fromcol >= 0) { area_highlighting = true; - attr = win_hl_attr(wp, HLF_V); + vi_attr = win_hl_attr(wp, HLF_V); } // handle 'incsearch' and ":s///c" highlighting } else if (highlight_match @@ -798,15 +914,16 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, tocol = fromcol + 1; } area_highlighting = true; - attr = win_hl_attr(wp, HLF_I); + vi_attr = win_hl_attr(wp, HLF_I); } } int bg_attr = win_bg_attr(wp); - filler_lines = diff_check(wp, lnum); - if (filler_lines < 0) { - if (filler_lines == -1) { + int linestatus = 0; + filler_lines = diff_check_with_linestatus(wp, lnum, &linestatus); + if (filler_lines < 0 || linestatus < 0) { + if (filler_lines == -1 || linestatus == -1) { if (diff_find_change(wp, lnum, &change_start, &change_end)) { diff_hlf = HLF_ADD; // added line } else if (change_start == 0) { @@ -817,11 +934,13 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } else { diff_hlf = HLF_ADD; // added line } - filler_lines = 0; + if (linestatus == 0) { + filler_lines = 0; + } area_highlighting = true; } VirtLines virt_lines = KV_INITIAL_VALUE; - int n_virt_lines = decor_virt_lines(wp, lnum, &virt_lines); + int n_virt_lines = decor_virt_lines(wp, lnum, &virt_lines, has_fold); filler_lines += n_virt_lines; if (lnum == wp->w_topline) { filler_lines = wp->w_topfill; @@ -866,13 +985,13 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, line_attr_lowprio_save = line_attr_lowprio; } - line = end_fill ? (char_u *)"" : (char_u *)ml_get_buf(wp->w_buffer, lnum, false); + line = end_fill ? "" : ml_get_buf(wp->w_buffer, lnum, false); ptr = line; if (has_spell && !number_only) { // For checking first word with a capital skip white space. if (cap_col == 0) { - cap_col = (int)getwhitecols((char *)line); + cap_col = (int)getwhitecols(line); } // To be able to spell-check over line boundaries copy the end of the @@ -883,7 +1002,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, nextlinecol = MAXCOL; nextline_idx = 0; } else { - v = (long)STRLEN(line); + v = (long)strlen(line); if (v < SPWORDLEN) { // Short line, use it completely and append the start of the // next line. @@ -911,7 +1030,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } // find start of trailing whitespace if (wp->w_p_lcs_chars.trail) { - trailcol = (colnr_T)STRLEN(ptr); + trailcol = (colnr_T)strlen(ptr); while (trailcol > (colnr_T)0 && ascii_iswhite(ptr[trailcol - 1])) { trailcol--; } @@ -941,19 +1060,19 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, v = wp->w_leftcol; } if (v > 0 && !number_only) { - char_u *prev_ptr = ptr; + char *prev_ptr = ptr; chartabsize_T cts; int charsize; - init_chartabsize_arg(&cts, wp, lnum, (colnr_T)vcol, (char *)line, (char *)ptr); + init_chartabsize_arg(&cts, wp, lnum, (colnr_T)vcol, line, ptr); while (cts.cts_vcol < v && *cts.cts_ptr != NUL) { charsize = win_lbr_chartabsize(&cts, NULL); cts.cts_vcol += charsize; - prev_ptr = (char_u *)cts.cts_ptr; + prev_ptr = cts.cts_ptr; MB_PTR_ADV(cts.cts_ptr); } vcol = cts.cts_vcol; - ptr = (char_u *)cts.cts_ptr; + ptr = cts.cts_ptr; clear_chartabsize_arg(&cts); // When: @@ -976,7 +1095,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, ptr = prev_ptr; // If the character fits on the screen, don't need to skip it. // Except for a TAB. - if (utf_ptr2cells((char *)ptr) >= charsize || *ptr == TAB) { + if (utf_ptr2cells(ptr) >= charsize || *ptr == TAB) { n_skip = (int)(v - vcol); } } @@ -1006,7 +1125,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, len = spell_move_to(wp, FORWARD, true, true, &spell_hlf); // spell_move_to() may call ml_get() and make "line" invalid - line = (char_u *)ml_get_buf(wp->w_buffer, lnum, false); + line = ml_get_buf(wp->w_buffer, lnum, false); ptr = line + linecol; if (len == 0 || (int)wp->w_cursor.col > ptr - line) { @@ -1077,7 +1196,16 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, extra_check = true; } + statuscol_T statuscol = { 0 }; + if (*wp->w_p_stc != NUL) { + // Draw the 'statuscolumn' if option is set. + statuscol.draw = true; + statuscol.width = win_col_off(wp); + } + int sign_idx = 0; + int virt_line_index; + int virt_line_offset = -1; // Repeat for the whole displayed line. for (;;) { int has_match_conc = 0; ///< match wants to conceal @@ -1105,8 +1233,25 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } if (draw_state == WL_FOLD - 1 && n_extra == 0) { - int fdc = compute_foldcolumn(wp, 0); + if (filler_todo > 0) { + int index = filler_todo - (filler_lines - n_virt_lines); + if (index > 0) { + virt_line_index = (int)kv_size(virt_lines) - index; + assert(virt_line_index >= 0); + virt_line_offset = kv_A(virt_lines, virt_line_index).left_col ? 0 : win_col_off(wp); + } + } + if (!virt_line_offset) { + // Skip the column states if there is a "virt_left_col" line. + draw_state = WL_BRI - 1; + } else if (statuscol.draw) { + // Skip fold, sign and number states if 'statuscolumn' is set. + draw_state = WL_STC - 1; + } + } + if (draw_state == WL_FOLD - 1 && n_extra == 0) { + int fdc = compute_foldcolumn(wp, 0); draw_state = WL_FOLD; if (fdc > 0) { // Draw the 'foldcolumn'. Allocate a buffer, "extra" may @@ -1164,7 +1309,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } else { // Draw the line number (empty space after wrapping). if (row == startrow + filler_lines) { - get_line_number_str(wp, lnum, (char_u *)extra, sizeof(extra)); + get_line_number_str(wp, lnum, extra, sizeof(extra)); if (wp->w_skipcol > 0) { for (p_extra = extra; *p_extra == ' '; p_extra++) { *p_extra = '-'; @@ -1172,10 +1317,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } if (wp->w_p_rl) { // reverse line numbers // like rl_mirror(), but keep the space at the end - char_u *p2 = (char_u *)skipwhite((char *)extra); - p2 = (char_u *)skiptowhite((char *)p2) - 1; - for (char_u *p1 = (char_u *)skipwhite((char *)extra); p1 < p2; p1++, p2--) { - const char_u t = *p1; + char *p2 = skipwhite(extra); + p2 = skiptowhite(p2) - 1; + for (char *p1 = skipwhite(extra); p1 < p2; p1++, p2--) { + const char t = *p1; *p1 = *p2; *p2 = t; } @@ -1196,7 +1341,23 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } } - if (draw_state == WL_NR && n_extra == 0) { + if (draw_state == WL_STC - 1 && n_extra == 0) { + draw_state = WL_STC; + // Draw the 'statuscolumn' if option is set. + if (statuscol.draw) { + if (statuscol.textp == NULL) { + get_statuscol_str(wp, lnum, row, startrow, filler_lines, cul_attr, + sign_num_attr, sign_cul_attr, &statuscol, foldinfo, sattrs); + if (wp->w_redr_statuscol) { + break; + } + } + get_statuscol_display_info(&statuscol, &draw_state, &char_attr, + &n_extra, &c_extra, &c_final, &p_extra); + } + } + + if (draw_state == WL_STC && n_extra == 0) { win_col_offset = off; } @@ -1224,7 +1385,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, c_extra = ' '; c_final = NUL; n_extra = - get_breakindent_win(wp, (char_u *)ml_get_buf(wp->w_buffer, lnum, false)); + get_breakindent_win(wp, ml_get_buf(wp->w_buffer, lnum, false)); if (row == startrow) { n_extra -= win_col_off2(wp); if (n_extra < 0) { @@ -1271,13 +1432,13 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } char_attr = win_hl_attr(wp, HLF_DED); } - char_u *const sbr = get_showbreak_value(wp); + char *const sbr = get_showbreak_value(wp); if (*sbr != NUL && need_showbreak) { // Draw 'showbreak' at the start of each broken line. p_extra = sbr; c_extra = NUL; c_final = NUL; - n_extra = (int)STRLEN(sbr); + n_extra = (int)strlen(sbr); char_attr = win_hl_attr(wp, HLF_AT); if (wp->w_skipcol == 0 || !wp->w_p_wrap) { need_showbreak = false; @@ -1328,7 +1489,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, && wp == curwin && lnum == wp->w_cursor.lnum && vcol >= (long)wp->w_virtcol) - || (number_only && draw_state > WL_NR)) + || (number_only && draw_state > WL_STC)) && filler_todo <= 0) { draw_virt_text(wp, buf, win_col_offset, &col, grid->cols, row); grid_put_linebuf(grid, row, 0, col, -grid->cols, wp->w_p_rl, wp, bg_attr, false); @@ -1346,13 +1507,13 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, && has_fold && col == win_col_offset && n_extra == 0 - && row == startrow) { + && row == startrow + filler_lines) { char_attr = win_hl_attr(wp, HLF_FL); linenr_T lnume = lnum + foldinfo.fi_lines - 1; memset(buf_fold, ' ', FOLD_TEXT_LEN); - p_extra = (char_u *)get_foldtext(wp, lnum, lnume, foldinfo, (char *)buf_fold); - n_extra = (int)STRLEN(p_extra); + p_extra = get_foldtext(wp, lnum, lnume, foldinfo, buf_fold); + n_extra = (int)strlen(p_extra); if (p_extra != buf_fold) { xfree(p_extra_free); @@ -1367,7 +1528,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, && has_fold && col < grid->cols && n_extra == 0 - && row == startrow) { + && row == startrow + filler_lines) { // fill rest of line with 'fold' c_extra = wp->w_p_fcs_chars.fold; c_final = NUL; @@ -1379,7 +1540,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, && has_fold && col >= grid->cols && n_extra != 0 - && row == startrow) { + && row == startrow + filler_lines) { // Truncate the folding. n_extra = 0; } @@ -1388,11 +1549,11 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // handle Visual or match highlighting in this line if (vcol == fromcol || (vcol + 1 == fromcol && n_extra == 0 - && utf_ptr2cells((char *)ptr) > 1) + && utf_ptr2cells(ptr) > 1) || ((int)vcol_prev == fromcol_prev && vcol_prev < vcol // not at margin && vcol < tocol)) { - area_attr = attr; // start highlighting + area_attr = vi_attr; // start highlighting if (area_highlighting) { area_active = true; } @@ -1409,8 +1570,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // When another match, have to check for start again. v = (ptr - line); search_attr = update_search_hl(wp, lnum, (colnr_T)v, &line, &screen_search_hl, - &has_match_conc, - &match_conc, lcs_eol_one, &search_attr_from_match); + &has_match_conc, &match_conc, lcs_eol_one, + &on_last_col, &search_attr_from_match); ptr = line + v; // "line" may have been changed // Do not allow a conceal over EOL otherwise EOL will be missed @@ -1482,16 +1643,16 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, mb_utf8 = check_mb_utf8(&c, u8cc); } else { assert(p_extra != NULL); - c = *p_extra; + c = (uint8_t)(*p_extra); mb_c = c; // If the UTF-8 character is more than one byte: // Decode it into "mb_c". - mb_l = utfc_ptr2len((char *)p_extra); + mb_l = utfc_ptr2len(p_extra); mb_utf8 = false; if (mb_l > n_extra) { mb_l = 1; } else if (mb_l > 1) { - mb_c = utfc_ptr2char((char *)p_extra, u8cc); + mb_c = utfc_ptr2char(p_extra, u8cc); mb_utf8 = true; c = 0xc0; } @@ -1535,14 +1696,14 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, XFREE_CLEAR(p_extra_free); // Get a character from the line itself. - c0 = c = *ptr; + c0 = c = (uint8_t)(*ptr); mb_c = c; // If the UTF-8 character is more than one byte: Decode it // into "mb_c". - mb_l = utfc_ptr2len((char *)ptr); + mb_l = utfc_ptr2len(ptr); mb_utf8 = false; if (mb_l > 1) { - mb_c = utfc_ptr2char((char *)ptr, u8cc); + mb_c = utfc_ptr2char(ptr, u8cc); // Overlong encoded ASCII or ASCII with composing char // is displayed normally, except a NUL. if (mb_c < 0x80) { @@ -1568,16 +1729,16 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, || (mb_l > 1 && (!vim_isprintc(mb_c)))) { // Illegal UTF-8 byte: display as <xx>. // Non-BMP character : display as ? or fullwidth ?. - transchar_hex((char *)extra, mb_c); + transchar_hex(extra, mb_c); if (wp->w_p_rl) { // reverse rl_mirror(extra); } p_extra = extra; - c = *p_extra; - mb_c = mb_ptr2char_adv((const char_u **)&p_extra); + c = (uint8_t)(*p_extra); + mb_c = mb_ptr2char_adv((const char **)&p_extra); mb_utf8 = (c >= 0x80); - n_extra = (int)STRLEN(p_extra); + n_extra = (int)strlen(p_extra); c_extra = NUL; c_final = NUL; if (area_attr == 0 && search_attr == 0) { @@ -1597,10 +1758,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, if (wp->w_p_rl) { pc = prev_c; pc1 = prev_c1; - nc = utf_ptr2char((char *)ptr + mb_l); + nc = utf_ptr2char(ptr + mb_l); prev_c1 = u8cc[0]; } else { - pc = utfc_ptr2char((char *)ptr + mb_l, pcc); + pc = utfc_ptr2char(ptr + mb_l, pcc); nc = prev_c; pc1 = pcc[0]; } @@ -1663,7 +1824,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, syntax_attr = get_syntax_attr((colnr_T)v - 1, has_spell ? &can_spell : NULL, false); - if (did_emsg) { + if (did_emsg) { // -V547 wp->w_s->b_syn_error = true; has_syntax = false; } else { @@ -1676,7 +1837,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // Need to get the line again, a multi-line regexp may // have made it invalid. - line = (char_u *)ml_get_buf(wp->w_buffer, lnum, false); + line = ml_get_buf(wp->w_buffer, lnum, false); ptr = line + v; if (!attr_pri) { @@ -1719,9 +1880,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, decor_conceal = 2; // really?? } - if (decor_state.spell) { - can_spell = true; - } + can_spell = TRISTATE_TO_BOOL(decor_state.spell, can_spell); } // Check spelling (unless at the end of the line). @@ -1735,8 +1894,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, char_attr = hl_combine_attr(char_attr, syntax_attr); } if (c != 0 && ((!has_syntax && !no_plain_buffer) || can_spell)) { - char_u *prev_ptr; - char_u *p; + char *prev_ptr; + char *p; int len; hlf_T spell_hlf = HLF_COUNT; prev_ptr = ptr - mb_l; @@ -1810,11 +1969,11 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // Found last space before word: check for line break. if (wp->w_p_lbr && c0 == c && vim_isbreak(c) && !vim_isbreak((int)(*ptr))) { - int mb_off = utf_head_off((char *)line, (char *)ptr - 1); - char_u *p = ptr - (mb_off + 1); + int mb_off = utf_head_off(line, ptr - 1); + char *p = ptr - (mb_off + 1); chartabsize_T cts; - init_chartabsize_arg(&cts, wp, lnum, (colnr_T)vcol, (char *)line, (char *)p); + init_chartabsize_arg(&cts, wp, lnum, (colnr_T)vcol, line, p); n_extra = win_lbr_chartabsize(&cts, NULL) - 1; // We have just drawn the showbreak value, no need to add @@ -1825,6 +1984,12 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, n_extra = 0; } } + if (on_last_col && c != TAB) { + // Do not continue search/match highlighting over the + // line break, but for TABs the highlighting should + // include the complete width of the character + search_attr = 0; + } if (c == TAB && n_extra + col > grid->cols) { n_extra = tabstop_padding((colnr_T)vcol, wp->w_buffer->b_p_ts, @@ -1910,7 +2075,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, if (c == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { int tab_len = 0; long vcol_adjusted = vcol; // removed showbreak length - char_u *const sbr = get_showbreak_value(wp); + char *const sbr = get_showbreak_value(wp); // Only adjust the tab_len, when at the first column after the // showbreak value was drawn. @@ -1925,7 +2090,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, if (!wp->w_p_lbr || !wp->w_p_list) { n_extra = tab_len; } else { - char_u *p; + char *p; int i; int saved_nextra = n_extra; @@ -1966,7 +2131,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) { lcs = wp->w_p_lcs_chars.tab3; } - p += utf_char2bytes(lcs, (char *)p); + p += utf_char2bytes(lcs, p); n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0); } p_extra = p_extra_free; @@ -2058,7 +2223,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, mb_c = c; mb_utf8 = check_mb_utf8(&c, u8cc); } else if (c != NUL) { - p_extra = transchar_buf(wp->w_buffer, c); + p_extra = (char *)transchar_buf(wp->w_buffer, c); if (n_extra == 0) { n_extra = byte2cells(c) - 1; } @@ -2068,18 +2233,20 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, c_extra = NUL; c_final = NUL; if (wp->w_p_lbr) { - char_u *p; + char *p; - c = *p_extra; + c = (uint8_t)(*p_extra); p = xmalloc((size_t)n_extra + 1); memset(p, ' ', (size_t)n_extra); - STRNCPY(p, p_extra + 1, STRLEN(p_extra) - 1); // NOLINT(runtime/printf) + strncpy(p, // NOLINT(runtime/printf) + p_extra + 1, + (size_t)strlen(p_extra) - 1); p[n_extra] = NUL; xfree(p_extra_free); p_extra_free = p_extra = p; } else { n_extra = byte2cells(c) - 1; - c = *p_extra++; + c = (uint8_t)(*p_extra++); } n_attr = n_extra + 1; extra_attr = win_hl_attr(wp, HLF_8); @@ -2187,7 +2354,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, && wp->w_p_list && (wp->w_p_wrap ? (wp->w_skipcol > 0 && row == 0) : wp->w_leftcol > 0) && filler_todo <= 0 - && draw_state > WL_NR + && draw_state > WL_STC && c != NUL) { c = wp->w_p_lcs_chars.prec; lcs_prec_todo = NUL; @@ -2212,7 +2379,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // flag to indicate whether prevcol equals startcol of search_hl or // one of the matches bool prevcol_hl_flag = get_prevcol_hl_flag(wp, &screen_search_hl, - (long)(ptr - line) - 1); + (long)(ptr - line) - 1); // NOLINT(google-readability-casting) // Invert at least one char, used for Visual and empty line or // highlight match at end of line. If it's beyond the last @@ -2248,7 +2415,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, if (area_attr == 0 && !has_fold) { // Use attributes from match with highest priority among // 'search_hl' and the match list. - get_search_match_hl(wp, &screen_search_hl, (long)(ptr - line), &char_attr); + get_search_match_hl(wp, + &screen_search_hl, + (long)(ptr - line), // NOLINT(google-readability-casting) + &char_attr); } int eol_attr = char_attr; @@ -2480,7 +2650,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, col++; // UTF-8: Put a 0 in the second screen char. linebuf_char[off][0] = 0; - if (draw_state > WL_NR && filler_todo <= 0) { + if (draw_state > WL_STC && filler_todo <= 0) { vcol++; } // When "tocol" is halfway through a character, set it to the end of @@ -2563,7 +2733,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // Only advance the "vcol" when after the 'number' or 'relativenumber' // column. - if (draw_state > WL_NR + if (draw_state > WL_STC && filler_todo <= 0) { vcol++; } @@ -2573,7 +2743,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } // restore attributes after "predeces" in 'listchars' - if (draw_state > WL_NR && n_attr3 > 0 && --n_attr3 == 0) { + if (draw_state > WL_STC && n_attr3 > 0 && --n_attr3 == 0) { char_attr = saved_attr3; } @@ -2585,7 +2755,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // At end of screen line and there is more to come: Display the line // so far. If there is no more to display it is caught above. if ((wp->w_p_rl ? (col < 0) : (col >= grid->cols)) - && foldinfo.fi_lines == 0 + && (!has_fold || virt_line_offset >= 0) && (draw_state != WL_LINE || *ptr != NUL || filler_todo > 0 @@ -2602,15 +2772,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, && !wp->w_p_rl; // Not right-to-left. int draw_col = col - boguscols; - if (filler_todo > 0) { - int index = filler_todo - (filler_lines - n_virt_lines); - if (index > 0) { - int i = (int)kv_size(virt_lines) - index; - assert(i >= 0); - int offset = kv_A(virt_lines, i).left_col ? 0 : win_col_offset; - draw_virt_text_item(buf, offset, kv_A(virt_lines, i).line, - kHlModeReplace, grid->cols, 0); - } + if (virt_line_offset >= 0) { + draw_virt_text_item(buf, virt_line_offset, kv_A(virt_lines, virt_line_index).line, + kHlModeReplace, grid->cols, 0); } else { draw_virt_text(wp, buf, win_col_offset, &draw_col, grid->cols, row); } @@ -2669,7 +2833,19 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, if (filler_todo <= 0) { need_showbreak = true; } + if (statuscol.draw) { + if (row == startrow + filler_lines + 1 || row == startrow + filler_lines) { + // Re-evaluate 'statuscolumn' for the first wrapped row and non filler line + statuscol.textp = NULL; + } else if (statuscol.textp) { + // Draw the already built 'statuscolumn' on the next wrapped or filler line + statuscol.textp = statuscol.text; + statuscol.hlrecp = statuscol.hlrec; + } // Fall back to default columns if the 'n' flag isn't in 'cpo' + statuscol.draw = vim_strchr(p_cpo, CPO_NUMCOL) == NULL; + } filler_todo--; + virt_line_offset = -1; // When the filler lines are actually below the last line of the // file, don't draw the line itself, break here. if (filler_todo == 0 && (wp->w_botfill || end_fill)) { @@ -2679,7 +2855,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } // for every character in the line // After an empty line check first word for capital. - if (*skipwhite((char *)line) == NUL) { + if (*skipwhite(line) == NUL) { capcol_lnum = lnum + 1; cap_col = 0; } diff --git a/src/nvim/drawline.h b/src/nvim/drawline.h index e50969983e..9f60b46e1b 100644 --- a/src/nvim/drawline.h +++ b/src/nvim/drawline.h @@ -1,9 +1,15 @@ #ifndef NVIM_DRAWLINE_H #define NVIM_DRAWLINE_H +#include <stdbool.h> +#include <stdint.h> + +#include "klib/kvec.h" #include "nvim/decoration_provider.h" #include "nvim/fold.h" +#include "nvim/macros.h" #include "nvim/screen.h" +#include "nvim/types.h" // Maximum columns for terminal highlight attributes #define TERM_ATTRS_MAX 1024 diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 15a7294496..04c342e068 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -59,28 +59,47 @@ #include <stdbool.h> #include <string.h> +#include "klib/kvec.h" +#include "nvim/api/private/defs.h" +#include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" +#include "nvim/decoration.h" +#include "nvim/decoration_provider.h" #include "nvim/diff.h" +#include "nvim/drawline.h" #include "nvim/drawscreen.h" #include "nvim/ex_getln.h" +#include "nvim/extmark_defs.h" +#include "nvim/fold.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/insexpand.h" #include "nvim/match.h" +#include "nvim/mbyte.h" +#include "nvim/memline.h" +#include "nvim/message.h" #include "nvim/move.h" +#include "nvim/normal.h" #include "nvim/option.h" #include "nvim/plines.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/regexp.h" +#include "nvim/screen.h" #include "nvim/statusline.h" #include "nvim/syntax.h" +#include "nvim/terminal.h" +#include "nvim/types.h" +#include "nvim/ui.h" #include "nvim/ui_compositor.h" -#include "nvim/undo.h" #include "nvim/version.h" +#include "nvim/vim.h" #include "nvim/window.h" /// corner value flags for hsep_connected and vsep_connected @@ -107,12 +126,14 @@ static char *provider_err = NULL; void conceal_check_cursor_line(void) { bool should_conceal = conceal_cursor_line(curwin); - if (curwin->w_p_cole > 0 && (conceal_cursor_used != should_conceal)) { - redrawWinline(curwin, curwin->w_cursor.lnum); - // Need to recompute cursor column, e.g., when starting Visual mode - // without concealing. - curs_columns(curwin, true); + if (curwin->w_p_cole <= 0 || conceal_cursor_used == should_conceal) { + return; } + + redrawWinline(curwin, curwin->w_cursor.lnum); + // Need to recompute cursor column, e.g., when starting Visual mode + // without concealing. + curs_columns(curwin, true); } /// Resize default_grid to Rows and Columns. @@ -125,6 +146,8 @@ void conceal_check_cursor_line(void) /// default_grid.Columns to access items in default_grid.chars[]. Use Rows /// and Columns for positioning text etc. where the final size of the screen is /// needed. +/// +/// @return whether resizing has been done bool default_grid_alloc(void) { static bool resizing = false; @@ -161,14 +184,10 @@ bool default_grid_alloc(void) // size is wrong. grid_alloc(&default_grid, Rows, Columns, true, true); - StlClickDefinition *new_tab_page_click_defs = - xcalloc((size_t)Columns, sizeof(*new_tab_page_click_defs)); stl_clear_click_defs(tab_page_click_defs, tab_page_click_defs_size); - xfree(tab_page_click_defs); - - tab_page_click_defs = new_tab_page_click_defs; - tab_page_click_defs_size = Columns; + tab_page_click_defs = stl_alloc_click_defs(tab_page_click_defs, Columns, + &tab_page_click_defs_size); default_grid.comp_height = Rows; default_grid.comp_width = Columns; @@ -185,14 +204,12 @@ void screenclear(void) { check_for_delay(false); - int i; - if (starting == NO_SCREEN || default_grid.chars == NULL) { return; } // blank out the default grid - for (i = 0; i < default_grid.rows; i++) { + for (int i = 0; i < default_grid.rows; i++) { grid_clear_line(&default_grid, default_grid.line_offset[i], default_grid.cols, true); default_grid.line_wraps[i] = false; @@ -266,17 +283,28 @@ void screen_resize(int width, int height) p_lines = Rows; p_columns = Columns; - // was invoked recursively from a VimResized autocmd, handled as a loop below - if (resizing_autocmd) { - return; - } + ui_call_grid_resize(1, width, height); int retry_count = 0; resizing_autocmd = true; - bool retry_resize = true; - while (retry_resize) { - retry_resize = default_grid_alloc(); + // In rare cases, autocommands may have altered Rows or Columns, + // so retry to check if we need to allocate the screen again. + while (default_grid_alloc()) { + // win_new_screensize will recompute floats position, but tell the + // compositor to not redraw them yet + ui_comp_set_screen_valid(false); + if (msg_grid.chars) { + msg_grid_invalid = true; + } + + RedrawingDisabled++; + + win_new_screensize(); // fit the windows in the new sized screen + + comp_col(); // recompute columns for shown command and ruler + + RedrawingDisabled--; // Do not apply autocommands more than 3 times to avoid an endless loop // in case applying autocommands always changes Rows or Columns. @@ -284,33 +312,10 @@ void screen_resize(int width, int height) break; } - if (retry_resize) { - // In rare cases, autocommands may have altered Rows or Columns, - // retry to check if we need to allocate the screen again. - apply_autocmds(EVENT_VIMRESIZED, NULL, NULL, false, curbuf); - } + apply_autocmds(EVENT_VIMRESIZED, NULL, NULL, false, curbuf); } resizing_autocmd = false; - - ui_call_grid_resize(1, width, height); - - // win_new_screensize will recompute floats position, but tell the - // compositor to not redraw them yet - ui_comp_set_screen_valid(false); - if (msg_grid.chars) { - msg_grid_invalid = true; - } - - // Note that the window sizes are updated before reallocating the arrays, - // thus we must not redraw here! - RedrawingDisabled++; - - win_new_screensize(); // fit the windows in the new sized screen - - comp_col(); // recompute columns for shown command and ruler - - RedrawingDisabled--; redraw_all_later(UPD_CLEAR); if (starting != NO_SCREEN) { @@ -465,6 +470,7 @@ int update_screen(void) } msg_scrolled = 0; msg_scrolled_at_flush = 0; + msg_grid_scroll_discount = 0; need_wait_return = false; } @@ -518,7 +524,7 @@ int update_screen(void) // TODO(bfredl): special casing curwin here is SÅ JÄVLA BULL. // Either this should be done for all windows or not at all. if (curwin->w_redr_type < UPD_NOT_VALID - && curwin->w_nrwidth != ((curwin->w_p_nu || curwin->w_p_rnu) + && curwin->w_nrwidth != ((curwin->w_p_nu || curwin->w_p_rnu || *curwin->w_p_stc) ? number_width(curwin) : 0)) { curwin->w_redr_type = UPD_NOT_VALID; } @@ -626,6 +632,20 @@ int update_screen(void) return OK; } +static void win_border_redr_title(win_T *wp, ScreenGrid *grid, int col) +{ + VirtText title_chunks = wp->w_float_config.title_chunks; + + for (size_t i = 0; i < title_chunks.size; i++) { + char *text = title_chunks.items[i].text; + int cell = (int)mb_string2cells(text); + int hl_id = title_chunks.items[i].hl_id; + int attr = hl_id ? syn_id2attr(hl_id) : 0; + grid_puts(grid, text, 0, col, attr); + col += cell; + } +} + static void win_redr_border(win_T *wp) { wp->w_redr_border = false; @@ -646,9 +666,24 @@ static void win_redr_border(win_T *wp) if (adj[3]) { grid_put_schar(grid, 0, 0, chars[0], attrs[0]); } + for (int i = 0; i < icol; i++) { grid_put_schar(grid, 0, i + adj[3], chars[1], attrs[1]); } + + if (wp->w_float_config.title) { + int title_col = 0; + int title_width = wp->w_float_config.title_width; + AlignTextPos title_pos = wp->w_float_config.title_pos; + + if (title_pos == kAlignCenter) { + title_col = (icol - title_width) / 2 + 1; + } else { + title_col = title_pos == kAlignLeft ? 1 : icol - title_width + 1; + } + + win_border_redr_title(wp, grid, title_col); + } if (adj[1]) { grid_put_schar(grid, 0, icol + adj[3], chars[2], attrs[2]); } @@ -804,29 +839,29 @@ static bool vsep_connected(win_T *wp, WindowCorner corner) /// Draw the vertical separator right of window "wp" static void draw_vsep_win(win_T *wp) { - int hl; - int c; - - 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, W_ENDROW(wp), - W_ENDCOL(wp), W_ENDCOL(wp) + 1, c, ' ', hl); + if (!wp->w_vsep_width) { + return; } + + // draw the vertical separator right of this window + int hl; + int c = fillchar_vsep(wp, &hl); + 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; - - 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); + if (!wp->w_hsep_height) { + return; } + + // draw the horizontal separator below this window + int hl; + int 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" @@ -927,11 +962,6 @@ static void draw_sep_connectors_win(win_T *wp) /// bot: from bot_start to last row (when scrolled up) static void win_update(win_T *wp, DecorProviders *providers) { - bool called_decor_providers = false; -win_update_start: - ; - buf_T *buf = wp->w_buffer; - int type; int top_end = 0; // Below last row of the top area that needs // updating. 0 when no top area updating. int mid_start = 999; // first row of the mid area that needs @@ -946,28 +976,20 @@ win_update_start: int bot_scroll_start = 999; // first line that needs to be redrawn due to // scrolling. only used for EOB - int row; // current window row to display - linenr_T lnum; // current buffer lnum to display - int idx; // current index in w_lines[] - int srow; // starting row of the current line - - bool eof = false; // if true, we hit the end of the file - bool didline = false; // if true, we finished the last line - int i; - long j; static bool recursive = false; // being called recursively - const linenr_T old_botline = wp->w_botline; + // Remember what happened to the previous line. -#define DID_NONE 1 // didn't update a line -#define DID_LINE 2 // updated a normal line -#define DID_FOLD 3 // updated a folded line - int did_update = DID_NONE; + enum { + DID_NONE = 1, // didn't update a line + DID_LINE = 2, // updated a normal line + DID_FOLD = 3, // updated a folded line + } did_update = DID_NONE; + linenr_T syntax_last_parsed = 0; // last parsed text line linenr_T mod_top = 0; linenr_T mod_bot = 0; - int save_got_int; - type = wp->w_redr_type; + int type = wp->w_redr_type; if (type >= UPD_NOT_VALID) { // TODO(bfredl): should only be implied for CLEAR, not NOT_VALID! @@ -994,16 +1016,32 @@ win_update_start: return; } + buf_T *buf = wp->w_buffer; + + // reset got_int, otherwise regexp won't work + int save_got_int = got_int; + got_int = 0; + // Set the time limit to 'redrawtime'. + proftime_T syntax_tm = profile_setlimit(p_rdt); + syn_set_timeout(&syntax_tm); + + win_extmark_arr.size = 0; + + decor_redraw_reset(buf, &decor_state); + + DecorProviders line_providers; + decor_providers_invoke_win(wp, providers, &line_providers, &provider_err); + redraw_win_signcol(wp); init_search_hl(wp, &screen_search_hl); // Force redraw when width of 'number' or 'relativenumber' column // changes. - i = (wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) : 0; - if (wp->w_nrwidth != i) { + int nrwidth = (wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc) ? number_width(wp) : 0; + if (wp->w_nrwidth != nrwidth) { type = UPD_NOT_VALID; - wp->w_nrwidth = i; + wp->w_nrwidth = nrwidth; if (buf->terminal) { terminal_check_size(buf->terminal); @@ -1075,7 +1113,7 @@ win_update_start: // to this line. If there is no valid entry, use MAXLNUM. lnumt = wp->w_topline; lnumb = MAXLNUM; - 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_lastlnum < mod_top) { lnumt = wp->w_lines[i].wl_lastlnum + 1; @@ -1130,8 +1168,8 @@ win_update_start: // When only displaying the lines at the top, set top_end. Used when // window has scrolled down for msg_scrolled. if (type == UPD_REDRAW_TOP) { - j = 0; - for (i = 0; i < wp->w_lines_valid; i++) { + long j = 0; + for (int i = 0; i < wp->w_lines_valid; i++) { j += wp->w_lines[i].wl_size; if (j >= wp->w_upd_rows) { top_end = (int)j; @@ -1167,6 +1205,7 @@ win_update_start: || (wp->w_topline == wp->w_lines[0].wl_lnum && wp->w_topfill > wp->w_old_topfill))) { // New topline is above old topline: May scroll down. + long j; if (hasAnyFolding(wp)) { linenr_T ln; @@ -1184,7 +1223,7 @@ win_update_start: j = wp->w_lines[0].wl_lnum - wp->w_topline; } if (j < wp->w_grid.rows - 2) { // not too far off - i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1); + int i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1); // insert extra lines for previously invisible filler lines if (wp->w_lines[0].wl_lnum != wp->w_topline) { i += win_get_fill(wp, wp->w_lines[0].wl_lnum) - wp->w_old_topfill; @@ -1206,6 +1245,7 @@ win_update_start: if ((wp->w_lines_valid += (linenr_T)j) > wp->w_grid.rows) { wp->w_lines_valid = wp->w_grid.rows; } + int idx; for (idx = wp->w_lines_valid; idx - j >= 0; idx--) { wp->w_lines[idx] = wp->w_lines[idx - j]; } @@ -1225,9 +1265,9 @@ win_update_start: // needs updating. // try to find wp->w_topline in wp->w_lines[].wl_lnum - j = -1; - row = 0; - for (i = 0; i < wp->w_lines_valid; i++) { + long j = -1; + int row = 0; + for (int i = 0; i < wp->w_lines_valid; i++) { if (wp->w_lines[i].wl_valid && wp->w_lines[i].wl_lnum == wp->w_topline) { j = i; @@ -1263,7 +1303,7 @@ win_update_start: // upwards, to compensate for the deleted lines. Set // bot_start to the first row that needs redrawing. bot_start = 0; - idx = 0; + int idx = 0; for (;;) { wp->w_lines[idx] = wp->w_lines[j]; // stop at line that didn't fit, unless it is still @@ -1460,9 +1500,9 @@ win_update_start: // above the Visual area and reset wl_valid, do count these for // mid_end (in srow). if (mid_start > 0) { - lnum = wp->w_topline; - idx = 0; - srow = 0; + linenr_T lnum = wp->w_topline; + int idx = 0; + int srow = 0; if (scrolled_down) { mid_start = top_end; } else { @@ -1508,38 +1548,18 @@ win_update_start: wp->w_old_visual_col = 0; } - // reset got_int, otherwise regexp won't work - save_got_int = got_int; - got_int = 0; - // Set the time limit to 'redrawtime'. - proftime_T syntax_tm = profile_setlimit(p_rdt); - syn_set_timeout(&syntax_tm); - - // Update all the window rows. - idx = 0; // first entry in w_lines[].wl_size - row = 0; - srow = 0; - lnum = wp->w_topline; // first line shown in window - - win_extmark_arr.size = 0; - - decor_redraw_reset(buf, &decor_state); - - DecorProviders line_providers; - decor_providers_invoke_win(wp, providers, &line_providers, &provider_err); - (void)win_signcol_count(wp); // check if provider changed signcol width - if (must_redraw != 0) { - must_redraw = 0; - if (!called_decor_providers) { - called_decor_providers = true; - goto win_update_start; - } - } - bool cursorline_standout = win_cursorline_standout(wp); win_check_ns_hl(wp); + // Update all the window rows. + int idx = 0; // first entry in w_lines[].wl_size + int row = 0; // current window row to display + int srow = 0; // starting row of the current line + linenr_T lnum = wp->w_topline; // first line shown in window + + bool eof = false; // if true, we hit the end of the file + bool didline = false; // if true, we finished the last line for (;;) { // stop updating when reached the end of the window (check for _past_ // the end of the window is at the end of the loop) @@ -1603,6 +1623,7 @@ win_update_start: int new_rows = 0; int xtra_rows; linenr_T l; + int i; // Count the old number of window rows, using w_lines[], which // should still contain the sizes for the lines as they are @@ -1637,7 +1658,7 @@ win_update_start: } else { // Able to count old number of rows: Count new window // rows, and may insert/delete lines - j = idx; + long j = idx; for (l = lnum; l < mod_bot; l++) { if (hasFoldingWin(wp, l, NULL, &l, true, NULL)) { new_rows++; @@ -1753,7 +1774,7 @@ win_update_start: // Let the syntax stuff know we skipped a few lines. if (syntax_last_parsed != 0 && syntax_last_parsed + 1 < lnum && syntax_present(wp)) { - syntax_end_parsing(syntax_last_parsed + 1); + syntax_end_parsing(wp, syntax_last_parsed + 1); } // Display one line @@ -1808,6 +1829,18 @@ win_update_start: did_update = DID_NONE; } + // 'statuscolumn' width has changed or errored, start from the top. + if (wp->w_redr_statuscol) { + wp->w_redr_statuscol = false; + idx = 0; + row = 0; + lnum = wp->w_topline; + wp->w_lines_valid = 0; + wp->w_valid &= ~VALID_WCOL; + decor_providers_invoke_win(wp, providers, &line_providers, &provider_err); + continue; + } + if (lnum > buf->b_ml.ml_line_count) { eof = true; break; @@ -1827,9 +1860,11 @@ win_update_start: // Let the syntax stuff know we stop parsing here. if (syntax_last_parsed != 0 && syntax_present(wp)) { - syntax_end_parsing(syntax_last_parsed + 1); + syntax_end_parsing(wp, syntax_last_parsed + 1); } + const linenr_T old_botline = wp->w_botline; + // If we didn't hit the end of the file, and we didn't finish the last // line we were working on, then the line didn't fit. wp->w_empty_rows = 0; @@ -1873,11 +1908,11 @@ win_update_start: } else { 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); + long j = win_get_fill(wp, wp->w_botline); 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; + foldinfo_T info = { 0 }; row = win_line(wp, wp->w_botline, row, wp->w_grid.rows, false, false, info, &line_providers, &provider_err); } @@ -1943,11 +1978,11 @@ win_update_start: update_topline(curwin); // may invalidate w_botline again if (must_redraw != 0) { // Don't update for changes in buffer again. - i = curbuf->b_mod_set; + int mod_set = curbuf->b_mod_set; curbuf->b_mod_set = false; win_update(curwin, providers); must_redraw = 0; - curbuf->b_mod_set = i; + curbuf->b_mod_set = mod_set; } recursive = false; } diff --git a/src/nvim/drawscreen.h b/src/nvim/drawscreen.h index ef99899581..c14703dfa9 100644 --- a/src/nvim/drawscreen.h +++ b/src/nvim/drawscreen.h @@ -1,7 +1,10 @@ #ifndef NVIM_DRAWSCREEN_H #define NVIM_DRAWSCREEN_H +#include <stdbool.h> + #include "nvim/drawline.h" +#include "nvim/macros.h" /// flags for update_screen() /// The higher the value, the higher the priority diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 09f20baebf..095d73f53f 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -4,11 +4,14 @@ // edit.c: functions for Insert mode #include <assert.h> +#include <ctype.h> #include <inttypes.h> #include <stdbool.h> #include <string.h> +#include <sys/types.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" @@ -17,20 +20,23 @@ #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" -#include "nvim/event/loop.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" -#include "nvim/ex_getln.h" #include "nvim/extmark.h" #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/grid.h" +#include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/insexpand.h" #include "nvim/keycodes.h" -#include "nvim/main.h" +#include "nvim/macros.h" #include "nvim/mapping.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -43,19 +49,18 @@ #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/os/input.h" -#include "nvim/os/time.h" -#include "nvim/path.h" #include "nvim/plines.h" #include "nvim/popupmenu.h" -#include "nvim/quickfix.h" +#include "nvim/pos.h" +#include "nvim/screen.h" #include "nvim/search.h" -#include "nvim/spell.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/terminal.h" #include "nvim/textformat.h" #include "nvim/textobject.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -81,16 +86,18 @@ typedef struct insert_state { int did_restart_edit; // remember if insert mode was restarted // after a ctrl+o bool nomove; - char_u *ptr; + char *ptr; } InsertState; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "edit.c.generated.h" #endif -#define BACKSPACE_CHAR 1 -#define BACKSPACE_WORD 2 -#define BACKSPACE_WORD_NOT_SPACE 3 -#define BACKSPACE_LINE 4 +enum { + BACKSPACE_CHAR = 1, + BACKSPACE_WORD = 2, + BACKSPACE_WORD_NOT_SPACE = 3, + BACKSPACE_LINE = 4, +}; /// Set when doing something for completion that may call edit() recursively, /// which is not allowed. @@ -100,10 +107,9 @@ static colnr_T Insstart_textlen; // length of line when insert started 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 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 char *last_insert = NULL; // the text of the previous insert, 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() static bool can_cindent; // may do cindenting on this line @@ -143,14 +149,14 @@ static void insert_enter(InsertState *s) pos_T save_cursor = curwin->w_cursor; if (s->cmdchar == 'R') { - s->ptr = (char_u *)"r"; + s->ptr = "r"; } else if (s->cmdchar == 'V') { - s->ptr = (char_u *)"v"; + s->ptr = "v"; } else { - s->ptr = (char_u *)"i"; + s->ptr = "i"; } - set_vim_var_string(VV_INSERTMODE, (char *)s->ptr, 1); + set_vim_var_string(VV_INSERTMODE, s->ptr, 1); set_vim_var_string(VV_CHAR, NULL, -1); ins_apply_autocmds(EVENT_INSERTENTER); @@ -188,7 +194,7 @@ static void insert_enter(InsertState *s) } } - Insstart_textlen = (colnr_T)linetabsize((char_u *)get_cursor_line_ptr()); + Insstart_textlen = (colnr_T)linetabsize(get_cursor_line_ptr()); Insstart_blank_vcol = MAXCOL; if (!did_ai) { @@ -272,11 +278,11 @@ static void insert_enter(InsertState *s) update_curswant(); if (((ins_at_eol && curwin->w_cursor.lnum == o_lnum) || curwin->w_curswant > curwin->w_virtcol) - && *(s->ptr = (char_u *)get_cursor_line_ptr() + curwin->w_cursor.col) != NUL) { + && *(s->ptr = get_cursor_line_ptr() + curwin->w_cursor.col) != NUL) { if (s->ptr[1] == NUL) { curwin->w_cursor.col++; } else { - s->i = utfc_ptr2len((char *)s->ptr); + s->i = utfc_ptr2len(s->ptr); if (s->ptr[s->i] == NUL) { curwin->w_cursor.col += s->i; } @@ -321,7 +327,7 @@ static void insert_enter(InsertState *s) if (s->ptr == NULL) { new_insert_skip = 0; } else { - new_insert_skip = (int)STRLEN(s->ptr); + new_insert_skip = (int)strlen(s->ptr); xfree(s->ptr); } @@ -439,8 +445,7 @@ static int insert_check(VimState *state) s->mincol = curwin->w_wcol; validate_cursor_col(); - if ( - curwin->w_wcol < s->mincol - tabstop_at(get_nolist_virtcol(), + if (curwin->w_wcol < s->mincol - tabstop_at(get_nolist_virtcol(), curbuf->b_p_ts, curbuf->b_p_vts_array) && curwin->w_wrow == curwin->w_winrow @@ -552,12 +557,12 @@ static int insert_execute(VimState *state, int key) // completion: Add to "compl_leader". if (ins_compl_accept_char(s->c)) { // Trigger InsertCharPre. - char_u *str = do_insert_char_pre(s->c); - char_u *p; + char *str = do_insert_char_pre(s->c); + char *p; if (str != NULL) { for (p = str; *p != NUL; MB_PTR_ADV(p)) { - ins_compl_addleader(utf_ptr2char((char *)p)); + ins_compl_addleader(utf_ptr2char(p)); } xfree(str); } else { @@ -902,6 +907,12 @@ check_pum: } pum_want.active = false; } + + if (curbuf->b_u_synced) { + // The K_EVENT, K_COMMAND, or K_LUA caused undo to be synced. + // Need to save the line for undo before inserting the next char. + ins_need_undo = true; + } break; case K_HOME: // <Home> @@ -1110,7 +1121,7 @@ normalchar: if (!p_paste) { // Trigger InsertCharPre. - char *str = (char *)do_insert_char_pre(s->c); + char *str = do_insert_char_pre(s->c); char *p; if (str != NULL) { @@ -1228,9 +1239,8 @@ bool edit(int cmdchar, bool startln, long count) restart_edit = 'i'; force_restart_edit = true; return false; - } else { - return terminal_enter(); } + return terminal_enter(); } // Don't allow inserting in the sandbox. @@ -1334,8 +1344,7 @@ void ins_redraw(bool ready) } if (ready) { - // Trigger Scroll if viewport changed. - may_trigger_winscrolled(); + may_trigger_win_scrolled_resized(); } // Trigger BufModified if b_changed_invalid is set. @@ -1432,7 +1441,7 @@ void edit_putchar(int c, bool highlight) // save the character to be able to put it back if (pc_status == PC_STATUS_UNSET) { - grid_getbytes(&curwin->w_grid, pc_row, pc_col, pc_bytes, &pc_attr); + grid_getbytes(&curwin->w_grid, pc_row, pc_col, (char *)pc_bytes, &pc_attr); pc_status = PC_STATUS_SET; } grid_putchar(&curwin->w_grid, c, pc_row, pc_col, attr); @@ -1449,28 +1458,28 @@ char *buf_prompt_text(const buf_T *const buf) return buf->b_prompt_text; } -// Return the effective prompt for the current buffer. -char_u *prompt_text(void) +/// @return the effective prompt for the current buffer. +char *prompt_text(void) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE { - return (char_u *)buf_prompt_text(curbuf); + return buf_prompt_text(curbuf); } // Prepare for prompt mode: Make sure the last line has the prompt text. // Move the cursor to this line. static void init_prompt(int cmdchar_todo) { - char_u *prompt = prompt_text(); - char_u *text; + char *prompt = prompt_text(); + char *text; curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - text = (char_u *)get_cursor_line_ptr(); - if (STRNCMP(text, prompt, STRLEN(prompt)) != 0) { + text = get_cursor_line_ptr(); + if (strncmp(text, prompt, strlen(prompt)) != 0) { // prompt is missing, insert it or append a line with it if (*text == NUL) { - ml_replace(curbuf->b_ml.ml_line_count, (char *)prompt, true); + ml_replace(curbuf->b_ml.ml_line_count, prompt, true); } else { - ml_append(curbuf->b_ml.ml_line_count, (char *)prompt, 0, false); + ml_append(curbuf->b_ml.ml_line_count, prompt, 0, false); } curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; coladvance(MAXCOL); @@ -1478,9 +1487,9 @@ static void init_prompt(int cmdchar_todo) } // 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)) { + 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.col = (colnr_T)strlen(prompt); Insstart_orig = Insstart; Insstart_textlen = Insstart.col; Insstart_blank_vcol = MAXCOL; @@ -1490,8 +1499,8 @@ static void init_prompt(int cmdchar_todo) if (cmdchar_todo == 'A') { coladvance(MAXCOL); } - if (curwin->w_cursor.col < (colnr_T)STRLEN(prompt)) { - curwin->w_cursor.col = (colnr_T)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(); @@ -1502,7 +1511,7 @@ bool prompt_curpos_editable(void) FUNC_ATTR_PURE { return curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count - && curwin->w_cursor.col >= (int)STRLEN(prompt_text()); + && curwin->w_cursor.col >= (int)strlen(prompt_text()); } // Undo the previous edit_putchar(). @@ -1534,8 +1543,8 @@ void display_dollar(colnr_T col) curwin->w_cursor.col = col; // If on the last byte of a multi-byte move to the first byte. - char_u *p = (char_u *)get_cursor_line_ptr(); - curwin->w_cursor.col -= utf_head_off((char *)p, (char *)p + col); + char *p = get_cursor_line_ptr(); + curwin->w_cursor.col -= utf_head_off(p, p + col); curs_columns(curwin, false); // Recompute w_wrow and w_wcol if (curwin->w_wcol < curwin->w_grid.cols) { edit_putchar('$', false); @@ -1569,7 +1578,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang int last_vcol; int insstart_less; // reduction for Insstart.col int new_cursor_col; - char_u *ptr; + char *ptr; int save_p_list; int start_col; colnr_T vc; @@ -1648,9 +1657,9 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang // Advance the cursor until we reach the right screen column. last_vcol = 0; - ptr = (char_u *)get_cursor_line_ptr(); + ptr = get_cursor_line_ptr(); chartabsize_T cts; - init_chartabsize_arg(&cts, curwin, 0, 0, (char *)ptr, (char *)ptr); + init_chartabsize_arg(&cts, curwin, 0, 0, ptr, ptr); while (cts.cts_vcol <= (int)curwin->w_virtcol) { last_vcol = cts.cts_vcol; if (cts.cts_vcol > 0) { @@ -1673,7 +1682,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang ptr = xmallocz(i); memset(ptr, ' ', i); new_cursor_col += (int)i; - ins_str((char *)ptr); + ins_str(ptr); xfree(ptr); } @@ -1940,7 +1949,7 @@ int get_literal(bool no_simplify) /// @param ctrlv `c` was typed after CTRL-V static void insert_special(int c, int allow_modmask, int ctrlv) { - char_u *p; + char *p; int len; // Special function key, translate into "<Key>". Up to the last '>' is @@ -1952,16 +1961,16 @@ static void insert_special(int c, int allow_modmask, int ctrlv) allow_modmask = true; } if (IS_SPECIAL(c) || (mod_mask && allow_modmask)) { - p = get_special_key_name(c, mod_mask); - len = (int)STRLEN(p); - c = p[len - 1]; + p = (char *)get_special_key_name(c, mod_mask); + len = (int)strlen(p); + c = (uint8_t)p[len - 1]; if (len > 2) { if (stop_arrow() == FAIL) { return; } p[len - 1] = NUL; - ins_str((char *)p); - AppendToRedobuffLit((char *)p, -1); + ins_str(p); + AppendToRedobuffLit(p, -1); ctrlv = false; } } @@ -2052,8 +2061,8 @@ void insertchar(int c, int flags, int second_indent) // Need to remove existing (middle) comment leader and insert end // comment leader. First, check what comment leader we can find. - char_u *line = (char_u *)get_cursor_line_ptr(); - int i = get_leader_len((char *)line, &p, false, true); + char *line = get_cursor_line_ptr(); + int i = get_leader_len(line, &p, false, true); if (i > 0 && vim_strchr(p, COM_MIDDLE) != NULL) { // Just checking // Skip middle-comment string while (*p && p[-1] != ':') { // find end of middle flags @@ -2114,11 +2123,11 @@ void insertchar(int c, int flags, int second_indent) && !cindent_on() && !p_ri) { #define INPUT_BUFLEN 100 - char_u buf[INPUT_BUFLEN + 1]; + char buf[INPUT_BUFLEN + 1]; int i; colnr_T virtcol = 0; - buf[0] = (char_u)c; + buf[0] = (char)c; i = 1; if (textwidth > 0) { virtcol = get_nolist_virtcol(); @@ -2134,27 +2143,27 @@ void insertchar(int c, int flags, int second_indent) && MB_BYTE2LEN(c) == 1 && i < INPUT_BUFLEN && (textwidth == 0 - || (virtcol += byte2cells(buf[i - 1])) < (colnr_T)textwidth) - && !(!no_abbr && !vim_iswordc(c) && vim_iswordc(buf[i - 1]))) { + || (virtcol += byte2cells((uint8_t)buf[i - 1])) < (colnr_T)textwidth) + && !(!no_abbr && !vim_iswordc(c) && vim_iswordc((uint8_t)buf[i - 1]))) { c = vgetc(); if (p_hkmap && KeyTyped) { c = hkmap(c); // Hebrew mode mapping } - buf[i++] = (char_u)c; + buf[i++] = (char)c; } do_digraph(-1); // clear digraphs - do_digraph(buf[i - 1]); // may be the start of a digraph + do_digraph((uint8_t)buf[i - 1]); // may be the start of a digraph buf[i] = NUL; - ins_str((char *)buf); + ins_str(buf); if (flags & INSCHAR_CTRLV) { - redo_literal(*buf); + redo_literal((uint8_t)(*buf)); i = 1; } else { i = 0; } if (buf[i] != NUL) { - AppendToRedobuffLit((char *)buf + i, -1); + AppendToRedobuffLit(buf + i, -1); } } else { int cc; @@ -2162,7 +2171,7 @@ void insertchar(int c, int flags, int second_indent) if ((cc = utf_char2len(c)) > 1) { char buf[MB_MAXBYTES + 1]; - utf_char2bytes(c, (char *)buf); + utf_char2bytes(c, buf); buf[cc] = NUL; ins_char_bytes(buf, (size_t)cc); AppendCharToRedobuff(c); @@ -2251,7 +2260,7 @@ int stop_arrow(void) // right, except when nothing was inserted yet. update_Insstart_orig = false; } - Insstart_textlen = (colnr_T)linetabsize((char_u *)get_cursor_line_ptr()); + Insstart_textlen = (colnr_T)linetabsize(get_cursor_line_ptr()); if (u_save_cursor() == OK) { arrow_used = false; @@ -2286,7 +2295,7 @@ int stop_arrow(void) static void stop_insert(pos_T *end_insert_pos, int esc, int nomove) { int cc; - char_u *ptr; + char *ptr; stop_redo_ins(); replace_flush(); // abandon replace stack @@ -2296,7 +2305,7 @@ static void stop_insert(pos_T *end_insert_pos, int esc, int nomove) // otherwise CTRL-O w and then <Left> will clear "last_insert". ptr = get_inserted(); if (did_restart_edit == 0 || (ptr != NULL - && (int)STRLEN(ptr) > new_insert_skip)) { + && (int)strlen(ptr) > new_insert_skip)) { xfree(last_insert); last_insert = ptr; last_insert_skip = new_insert_skip; @@ -2404,7 +2413,7 @@ static void stop_insert(pos_T *end_insert_pos, int esc, int nomove) // Used for the replace command. void set_last_insert(int c) { - char_u *s; + char *s; xfree(last_insert); last_insert = xmalloc(MB_MAXBYTES * 3 + 5); @@ -2413,7 +2422,7 @@ void set_last_insert(int c) if (c < ' ' || c == DEL) { *s++ = Ctrl_V; } - s = add_char2buf(c, s); + s = (char *)add_char2buf(c, (char_u *)s); *s++ = ESC; *s++ = NUL; last_insert_skip = 0; @@ -2728,12 +2737,12 @@ char_u *get_last_insert(void) if (last_insert == NULL) { return NULL; } - return last_insert + last_insert_skip; + return (char_u *)last_insert + last_insert_skip; } // Get last inserted string, and remove trailing <Esc>. // Returns pointer to allocated memory (must be freed) or NULL. -char_u *get_last_insert_save(void) +char *get_last_insert_save(void) { char *s; int len; @@ -2741,13 +2750,13 @@ char_u *get_last_insert_save(void) if (last_insert == NULL) { return NULL; } - s = xstrdup((char *)last_insert + last_insert_skip); - len = (int)STRLEN(s); + s = xstrdup(last_insert + last_insert_skip); + len = (int)strlen(s); if (len > 0 && s[len - 1] == ESC) { // remove trailing ESC s[len - 1] = NUL; } - return (char_u *)s; + return s; } /// Check the word in front of the cursor for an abbreviation. @@ -2767,7 +2776,7 @@ static bool echeck_abbr(int c) return false; } - return check_abbr(c, (char_u *)get_cursor_line_ptr(), curwin->w_cursor.col, + return check_abbr(c, get_cursor_line_ptr(), curwin->w_cursor.col, curwin->w_cursor.lnum == Insstart.lnum ? Insstart.col : 0); } @@ -2938,7 +2947,7 @@ static void replace_do_bs(int limit_col) int ins_len; int orig_vcols = 0; colnr_T start_vcol; - char_u *p; + char *p; int i; int vcol; const int l_State = State; @@ -2960,12 +2969,12 @@ static void replace_do_bs(int limit_col) if (l_State & VREPLACE_FLAG) { // Get the number of screen cells used by the inserted characters - p = (char_u *)get_cursor_pos_ptr(); - ins_len = (int)STRLEN(p) - orig_len; + p = get_cursor_pos_ptr(); + ins_len = (int)strlen(p) - orig_len; vcol = start_vcol; for (i = 0; i < ins_len; i++) { - vcol += win_chartabsize(curwin, (char *)p + i, vcol); - i += utfc_ptr2len((char *)p) - 1; + vcol += win_chartabsize(curwin, p + i, vcol); + i += utfc_ptr2len(p) - 1; } vcol -= start_vcol; @@ -2993,34 +3002,6 @@ bool cindent_on(void) return !p_paste && (curbuf->b_p_cin || *curbuf->b_p_inde != NUL); } -// Re-indent the current line, based on the current contents of it and the -// surrounding lines. Fixing the cursor position seems really easy -- I'm very -// confused what all the part that handles Control-T is doing that I'm not. -// "get_the_indent" should be get_c_indent, get_expr_indent or get_lisp_indent. -void fixthisline(IndentGetter get_the_indent) -{ - int amount = get_the_indent(); - - if (amount >= 0) { - change_indent(INDENT_SET, amount, false, 0, true); - if (linewhite(curwin->w_cursor.lnum)) { - did_ai = true; // delete the indent if the line stays empty - } - } -} - -void fix_indent(void) -{ - if (p_paste) { - return; - } - if (curbuf->b_p_lisp && curbuf->b_p_ai) { - fixthisline(get_lisp_indent); - } else if (cindent_on()) { - do_c_expr_indent(); - } -} - /// Check that "cinkeys" contains the key "keytyped", /// when == '*': Only if key is preceded with '*' (indent before insert) /// when == '!': Only if key is preceded with '!' (don't insert) @@ -3036,11 +3017,11 @@ void fix_indent(void) /// @param line_is_empty when true, accept keys with '0' before them. bool in_cinkeys(int keytyped, int when, bool line_is_empty) { - char_u *look; + char *look; int try_match; int try_match_word; - char_u *p; - char_u *line; + char *p; + char *line; bool icase; if (keytyped == NUL) { @@ -3049,9 +3030,9 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) } if (*curbuf->b_p_inde != NUL) { - look = (char_u *)curbuf->b_p_indk; // 'indentexpr' set: use 'indentkeys' + look = curbuf->b_p_indk; // 'indentexpr' set: use 'indentkeys' } else { - look = (char_u *)curbuf->b_p_cink; // 'indentexpr' empty: use 'cinkeys' + look = curbuf->b_p_cink; // 'indentexpr' empty: use 'cinkeys' } while (*look) { // Find out if we want to try a match with this key, depending on @@ -3104,9 +3085,9 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) // cursor. } else if (*look == 'e') { if (try_match && keytyped == 'e' && curwin->w_cursor.col >= 4) { - p = (char_u *)get_cursor_line_ptr(); - if ((char_u *)skipwhite((char *)p) == p + curwin->w_cursor.col - 4 - && STRNCMP(p + curwin->w_cursor.col - 4, "else", 4) == 0) { + p = get_cursor_line_ptr(); + if (skipwhite(p) == p + curwin->w_cursor.col - 4 + && strncmp(p + curwin->w_cursor.col - 4, "else", 4) == 0) { return true; } } @@ -3117,12 +3098,12 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) // class::method for C++). } else if (*look == ':') { if (try_match && keytyped == ':') { - p = (char_u *)get_cursor_line_ptr(); + p = get_cursor_line_ptr(); if (cin_iscase(p, false) || cin_isscopedecl(p) || cin_islabel()) { return true; } // Need to get the line again after cin_islabel(). - p = (char_u *)get_cursor_line_ptr(); + p = get_cursor_line_ptr(); if (curwin->w_cursor.col > 2 && p[curwin->w_cursor.col - 1] == ':' && p[curwin->w_cursor.col - 2] == ':') { @@ -3130,7 +3111,7 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) const bool i = cin_iscase(p, false) || cin_isscopedecl(p) || cin_islabel(); - p = (char_u *)get_cursor_line_ptr(); + p = get_cursor_line_ptr(); p[curwin->w_cursor.col - 1] = ':'; if (i) { return true; @@ -3145,12 +3126,12 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) // make up some named keys <o>, <O>, <e>, <0>, <>>, <<>, <*>, // <:> and <!> so that people can re-indent on o, O, e, 0, <, // >, *, : and ! keys if they really really want to. - if (vim_strchr("<>!*oOe0:", look[1]) != NULL + if (vim_strchr("<>!*oOe0:", (uint8_t)look[1]) != NULL && keytyped == look[1]) { return true; } - if (keytyped == get_special_key_code(look + 1)) { + if (keytyped == get_special_key_code((char_u *)look + 1)) { return true; } } @@ -3169,20 +3150,20 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) } else { icase = false; } - p = (char_u *)vim_strchr((char *)look, ','); + p = vim_strchr(look, ','); if (p == NULL) { - p = look + STRLEN(look); + p = look + strlen(look); } if ((try_match || try_match_word) && curwin->w_cursor.col >= (colnr_T)(p - look)) { bool match = false; if (keytyped == KEY_COMPLETE) { - char_u *n, *s; + char *n, *s; // Just completed a word, check if it starts with "look". // search back for the start of a word. - line = (char_u *)get_cursor_line_ptr(); + line = get_cursor_line_ptr(); for (s = line + curwin->w_cursor.col; s > line; s = n) { n = mb_prevptr(line, s); if (!vim_iswordp(n)) { @@ -3192,22 +3173,22 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) assert(p >= look && (uintmax_t)(p - look) <= SIZE_MAX); if (s + (p - look) <= line + curwin->w_cursor.col && (icase - ? mb_strnicmp((char *)s, (char *)look, (size_t)(p - look)) - : STRNCMP(s, look, p - look)) == 0) { + ? mb_strnicmp(s, look, (size_t)(p - look)) + : strncmp(s, look, (size_t)(p - look))) == 0) { match = true; } } else { // TODO(@brammool): multi-byte - if (keytyped == (int)p[-1] + if (keytyped == (int)(uint8_t)p[-1] || (icase && keytyped < 256 - && TOLOWER_LOC(keytyped) == TOLOWER_LOC((int)p[-1]))) { - line = (char_u *)get_cursor_pos_ptr(); + && TOLOWER_LOC(keytyped) == TOLOWER_LOC((uint8_t)p[-1]))) { + line = get_cursor_pos_ptr(); assert(p >= look && (uintmax_t)(p - look) <= SIZE_MAX); if ((curwin->w_cursor.col == (colnr_T)(p - look) - || !vim_iswordc(line[-(p - look) - 1])) + || !vim_iswordc((uint8_t)line[-(p - look) - 1])) && (icase - ? mb_strnicmp((char *)line - (p - look), (char *)look, (size_t)(p - look)) - : STRNCMP(line - (p - look), look, p - look)) == 0) { + ? mb_strnicmp(line - (p - look), look, (size_t)(p - look)) + : strncmp(line - (p - look), look, (size_t)(p - look))) == 0) { match = true; } } @@ -3228,7 +3209,7 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) // Ok, it's a boring generic character. } else { - if (try_match && *look == keytyped) { + if (try_match && (uint8_t)(*look) == keytyped) { return true; } if (*look != NUL) { @@ -3237,7 +3218,7 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) } // Skip over ", ". - look = (char_u *)skip_to_option_part((char *)look); + look = skip_to_option_part(look); } return false; } @@ -3493,7 +3474,7 @@ static void ins_ctrl_hat(void) State |= MODE_LANGMAP; } } - set_iminsert_global(); + set_iminsert_global(curbuf); showmode(); // Show/unshow value of 'keymap' in status lines. status_redraw_curbuf(); @@ -3679,8 +3660,7 @@ static bool ins_start_select(int c) static void ins_insert(int replaceState) { set_vim_var_string(VV_INSERTMODE, ((State & REPLACE_FLAG) ? "i" : - replaceState == MODE_VREPLACE ? "v" : - "r"), 1); + replaceState == MODE_VREPLACE ? "v" : "r"), 1); ins_apply_autocmds(EVENT_INSERTCHANGE); if (State & REPLACE_FLAG) { State = MODE_INSERT | (State & MODE_LANGMAP); @@ -3895,10 +3875,10 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) // again when auto-formatting. if (has_format_option(FO_AUTO) && has_format_option(FO_WHITE_PAR)) { - char_u *ptr = (char_u *)ml_get_buf(curbuf, curwin->w_cursor.lnum, true); + char *ptr = ml_get_buf(curbuf, curwin->w_cursor.lnum, true); int len; - len = (int)STRLEN(ptr); + len = (int)strlen(ptr); if (len > 0 && ptr[len - 1] == ' ') { ptr[len - 1] = NUL; } @@ -4021,7 +4001,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) // Delete up to starting point, start of line or previous word. int prev_cclass = 0; - int cclass = mb_get_class((char_u *)get_cursor_pos_ptr()); + int cclass = mb_get_class(get_cursor_pos_ptr()); do { if (!revins_on) { // put cursor on char to be deleted dec_cursor(); @@ -4029,7 +4009,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) cc = gchar_cursor(); // look multi-byte character class prev_cclass = cclass; - cclass = mb_get_class((char_u *)get_cursor_pos_ptr()); + cclass = mb_get_class(get_cursor_pos_ptr()); if (mode == BACKSPACE_WORD && !ascii_isspace(cc)) { // start of word? mode = BACKSPACE_WORD_NOT_SPACE; temp = vim_iswordc(cc); @@ -4176,7 +4156,7 @@ static void ins_mousescroll(int dir) if (dir == MSCR_DOWN || dir == MSCR_UP) { if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { scroll_redraw(dir, (long)(curwin->w_botline - curwin->w_topline)); - } else { + } else if (p_mousescroll_vert > 0) { scroll_redraw(dir, p_mousescroll_vert); } } else { @@ -4521,7 +4501,7 @@ static bool ins_tab(void) if (!curbuf->b_p_et && (tabstop_count(curbuf->b_p_vsts_array) > 0 || get_sts_value() > 0 || (p_sta && ind))) { - char_u *ptr; + char *ptr; char *saved_line = NULL; // init for GCC pos_T pos; pos_T fpos; @@ -4536,9 +4516,9 @@ static bool ins_tab(void) pos = curwin->w_cursor; cursor = &pos; saved_line = xstrdup(get_cursor_line_ptr()); - ptr = (char_u *)saved_line + pos.col; + ptr = saved_line + pos.col; } else { - ptr = (char_u *)get_cursor_pos_ptr(); + ptr = get_cursor_pos_ptr(); cursor = &curwin->w_cursor; } @@ -4566,9 +4546,9 @@ static bool ins_tab(void) getvcol(curwin, &fpos, &vcol, NULL, NULL); getvcol(curwin, cursor, &want_vcol, NULL, NULL); - char_u *tab = (char_u *)"\t"; + char *tab = "\t"; chartabsize_T cts; - init_chartabsize_arg(&cts, curwin, 0, vcol, (char *)tab, (char *)tab); + init_chartabsize_arg(&cts, curwin, 0, vcol, tab, tab); // Use as many TABs as possible. Beware of 'breakindent', 'showbreak' // and 'linebreak' adding extra virtual columns. @@ -4597,13 +4577,13 @@ static bool ins_tab(void) if (change_col >= 0) { int repl_off = 0; // Skip over the spaces we need. - init_chartabsize_arg(&cts, curwin, 0, vcol, (char *)ptr, (char *)ptr); + init_chartabsize_arg(&cts, curwin, 0, vcol, ptr, ptr); while (cts.cts_vcol < want_vcol && *cts.cts_ptr == ' ') { cts.cts_vcol += lbr_chartabsize(&cts); cts.cts_ptr++; repl_off++; } - ptr = (char_u *)cts.cts_ptr; + ptr = cts.cts_ptr; vcol = cts.cts_vcol; clear_chartabsize_arg(&cts); @@ -4617,7 +4597,7 @@ static bool ins_tab(void) // Delete following spaces. i = cursor->col - fpos.col; if (i > 0) { - STRMOVE(ptr, ptr + i); + STRMOVE(ptr, (char *)ptr + i); // correct replace stack. if ((State & REPLACE_FLAG) && !(State & VREPLACE_FLAG)) { @@ -4780,8 +4760,8 @@ static int ins_digraph(void) int ins_copychar(linenr_T lnum) { int c; - char_u *ptr, *prev_ptr; - char_u *line; + char *ptr, *prev_ptr; + char *line; if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) { vim_beep(BO_COPY); @@ -4789,25 +4769,25 @@ int ins_copychar(linenr_T lnum) } // try to advance to the cursor column - line = (char_u *)ml_get(lnum); + line = ml_get(lnum); prev_ptr = line; validate_virtcol(); chartabsize_T cts; - init_chartabsize_arg(&cts, curwin, lnum, 0, (char *)line, (char *)line); + init_chartabsize_arg(&cts, curwin, lnum, 0, line, line); while (cts.cts_vcol < curwin->w_virtcol && *cts.cts_ptr != NUL) { - prev_ptr = (char_u *)cts.cts_ptr; + prev_ptr = cts.cts_ptr; cts.cts_vcol += lbr_chartabsize_adv(&cts); } if (cts.cts_vcol > curwin->w_virtcol) { ptr = prev_ptr; } else { - ptr = (char_u *)cts.cts_ptr; + ptr = cts.cts_ptr; } clear_chartabsize_arg(&cts); - c = utf_ptr2char((char *)ptr); + c = utf_ptr2char(ptr); if (c == NUL) { vim_beep(BO_COPY); } @@ -4856,7 +4836,7 @@ static int ins_ctrl_ey(int tc) static void ins_try_si(int c) { pos_T *pos, old_pos; - char_u *ptr; + char *ptr; int i; bool temp; @@ -4870,7 +4850,7 @@ static void ins_try_si(int c) // containing the matching '(' if there is one. This handles the // case where an "if (..\n..) {" statement continues over multiple // lines -- webb - ptr = (char_u *)ml_get(pos->lnum); + ptr = ml_get(pos->lnum); i = pos->col; if (i > 0) { // skip blanks before '{' while (--i > 0 && ascii_iswhite(ptr[i])) {} @@ -4895,7 +4875,7 @@ static void ins_try_si(int c) old_pos = curwin->w_cursor; i = get_indent(); while (curwin->w_cursor.lnum > 1) { - ptr = (char_u *)skipwhite(ml_get(--(curwin->w_cursor.lnum))); + ptr = skipwhite(ml_get(--(curwin->w_cursor.lnum))); // ignore empty lines and lines starting with '#'. if (*ptr != '#' && *ptr != NUL) { @@ -4946,7 +4926,7 @@ colnr_T get_nolist_virtcol(void) // "c" is the character that was typed. // Return a pointer to allocated memory with the replacement string. // Return NULL to continue inserting "c". -static char_u *do_insert_char_pre(int c) +static char *do_insert_char_pre(int c) { char buf[MB_MAXBYTES + 1]; const int save_State = State; @@ -4977,7 +4957,7 @@ static char_u *do_insert_char_pre(int c) // Restore the State, it may have been changed. State = save_State; - return (char_u *)res; + return res; } bool get_can_cindent(void) diff --git a/src/nvim/edit.h b/src/nvim/edit.h index eda6d8c9db..91c519f015 100644 --- a/src/nvim/edit.h +++ b/src/nvim/edit.h @@ -4,8 +4,6 @@ #include "nvim/autocmd.h" #include "nvim/vim.h" -typedef int (*IndentGetter)(void); - // Values for in_cinkeys() #define KEY_OPEN_FORW 0x101 #define KEY_OPEN_BACK 0x102 diff --git a/src/nvim/eval.c b/src/nvim/eval.c index ebc60ea5c7..4392ea306f 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -3,13 +3,19 @@ // eval.c: Expression evaluation. +#include <assert.h> +#include <ctype.h> +#include <inttypes.h> #include <math.h> +#include <stdio.h> #include <stdlib.h> +#include <string.h> +#include "auto/config.h" +#include "nvim/api/private/defs.h" #include "nvim/ascii.h" -#include "nvim/autocmd.h" #include "nvim/buffer.h" -#include "nvim/change.h" +#include "nvim/buffer_defs.h" #include "nvim/channel.h" #include "nvim/charset.h" #include "nvim/cmdhist.h" @@ -22,37 +28,58 @@ #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" +#include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" +#include "nvim/event/process.h" #include "nvim/ex_cmds.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/ex_session.h" +#include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/grid_defs.h" #include "nvim/highlight_group.h" +#include "nvim/insexpand.h" +#include "nvim/keycodes.h" +#include "nvim/lib/queue.h" #include "nvim/locale.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" +#include "nvim/main.h" +#include "nvim/map.h" #include "nvim/mark.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/move.h" +#include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/optionstr.h" -#include "nvim/os/input.h" +#include "nvim/os/fileio.h" +#include "nvim/os/fs_defs.h" +#include "nvim/os/os.h" #include "nvim/os/shell.h" +#include "nvim/os/stdpaths_defs.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/runtime.h" -#include "nvim/screen.h" #include "nvim/search.h" -#include "nvim/sign.h" -#include "nvim/syntax.h" +#include "nvim/strings.h" +#include "nvim/tag.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" -#include "nvim/undo.h" +#include "nvim/usercmd.h" #include "nvim/version.h" +#include "nvim/vim.h" #include "nvim/window.h" // TODO(ZyX-I): Remove DICT_MAXNEST, make users be non-recursive instead @@ -60,11 +87,15 @@ #define DICT_MAXNEST 100 // maximum nesting of lists and dicts static char *e_missbrac = N_("E111: Missing ']'"); +static char *e_list_end = N_("E697: Missing end of List ']': %s"); static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary"); static char *e_nowhitespace = N_("E274: No white space allowed before parenthesis"); static char *e_write2 = N_("E80: Error while writing: %s"); static char *e_string_list_or_blob_required = N_("E1098: String, List or Blob required"); +static char e_expression_too_recursive_str[] = N_("E1169: Expression too recursive: %s"); +static char e_dot_can_only_be_used_on_dictionary_str[] + = N_("E1203: Dot can only be used on a dictionary: %s"); static char * const namespace_char = "abglstvw"; @@ -132,8 +163,7 @@ static struct vimvar { char *vv_name; ///< Name of the variable, without v:. TV_DICTITEM_STRUCT(VIMVAR_KEY_LEN + 1) vv_di; ///< Value and name for key (max 16 chars). char vv_flags; ///< Flags: #VV_COMPAT, #VV_RO, #VV_RO_SBX. -} vimvars[] = -{ +} vimvars[] = { // VV_ tails differing from upcased string literals: // VV_CC_FROM "charconvert_from" // VV_CC_TO "charconvert_to" @@ -237,6 +267,8 @@ static struct vimvar { 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_RELNUM, "relnum", VAR_NUMBER, VV_RO), + VV(VV_VIRTNUM, "virtnum", VAR_NUMBER, VV_RO), }; #undef VV @@ -330,6 +362,10 @@ varnumber_T num_divide(varnumber_T n1, varnumber_T n2) } else { result = VARNUMBER_MAX; } + } else if (n1 == VARNUMBER_MIN && n2 == -1) { + // specific case: trying to do VARNUMBAR_MIN / -1 results in a positive + // number that doesn't fit in varnumber_T and causes an FPE + result = VARNUMBER_MAX; } else { result = n1 / n2; } @@ -370,11 +406,11 @@ void eval_init(void) // add to v: scope dict, unless the value is not always available if (p->vv_type != VAR_UNKNOWN) { - hash_add(&vimvarht, p->vv_di.di_key); + hash_add(&vimvarht, (char *)p->vv_di.di_key); } if (p->vv_flags & VV_COMPAT) { // add to compat scope dict - hash_add(&compat_hashtab, p->vv_di.di_key); + hash_add(&compat_hashtab, (char *)p->vv_di.di_key); } } vimvars[VV_VERSION].vv_nr = VIM_VERSION_100; @@ -485,7 +521,7 @@ void eval_clear(void) /// Set an internal variable to a string value. Creates the variable if it does /// not already exist. -void set_internal_string_var(const char *name, char *value) +void set_internal_string_var(const char *name, char *value) // NOLINT(readability-non-const-parameter) FUNC_ATTR_NONNULL_ARG(1) { typval_T tv = { @@ -541,7 +577,7 @@ int var_redir_start(char *name, int append) // check if we can write to the variable: set it to or append an empty // string - int save_emsg = did_emsg; + const int called_emsg_before = called_emsg; did_emsg = false; typval_T tv; tv.v_type = VAR_STRING; @@ -552,9 +588,7 @@ int var_redir_start(char *name, int append) set_var_lval(redir_lval, redir_endp, &tv, true, false, "="); } clear_lval(redir_lval); - int err = did_emsg; - did_emsg |= save_emsg; - if (err) { + if (called_emsg > called_emsg_before) { redir_endp = NULL; // don't store a value, only cleanup var_redir_stop(); return FAIL; @@ -640,25 +674,6 @@ int eval_charconvert(const char *const enc_from, const char *const enc_to, return OK; } -int eval_printexpr(const char *const fname, const char *const args) -{ - bool err = false; - - set_vim_var_string(VV_FNAME_IN, fname, -1); - set_vim_var_string(VV_CMDARG, args, -1); - if (eval_to_bool(p_pexpr, &err, NULL, false)) { - err = true; - } - set_vim_var_string(VV_FNAME_IN, NULL, -1); - set_vim_var_string(VV_CMDARG, NULL, -1); - - if (err) { - os_remove(fname); - return FAIL; - } - return OK; -} - void eval_diff(const char *const origfile, const char *const newfile, const char *const outfile) { bool err = false; @@ -679,7 +694,7 @@ void eval_patch(const char *const origfile, const char *const difffile, const ch set_vim_var_string(VV_FNAME_IN, origfile, -1); set_vim_var_string(VV_FNAME_DIFF, difffile, -1); set_vim_var_string(VV_FNAME_OUT, outfile, -1); - (void)eval_to_bool((char *)p_pex, &err, NULL, false); + (void)eval_to_bool(p_pex, &err, NULL, false); set_vim_var_string(VV_FNAME_IN, NULL, -1); set_vim_var_string(VV_FNAME_DIFF, NULL, -1); set_vim_var_string(VV_FNAME_OUT, NULL, -1); @@ -977,7 +992,7 @@ void prepare_vimvar(int idx, typval_T *save_tv) { *save_tv = vimvars[idx].vv_tv; if (vimvars[idx].vv_type == VAR_UNKNOWN) { - hash_add(&vimvarht, vimvars[idx].vv_di.di_key); + hash_add(&vimvarht, (char *)vimvars[idx].vv_di.di_key); } } @@ -986,24 +1001,15 @@ void prepare_vimvar(int idx, typval_T *save_tv) void restore_vimvar(int idx, typval_T *save_tv) { vimvars[idx].vv_tv = *save_tv; - if (vimvars[idx].vv_type == VAR_UNKNOWN) { - hashitem_T *hi = hash_find(&vimvarht, (char *)vimvars[idx].vv_di.di_key); - if (HASHITEM_EMPTY(hi)) { - internal_error("restore_vimvar()"); - } else { - hash_remove(&vimvarht, hi); - } + if (vimvars[idx].vv_type != VAR_UNKNOWN) { + return; } -} -/// If there is a window for "curbuf", make it the current window. -void find_win_for_curbuf(void) -{ - for (wininfo_T *wip = curbuf->b_wininfo; wip != NULL; wip = wip->wi_next) { - if (wip->wi_win != NULL) { - curwin = wip->wi_win; - break; - } + hashitem_T *hi = hash_find(&vimvarht, (char *)vimvars[idx].vv_di.di_key); + if (HASHITEM_EMPTY(hi)) { + internal_error("restore_vimvar()"); + } else { + hash_remove(&vimvarht, hi); } } @@ -1069,11 +1075,11 @@ int get_spellword(list_T *const list, const char **ret_word) return (int)tv_list_find_nr(list, -1, NULL); } -// Call some vim script function and return the result in "*rettv". -// Uses argv[0] to argv[argc-1] for the function arguments. argv[argc] -// should have type VAR_UNKNOWN. -// -// @return OK or FAIL. +/// Call some Vim script function and return the result in "*rettv". +/// Uses argv[0] to argv[argc - 1] for the function arguments. argv[argc] +/// should have type VAR_UNKNOWN. +/// +/// @return OK or FAIL. int call_vim_function(const char *func, int argc, typval_T *argv, typval_T *rettv) FUNC_ATTR_NONNULL_ALL { @@ -1106,26 +1112,10 @@ fail: return ret; } -/// Call Vim script function and return the result as a number -/// -/// @param[in] func Function name. -/// @param[in] argc Number of arguments. -/// @param[in] argv Array with typval_T arguments. -/// -/// @return -1 when calling function fails, result of function otherwise. -varnumber_T call_func_retnr(const char *func, int argc, typval_T *argv) - FUNC_ATTR_NONNULL_ALL -{ - typval_T rettv; - if (call_vim_function((char *)func, argc, argv, &rettv) == FAIL) { - return -1; - } - varnumber_T retval = tv_get_number_chk(&rettv, NULL); - tv_clear(&rettv); - return retval; -} -/// Call Vim script function and return the result as a string +/// Call Vim script function and return the result as a string. +/// Uses "argv[0]" to "argv[argc - 1]" for the function arguments. "argv[argc]" +/// should have type VAR_UNKNOWN. /// /// @param[in] func Function name. /// @param[in] argc Number of arguments. @@ -1147,7 +1137,9 @@ char *call_func_retstr(const char *const func, int argc, typval_T *argv) tv_clear(&rettv); return retval; } -/// Call Vim script function and return the result as a List + +/// Call Vim script function and return the result as a List. +/// Uses "argv" and "argc" as call_func_retstr(). /// /// @param[in] func Function name. /// @param[in] argc Number of arguments. @@ -1215,8 +1207,6 @@ int eval_foldexpr(char *arg, int *cp) return (int)retval; } -// TODO(ZyX-I): move to eval/executor - /// Get an lvalue /// /// Lvalue may be @@ -1311,13 +1301,26 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const return NULL; } - // Loop until no more [idx] or .key is following. lp->ll_tv = &v->di_tv; + + if (tv_is_luafunc(lp->ll_tv)) { + // For v:lua just return a pointer to the "." after the "v:lua". + // If the caller is trans_function_name() it will check for a Lua function name. + return p; + } + + // Loop until no more [idx] or .key is following. typval_T var1; var1.v_type = VAR_UNKNOWN; typval_T var2; var2.v_type = VAR_UNKNOWN; - while (*p == '[' || (*p == '.' && lp->ll_tv->v_type == VAR_DICT)) { + while (*p == '[' || (*p == '.' && p[1] != '=' && p[1] != '.')) { + if (*p == '.' && lp->ll_tv->v_type != VAR_DICT) { + if (!quiet) { + semsg(_(e_dot_can_only_be_used_on_dictionary_str), name); + } + return NULL; + } if (!(lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list != NULL) && !(lp->ll_tv->v_type == VAR_DICT && lp->ll_tv->vval.v_dict != NULL) && !(lp->ll_tv->v_type == VAR_BLOB && lp->ll_tv->vval.v_blob != NULL)) { @@ -1439,12 +1442,13 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const } wrong = ((lp->ll_dict->dv_scope == VAR_DEF_SCOPE && tv_is_func(*rettv) - && !var_check_func_name((const char *)key, lp->ll_di == NULL)) + && var_wrong_func_name((const char *)key, lp->ll_di == NULL)) || !valid_varname((const char *)key)); if (len != -1) { key[len] = prevval; } if (wrong) { + tv_clear(&var1); return NULL; } } @@ -1481,9 +1485,9 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const tv_clear(&var1); break; // existing variable, need to check if it can be changed - } else if (!(flags & GLV_READ_ONLY) && var_check_ro(lp->ll_di->di_flags, - (const char *)name, - (size_t)(p - name))) { + } else if (!(flags & GLV_READ_ONLY) + && (var_check_ro(lp->ll_di->di_flags, name, (size_t)(p - name)) + || var_check_lock(lp->ll_di->di_flags, name, (size_t)(p - name)))) { tv_clear(&var1); return NULL; } @@ -1587,8 +1591,6 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const return p; } -// TODO(ZyX-I): move to eval/executor - /// Clear lval "lp" that was filled by get_lval(). void clear_lval(lval_T *lp) { @@ -1596,8 +1598,6 @@ void clear_lval(lval_T *lp) xfree(lp->ll_newkey); } -// TODO(ZyX-I): move to eval/executor - /// Set a variable that was parsed by get_lval() to "rettv". /// /// @param endp points to just after the parsed name. @@ -1618,7 +1618,7 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool semsg(_(e_letwrong), op); return; } - if (var_check_lock(lp->ll_blob->bv_lock, lp->ll_name, TV_CSTRING)) { + if (value_check_lock(lp->ll_blob->bv_lock, lp->ll_name, TV_CSTRING)) { return; } @@ -1648,7 +1648,7 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool // the end is an error otherwise. if (lp->ll_n1 < gap->ga_len || lp->ll_n1 == gap->ga_len) { ga_grow(&lp->ll_blob->bv_ga, 1); - tv_blob_set(lp->ll_blob, (int)lp->ll_n1, (char_u)val); + tv_blob_set(lp->ll_blob, (int)lp->ll_n1, (uint8_t)val); if (lp->ll_n1 == gap->ga_len) { gap->ga_len++; } @@ -1681,10 +1681,10 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool set_var_const(lp->ll_name, lp->ll_name_len, rettv, copy, is_const); } *endp = (char)cc; - } else if (var_check_lock(lp->ll_newkey == NULL - ? lp->ll_tv->v_lock - : lp->ll_tv->vval.v_dict->dv_lock, - lp->ll_name, TV_CSTRING)) { + } else if (value_check_lock(lp->ll_newkey == NULL + ? lp->ll_tv->v_lock + : lp->ll_tv->vval.v_dict->dv_lock, + lp->ll_name, TV_CSTRING)) { // Skip } else if (lp->ll_range) { listitem_T *ll_li = lp->ll_li; @@ -1698,8 +1698,8 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool // Check whether any of the list items is locked for (ri = tv_list_first(rettv->vval.v_list); ri != NULL && ll_li != NULL;) { - if (var_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock, lp->ll_name, - TV_CSTRING)) { + if (value_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock, lp->ll_name, + TV_CSTRING)) { return; } ri = TV_LIST_ITEM_NEXT(rettv->vval.v_list, ri); @@ -1754,7 +1754,10 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool // Assign to a List or Dictionary item. if (lp->ll_newkey != NULL) { if (op != NULL && *op != '=') { - semsg(_(e_letwrong), op); + semsg(_(e_dictkey), lp->ll_newkey); + return; + } + if (tv_dict_wrong_func_name(lp->ll_tv->vval.v_dict, rettv, lp->ll_newkey)) { return; } @@ -1802,8 +1805,6 @@ notify: } } -// TODO(ZyX-I): move to eval/ex_cmds - /// Evaluate the expression used in a ":for var in expr" command. /// "arg" points to "var". /// @@ -1879,8 +1880,6 @@ void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip) return fi; } -// 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. /// @@ -1921,15 +1920,12 @@ bool next_for_item(void *fi_void, char *arg) listitem_T *item = fi->fi_lw.lw_item; if (item == NULL) { return false; - } else { - fi->fi_lw.lw_item = TV_LIST_ITEM_NEXT(fi->fi_list, item); - return (ex_let_vars(arg, TV_LIST_ITEM_TV(item), true, - fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK); } + fi->fi_lw.lw_item = TV_LIST_ITEM_NEXT(fi->fi_list, item); + return (ex_let_vars(arg, TV_LIST_ITEM_TV(item), true, + fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK); } -// TODO(ZyX-I): move to eval/ex_cmds - /// Free the structure used to store info used by ":for". void free_for_info(void *fi_void) { @@ -2059,7 +2055,7 @@ void del_menutrans_vars(void) { hash_lock(&globvarht); HASHTAB_ITER(&globvarht, hi, { - if (STRNCMP(hi->hi_key, "menutrans_", 10) == 0) { + if (strncmp(hi->hi_key, "menutrans_", 10) == 0) { delete_var(&globvarht, hi); } }); @@ -2117,10 +2113,10 @@ char *get_user_var_name(expand_T *xp, int idx) while (HASHITEM_EMPTY(hi)) { hi++; } - if (STRNCMP("g:", xp->xp_pattern, 2) == 0) { - return cat_prefix_varname('g', (char *)hi->hi_key); + if (strncmp("g:", xp->xp_pattern, 2) == 0) { + return cat_prefix_varname('g', hi->hi_key); } - return (char *)hi->hi_key; + return hi->hi_key; } // b: variables @@ -2134,7 +2130,7 @@ char *get_user_var_name(expand_T *xp, int idx) while (HASHITEM_EMPTY(hi)) { hi++; } - return cat_prefix_varname('b', (char *)hi->hi_key); + return cat_prefix_varname('b', hi->hi_key); } // w: variables @@ -2148,7 +2144,7 @@ char *get_user_var_name(expand_T *xp, int idx) while (HASHITEM_EMPTY(hi)) { hi++; } - return cat_prefix_varname('w', (char *)hi->hi_key); + return cat_prefix_varname('w', hi->hi_key); } // t: variables @@ -2162,7 +2158,7 @@ char *get_user_var_name(expand_T *xp, int idx) while (HASHITEM_EMPTY(hi)) { hi++; } - return cat_prefix_varname('t', (char *)hi->hi_key); + return cat_prefix_varname('t', hi->hi_key); } // v: variables @@ -2175,12 +2171,10 @@ char *get_user_var_name(expand_T *xp, int idx) return NULL; } -// TODO(ZyX-I): move to eval/expressions - /// Does not use 'cpo' and always uses 'magic'. /// /// @return true if "pat" matches "text". -int pattern_match(char *pat, char *text, bool ic) +int pattern_match(const char *pat, const char *text, bool ic) { int matches = 0; regmatch_T regmatch; @@ -2188,10 +2182,10 @@ int pattern_match(char *pat, char *text, bool ic) // avoid 'l' flag in 'cpoptions' char *save_cpo = p_cpo; p_cpo = empty_option; - regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); + regmatch.regprog = vim_regcomp((char *)pat, RE_MAGIC + RE_STRING); if (regmatch.regprog != NULL) { regmatch.rm_ic = ic; - matches = vim_regexec_nl(®match, (char_u *)text, (colnr_T)0); + matches = vim_regexec_nl(®match, (char *)text, (colnr_T)0); vim_regfree(regmatch.regprog); } p_cpo = save_cpo; @@ -2219,7 +2213,7 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ // If "s" is the name of a variable of type VAR_FUNC // use its contents. partial_T *partial; - s = (char *)deref_func_name((const char *)s, &len, &partial, !evaluate); + s = deref_func_name((const char *)s, &len, &partial, !evaluate); // Need to make a copy, in case evaluating the arguments makes // the name invalid. @@ -2232,7 +2226,7 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ funcexe.fe_evaluate = evaluate; funcexe.fe_partial = partial; funcexe.fe_basetv = basetv; - int ret = get_func_tv((char_u *)s, len, rettv, arg, &funcexe); + int ret = get_func_tv(s, len, rettv, arg, &funcexe); xfree(s); @@ -2256,8 +2250,6 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ return ret; } -// TODO(ZyX-I): move to eval/expressions - /// The "evaluate" argument: When false, the argument is only parsed but not /// executed. The function may return OK, but the rettv will be of type /// VAR_UNKNOWN. The function still returns FAIL for a syntax error. @@ -2274,10 +2266,15 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate) char *p; const int did_emsg_before = did_emsg; const int called_emsg_before = called_emsg; + bool end_error = false; p = skipwhite(arg); ret = eval1(&p, rettv, evaluate); - if (ret == FAIL || !ends_excmd(*p)) { + + if (ret != FAIL) { + end_error = !ends_excmd(*p); + } + if (ret == FAIL || end_error) { if (ret != FAIL) { tv_clear(rettv); } @@ -2287,7 +2284,11 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate) // Also check called_emsg for when using assert_fails(). if (!aborting() && did_emsg == did_emsg_before && called_emsg == called_emsg_before) { - semsg(_(e_invexpr2), arg); + if (end_error) { + semsg(_(e_trailing_arg), p); + } else { + semsg(_(e_invexpr2), arg); + } } ret = FAIL; } @@ -2298,8 +2299,6 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate) return ret; } -// TODO(ZyX-I): move to eval/expressions - /// Handle top level expression: /// expr2 ? expr1 : expr1 /// @@ -2364,8 +2363,6 @@ int eval1(char **arg, typval_T *rettv, int evaluate) return OK; } -// TODO(ZyX-I): move to eval/expressions - /// Handle first level expression: /// expr2 || expr2 || expr2 logical OR /// @@ -2423,8 +2420,6 @@ static int eval2(char **arg, typval_T *rettv, int evaluate) return OK; } -// TODO(ZyX-I): move to eval/expressions - /// Handle second level expression: /// expr3 && expr3 && expr3 logical AND /// @@ -2482,8 +2477,6 @@ static int eval3(char **arg, typval_T *rettv, int evaluate) return OK; } -// TODO(ZyX-I): move to eval/expressions - /// Handle third level expression: /// var1 == var2 /// var1 =~ var2 @@ -2550,7 +2543,7 @@ static int eval4(char **arg, typval_T *rettv, int evaluate) if (p[2] == 'n' && p[3] == 'o' && p[4] == 't') { len = 5; } - if (!isalnum(p[len]) && p[len] != '_') { + if (!isalnum((uint8_t)p[len]) && p[len] != '_') { type = len == 2 ? EXPR_IS : EXPR_ISNOT; } } @@ -2587,8 +2580,6 @@ static int eval4(char **arg, typval_T *rettv, int evaluate) return OK; } -// TODO(ZyX-I): move to eval/expressions - /// Handle fourth level expression: /// + number addition /// - number subtraction @@ -2669,10 +2660,10 @@ static int eval5(char **arg, typval_T *rettv, int evaluate) blob_T *const b = tv_blob_alloc(); for (int i = 0; i < tv_blob_len(b1); i++) { - ga_append(&b->bv_ga, (char)tv_blob_get(b1, i)); + ga_append(&b->bv_ga, tv_blob_get(b1, i)); } for (int i = 0; i < tv_blob_len(b2); i++) { - ga_append(&b->bv_ga, (char)tv_blob_get(b2, i)); + ga_append(&b->bv_ga, tv_blob_get(b2, i)); } tv_clear(rettv); @@ -2750,8 +2741,6 @@ static int eval5(char **arg, typval_T *rettv, int evaluate) return OK; } -// TODO(ZyX-I): move to eval/expressions - /// Handle fifth level expression: /// - * number multiplication /// - / number division @@ -2867,8 +2856,6 @@ static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string) return OK; } -// TODO(ZyX-I): move to eval/expressions - /// Handle sixth level expression: /// number number constant /// 0zFFFFFFFF Blob constant @@ -2900,23 +2887,33 @@ static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string) /// @return OK or FAIL. static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) { - varnumber_T n; - int len; - char *s; - const char *start_leader, *end_leader; int ret = OK; - char *alias; + static int recurse = 0; // Initialise variable so that tv_clear() can't mistake this for a // string and free a string that isn't there. rettv->v_type = VAR_UNKNOWN; // Skip '!', '-' and '+' characters. They are handled later. - start_leader = *arg; + const char *start_leader = *arg; while (**arg == '!' || **arg == '-' || **arg == '+') { *arg = skipwhite(*arg + 1); } - end_leader = *arg; + const char *end_leader = *arg; + + // Limit recursion to 1000 levels. At least at 10000 we run out of stack + // and crash. With MSVC the stack is smaller. + if (recurse == +#ifdef _MSC_VER + 300 +#else + 1000 +#endif + ) { + semsg(_(e_expression_too_recursive_str), *arg); + return FAIL; + } + recurse++; switch (**arg) { // Number constant. @@ -2929,88 +2926,9 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) case '6': case '7': case '8': - case '9': { - char *p = skipdigits(*arg + 1); - int get_float = false; - - // We accept a float when the format matches - // "[0-9]\+\.[0-9]\+\([eE][+-]\?[0-9]\+\)\?". This is very - // strict to avoid backwards compatibility problems. - // Don't look for a float after the "." operator, so that - // ":let vers = 1.2.3" doesn't fail. - if (!want_string && p[0] == '.' && ascii_isdigit(p[1])) { - get_float = true; - p = skipdigits(p + 2); - if (*p == 'e' || *p == 'E') { - p++; - if (*p == '-' || *p == '+') { - p++; - } - if (!ascii_isdigit(*p)) { - get_float = false; - } else { - p = skipdigits(p + 1); - } - } - if (ASCII_ISALPHA(*p) || *p == '.') { - get_float = false; - } - } - if (get_float) { - float_T f; - - *arg += string2float(*arg, &f); - if (evaluate) { - rettv->v_type = VAR_FLOAT; - rettv->vval.v_float = f; - } - } else if (**arg == '0' && ((*arg)[1] == 'z' || (*arg)[1] == 'Z')) { - blob_T *blob = NULL; - // Blob constant: 0z0123456789abcdef - if (evaluate) { - blob = tv_blob_alloc(); - } - char *bp; - for (bp = *arg + 2; ascii_isxdigit(bp[0]); bp += 2) { - if (!ascii_isxdigit(bp[1])) { - if (blob != NULL) { - emsg(_("E973: Blob literal should have an even number of hex " - "characters")); - ga_clear(&blob->bv_ga); - XFREE_CLEAR(blob); - } - ret = FAIL; - break; - } - if (blob != NULL) { - ga_append(&blob->bv_ga, (char)((hex2nr(*bp) << 4) + hex2nr(*(bp + 1)))); - } - if (bp[2] == '.' && ascii_isxdigit(bp[3])) { - bp++; - } - } - if (blob != NULL) { - tv_blob_set_ret(rettv, blob); - } - *arg = bp; - } else { - // decimal, hex or octal number - vim_str2nr(*arg, NULL, &len, STR2NR_ALL, &n, NULL, 0, true); - if (len == 0) { - if (evaluate) { - semsg(_(e_invexpr2), *arg); - } - ret = FAIL; - break; - } - *arg += len; - if (evaluate) { - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = n; - } - } + case '9': + ret = get_number_tv(arg, rettv, evaluate, want_string); break; - } // String constant: "string". case '"': @@ -3031,7 +2949,7 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) case '#': if ((*arg)[1] == '{') { (*arg)++; - ret = dict_get_tv(arg, rettv, evaluate, true); + ret = eval_dict(arg, rettv, evaluate, true); } else { ret = NOTDONE; } @@ -3042,7 +2960,7 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) case '{': ret = get_lambda_tv(arg, rettv, evaluate); if (ret == NOTDONE) { - ret = dict_get_tv(arg, rettv, evaluate, false); + ret = eval_dict(arg, rettv, evaluate, false); } break; @@ -3088,8 +3006,9 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) if (ret == NOTDONE) { // Must be a variable or function name. // Can also be a curly-braces kind of name: {expr}. - s = *arg; - len = get_name_len((const char **)arg, &alias, evaluate, true); + char *s = *arg; + char *alias; + int len = get_name_len((const char **)arg, &alias, evaluate, true); if (alias != NULL) { s = alias; } @@ -3122,6 +3041,8 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) if (ret == OK && evaluate && end_leader > start_leader) { ret = eval7_leader(rettv, (char *)start_leader, &end_leader); } + + recurse--; return ret; } @@ -3217,7 +3138,7 @@ static int call_func_rettv(char **const arg, typval_T *const rettv, const bool e funcexe.fe_partial = pt; funcexe.fe_selfdict = selfdict; funcexe.fe_basetv = basetv; - const int ret = get_func_tv((char_u *)funcname, is_lua ? (int)(*arg - funcname) : -1, rettv, + const int ret = get_func_tv(funcname, is_lua ? (int)(*arg - funcname) : -1, rettv, arg, &funcexe); // Clear the funcref afterwards, so that deleting it while @@ -3290,7 +3211,7 @@ static int eval_method(char **const arg, typval_T *const rettv, const bool evalu int len; char *name = *arg; char *lua_funcname = NULL; - if (STRNCMP(name, "v:lua.", 6) == 0) { + if (strncmp(name, "v:lua.", 6) == 0) { lua_funcname = name + 6; *arg = (char *)skip_luafunc_name((const char *)lua_funcname); *arg = skipwhite(*arg); // to detect trailing whitespace later @@ -3345,8 +3266,6 @@ static int eval_method(char **const arg, typval_T *const rettv, const bool evalu return ret; } -// TODO(ZyX-I): move to eval/expressions - /// Evaluate an "[expr]" or "[expr:expr]" index. Also "dict.key". /// "*arg" points to the '[' or '.'. /// @@ -3398,7 +3317,7 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose) if (**arg == '.') { // dict.name key = *arg + 1; - for (len = 0; ASCII_ISALNUM(key[len]) || key[len] == '_'; len++) {} + for (len = 0; eval_isdictc(key[len]); len++) {} if (len == 0) { return FAIL; } @@ -3646,8 +3565,6 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose) return OK; } -// TODO(ZyX-I): move to eval/executor - /// Get an option value /// /// @param[in,out] arg Points to the '&' or '+' before the option name. Is @@ -3659,10 +3576,11 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose) int get_option_tv(const char **const arg, typval_T *const rettv, const bool evaluate) FUNC_ATTR_NONNULL_ARG(1) { - int opt_flags; + const bool working = (**arg == '+'); // has("+option") + int scope; // Isolate the option name and find its value. - char *option_end = (char *)find_option_end(arg, &opt_flags); + char *option_end = (char *)find_option_end(arg, &scope); if (option_end == NULL) { if (rettv != NULL) { semsg(_("E112: Option name missing: %s"), *arg); @@ -3682,7 +3600,7 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval char c = *option_end; *option_end = NUL; getoption_T opt_type = get_option_value(*arg, &numval, - rettv == NULL ? NULL : &stringval, opt_flags); + rettv == NULL ? NULL : &stringval, NULL, scope); if (opt_type == gov_unknown) { if (rettv != NULL) { @@ -3703,6 +3621,10 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval rettv->v_type = VAR_STRING; rettv->vval.v_string = stringval; } + } else if (working && (opt_type == gov_hidden_bool + || opt_type == gov_hidden_number + || opt_type == gov_hidden_string)) { + ret = FAIL; } *option_end = c; // put back for error messages @@ -3711,6 +3633,91 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval return ret; } +/// Allocate a variable for a number constant. Also deals with "0z" for blob. +/// +/// @return OK or FAIL. +static int get_number_tv(char **arg, typval_T *rettv, bool evaluate, bool want_string) +{ + char *p = skipdigits(*arg + 1); + bool get_float = false; + + // We accept a float when the format matches + // "[0-9]\+\.[0-9]\+\([eE][+-]\?[0-9]\+\)\?". This is very + // strict to avoid backwards compatibility problems. + // Don't look for a float after the "." operator, so that + // ":let vers = 1.2.3" doesn't fail. + if (!want_string && p[0] == '.' && ascii_isdigit(p[1])) { + get_float = true; + p = skipdigits(p + 2); + if (*p == 'e' || *p == 'E') { + p++; + if (*p == '-' || *p == '+') { + p++; + } + if (!ascii_isdigit(*p)) { + get_float = false; + } else { + p = skipdigits(p + 1); + } + } + if (ASCII_ISALPHA(*p) || *p == '.') { + get_float = false; + } + } + if (get_float) { + float_T f; + *arg += string2float(*arg, &f); + if (evaluate) { + rettv->v_type = VAR_FLOAT; + rettv->vval.v_float = f; + } + } else if (**arg == '0' && ((*arg)[1] == 'z' || (*arg)[1] == 'Z')) { + // Blob constant: 0z0123456789abcdef + blob_T *blob = NULL; + if (evaluate) { + blob = tv_blob_alloc(); + } + char *bp; + for (bp = *arg + 2; ascii_isxdigit(bp[0]); bp += 2) { + if (!ascii_isxdigit(bp[1])) { + if (blob != NULL) { + emsg(_("E973: Blob literal should have an even number of hex characters")); + ga_clear(&blob->bv_ga); + XFREE_CLEAR(blob); + } + return FAIL; + } + if (blob != NULL) { + ga_append(&blob->bv_ga, (uint8_t)((hex2nr(*bp) << 4) + hex2nr(*(bp + 1)))); + } + if (bp[2] == '.' && ascii_isxdigit(bp[3])) { + bp++; + } + } + if (blob != NULL) { + tv_blob_set_ret(rettv, blob); + } + *arg = bp; + } else { + // decimal, hex or octal number + int len; + varnumber_T n; + vim_str2nr(*arg, NULL, &len, STR2NR_ALL, &n, NULL, 0, true); + if (len == 0) { + if (evaluate) { + semsg(_(e_invexpr2), *arg); + } + return FAIL; + } + *arg += len; + if (evaluate) { + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = n; + } + } + return OK; +} + /// Allocate a variable for a string constant. /// /// @return OK or FAIL. @@ -3772,7 +3779,7 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate) case 'U': if (ascii_isxdigit(p[1])) { int n, nr; - int c = toupper(*p); + int c = toupper((uint8_t)(*p)); if (c == 'X') { n = 2; @@ -3823,7 +3830,7 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate) if (p[1] != '*') { flags |= FSK_SIMPLIFY; } - extra = trans_special((const char_u **)&p, strlen(p), (char_u *)name, flags, false, NULL); + extra = trans_special((const char **)&p, strlen(p), name, flags, false, NULL); if (extra != 0) { name += extra; if (name >= rettv->vval.v_string + len) { @@ -3911,8 +3918,6 @@ char *partial_name(partial_T *pt) return (char *)pt->pt_func->uf_name; } -// TODO(ZyX-I): Move to eval/typval.h - static void partial_free(partial_T *pt) { for (int i = 0; i < pt->pt_argc; i++) { @@ -3921,7 +3926,7 @@ static void partial_free(partial_T *pt) xfree(pt->pt_argv); tv_dict_unref(pt->pt_dict); if (pt->pt_name != NULL) { - func_unref((char_u *)pt->pt_name); + func_unref(pt->pt_name); xfree(pt->pt_name); } else { func_ptr_unref(pt->pt_func); @@ -3929,8 +3934,6 @@ static void partial_free(partial_T *pt) xfree(pt); } -// TODO(ZyX-I): Move to eval/typval.h - /// Unreference a closure: decrement the reference count and free it when it /// becomes zero. void partial_unref(partial_T *pt) @@ -3973,7 +3976,7 @@ static int get_list_tv(char **arg, typval_T *rettv, int evaluate) } if (**arg != ']') { - semsg(_("E697: Missing end of List ']': %s"), *arg); + semsg(_(e_list_end), *arg); failret: if (evaluate) { tv_list_free(l); @@ -4138,10 +4141,23 @@ bool garbage_collect(bool testing) ABORTING(set_ref_dict)(buf->additional_data, copyID); // buffer callback functions - set_ref_in_callback(&buf->b_prompt_callback, copyID, NULL, NULL); - set_ref_in_callback(&buf->b_prompt_interrupt, copyID, NULL, NULL); + ABORTING(set_ref_in_callback)(&buf->b_prompt_callback, copyID, NULL, NULL); + ABORTING(set_ref_in_callback)(&buf->b_prompt_interrupt, copyID, NULL, NULL); + ABORTING(set_ref_in_callback)(&buf->b_cfu_cb, copyID, NULL, NULL); + ABORTING(set_ref_in_callback)(&buf->b_ofu_cb, copyID, NULL, NULL); + ABORTING(set_ref_in_callback)(&buf->b_tsrfu_cb, copyID, NULL, NULL); + ABORTING(set_ref_in_callback)(&buf->b_tfu_cb, copyID, NULL, NULL); } + // 'completefunc', 'omnifunc' and 'thesaurusfunc' callbacks + ABORTING(set_ref_in_insexpand_funcs)(copyID); + + // 'operatorfunc' callback + ABORTING(set_ref_in_opfunc)(copyID); + + // 'tagfunc' callback + ABORTING(set_ref_in_tagfunc)(copyID); + FOR_ALL_TAB_WINDOWS(tp, wp) { // window-local variables ABORTING(set_ref_in_item)(&wp->w_winvar.di_tv, copyID, NULL, NULL); @@ -4150,8 +4166,11 @@ bool garbage_collect(bool testing) ABORTING(set_ref_in_fmark)(wp->w_jumplist[i].fmark, copyID); } } - if (aucmd_win != NULL) { - ABORTING(set_ref_in_item)(&aucmd_win->w_winvar.di_tv, copyID, NULL, NULL); + // window-local variables in autocmd windows + for (int i = 0; i < AUCMD_WIN_COUNT; i++) { + if (aucmd_win[i].auc_win != NULL) { + ABORTING(set_ref_in_item)(&aucmd_win[i].auc_win->w_winvar.di_tv, copyID, NULL, NULL); + } } // registers (ShaDa additional data) @@ -4221,11 +4240,11 @@ bool garbage_collect(bool testing) // history items (ShaDa additional elements) if (p_hi) { - for (uint8_t i = 0; i < HIST_COUNT; i++) { + for (HistoryType i = 0; i < HIST_COUNT; i++) { const void *iter = NULL; do { histentry_T hist; - iter = hist_iter(iter, i, false, &hist); + iter = hist_iter(iter, (uint8_t)i, false, &hist); if (hist.hisstr != NULL) { ABORTING(set_ref_list)(hist.additional_elements, copyID); } @@ -4473,7 +4492,7 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, list_stack // A partial does not have a copyID, because it cannot contain itself. if (pt != NULL) { - abort = set_ref_in_func((char_u *)pt->pt_name, pt->pt_func, copyID); + abort = set_ref_in_func(pt->pt_name, pt->pt_func, copyID); if (pt->pt_dict != NULL) { typval_T dtv; @@ -4490,7 +4509,7 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, list_stack break; } case VAR_FUNC: - abort = set_ref_in_func((char_u *)tv->vval.v_string, NULL, copyID); + abort = set_ref_in_func(tv->vval.v_string, NULL, copyID); break; case VAR_UNKNOWN: case VAR_BOOL: @@ -4573,7 +4592,7 @@ static int get_literal_key(char **arg, typval_T *tv) /// "literal" is true for #{key: val} /// /// @return OK or FAIL. Returns NOTDONE for {expr}. -static int dict_get_tv(char **arg, typval_T *rettv, int evaluate, bool literal) +static int eval_dict(char **arg, typval_T *rettv, int evaluate, bool literal) { typval_T tv; char *key = NULL; @@ -4754,19 +4773,6 @@ void assert_error(garray_T *gap) (const char *)gap->ga_data, (ptrdiff_t)gap->ga_len); } -/// 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) -{ - int nr = (int)tv_get_number_chk(vp, NULL); - - if (nr >= LOWEST_WIN_ID) { - return win_id2wp((int)tv_get_number(vp)); - } - - return find_win_by_nr(vp, NULL); -} - /// Implementation of map() and filter(). void filter_map(typval_T *argvars, typval_T *rettv, int map) { @@ -4781,22 +4787,22 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) int save_did_emsg; int idx = 0; + // Always return the first argument, also on failure. + tv_copy(&argvars[0], rettv); + if (argvars[0].v_type == VAR_BLOB) { - tv_copy(&argvars[0], rettv); if ((b = argvars[0].vval.v_blob) == NULL) { return; } } else if (argvars[0].v_type == VAR_LIST) { - tv_copy(&argvars[0], rettv); if ((l = argvars[0].vval.v_list) == NULL || (!map - && var_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE))) { + && value_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE))) { return; } } else if (argvars[0].v_type == VAR_DICT) { - tv_copy(&argvars[0], rettv); if ((d = argvars[0].vval.v_dict) == NULL - || (!map && var_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) { + || (!map && value_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) { return; } } else { @@ -4835,7 +4841,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) dictitem_T *di = TV_DICT_HI2DI(hi); if (map - && (var_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE) + && (value_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE) || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) { break; } @@ -4875,7 +4881,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) } if (map) { if (tv.vval.v_number != val) { - tv_blob_set(b, i, (char_u)tv.vval.v_number); + tv_blob_set(b, i, (uint8_t)tv.vval.v_number); } } else if (rem) { char *const p = argvars[0].vval.v_blob->bv_ga.ga_data; @@ -4895,8 +4901,8 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) } for (listitem_T *li = tv_list_first(l); li != NULL;) { if (map - && var_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, - TV_TRANSLATE)) { + && value_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, + TV_TRANSLATE)) { break; } vimvars[VV_KEY].vv_nr = idx; @@ -4982,9 +4988,8 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref) if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref) { name = s; - trans_name = (char *)trans_function_name(&name, false, - TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD - | TFN_NO_DEREF, NULL, NULL); + trans_name = save_function_name(&name, false, + TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, NULL); if (*name != NUL) { s = NULL; } @@ -4997,26 +5002,19 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref) // Don't check an autoload name for existence here. } else if (trans_name != NULL && (is_funcref - ? find_func((char_u *)trans_name) == NULL + ? find_func(trans_name) == NULL : !translated_function_exists((const char *)trans_name))) { semsg(_("E700: Unknown function: %s"), s); } else { int dict_idx = 0; int arg_idx = 0; list_T *list = NULL; - if (STRNCMP(s, "s:", 2) == 0 || STRNCMP(s, "<SID>", 5) == 0) { - char sid_buf[25]; - int off = *s == 's' ? 2 : 5; - + if (strncmp(s, "s:", 2) == 0 || strncmp(s, "<SID>", 5) == 0) { // Expand s: and <SID> into <SNR>nr_, so that the function can // also be called from another script. Using trans_function_name() // would also work, but some plugins depend on the name being // printable text. - snprintf(sid_buf, sizeof(sid_buf), "<SNR>%" PRId64 "_", - (int64_t)current_sctx.sc_sid); - name = xmalloc(strlen(sid_buf) + strlen(s + off) + 1); - STRCPY(name, sid_buf); - STRCAT(name, s + off); + name = get_scriptlocal_funcname(s); } else { name = xstrdup(s); } @@ -5054,7 +5052,7 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref) if (tv_list_len(list) == 0) { arg_idx = 0; } else if (tv_list_len(list) > MAX_FUNC_ARGS) { - emsg_funcname((char *)e_toomanyarg, (char_u *)s); + emsg_funcname((char *)e_toomanyarg, s); xfree(name); goto theend; } @@ -5103,12 +5101,12 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref) func_ptr_ref(pt->pt_func); xfree(name); } else if (is_funcref) { - pt->pt_func = find_func((char_u *)trans_name); + pt->pt_func = find_func(trans_name); func_ptr_ref(pt->pt_func); xfree(name); } else { pt->pt_name = name; - func_ref((char_u *)name); + func_ref(name); } rettv->v_type = VAR_PARTIAL; @@ -5117,53 +5115,13 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref) // result is a VAR_FUNC rettv->v_type = VAR_FUNC; rettv->vval.v_string = name; - func_ref((char_u *)name); + func_ref(name); } } theend: xfree(trans_name); } -/// @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(); - - tv_dict_add_nr(dict, S_LEN("bufnr"), buf->b_fnum); - tv_dict_add_str(dict, S_LEN("name"), - buf->b_ffname != NULL ? (const char *)buf->b_ffname : ""); - tv_dict_add_nr(dict, S_LEN("lnum"), - buf == curbuf ? curwin->w_cursor.lnum : buflist_findlnum(buf)); - tv_dict_add_nr(dict, S_LEN("linecount"), buf->b_ml.ml_line_count); - tv_dict_add_nr(dict, S_LEN("loaded"), buf->b_ml.ml_mfp != NULL); - tv_dict_add_nr(dict, S_LEN("listed"), buf->b_p_bl); - tv_dict_add_nr(dict, S_LEN("changed"), bufIsChanged(buf)); - tv_dict_add_nr(dict, S_LEN("changedtick"), buf_get_changedtick(buf)); - tv_dict_add_nr(dict, S_LEN("hidden"), - buf->b_ml.ml_mfp != NULL && buf->b_nwindows == 0); - - // Get a reference to buffer variables - tv_dict_add_dict(dict, S_LEN("variables"), buf->b_vars); - - // List of windows displaying this buffer - list_T *const windows = tv_list_alloc(kListLenMayKnow); - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer == buf) { - tv_list_append_number(windows, (varnumber_T)wp->handle); - } - } - tv_dict_add_list(dict, S_LEN("windows"), windows); - - if (buf->b_signlist != NULL) { - // List of signs placed in this buffer - tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf)); - } - - tv_dict_add_nr(dict, S_LEN("lastused"), buf->b_last_used); - - return dict; -} - /// Get the line number from VimL object /// /// @note Unlike tv_get_lnum(), this one supports only "$" special string. @@ -5180,121 +5138,13 @@ linenr_T tv_get_lnum_buf(const typval_T *const tv, const buf_T *const buf) if (tv->v_type == VAR_STRING && tv->vval.v_string != NULL && tv->vval.v_string[0] == '$' + && tv->vval.v_string[1] == NUL && buf != NULL) { return buf->b_ml.ml_line_count; } return (linenr_T)tv_get_number_chk(tv, NULL); } -/// @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(); - - tv_dict_add_nr(dict, S_LEN("tabnr"), tp_idx); - - list_T *const l = tv_list_alloc(kListLenMayKnow); - FOR_ALL_WINDOWS_IN_TAB(wp, tp) { - tv_list_append_number(l, (varnumber_T)wp->handle); - } - tv_dict_add_list(dict, S_LEN("windows"), l); - - // Make a reference to tabpage variables - tv_dict_add_dict(dict, S_LEN("variables"), tp->tp_vars); - - return dict; -} - -/// @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(); - - // make sure w_botline is valid - validate_botline(wp); - - tv_dict_add_nr(dict, S_LEN("tabnr"), tpnr); - tv_dict_add_nr(dict, S_LEN("winnr"), winnr); - tv_dict_add_nr(dict, S_LEN("winid"), wp->handle); - tv_dict_add_nr(dict, S_LEN("height"), wp->w_height_inner); - tv_dict_add_nr(dict, S_LEN("winrow"), wp->w_winrow + 1); - tv_dict_add_nr(dict, S_LEN("topline"), wp->w_topline); - tv_dict_add_nr(dict, S_LEN("botline"), wp->w_botline - 1); - tv_dict_add_nr(dict, S_LEN("winbar"), wp->w_winbar_height); - tv_dict_add_nr(dict, S_LEN("width"), wp->w_width_inner); - 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"), - (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL)); - - // Add a reference to window variables - tv_dict_add_dict(dict, S_LEN("variables"), wp->w_vars); - - return dict; -} - -/// Find window specified by "vp" in tabpage "tp". -/// -/// @param tp NULL for current tab page -win_T *find_win_by_nr(typval_T *vp, tabpage_T *tp) -{ - int nr = (int)tv_get_number_chk(vp, NULL); - - if (nr < 0) { - return NULL; - } - - if (nr == 0) { - return curwin; - } - - // This method accepts NULL as an alias for curtab. - if (tp == NULL) { - tp = curtab; - } - - FOR_ALL_WINDOWS_IN_TAB(wp, tp) { - if (nr >= LOWEST_WIN_ID) { - if (wp->handle == nr) { - return wp; - } - } else if (--nr <= 0) { - return wp; - } - } - return NULL; -} - -/// Find window specified by "wvp" in tabpage "tvp". -win_T *find_tabwin(typval_T *wvp, typval_T *tvp) -{ - win_T *wp = NULL; - tabpage_T *tp = NULL; - - if (wvp->v_type != VAR_UNKNOWN) { - if (tvp->v_type != VAR_UNKNOWN) { - long n = tv_get_number(tvp); - if (n >= 0) { - tp = find_tabpage((int)n); - } - } else { - tp = curtab; - } - - if (tp != NULL) { - wp = find_win_by_nr(wvp, tp); - } - } else { - wp = curwin; - } - - return wp; -} - /// 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 @@ -5516,126 +5366,6 @@ void screenchar_adjust(ScreenGrid **grid, int *row, int *col) *col -= (*grid)->comp_col; } -/// Set line or list of lines in buffer "buf". -void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, const typval_T *lines, - typval_T *rettv) - FUNC_ATTR_NONNULL_ARG(4, 5) -{ - linenr_T lnum = lnum_arg + (append ? 1 : 0); - long added = 0; - buf_T *curbuf_save = NULL; - win_T *curwin_save = NULL; - const bool is_curbuf = buf == curbuf; - const bool save_VIsual_active = VIsual_active; - - // When using the current buffer ml_mfp will be set if needed. Useful when - // setline() is used on startup. For other buffers the buffer must be - // loaded. - if (buf == NULL || (!is_curbuf && buf->b_ml.ml_mfp == NULL) || lnum < 1) { - rettv->vval.v_number = 1; // FAIL - return; - } - - if (!is_curbuf) { - VIsual_active = false; - curbuf_save = curbuf; - curwin_save = curwin; - curbuf = buf; - find_win_for_curbuf(); - } - - linenr_T append_lnum; - if (append) { - // appendbufline() uses the line number below which we insert - append_lnum = lnum - 1; - } else { - // setbufline() uses the line number above which we insert, we only - // append if it's below the last line - append_lnum = curbuf->b_ml.ml_line_count; - } - - list_T *l = NULL; - listitem_T *li = NULL; - const char *line = NULL; - if (lines->v_type == VAR_LIST) { - l = lines->vval.v_list; - li = tv_list_first(l); - } else { - line = tv_get_string_chk(lines); - } - - // Default result is zero == OK. - for (;;) { - if (lines->v_type == VAR_LIST) { - // List argument, get next string. - if (li == NULL) { - break; - } - line = tv_get_string_chk(TV_LIST_ITEM_TV(li)); - li = TV_LIST_ITEM_NEXT(l, li); - } - - rettv->vval.v_number = 1; // FAIL - if (line == NULL || lnum > curbuf->b_ml.ml_line_count + 1) { - break; - } - - // When coming here from Insert mode, sync undo, so that this can be - // undone separately from what was previously inserted. - if (u_sync_once == 2) { - u_sync_once = 1; // notify that u_sync() was called - u_sync(true); - } - - if (!append && lnum <= curbuf->b_ml.ml_line_count) { - // Existing line, replace it. - int old_len = (int)strlen(ml_get(lnum)); - if (u_savesub(lnum) == OK - && ml_replace(lnum, (char *)line, true) == OK) { - inserted_bytes(lnum, 0, old_len, (int)strlen(line)); - if (is_curbuf && lnum == curwin->w_cursor.lnum) { - check_cursor_col(); - } - rettv->vval.v_number = 0; // OK - } - } else if (added > 0 || u_save(lnum - 1, lnum) == OK) { - // append the line. - added++; - if (ml_append(lnum - 1, (char *)line, 0, false) == OK) { - rettv->vval.v_number = 0; // OK - } - } - - if (l == NULL) { // only one string argument - break; - } - lnum++; - } - - if (added > 0) { - appended_lines_mark(append_lnum, added); - - // Only adjust the cursor for buffers other than the current, unless it - // is the current window. For curbuf and other windows it has been done - // in mark_adjust_internal(). - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer == buf - && (wp->w_buffer != curbuf || wp == curwin) - && wp->w_cursor.lnum > append_lnum) { - wp->w_cursor.lnum += (linenr_T)added; - } - } - check_cursor_col(); - update_topline(curwin); - } - - if (!is_curbuf) { - curbuf = curbuf_save; - curwin = curwin_save; - VIsual_active = save_VIsual_active; - } -} - /// "stdpath()" helper for list results void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) FUNC_ATTR_NONNULL_ALL @@ -5688,7 +5418,7 @@ void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, bool retlist // get input to the shell command (if any), and its length ptrdiff_t input_len; - char *input = save_tv_as_string(&argvars[1], &input_len, false); + char *input = save_tv_as_string(&argvars[1], &input_len, false, false); if (input_len < 0) { assert(input == NULL); return; @@ -5772,7 +5502,8 @@ void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, bool retlist } } -bool callback_from_typval(Callback *const callback, typval_T *const arg) +/// Get a callback from "arg". It can be a Funcref or a function name. +bool callback_from_typval(Callback *const callback, const typval_T *const arg) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { int r = OK; @@ -5793,13 +5524,19 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg) callback->type = kCallbackNone; callback->data.funcref = NULL; } else { - func_ref((char_u *)name); - callback->data.funcref = xstrdup(name); + callback->data.funcref = NULL; + if (arg->v_type == VAR_STRING) { + callback->data.funcref = get_scriptlocal_funcname(name); + } + if (callback->data.funcref == NULL) { + callback->data.funcref = xstrdup(name); + } + func_ref(callback->data.funcref); callback->type = kCallbackFuncref; } } else if (nlua_is_table_from_lua(arg)) { // TODO(tjdvries): UnifiedCallback - char *name = (char *)nlua_register_table_as_callable(arg); + char *name = nlua_register_table_as_callable(arg); if (name != NULL) { callback->data.funcref = xstrdup(name); @@ -5822,6 +5559,7 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg) return true; } +/// @return whether the callback could be called. bool callback_call(Callback *const callback, const int argcount_in, typval_T *const argvars_in, typval_T *const rettv) FUNC_ATTR_NONNULL_ALL @@ -5871,8 +5609,8 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co return call_func(name, -1, rettv, argcount_in, argvars_in, &funcexe); } -static bool set_ref_in_callback(Callback *callback, int copyID, ht_stack_T **ht_stack, - list_stack_T **list_stack) +bool set_ref_in_callback(Callback *callback, int copyID, ht_stack_T **ht_stack, + list_stack_T **list_stack) { typval_T tv; switch (callback->type) { @@ -6175,9 +5913,10 @@ bool read_blob(FILE *const fd, blob_T *const blob) /// @param[in] tv Value to store as a string. /// @param[out] len Length of the resulting string or -1 on error. /// @param[in] endnl If true, the output will end in a newline (if a list). +/// @param[in] crlf If true, list items will be joined with CRLF (if a list). /// @returns an allocated string if `tv` represents a VimL string, list, or /// number; NULL otherwise. -char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) +char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl, bool crlf) FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { *len = 0; @@ -6234,20 +5973,23 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) // Pre-calculate the resulting length. list_T *list = tv->vval.v_list; TV_LIST_ITER_CONST(list, li, { - *len += (ptrdiff_t)strlen(tv_get_string(TV_LIST_ITEM_TV(li))) + 1; + *len += (ptrdiff_t)strlen(tv_get_string(TV_LIST_ITEM_TV(li))) + (crlf ? 2 : 1); }); if (*len == 0) { return NULL; } - char *ret = xmalloc((size_t)(*len) + endnl); + char *ret = xmalloc((size_t)(*len) + (endnl ? (crlf ? 2 : 1) : 0)); char *end = ret; TV_LIST_ITER_CONST(list, li, { for (const char *s = tv_get_string(TV_LIST_ITEM_TV(li)); *s != NUL; s++) { *end++ = (*s == '\n') ? NUL : *s; } if (endnl || TV_LIST_ITEM_NEXT(list, li) != NULL) { + if (crlf) { + *end++ = '\r'; + } *end++ = '\n'; } }); @@ -6358,7 +6100,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret } int len; if (charcol) { - len = mb_charlen((char_u *)ml_get(pos.lnum)); + len = mb_charlen(ml_get(pos.lnum)); } else { len = (int)strlen(ml_get(pos.lnum)); } @@ -6444,7 +6186,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret } else { pos.lnum = curwin->w_cursor.lnum; if (charcol) { - pos.col = (colnr_T)mb_charlen((char_u *)get_cursor_line_ptr()); + pos.col = (colnr_T)mb_charlen(get_cursor_line_ptr()); } else { pos.col = (colnr_T)strlen(get_cursor_line_ptr()); } @@ -6535,7 +6277,7 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool c int get_env_len(const char **arg) { const char *p; - for (p = *arg; vim_isIDc(*p); p++) {} + for (p = *arg; vim_isIDc((uint8_t)(*p)); p++) {} if (p == *arg) { // No name found. return 0; } @@ -6562,7 +6304,7 @@ int get_id_len(const char **const arg) // slice "[n:]". Also "xx:" is not a namespace. len = (int)(p - *arg); if (len > 1 - || (len == 1 && vim_strchr(namespace_char, **arg) == NULL)) { + || (len == 1 && vim_strchr(namespace_char, (uint8_t)(**arg)) == NULL)) { break; } } @@ -6665,7 +6407,8 @@ const char *find_name_end(const char *arg, const char **expr_start, const char * for (p = arg; *p != NUL && (eval_isnamec(*p) || *p == '{' - || ((flags & FNE_INCL_BR) && (*p == '[' || *p == '.')) + || ((flags & FNE_INCL_BR) && (*p == '[' + || (*p == '.' && eval_isdictc(p[1])))) || mb_nest != 0 || br_nest != 0); MB_PTR_ADV(p)) { if (*p == '\'') { @@ -6689,7 +6432,7 @@ const char *find_name_end(const char *arg, const char **expr_start, const char * // slice "[n:]". Also "xx:" is not a namespace. But {ns}: is. len = (int)(p - arg); if ((len > 1 && p[-1] != '}') - || (len == 1 && vim_strchr(namespace_char, *arg) == NULL)) { + || (len == 1 && vim_strchr(namespace_char, (uint8_t)(*arg)) == NULL)) { break; } } @@ -6778,18 +6521,25 @@ static char *make_expanded_name(const char *in_start, char *expr_start, char *ex /// @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) +bool 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 '}'). -int eval_isnamec1(int c) +bool eval_isnamec1(int c) { return ASCII_ISALPHA(c) || c == '_'; } +/// @return true if character "c" can be used as the first character of a +/// dictionary key. +bool eval_isdictc(int c) +{ + return ASCII_ISALNUM(c) || c == '_'; +} + /// Get typval_T v: variable value. typval_T *get_vim_var_tv(int idx) { @@ -6922,12 +6672,13 @@ void set_vim_var_dict(const VimVarIndex idx, dict_T *const val) tv_clear(&vimvars[idx].vv_di.di_tv); vimvars[idx].vv_type = VAR_DICT; vimvars[idx].vv_dict = val; - - if (val != NULL) { - val->dv_refcount++; - // Set readonly - tv_dict_set_keys_readonly(val); + if (val == NULL) { + return; } + + val->dv_refcount++; + // Set readonly + tv_dict_set_keys_readonly(val); } /// Set the v:argv list. @@ -7021,6 +6772,9 @@ char *set_cmdarg(exarg_T *eap, char *oldarg) if (eap->bad_char != 0) { len += 7 + 4; // " ++bad=" + "keep" or "drop" } + if (eap->mkdir_p != 0) { + len += 4; + } const size_t newval_len = len + 1; char *newval = xmalloc(newval_len); @@ -7054,6 +6808,11 @@ char *set_cmdarg(exarg_T *eap, char *oldarg) snprintf(newval + strlen(newval), newval_len, " ++bad=%c", eap->bad_char); } + + if (eap->mkdir_p) { + snprintf(newval, newval_len, " ++p"); + } + vimvars[VV_CMDARG].vv_str = newval; return oldval; } @@ -7108,9 +6867,8 @@ int check_luafunc_name(const char *const str, const bool paren) const char *const p = skip_luafunc_name(str); if (*p != (paren ? '(' : NUL)) { return 0; - } else { - return (int)(p - str); } + return (int)(p - str); } /// Handle: @@ -7652,7 +7410,7 @@ void ex_echo(exarg_T *eap) /// ":echohl {name}". void ex_echohl(exarg_T *eap) { - echo_attr = syn_name2attr((char_u *)eap->arg); + echo_attr = syn_name2attr(eap->arg); } /// ":execute expr1 ..." execute the result of an expression. @@ -7740,19 +7498,19 @@ void ex_execute(exarg_T *eap) /// /// @return NULL when no option name found. Otherwise pointer to the char /// after the option name. -const char *find_option_end(const char **const arg, int *const opt_flags) +const char *find_option_end(const char **const arg, int *const scope) { const char *p = *arg; p++; if (*p == 'g' && p[1] == ':') { - *opt_flags = OPT_GLOBAL; + *scope = OPT_GLOBAL; p += 2; } else if (*p == 'l' && p[1] == ':') { - *opt_flags = OPT_LOCAL; + *scope = OPT_LOCAL; p += 2; } else { - *opt_flags = 0; + *scope = 0; } if (!ASCII_ISALPHA(*p)) { @@ -7856,9 +7614,8 @@ static var_flavour_T var_flavour(char *varname) } } return VAR_FLAVOUR_SHADA; - } else { - return VAR_FLAVOUR_DEFAULT; } + return VAR_FLAVOUR_DEFAULT; } /// Iterate over global variables @@ -7884,7 +7641,7 @@ const void *var_shada_iter(const void *const iter, const char **const name, typv hi = globvarht.ht_array; while ((size_t)(hi - hifirst) < hinum && (HASHITEM_EMPTY(hi) - || !(var_flavour((char *)hi->hi_key) & flavour))) { + || !(var_flavour(hi->hi_key) & flavour))) { hi++; } if ((size_t)(hi - hifirst) == hinum) { @@ -7896,7 +7653,7 @@ const void *var_shada_iter(const void *const iter, const char **const name, typv *name = (char *)TV_DICT_HI2DI(hi)->di_key; tv_copy(&TV_DICT_HI2DI(hi)->di_tv, rettv); while ((size_t)(++hi - hifirst) < hinum) { - if (!HASHITEM_EMPTY(hi) && (var_flavour((char *)hi->hi_key) & flavour)) { + if (!HASHITEM_EMPTY(hi) && (var_flavour(hi->hi_key) & flavour)) { return hi; } } @@ -7920,8 +7677,7 @@ int store_session_globals(FILE *fd) && var_flavour((char *)this_var->di_key) == VAR_FLAVOUR_SESSION) { // Escape special characters with a backslash. Turn a LF and // CR into \n and \r. - char *const p = (char *)vim_strsave_escaped((const char_u *)tv_get_string(&this_var->di_tv), - (const char_u *)"\\\"\n\r"); + char *const p = (char *)vim_strsave_escaped(tv_get_string(&this_var->di_tv), "\\\"\n\r"); for (char *t = p; *t != NUL; t++) { if (*t == '\n') { *t = 'n'; @@ -8062,8 +7818,8 @@ repeat: } // FullName_save() is slow, don't use it when not needed. - if (*p != NUL || !vim_isAbsName((char_u *)(*fnamep))) { - *fnamep = FullName_save((*fnamep), *p != NUL); + if (*p != NUL || !vim_isAbsName(*fnamep)) { + *fnamep = FullName_save(*fnamep, *p != NUL); xfree(*bufp); // free any allocated file name *bufp = *fnamep; if (*fnamep == NULL) { @@ -8108,7 +7864,7 @@ repeat: if (p != NULL) { if (c == '.') { - os_dirname((char_u *)dirname, MAXPATHL); + os_dirname(dirname, MAXPATHL); if (has_homerelative) { s = xstrdup(dirname); home_replace(NULL, s, dirname, MAXPATHL, true); @@ -8252,7 +8008,7 @@ repeat: s++; } - int sep = (char_u)(*s++); + int sep = (uint8_t)(*s++); if (sep) { // find end of pattern p = vim_strchr(s, sep); @@ -8285,11 +8041,11 @@ repeat: if (src[*usedlen] == ':' && src[*usedlen + 1] == 'S') { // vim_strsave_shellescape() needs a NUL terminated string. - c = (char_u)(*fnamep)[*fnamelen]; + c = (uint8_t)(*fnamep)[*fnamelen]; if (c != NUL) { (*fnamep)[*fnamelen] = NUL; } - p = (char *)vim_strsave_shellescape((char_u *)(*fnamep), false, false); + p = vim_strsave_shellescape(*fnamep, false, false); if (c != NUL) { (*fnamep)[*fnamelen] = (char)c; } @@ -8307,7 +8063,7 @@ repeat: /// "flags" can be "g" to do a global substitute. /// /// @return an allocated string, NULL for error. -char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, char *flags) +char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, const char *flags) { int sublen; regmatch_T regmatch; @@ -8327,7 +8083,7 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, char *flags if (regmatch.regprog != NULL) { char *tail = str; char *end = str + strlen(str); - while (vim_regexec_nl(®match, (char_u *)str, (colnr_T)(tail - str))) { + while (vim_regexec_nl(®match, str, (colnr_T)(tail - str))) { // Skip empty match except for first match. if (regmatch.startp[0] == regmatch.endp[0]) { if (zero_width == regmatch.startp[0]) { @@ -8346,7 +8102,7 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, char *flags // - The text up to where the match is. // - The substituted text. // - The text after the match. - sublen = vim_regsub(®match, (char_u *)sub, expr, (char_u *)tail, 0, REGSUB_MAGIC); + sublen = vim_regsub(®match, sub, expr, tail, 0, REGSUB_MAGIC); ga_grow(&ga, (int)((end - tail) + sublen - (regmatch.endp[0] - regmatch.startp[0]))); @@ -8354,8 +8110,8 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, char *flags int i = (int)(regmatch.startp[0] - tail); memmove((char_u *)ga.ga_data + ga.ga_len, tail, (size_t)i); // add the substituted text - (void)vim_regsub(®match, (char_u *)sub, expr, - (char_u *)ga.ga_data + ga.ga_len + i, sublen, + (void)vim_regsub(®match, sub, expr, + (char *)ga.ga_data + ga.ga_len + i, sublen, REGSUB_COPY | REGSUB_MAGIC); ga.ga_len += i + sublen - 1; tail = regmatch.endp[0]; @@ -8543,7 +8299,7 @@ bool eval_has_provider(const char *feat) if (get_var_tv(buf, len, &tv, NULL, false, true) == FAIL) { // Show a hint if Call() is defined but g:loaded_xx_provider is missing. snprintf(buf, sizeof(buf), "provider#%s#Call", name); - if (!!find_func((char_u *)buf) && p_lpl) { + if (!!find_func(buf) && p_lpl) { semsg("provider: %s: missing required variable g:loaded_%s_provider", name, name); } @@ -8558,7 +8314,7 @@ bool eval_has_provider(const char *feat) if (ok) { // Call() must be defined if provider claims to be working. snprintf(buf, sizeof(buf), "provider#%s#Call", name); - if (!find_func((char_u *)buf)) { + if (!find_func(buf)) { semsg("provider: %s: g:loaded_%s_provider=2 but %s is not defined", name, name, buf); ok = false; @@ -8581,10 +8337,10 @@ void eval_fmt_source_name_line(char *buf, size_t bufsize) /// ":checkhealth [plugins]" void ex_checkhealth(exarg_T *eap) { - bool found = !!find_func((char_u *)"health#check"); + bool found = !!find_func("health#check"); if (!found && script_autoload("health#check", sizeof("health#check") - 1, false)) { - found = !!find_func((char_u *)"health#check"); + found = !!find_func("health#check"); } if (!found) { const char *vimruntime_env = os_getenv("VIMRUNTIME"); @@ -8626,7 +8382,7 @@ void invoke_prompt_callback(void) return; } char *text = ml_get(lnum); - char *prompt = (char *)prompt_text(); + char *prompt = prompt_text(); if (strlen(text) >= strlen(prompt)) { text += strlen(prompt); } @@ -8651,9 +8407,9 @@ bool invoke_prompt_interrupt(void) argv[0].v_type = VAR_UNKNOWN; got_int = false; // don't skip executing commands - callback_call(&curbuf->b_prompt_interrupt, 0, argv, &rettv); + int ret = callback_call(&curbuf->b_prompt_interrupt, 0, argv, &rettv); tv_clear(&rettv); - return true; + return ret != FAIL; } /// Compare "typ1" and "typ2". Put the result in "typ1". @@ -8851,7 +8607,7 @@ int typval_compare(typval_T *typ1, typval_T *typ2, exprtype_T type, bool ic) case EXPR_MATCH: case EXPR_NOMATCH: - n1 = pattern_match((char *)s2, (char *)s1, ic); + n1 = pattern_match(s2, s1, ic); if (type == EXPR_NOMATCH) { n1 = !n1; } @@ -8866,10 +8622,16 @@ int typval_compare(typval_T *typ1, typval_T *typ2, exprtype_T type, bool ic) return OK; } -char *typval_tostring(typval_T *arg) +/// Convert any type to a string, never give an error. +/// When "quotes" is true add quotes to a string. +/// Returns an allocated string. +char *typval_tostring(typval_T *arg, bool quotes) { if (arg == NULL) { return xstrdup("(does not exist)"); } + if (!quotes && arg->v_type == VAR_STRING) { + return xstrdup(arg->vval.v_string == NULL ? "" : arg->vval.v_string); + } return encode_tv2string(arg, NULL); } diff --git a/src/nvim/eval.h b/src/nvim/eval.h index afebdf5acb..86bc76e793 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -1,12 +1,17 @@ #ifndef NVIM_EVAL_H #define NVIM_EVAL_H +#include <stdbool.h> +#include <stddef.h> + #include "nvim/buffer_defs.h" #include "nvim/channel.h" -#include "nvim/event/time.h" // For TimeWatcher -#include "nvim/ex_cmds_defs.h" // For exarg_T -#include "nvim/os/fileio.h" // For FileDescriptor -#include "nvim/os/stdpaths_defs.h" // For XDGVarType +#include "nvim/eval/typval_defs.h" +#include "nvim/event/time.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/hashtab.h" +#include "nvim/os/fileio.h" +#include "nvim/os/stdpaths_defs.h" #define COPYID_INC 2 #define COPYID_MASK (~0x1) @@ -160,6 +165,8 @@ typedef enum { VV__NULL_DICT, // Dictionary with NULL value. For test purposes only. VV__NULL_BLOB, // Blob with NULL value. For test purposes only. VV_LUA, + VV_RELNUM, + VV_VIRTNUM, } VimVarIndex; /// All recognized msgpack types diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 837fef23f4..c17a44b990 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -38,7 +38,7 @@ return { assert_equal={args={2, 3}, base=2}, assert_equalfile={args={2, 3}, base=1}, assert_exception={args={1, 2}}, - assert_fails={args={1, 3}, base=1}, + assert_fails={args={1, 5}, base=1}, assert_false={args={1, 2}, base=1}, assert_inrange={args={3, 4}, base=3}, assert_match={args={2, 3}, base=2}, @@ -73,12 +73,12 @@ return { chansend={args=2}, char2nr={args={1, 2}, base=1}, charclass={args=1, base=1}, - charcol={args=1, base=1}, + charcol={args={1, 2}, base=1}, charidx={args={2, 3}, base=1}, chdir={args=1, base=1}, cindent={args=1, base=1}, clearmatches={args={0, 1}, base=1}, - col={args=1, base=1}, + col={args={1, 2}, base=1}, complete={args=2, base=2}, complete_add={args=1, base=1}, complete_check={}, @@ -88,7 +88,6 @@ return { cos={args=1, base=1, float_func="cos"}, cosh={args=1, base=1, float_func="cosh"}, count={args={2, 4}, base=1}, - cscope_connection={args={0, 3}}, ctxget={args={0, 1}}, ctxpop={}, ctxpush={args={0, 1}}, @@ -119,7 +118,7 @@ return { exists={args=1, base=1}, exp={args=1, base=1, float_func="exp"}, expand={args={1, 3}, base=1}, - expandcmd={args=1, base=1}, + expandcmd={args={1, 2}, base=1}, extend={args={2, 3}, base=1}, feedkeys={args={1, 2}, base=1}, file_readable={args=1, base=1, func='f_filereadable'}, -- obsolete @@ -147,7 +146,9 @@ return { get={args={2, 3}, base=1}, getbufinfo={args={0, 1}, base=1}, getbufline={args={2, 3}, base=1}, + getbufoneline={args=2, base=1}, getbufvar={args={2, 3}, base=1}, + getcellwidths={}, getchangelist={args={0, 1}, base=1}, getchar={args={0, 1}}, getcharmod={}, @@ -186,6 +187,7 @@ return { gettabvar={args={2, 3}, base=1}, gettabwinvar={args={3, 4}, base=1}, gettagstack={args={0, 1}, base=1}, + gettext={args=1, base=1}, getwininfo={args={0, 1}, base=1}, getwinpos={args={0, 1}, base=1}, getwinposx={}, @@ -291,6 +293,7 @@ return { perleval={args=1, base=1}, rand={args={0, 1}, base=1}, range={args={1, 3}, base=1}, + readblob={args=1, base=1}, readdir={args={1, 2}, base=1}, readfile={args={1, 3}, base=1}, reduce={args={2, 3}, base=1}, @@ -375,6 +378,7 @@ return { str2float={args=1, base=1}, str2list={args={1, 2}, base=1}, str2nr={args={1, 3}, base=1}, + strcharlen={args=1, base=1}, strcharpart={args={2, 3}, base=1}, strchars={args={1, 2}, base=1}, strdisplaywidth={args={1, 2}, base=1}, diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c new file mode 100644 index 0000000000..2f37d1ba2e --- /dev/null +++ b/src/nvim/eval/buffer.c @@ -0,0 +1,734 @@ +// 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 + +// eval/buffer.c: Buffer related builtin functions + +#include <stdbool.h> +#include <string.h> + +#include "nvim/ascii.h" +#include "nvim/autocmd.h" +#include "nvim/buffer.h" +#include "nvim/buffer_defs.h" +#include "nvim/change.h" +#include "nvim/cursor.h" +#include "nvim/eval.h" +#include "nvim/eval/buffer.h" +#include "nvim/eval/funcs.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/globals.h" +#include "nvim/macros.h" +#include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/move.h" +#include "nvim/path.h" +#include "nvim/pos.h" +#include "nvim/sign.h" +#include "nvim/types.h" +#include "nvim/undo.h" +#include "nvim/vim.h" + +typedef struct { + win_T *cob_curwin_save; + aco_save_T cob_aco; + int cob_using_aco; + int cob_save_VIsual_active; +} cob_T; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/buffer.c.generated.h" +#endif + +/// Find a buffer by number or exact name. +buf_T *find_buffer(typval_T *avar) +{ + buf_T *buf = NULL; + + if (avar->v_type == VAR_NUMBER) { + buf = buflist_findnr((int)avar->vval.v_number); + } else if (avar->v_type == VAR_STRING && avar->vval.v_string != NULL) { + buf = buflist_findname_exp(avar->vval.v_string); + if (buf == NULL) { + // No full path name match, try a match with a URL or a "nofile" + // buffer, these don't use the full path. + FOR_ALL_BUFFERS(bp) { + if (bp->b_fname != NULL + && (path_with_url(bp->b_fname) || bt_nofilename(bp)) + && strcmp(bp->b_fname, avar->vval.v_string) == 0) { + buf = bp; + break; + } + } + } + } + return buf; +} + +/// If there is a window for "curbuf", make it the current window. +static void find_win_for_curbuf(void) +{ + wininfo_T *wip; + + // The b_wininfo list should have the windows that recently contained the + // buffer, going over this is faster than going over all the windows. + // Do check the buffer is still there. + FOR_ALL_BUF_WININFO(curbuf, wip) { + if (wip->wi_win != NULL && wip->wi_win->w_buffer == curbuf) { + curwin = wip->wi_win; + break; + } + } +} + +/// Used before making a change in "buf", which is not the current one: Make +/// "buf" the current buffer and find a window for this buffer, so that side +/// effects are done correctly (e.g., adjusting marks). +/// +/// Information is saved in "cob" and MUST be restored by calling +/// change_other_buffer_restore(). +static void change_other_buffer_prepare(cob_T *cob, buf_T *buf) +{ + CLEAR_POINTER(cob); + + // Set "curbuf" to the buffer being changed. Then make sure there is a + // window for it to handle any side effects. + cob->cob_save_VIsual_active = VIsual_active; + VIsual_active = false; + cob->cob_curwin_save = curwin; + curbuf = buf; + find_win_for_curbuf(); // simplest: find existing window for "buf" + + if (curwin->w_buffer != buf) { + // No existing window for this buffer. It is dangerous to have + // curwin->w_buffer differ from "curbuf", use the autocmd window. + curbuf = curwin->w_buffer; + aucmd_prepbuf(&cob->cob_aco, buf); + cob->cob_using_aco = true; + } +} + +static void change_other_buffer_restore(cob_T *cob) +{ + if (cob->cob_using_aco) { + aucmd_restbuf(&cob->cob_aco); + } else { + curwin = cob->cob_curwin_save; + curbuf = curwin->w_buffer; + } + VIsual_active = cob->cob_save_VIsual_active; +} + +/// Set line or list of lines in buffer "buf" to "lines". +/// Any type is allowed and converted to a string. +static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, typval_T *lines, + typval_T *rettv) + FUNC_ATTR_NONNULL_ARG(4, 5) +{ + linenr_T lnum = lnum_arg + (append ? 1 : 0); + long added = 0; + + // When using the current buffer ml_mfp will be set if needed. Useful when + // setline() is used on startup. For other buffers the buffer must be + // loaded. + const bool is_curbuf = buf == curbuf; + if (buf == NULL || (!is_curbuf && buf->b_ml.ml_mfp == NULL) || lnum < 1) { + rettv->vval.v_number = 1; // FAIL + return; + } + + // After this don't use "return", goto "cleanup"! + cob_T cob; + if (!is_curbuf) { + // set "curbuf" to "buf" and find a window for this buffer + change_other_buffer_prepare(&cob, buf); + } + + linenr_T append_lnum; + if (append) { + // appendbufline() uses the line number below which we insert + append_lnum = lnum - 1; + } else { + // setbufline() uses the line number above which we insert, we only + // append if it's below the last line + append_lnum = curbuf->b_ml.ml_line_count; + } + + list_T *l = NULL; + listitem_T *li = NULL; + char *line = NULL; + if (lines->v_type == VAR_LIST) { + l = lines->vval.v_list; + if (l == NULL || tv_list_len(l) == 0) { + // set proper return code + if (lnum > curbuf->b_ml.ml_line_count) { + rettv->vval.v_number = 1; // FAIL + } + goto cleanup; + } + li = tv_list_first(l); + } else { + line = typval_tostring(lines, false); + } + + // Default result is zero == OK. + for (;;) { + if (lines->v_type == VAR_LIST) { + // List argument, get next string. + if (li == NULL) { + break; + } + xfree(line); + line = typval_tostring(TV_LIST_ITEM_TV(li), false); + li = TV_LIST_ITEM_NEXT(l, li); + } + + rettv->vval.v_number = 1; // FAIL + if (line == NULL || lnum > curbuf->b_ml.ml_line_count + 1) { + break; + } + + // When coming here from Insert mode, sync undo, so that this can be + // undone separately from what was previously inserted. + if (u_sync_once == 2) { + u_sync_once = 1; // notify that u_sync() was called + u_sync(true); + } + + if (!append && lnum <= curbuf->b_ml.ml_line_count) { + // Existing line, replace it. + int old_len = (int)strlen(ml_get(lnum)); + if (u_savesub(lnum) == OK + && ml_replace(lnum, line, true) == OK) { + inserted_bytes(lnum, 0, old_len, (int)strlen(line)); + if (is_curbuf && lnum == curwin->w_cursor.lnum) { + check_cursor_col(); + } + rettv->vval.v_number = 0; // OK + } + } else if (added > 0 || u_save(lnum - 1, lnum) == OK) { + // append the line. + added++; + if (ml_append(lnum - 1, line, 0, false) == OK) { + rettv->vval.v_number = 0; // OK + } + } + + if (l == NULL) { // only one string argument + break; + } + lnum++; + } + xfree(line); + + if (added > 0) { + appended_lines_mark(append_lnum, added); + + // Only adjust the cursor for buffers other than the current, unless it + // is the current window. For curbuf and other windows it has been done + // in mark_adjust_internal(). + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer == buf + && (wp->w_buffer != curbuf || wp == curwin) + && wp->w_cursor.lnum > append_lnum) { + wp->w_cursor.lnum += (linenr_T)added; + } + } + check_cursor_col(); + update_topline(curwin); + } + +cleanup: + if (!is_curbuf) { + change_other_buffer_restore(&cob); + } +} + +/// "append(lnum, string/list)" function +void f_append(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const int did_emsg_before = did_emsg; + const linenr_T lnum = tv_get_lnum(&argvars[0]); + if (did_emsg == did_emsg_before) { + set_buffer_lines(curbuf, lnum, true, &argvars[1], rettv); + } +} + +/// Set or append lines to a buffer. +static void buf_set_append_line(typval_T *argvars, typval_T *rettv, bool append) +{ + const int did_emsg_before = did_emsg; + buf_T *const buf = tv_get_buf(&argvars[0], false); + if (buf == NULL) { + rettv->vval.v_number = 1; // FAIL + } else { + const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); + if (did_emsg == did_emsg_before) { + set_buffer_lines(buf, lnum, append, &argvars[2], rettv); + } + } +} + +/// "appendbufline(buf, lnum, string/list)" function +void f_appendbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + buf_set_append_line(argvars, rettv, true); +} + +/// "bufadd(expr)" function +void f_bufadd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + char *name = (char *)tv_get_string(&argvars[0]); + + rettv->vval.v_number = buflist_add(*name == NUL ? NULL : name, 0); +} + +/// "bufexists(expr)" function +void f_bufexists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = (find_buffer(&argvars[0]) != NULL); +} + +/// "buflisted(expr)" function +void f_buflisted(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + buf_T *buf; + + buf = find_buffer(&argvars[0]); + rettv->vval.v_number = (buf != NULL && buf->b_p_bl); +} + +/// "bufload(expr)" function +void f_bufload(typval_T *argvars, typval_T *unused, EvalFuncData fptr) +{ + buf_T *buf = get_buf_arg(&argvars[0]); + + if (buf != NULL) { + buffer_ensure_loaded(buf); + } +} + +/// "bufloaded(expr)" function +void f_bufloaded(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + buf_T *buf; + + buf = find_buffer(&argvars[0]); + rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL); +} + +/// "bufname(expr)" function +void f_bufname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const buf_T *buf; + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (argvars[0].v_type == VAR_UNKNOWN) { + buf = curbuf; + } else { + buf = tv_get_buf_from_arg(&argvars[0]); + } + if (buf != NULL && buf->b_fname != NULL) { + rettv->vval.v_string = xstrdup(buf->b_fname); + } +} + +/// "bufnr(expr)" function +void f_bufnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const buf_T *buf; + bool error = false; + + rettv->vval.v_number = -1; + + if (argvars[0].v_type == VAR_UNKNOWN) { + buf = curbuf; + } else { + // Don't use tv_get_buf_from_arg(); we continue if the buffer wasn't found + // and the second argument isn't zero, but we want to return early if the + // first argument isn't a string or number so only one error is shown. + if (!tv_check_str_or_nr(&argvars[0])) { + return; + } + emsg_off++; + buf = tv_get_buf(&argvars[0], false); + emsg_off--; + } + + // If the buffer isn't found and the second argument is not zero create a + // new buffer. + const char *name; + if (buf == NULL + && argvars[1].v_type != VAR_UNKNOWN + && tv_get_number_chk(&argvars[1], &error) != 0 + && !error + && (name = tv_get_string_chk(&argvars[0])) != NULL) { + buf = buflist_new((char *)name, NULL, 1, 0); + } + + if (buf != NULL) { + rettv->vval.v_number = buf->b_fnum; + } +} + +static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr) +{ + const buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); + if (buf == NULL) { // no need to search if invalid arg or buffer not found + rettv->vval.v_number = -1; + return; + } + + int winnr = 0; + int winid; + bool found_buf = false; + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + winnr++; + if (wp->w_buffer == buf) { + found_buf = true; + winid = wp->handle; + break; + } + } + rettv->vval.v_number = (found_buf ? (get_nr ? winnr : winid) : -1); +} + +/// "bufwinid(nr)" function +void f_bufwinid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + buf_win_common(argvars, rettv, false); +} + +/// "bufwinnr(nr)" function +void f_bufwinnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + buf_win_common(argvars, rettv, true); +} + +/// "deletebufline()" function +void f_deletebufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const int did_emsg_before = did_emsg; + rettv->vval.v_number = 1; // FAIL by default + buf_T *const buf = tv_get_buf(&argvars[0], false); + if (buf == NULL) { + return; + } + + linenr_T last; + const linenr_T first = tv_get_lnum_buf(&argvars[1], buf); + if (did_emsg > did_emsg_before) { + return; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + last = tv_get_lnum_buf(&argvars[2], buf); + } else { + last = first; + } + + if (buf->b_ml.ml_mfp == NULL || first < 1 + || first > buf->b_ml.ml_line_count || last < first) { + return; + } + + // After this don't use "return", goto "cleanup"! + const bool is_curbuf = buf == curbuf; + cob_T cob; + if (!is_curbuf) { + // set "curbuf" to "buf" and find a window for this buffer + change_other_buffer_prepare(&cob, buf); + } + + if (last > curbuf->b_ml.ml_line_count) { + last = curbuf->b_ml.ml_line_count; + } + const long count = last - first + 1; + + // When coming here from Insert mode, sync undo, so that this can be + // undone separately from what was previously inserted. + if (u_sync_once == 2) { + u_sync_once = 1; // notify that u_sync() was called + u_sync(true); + } + + if (u_save(first - 1, last + 1) == FAIL) { + goto cleanup; + } + + for (linenr_T lnum = first; lnum <= last; lnum++) { + ml_delete(first, true); + } + + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer == buf) { + if (wp->w_cursor.lnum > last) { + wp->w_cursor.lnum -= (linenr_T)count; + } else if (wp->w_cursor.lnum > first) { + wp->w_cursor.lnum = first; + } + if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) { + wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count; + } + } + } + check_cursor_col(); + deleted_lines_mark(first, count); + rettv->vval.v_number = 0; // OK + +cleanup: + if (!is_curbuf) { + change_other_buffer_restore(&cob); + } +} + +/// @return buffer options, variables and other attributes in a dictionary. +static dict_T *get_buffer_info(buf_T *buf) +{ + dict_T *const dict = tv_dict_alloc(); + + tv_dict_add_nr(dict, S_LEN("bufnr"), buf->b_fnum); + tv_dict_add_str(dict, S_LEN("name"), + buf->b_ffname != NULL ? (const char *)buf->b_ffname : ""); + tv_dict_add_nr(dict, S_LEN("lnum"), + buf == curbuf ? curwin->w_cursor.lnum : buflist_findlnum(buf)); + tv_dict_add_nr(dict, S_LEN("linecount"), buf->b_ml.ml_line_count); + tv_dict_add_nr(dict, S_LEN("loaded"), buf->b_ml.ml_mfp != NULL); + tv_dict_add_nr(dict, S_LEN("listed"), buf->b_p_bl); + tv_dict_add_nr(dict, S_LEN("changed"), bufIsChanged(buf)); + tv_dict_add_nr(dict, S_LEN("changedtick"), buf_get_changedtick(buf)); + tv_dict_add_nr(dict, S_LEN("hidden"), + buf->b_ml.ml_mfp != NULL && buf->b_nwindows == 0); + + // Get a reference to buffer variables + tv_dict_add_dict(dict, S_LEN("variables"), buf->b_vars); + + // List of windows displaying this buffer + list_T *const windows = tv_list_alloc(kListLenMayKnow); + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer == buf) { + tv_list_append_number(windows, (varnumber_T)wp->handle); + } + } + tv_dict_add_list(dict, S_LEN("windows"), windows); + + if (buf->b_signlist != NULL) { + // List of signs placed in this buffer + tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf)); + } + + tv_dict_add_nr(dict, S_LEN("lastused"), buf->b_last_used); + + return dict; +} + +/// "getbufinfo()" function +void f_getbufinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + buf_T *argbuf = NULL; + bool filtered = false; + bool sel_buflisted = false; + bool sel_bufloaded = false; + bool sel_bufmodified = false; + + tv_list_alloc_ret(rettv, kListLenMayKnow); + + // List of all the buffers or selected buffers + if (argvars[0].v_type == VAR_DICT) { + dict_T *sel_d = argvars[0].vval.v_dict; + + if (sel_d != NULL) { + dictitem_T *di; + + filtered = true; + + di = tv_dict_find(sel_d, S_LEN("buflisted")); + if (di != NULL && tv_get_number(&di->di_tv)) { + sel_buflisted = true; + } + + di = tv_dict_find(sel_d, S_LEN("bufloaded")); + if (di != NULL && tv_get_number(&di->di_tv)) { + sel_bufloaded = true; + } + di = tv_dict_find(sel_d, S_LEN("bufmodified")); + if (di != NULL && tv_get_number(&di->di_tv)) { + sel_bufmodified = true; + } + } + } else if (argvars[0].v_type != VAR_UNKNOWN) { + // Information about one buffer. Argument specifies the buffer + argbuf = tv_get_buf_from_arg(&argvars[0]); + if (argbuf == NULL) { + return; + } + } + + // Return information about all the buffers or a specified buffer + FOR_ALL_BUFFERS(buf) { + if (argbuf != NULL && argbuf != buf) { + continue; + } + if (filtered && ((sel_bufloaded && buf->b_ml.ml_mfp == NULL) + || (sel_buflisted && !buf->b_p_bl) + || (sel_bufmodified && !buf->b_changed))) { + continue; + } + + dict_T *const d = get_buffer_info(buf); + tv_list_append_dict(rettv->vval.v_list, d); + if (argbuf != NULL) { + return; + } + } +} + +/// 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); + rettv->vval.v_string = NULL; + + if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0 || end < start) { + if (retlist) { + tv_list_alloc_ret(rettv, 0); + } + return; + } + + if (retlist) { + if (start < 1) { + start = 1; + } + if (end > buf->b_ml.ml_line_count) { + end = buf->b_ml.ml_line_count; + } + tv_list_alloc_ret(rettv, end - start + 1); + while (start <= end) { + tv_list_append_string(rettv->vval.v_list, + (const char *)ml_get_buf(buf, start++, false), -1); + } + } else { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = ((start >= 1 && start <= buf->b_ml.ml_line_count) + ? xstrdup(ml_get_buf(buf, start, false)) : NULL); + } +} + +/// @param retlist true: "getbufline()" function +/// false: "getbufoneline()" function +static void getbufline(typval_T *argvars, typval_T *rettv, bool retlist) +{ + const int did_emsg_before = did_emsg; + buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); + const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); + if (did_emsg > did_emsg_before) { + return; + } + const linenr_T end = (argvars[2].v_type == VAR_UNKNOWN + ? lnum + : tv_get_lnum_buf(&argvars[2], buf)); + + get_buffer_lines(buf, lnum, end, retlist, rettv); +} + +/// "getbufline()" function +void f_getbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + getbufline(argvars, rettv, true); +} + +/// "getbufoneline()" function +void f_getbufoneline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + getbufline(argvars, rettv, false); +} + +/// "getline(lnum, [end])" function +void f_getline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + linenr_T end; + bool retlist; + + const linenr_T lnum = tv_get_lnum(argvars); + if (argvars[1].v_type == VAR_UNKNOWN) { + end = lnum; + retlist = false; + } else { + end = tv_get_lnum(&argvars[1]); + retlist = true; + } + + get_buffer_lines(curbuf, lnum, end, retlist, rettv); +} + +/// "setbufline()" function +void f_setbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + buf_set_append_line(argvars, rettv, false); +} + +/// "setline()" function +void f_setline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const int did_emsg_before = did_emsg; + linenr_T lnum = tv_get_lnum(&argvars[0]); + if (did_emsg == did_emsg_before) { + set_buffer_lines(curbuf, lnum, false, &argvars[1], rettv); + } +} + +/// Make "buf" the current buffer. +/// +/// restore_buffer() MUST be called to undo. +/// No autocommands will be executed. Use aucmd_prepbuf() if there are any. +void switch_buffer(bufref_T *save_curbuf, buf_T *buf) +{ + block_autocmds(); + set_bufref(save_curbuf, curbuf); + curbuf->b_nwindows--; + curbuf = buf; + curwin->w_buffer = buf; + curbuf->b_nwindows++; +} + +/// Restore the current buffer after using switch_buffer(). +void restore_buffer(bufref_T *save_curbuf) +{ + unblock_autocmds(); + // Check for valid buffer, just in case. + if (bufref_valid(save_curbuf)) { + curbuf->b_nwindows--; + curwin->w_buffer = save_curbuf->br_buf; + curbuf = save_curbuf->br_buf; + curbuf->b_nwindows++; + } +} + +/// Find a window for buffer "buf". +/// If found true is returned and "wp" and "tp" are set to +/// the window and tabpage. +/// If not found, false is returned. +/// +/// @param buf buffer to find a window for +/// @param[out] wp stores the found window +/// @param[out] tp stores the found tabpage +/// +/// @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; + *tp = NULL; + FOR_ALL_TAB_WINDOWS(tp2, wp2) { + if (wp2->w_buffer == buf) { + *tp = tp2; + *wp = wp2; + return true; + } + } + return false; +} diff --git a/src/nvim/eval/buffer.h b/src/nvim/eval/buffer.h new file mode 100644 index 0000000000..4a2f8f9e94 --- /dev/null +++ b/src/nvim/eval/buffer.h @@ -0,0 +1,10 @@ +#ifndef NVIM_EVAL_BUFFER_H +#define NVIM_EVAL_BUFFER_H + +#include "nvim/buffer_defs.h" +#include "nvim/eval/typval_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/buffer.h.generated.h" +#endif +#endif // NVIM_EVAL_BUFFER_H diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index 94ef419bed..cd1479f150 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -1,20 +1,31 @@ // 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 <msgpack.h> +#include <assert.h> +#include <msgpack/object.h> +#include <stdbool.h> #include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> #include "klib/kvec.h" #include "nvim/ascii.h" -#include "nvim/charset.h" // vim_str2nr +#include "nvim/charset.h" #include "nvim/eval.h" #include "nvim/eval/decode.h" #include "nvim/eval/encode.h" #include "nvim/eval/typval.h" -#include "nvim/globals.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/hashtab.h" #include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/vim.h" // OK, FAIL +#include "nvim/types.h" +#include "nvim/vim.h" /// Helper structure for container_struct typedef struct { @@ -271,11 +282,9 @@ typval_T decode_string(const char *const s, const size_t len, const TriState has list_T *const list = tv_list_alloc(kListLenMayKnow); tv_list_ref(list); create_special_dict(&tv, kMPString, - ((typval_T){ - .v_type = VAR_LIST, - .v_lock = VAR_UNLOCKED, - .vval = { .v_list = list }, - })); + (typval_T){ .v_type = VAR_LIST, + .v_lock = VAR_UNLOCKED, + .vval = { .v_list = list } }); const int elw_ret = encode_list_write((void *)list, s, len); if (s_allocated) { xfree((void *)s); @@ -286,13 +295,12 @@ typval_T decode_string(const char *const s, const size_t len, const TriState has } } return tv; - } else { - return (typval_T) { - .v_type = VAR_STRING, - .v_lock = VAR_UNLOCKED, - .vval = { .v_string = ((s == NULL || s_allocated) ? (char *)s : xmemdupz(s, len)) }, - }; } + return (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval = { .v_string = ((s == NULL || s_allocated) ? (char *)s : xmemdupz(s, len)) }, + }; } /// Parse JSON double-quoted string @@ -368,7 +376,7 @@ static inline int parse_json_string(const char *const buf, const size_t buf_len, goto parse_json_string_fail; } } else { - uint8_t p_byte = (uint8_t)*p; + uint8_t p_byte = (uint8_t)(*p); // unescaped = %x20-21 / %x23-5B / %x5D-10FFFF if (p_byte < 0x20) { semsg(_("E474: ASCII control characters cannot be present " @@ -469,7 +477,7 @@ static inline int parse_json_string(const char *const buf, const size_t buf_len, ['r'] = CAR, ['f'] = FF, }; - *str_end++ = escapes[(int)*t]; + *str_end++ = escapes[(int)(*t)]; break; } default: @@ -838,12 +846,10 @@ json_decode_string_cycle_start: .v_lock = VAR_UNLOCKED, .vval = { .v_list = list }, }; - kv_push(container_stack, ((ContainerStackItem) { - .stack_index = kv_size(stack), - .s = p, - .container = tv, - .special_val = NULL, - })); + kv_push(container_stack, ((ContainerStackItem) { .stack_index = kv_size(stack), + .s = p, + .container = tv, + .special_val = NULL })); kv_push(stack, OBJ(tv, false, didcomma, didcolon)); break; } @@ -862,12 +868,10 @@ json_decode_string_cycle_start: .vval = { .v_dict = dict }, }; } - kv_push(container_stack, ((ContainerStackItem) { - .stack_index = kv_size(stack), - .s = p, - .container = tv, - .special_val = val_list, - })); + kv_push(container_stack, ((ContainerStackItem) { .stack_index = kv_size(stack), + .s = p, + .container = tv, + .special_val = val_list })); kv_push(stack, OBJ(tv, false, didcomma, didcolon)); break; } @@ -1089,11 +1093,9 @@ msgpack_to_vim_generic_map: {} tv_list_append_number(list, mobj.via.ext.type); list_T *const ext_val_list = tv_list_alloc(kListLenMayKnow); tv_list_append_list(list, ext_val_list); - create_special_dict(rettv, kMPExt, ((typval_T) { - .v_type = VAR_LIST, - .v_lock = VAR_UNLOCKED, - .vval = { .v_list = list }, - })); + create_special_dict(rettv, kMPExt, ((typval_T) { .v_type = VAR_LIST, + .v_lock = VAR_UNLOCKED, + .vval = { .v_list = list } })); if (encode_list_write((void *)ext_val_list, mobj.via.ext.ptr, mobj.via.ext.size) == -1) { return FAIL; diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 27d35ea24f..c2f1eae8af 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -10,23 +10,28 @@ #include <assert.h> #include <inttypes.h> #include <math.h> -#include <msgpack.h> +#include <stdbool.h> #include <stddef.h> +#include <stdlib.h> +#include <string.h> #include "klib/kvec.h" +#include "msgpack/pack.h" #include "nvim/ascii.h" -#include "nvim/buffer_defs.h" -#include "nvim/charset.h" // vim_isprintc() #include "nvim/eval.h" #include "nvim/eval/encode.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_encode.h" #include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/hashtab.h" #include "nvim/macros.h" #include "nvim/math.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/vim.h" // For _() const char *const encode_bool_var_names[] = { @@ -131,35 +136,35 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, typval_T key_tv = { .v_type = VAR_STRING, .vval = { .v_string = - (char *)(v.data.d.hi == - NULL ? v.data.d.dict->dv_hashtab.ht_array : (v.data.d.hi - - 1))->hi_key }, + (v.data.d.hi == + NULL ? v.data.d.dict->dv_hashtab.ht_array : (v.data.d.hi - + 1))->hi_key }, }; char *const key = encode_tv2string(&key_tv, NULL); - vim_snprintf((char *)IObuff, IOSIZE, key_msg, key); + vim_snprintf(IObuff, IOSIZE, key_msg, key); xfree(key); - ga_concat(&msg_ga, (char *)IObuff); + ga_concat(&msg_ga, IObuff); break; } case kMPConvPairs: case kMPConvList: { const int idx = (v.data.l.li == tv_list_first(v.data.l.list) - ? 0 - : (v.data.l.li == NULL - ? tv_list_len(v.data.l.list) - 1 - : (int)tv_list_idx_of_item(v.data.l.list, - TV_LIST_ITEM_PREV(v.data.l.list, - v.data.l.li)))); + ? 0 + : (v.data.l.li == NULL + ? tv_list_len(v.data.l.list) - 1 + : (int)tv_list_idx_of_item(v.data.l.list, + TV_LIST_ITEM_PREV(v.data.l.list, + v.data.l.li)))); const listitem_T *const li = (v.data.l.li == NULL - ? tv_list_last(v.data.l.list) - : TV_LIST_ITEM_PREV(v.data.l.list, - v.data.l.li)); + ? tv_list_last(v.data.l.list) + : TV_LIST_ITEM_PREV(v.data.l.list, + v.data.l.li)); if (v.type == kMPConvList || li == NULL || (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST && tv_list_len(TV_LIST_ITEM_TV(li)->vval.v_list) <= 0)) { - vim_snprintf((char *)IObuff, IOSIZE, idx_msg, idx); - ga_concat(&msg_ga, (char *)IObuff); + vim_snprintf(IObuff, IOSIZE, idx_msg, idx); + ga_concat(&msg_ga, IObuff); } else { assert(li != NULL); listitem_T *const first_item = @@ -167,9 +172,9 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, assert(first_item != NULL); typval_T key_tv = *TV_LIST_ITEM_TV(first_item); char *const key = encode_tv2echo(&key_tv, NULL); - vim_snprintf((char *)IObuff, IOSIZE, key_pair_msg, key, idx); + vim_snprintf(IObuff, IOSIZE, key_pair_msg, key, idx); xfree(key); - ga_concat(&msg_ga, (char *)IObuff); + ga_concat(&msg_ga, IObuff); } break; } @@ -188,8 +193,8 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, break; case kMPConvPartialList: { const int idx = (int)(v.data.a.arg - v.data.a.argv) - 1; - vim_snprintf((char *)IObuff, IOSIZE, partial_arg_i_msg, idx); - ga_concat(&msg_ga, (char *)IObuff); + vim_snprintf(IObuff, IOSIZE, partial_arg_i_msg, idx); + ga_concat(&msg_ga, IObuff); break; } } @@ -304,7 +309,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s if (buf_[i_] == '\'') { \ ga_append(gap, '\''); \ } \ - ga_append(gap, buf_[i_]); \ + ga_append(gap, (uint8_t)buf_[i_]); \ } \ ga_append(gap, '\''); \ } \ diff --git a/src/nvim/eval/encode.h b/src/nvim/eval/encode.h index 8755ff48ac..41e7614fc0 100644 --- a/src/nvim/eval/encode.h +++ b/src/nvim/eval/encode.h @@ -2,11 +2,15 @@ #define NVIM_EVAL_ENCODE_H #include <msgpack.h> +#include <msgpack/pack.h> #include <stddef.h> +#include <string.h> #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/garray.h" -#include "nvim/vim.h" // For STRLEN +#include "nvim/vim.h" /// Convert VimL value to msgpack string /// @@ -15,9 +19,7 @@ /// @param[in] objname Object name, used for error message. /// /// @return OK in case of success, FAIL otherwise. -int encode_vim_to_msgpack(msgpack_packer *const packer, - typval_T *const tv, - const char *const objname); +int encode_vim_to_msgpack(msgpack_packer *packer, typval_T *tv, const char *objname); /// Convert VimL value to :echo output /// @@ -26,9 +28,7 @@ int encode_vim_to_msgpack(msgpack_packer *const packer, /// @param[in] objname Object name, used for error message. /// /// @return OK in case of success, FAIL otherwise. -int encode_vim_to_echo(garray_T *const packer, - typval_T *const tv, - const char *const objname); +int encode_vim_to_echo(garray_T *packer, typval_T *tv, const char *objname); /// Structure defining state for read_from_list() typedef struct { @@ -48,7 +48,7 @@ static inline ListReaderState encode_init_lrstate(const list_T *const list) .offset = 0, .li_length = (TV_LIST_ITEM_TV(tv_list_first(list))->vval.v_string == NULL ? 0 - : STRLEN(TV_LIST_ITEM_TV(tv_list_first(list))->vval.v_string)), + : strlen(TV_LIST_ITEM_TV(tv_list_first(list))->vval.v_string)), }; } diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c index 0e0d0fe696..9caea2fef1 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -1,15 +1,23 @@ // 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 <inttypes.h> +#include <stdlib.h> + #include "nvim/eval.h" #include "nvim/eval/executor.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/message.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "eval/executor.c.generated.h" +# include "eval/executor.c.generated.h" // IWYU pragma: export #endif char *e_listidx = N_("E684: list index out of range: %" PRId64); @@ -43,7 +51,7 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, const char *cons blob_T *const b1 = tv1->vval.v_blob; blob_T *const b2 = tv2->vval.v_blob; for (int i = 0; i < tv_blob_len(b2); i++) { - ga_append(&b1->bv_ga, (char)tv_blob_get(b2, i)); + ga_append(&b1->bv_ga, tv_blob_get(b2, i)); } } return OK; @@ -61,7 +69,7 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, const char *cons if (tv2->v_type == VAR_LIST) { break; } - if (vim_strchr("+-*/%", *op) != NULL) { + if (vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) { // nr += nr or nr -= nr, nr *= nr, nr /= nr, nr %= nr varnumber_T n = tv_get_number(tv1); if (tv2->v_type == VAR_FLOAT) { @@ -122,8 +130,8 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, const char *cons break; } const float_T f = (tv2->v_type == VAR_FLOAT - ? tv2->vval.v_float - : (float_T)tv_get_number(tv2)); + ? tv2->vval.v_float + : (float_T)tv_get_number(tv2)); switch (*op) { case '+': tv1->vval.v_float += f; break; diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index a326c44371..5a31288ecd 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1,27 +1,44 @@ // 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 <assert.h> +#include <fcntl.h> #include <float.h> +#include <inttypes.h> +#include <limits.h> #include <math.h> - +#include <msgpack/object.h> +#include <msgpack/pack.h> +#include <msgpack/unpack.h> +#include <signal.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <time.h> +#include <uv.h> + +#include "auto/config.h" #include "nvim/api/private/converter.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" -#include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/assert.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" -#include "nvim/change.h" +#include "nvim/buffer_defs.h" #include "nvim/channel.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" -#include "nvim/cmdhist.h" #include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/diff.h" -#include "nvim/digraph.h" #include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/buffer.h" #include "nvim/eval/decode.h" #include "nvim/eval/encode.h" #include "nvim/eval/executor.h" @@ -29,55 +46,72 @@ #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" +#include "nvim/eval/window.h" +#include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" +#include "nvim/event/process.h" +#include "nvim/event/time.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/file_search.h" #include "nvim/fileio.h" -#include "nvim/fold.h" +#include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/globals.h" +#include "nvim/grid_defs.h" +#include "nvim/hashtab.h" +#include "nvim/highlight_defs.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/insexpand.h" +#include "nvim/keycodes.h" #include "nvim/lua/executor.h" #include "nvim/macros.h" -#include "nvim/mapping.h" +#include "nvim/main.h" #include "nvim/mark.h" -#include "nvim/match.h" #include "nvim/math.h" +#include "nvim/mbyte.h" +#include "nvim/memfile_defs.h" #include "nvim/memline.h" +#include "nvim/memory.h" #include "nvim/menu.h" +#include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/msgpack_rpc/server.h" +#include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/optionstr.h" #include "nvim/os/dl.h" +#include "nvim/os/fileio.h" +#include "nvim/os/fs_defs.h" +#include "nvim/os/os.h" +#include "nvim/os/pty_process.h" #include "nvim/os/shell.h" +#include "nvim/os/stdpaths_defs.h" +#include "nvim/os/time.h" #include "nvim/path.h" #include "nvim/plines.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" #include "nvim/profile.h" -#include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/runtime.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/sha256.h" -#include "nvim/sign.h" #include "nvim/spell.h" #include "nvim/spellsuggest.h" #include "nvim/state.h" +#include "nvim/strings.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" @@ -106,6 +140,7 @@ typedef enum { PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH # include "funcs.generated.h" + PRAGMA_DIAG_POP PRAGMA_DIAG_POP #endif @@ -113,6 +148,8 @@ PRAGMA_DIAG_POP static char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob"); static char *e_invalwindow = N_("E957: Invalid window number"); static char *e_reduceempty = N_("E998: Reduce of an empty %s with no initial value"); +static char e_using_number_as_bool_nr[] + = N_("E1023: Using a Number as a Bool: %d"); /// Dummy va_list for passing to vim_snprintf /// @@ -132,13 +169,13 @@ char *get_function_name(expand_T *xp, int idx) intidx = -1; } if (intidx < 0) { - char_u *name = (char_u *)get_user_func_name(xp, idx); + char *name = get_user_func_name(xp, idx); if (name != NULL) { if (*name != NUL && *name != '<' - && STRNCMP("g:", xp->xp_pattern, 2) == 0) { - return cat_prefix_varname('g', (char *)name); + && strncmp("g:", xp->xp_pattern, 2) == 0) { + return cat_prefix_varname('g', name); } - return (char *)name; + return name; } } @@ -155,7 +192,7 @@ char *get_function_name(expand_T *xp, int idx) } else { IObuff[key_len + 1] = NUL; } - return (char *)IObuff; + return IObuff; } /// Function given to ExpandGeneric() to obtain the list of internal or @@ -168,9 +205,9 @@ char *get_expr_name(expand_T *xp, int idx) intidx = -1; } if (intidx < 0) { - char_u *name = (char_u *)get_function_name(xp, idx); + char *name = get_function_name(xp, idx); if (name != NULL) { - return (char *)name; + return name; } } return get_user_var_name(xp, ++intidx); @@ -189,11 +226,11 @@ const EvalFuncDef *find_internal_func(const char *const name) return index >= 0 ? &functions[index] : NULL; } -int call_internal_func(const char_u *const fname, const int argcount, typval_T *const argvars, +int call_internal_func(const char *const fname, const int argcount, typval_T *const argvars, typval_T *const rettv) FUNC_ATTR_NONNULL_ALL { - const EvalFuncDef *const fdef = find_internal_func((const char *)fname); + const EvalFuncDef *const fdef = find_internal_func(fname); if (fdef == NULL) { return FCERR_UNKNOWN; } else if (argcount < fdef->min_argc) { @@ -207,11 +244,11 @@ int call_internal_func(const char_u *const fname, const int argcount, typval_T * } /// Invoke a method for base->method(). -int call_internal_method(const char_u *const fname, const int argcount, typval_T *const argvars, +int call_internal_method(const char *const fname, const int argcount, typval_T *const argvars, typval_T *const rettv, typval_T *const basetv) FUNC_ATTR_NONNULL_ALL { - const EvalFuncDef *const fdef = find_internal_func((const char *)fname); + const EvalFuncDef *const fdef = find_internal_func(fname); if (fdef == NULL) { return FCERR_UNKNOWN; } else if (fdef->base_arg == BASE_NONE) { @@ -325,20 +362,20 @@ static void f_add(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_number = 1; // Default: failed. if (argvars[0].v_type == VAR_LIST) { list_T *const l = argvars[0].vval.v_list; - if (!var_check_lock(tv_list_locked(l), N_("add() argument"), - TV_TRANSLATE)) { + if (!value_check_lock(tv_list_locked(l), N_("add() argument"), + TV_TRANSLATE)) { tv_list_append_tv(l, &argvars[1]); tv_copy(&argvars[0], rettv); } } else if (argvars[0].v_type == VAR_BLOB) { blob_T *const b = argvars[0].vval.v_blob; if (b != NULL - && !var_check_lock(b->bv_lock, N_("add() argument"), TV_TRANSLATE)) { + && !value_check_lock(b->bv_lock, N_("add() argument"), TV_TRANSLATE)) { bool error = false; const varnumber_T n = tv_get_number_chk(&argvars[1], &error); if (!error) { - ga_append(&b->bv_ga, (char)n); + ga_append(&b->bv_ga, (uint8_t)n); tv_copy(&argvars[0], rettv); } } @@ -361,26 +398,6 @@ static void f_api_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) (void)object_to_vim(DICTIONARY_OBJ(metadata), rettv, NULL); } -/// "append(lnum, string/list)" function -static void f_append(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const linenr_T lnum = tv_get_lnum(&argvars[0]); - - set_buffer_lines(curbuf, lnum, true, &argvars[1], rettv); -} - -/// "appendbufline(buf, lnum, string/list)" function -static void f_appendbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - buf_T *const buf = tv_get_buf(&argvars[0], false); - if (buf == NULL) { - rettv->vval.v_number = 1; // FAIL - } else { - const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); - set_buffer_lines(buf, lnum, true, &argvars[2], rettv); - } -} - /// "atan2()" function static void f_atan2(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -408,166 +425,6 @@ static void f_browsedir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) f_browse(argvars, rettv, fptr); } -/// Find a buffer by number or exact name. -static buf_T *find_buffer(typval_T *avar) -{ - buf_T *buf = NULL; - - if (avar->v_type == VAR_NUMBER) { - buf = buflist_findnr((int)avar->vval.v_number); - } else if (avar->v_type == VAR_STRING && avar->vval.v_string != NULL) { - buf = buflist_findname_exp(avar->vval.v_string); - if (buf == NULL) { - // No full path name match, try a match with a URL or a "nofile" - // buffer, these don't use the full path. - FOR_ALL_BUFFERS(bp) { - if (bp->b_fname != NULL - && (path_with_url(bp->b_fname) || bt_nofilename(bp)) - && strcmp(bp->b_fname, avar->vval.v_string) == 0) { - buf = bp; - break; - } - } - } - } - return buf; -} - -/// "bufadd(expr)" function -static void f_bufadd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - char_u *name = (char_u *)tv_get_string(&argvars[0]); - - rettv->vval.v_number = buflist_add(*name == NUL ? NULL : (char *)name, 0); -} - -/// "bufexists(expr)" function -static void f_bufexists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = (find_buffer(&argvars[0]) != NULL); -} - -/// "buflisted(expr)" function -static void f_buflisted(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - buf_T *buf; - - buf = find_buffer(&argvars[0]); - rettv->vval.v_number = (buf != NULL && buf->b_p_bl); -} - -/// "bufload(expr)" function -static void f_bufload(typval_T *argvars, typval_T *unused, EvalFuncData fptr) -{ - buf_T *buf = get_buf_arg(&argvars[0]); - - if (buf != NULL && buf->b_ml.ml_mfp == NULL) { - aco_save_T aco; - - aucmd_prepbuf(&aco, buf); - swap_exists_action = SEA_NONE; - open_buffer(false, NULL, 0); - aucmd_restbuf(&aco); - } -} - -/// "bufloaded(expr)" function -static void f_bufloaded(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - buf_T *buf; - - buf = find_buffer(&argvars[0]); - rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL); -} - -/// "bufname(expr)" function -static void f_bufname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const buf_T *buf; - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (argvars[0].v_type == VAR_UNKNOWN) { - buf = curbuf; - } else { - buf = tv_get_buf_from_arg(&argvars[0]); - } - if (buf != NULL && buf->b_fname != NULL) { - rettv->vval.v_string = xstrdup(buf->b_fname); - } -} - -/// "bufnr(expr)" function -static void f_bufnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const buf_T *buf; - bool error = false; - - rettv->vval.v_number = -1; - - if (argvars[0].v_type == VAR_UNKNOWN) { - buf = curbuf; - } else { - // Don't use tv_get_buf_from_arg(); we continue if the buffer wasn't found - // and the second argument isn't zero, but we want to return early if the - // first argument isn't a string or number so only one error is shown. - if (!tv_check_str_or_nr(&argvars[0])) { - return; - } - emsg_off++; - buf = tv_get_buf(&argvars[0], false); - emsg_off--; - } - - // If the buffer isn't found and the second argument is not zero create a - // new buffer. - const char *name; - if (buf == NULL - && argvars[1].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[1], &error) != 0 - && !error - && (name = tv_get_string_chk(&argvars[0])) != NULL) { - buf = buflist_new((char *)name, NULL, 1, 0); - } - - if (buf != NULL) { - rettv->vval.v_number = buf->b_fnum; - } -} - -static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr) -{ - const buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); - if (buf == NULL) { // no need to search if invalid arg or buffer not found - rettv->vval.v_number = -1; - return; - } - - int winnr = 0; - int winid; - bool found_buf = false; - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - winnr++; - if (wp->w_buffer == buf) { - found_buf = true; - winid = wp->handle; - break; - } - } - rettv->vval.v_number = (found_buf ? (get_nr ? winnr : winid) : -1); -} - -/// "bufwinid(nr)" function -static void f_bufwinid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - buf_win_common(argvars, rettv, false); -} - -/// "bufwinnr(nr)" function -static void f_bufwinnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - buf_win_common(argvars, rettv, true); -} - /// Get buffer by number or pattern. buf_T *tv_get_buf(typval_T *tv, int curtab_only) { @@ -578,7 +435,7 @@ buf_T *tv_get_buf(typval_T *tv, int curtab_only) return NULL; } - char_u *name = (char_u *)tv->vval.v_string; + char *name = tv->vval.v_string; if (name == NULL || *name == NUL) { return curbuf; @@ -593,7 +450,7 @@ buf_T *tv_get_buf(typval_T *tv, int curtab_only) char *save_cpo = p_cpo; p_cpo = empty_option; - buf_T *buf = buflist_findnr(buflist_findpat((char *)name, (char *)name + STRLEN(name), + buf_T *buf = buflist_findnr(buflist_findpat(name, name + strlen(name), true, false, curtab_only)); p_magic = save_magic; @@ -691,19 +548,19 @@ static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } bool owned = false; - char_u *func; + char *func; partial_T *partial = NULL; if (argvars[0].v_type == VAR_FUNC) { - func = (char_u *)argvars[0].vval.v_string; + func = argvars[0].vval.v_string; } else if (argvars[0].v_type == VAR_PARTIAL) { partial = argvars[0].vval.v_partial; - func = (char_u *)partial_name(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 { - func = (char_u *)tv_get_string(&argvars[0]); + func = (char *)tv_get_string(&argvars[0]); } if (*func == NUL) { @@ -791,6 +648,14 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) ptrdiff_t input_len = 0; char *input = NULL; + uint64_t id = (uint64_t)argvars[0].vval.v_number; +#ifdef UNIX + bool crlf = false; +#else + Channel *chan = find_channel(id); + bool crlf = (chan != NULL && chan->term) ? true: false; +#endif + if (argvars[1].v_type == VAR_BLOB) { const blob_T *const b = argvars[1].vval.v_blob; input_len = tv_blob_len(b); @@ -798,7 +663,7 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) input = xmemdup(b->bv_ga.ga_data, (size_t)input_len); } } else { - input = save_tv_as_string(&argvars[1], &input_len, false); + input = save_tv_as_string(&argvars[1], &input_len, false, crlf); } if (!input) { @@ -806,7 +671,6 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // or there is no input to send. return; } - uint64_t id = (uint64_t)argvars[0].vval.v_number; const char *error = NULL; rettv->vval.v_number = (varnumber_T)channel_send(id, input, (size_t)input_len, true, &error); if (error) { @@ -832,9 +696,32 @@ static void f_char2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// otherwise the byte index of the column. static void get_col(typval_T *argvars, typval_T *rettv, bool charcol) { + if (tv_check_for_string_or_list_arg(argvars, 0) == FAIL + || tv_check_for_opt_number_arg(argvars, 1) == FAIL) { + return; + } + + switchwin_T switchwin; + bool winchanged = false; + + if (argvars[1].v_type != VAR_UNKNOWN) { + // use the window specified in the second argument + tabpage_T *tp; + win_T *wp = win_id2wp_tp((int)tv_get_number(&argvars[1]), &tp); + if (wp == NULL || tp == NULL) { + return; + } + + if (switch_win_noblock(&switchwin, wp, tp, true) != OK) { + return; + } + + check_cursor(); + winchanged = true; + } + colnr_T col = 0; int fnum = curbuf->b_fnum; - pos_T *fp = var2fpos(&argvars[0], false, &fnum, charcol); if (fp != NULL && fnum == curbuf->b_fnum) { if (fp->col == MAXCOL) { @@ -862,6 +749,10 @@ static void get_col(typval_T *argvars, typval_T *rettv, bool charcol) } } rettv->vval.v_number = col; + + if (winchanged) { + restore_win_noblock(&switchwin, true); + } } /// "charcol()" function @@ -894,7 +785,7 @@ static void f_charidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) countcc = (int)tv_get_number(&argvars[2]); } if (countcc < 0 || countcc > 1) { - emsg(_(e_invarg)); + semsg(_(e_using_number_as_bool_nr), countcc); return; } @@ -931,7 +822,7 @@ static void f_chdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // Return the current directory char *cwd = xmalloc(MAXPATHL); - if (os_dirname((char_u *)cwd, MAXPATHL) != FAIL) { + if (os_dirname(cwd, MAXPATHL) != FAIL) { #ifdef BACKSLASH_IN_FILENAME slash_adjust(cwd); #endif @@ -968,14 +859,14 @@ static void f_cindent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) win_T *get_optional_window(typval_T *argvars, int idx) { - win_T *win = curwin; + if (argvars[idx].v_type == VAR_UNKNOWN) { + return curwin; + } - if (argvars[idx].v_type != VAR_UNKNOWN) { - win = find_win_by_nr_or_id(&argvars[idx]); - if (win == NULL) { - emsg(_(e_invalwindow)); - return NULL; - } + win_T *win = find_win_by_nr_or_id(&argvars[idx]); + if (win == NULL) { + emsg(_(e_invalwindow)); + return NULL; } return win; } @@ -1057,12 +948,12 @@ static void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } if (argvars[0].v_type == VAR_STRING) { - const char_u *expr = (char_u *)tv_get_string_chk(&argvars[1]); - const char_u *p = (char_u *)argvars[0].vval.v_string; + const char *expr = tv_get_string_chk(&argvars[1]); + const char *p = argvars[0].vval.v_string; if (!error && expr != NULL && *expr != NUL && p != NULL) { if (ic) { - const size_t len = STRLEN(expr); + const size_t len = strlen(expr); while (*p != NUL) { if (mb_strnicmp((char *)p, (char *)expr, len) == 0) { @@ -1073,10 +964,10 @@ static void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } } else { - char_u *next; - while ((next = (char_u *)strstr((char *)p, (char *)expr)) != NULL) { + char *next; + while ((next = strstr((char *)p, (char *)expr)) != NULL) { n++; - p = next + STRLEN(expr); + p = next + strlen(expr); } } } @@ -1132,29 +1023,6 @@ static void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_number = n; } -/// "cscope_connection([{num} , {dbpath} [, {prepend}]])" function -/// -/// Checks the existence of a cscope connection. -static void f_cscope_connection(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - int num = 0; - const char *dbpath = NULL; - const char *prepend = NULL; - char buf[NUMBUFLEN]; - - if (argvars[0].v_type != VAR_UNKNOWN - && argvars[1].v_type != VAR_UNKNOWN) { - num = (int)tv_get_number(&argvars[0]); - dbpath = tv_get_string(&argvars[1]); - if (argvars[2].v_type != VAR_UNKNOWN) { - prepend = tv_get_string_buf(&argvars[2], buf); - } - } - - rettv->vval.v_number = cs_connection(num, (char_u *)dbpath, - (char_u *)prepend); -} - /// "ctxget([{index}])" function static void f_ctxget(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -1348,19 +1216,21 @@ static void f_debugbreak(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) int pid = (int)tv_get_number(&argvars[0]); if (pid == 0) { emsg(_(e_invarg)); - } else { + return; + } + #ifdef MSWIN - HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid); + HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid); + if (hProcess == NULL) { + return; + } - if (hProcess != NULL) { - DebugBreakProcess(hProcess); - CloseHandle(hProcess); - rettv->vval.v_number = OK; - } + DebugBreakProcess(hProcess); + CloseHandle(hProcess); + rettv->vval.v_number = OK; #else - uv_kill(pid, SIGINT); + uv_kill(pid, SIGINT); #endif - } } /// "deepcopy()" function @@ -1369,10 +1239,10 @@ static void f_deepcopy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) int noref = 0; if (argvars[1].v_type != VAR_UNKNOWN) { - noref = (int)tv_get_number_chk(&argvars[1], NULL); + noref = (int)tv_get_bool_chk(&argvars[1], NULL); } if (noref < 0 || noref > 1) { - emsg(_(e_invarg)); + semsg(_(e_using_number_as_bool_nr), noref); } else { var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0 ? get_copyID() @@ -1489,82 +1359,6 @@ static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, EvalFuncData fp callback_free(&callback); } -/// "deletebufline()" function -static void f_deletebufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - buf_T *const buf = tv_get_buf(&argvars[0], false); - if (buf == NULL) { - rettv->vval.v_number = 1; // FAIL - return; - } - const bool is_curbuf = buf == curbuf; - const bool save_VIsual_active = VIsual_active; - - linenr_T last; - const linenr_T first = tv_get_lnum_buf(&argvars[1], buf); - if (argvars[2].v_type != VAR_UNKNOWN) { - last = tv_get_lnum_buf(&argvars[2], buf); - } else { - last = first; - } - - if (buf->b_ml.ml_mfp == NULL || first < 1 - || first > buf->b_ml.ml_line_count || last < first) { - rettv->vval.v_number = 1; // FAIL - return; - } - - buf_T *curbuf_save = NULL; - win_T *curwin_save = NULL; - if (!is_curbuf) { - VIsual_active = false; - curbuf_save = curbuf; - curwin_save = curwin; - curbuf = buf; - find_win_for_curbuf(); - } - if (last > curbuf->b_ml.ml_line_count) { - last = curbuf->b_ml.ml_line_count; - } - const long count = last - first + 1; - - // When coming here from Insert mode, sync undo, so that this can be - // undone separately from what was previously inserted. - if (u_sync_once == 2) { - u_sync_once = 1; // notify that u_sync() was called - u_sync(true); - } - - if (u_save(first - 1, last + 1) == FAIL) { - rettv->vval.v_number = 1; // FAIL - } else { - for (linenr_T lnum = first; lnum <= last; lnum++) { - ml_delete(first, true); - } - - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer == buf) { - if (wp->w_cursor.lnum > last) { - wp->w_cursor.lnum -= (linenr_T)count; - } else if (wp->w_cursor.lnum > first) { - wp->w_cursor.lnum = first; - } - if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) { - wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count; - } - } - } - check_cursor_col(); - deleted_lines_mark(first, count); - } - - if (!is_curbuf) { - curbuf = curbuf_save; - curwin = curwin_save; - VIsual_active = save_VIsual_active; - } -} - /// "did_filetype()" function static void f_did_filetype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -1595,9 +1389,10 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) || changedtick != buf_get_changedtick(curbuf) || fnum != curbuf->b_fnum) { // New line, buffer, change: need to get the values. - int filler_lines = diff_check(curwin, lnum); - if (filler_lines < 0) { - if (filler_lines == -1) { + int linestatus = 0; + int filler_lines = diff_check_with_linestatus(curwin, lnum, &linestatus); + if (filler_lines < 0 || linestatus < 0) { + if (filler_lines == -1 || linestatus == -1) { change_start = MAXCOL; change_end = -1; if (diff_find_change(curwin, lnum, &change_start, &change_end)) { @@ -1624,7 +1419,7 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) hlID = HLF_CHD; // Changed line. } } - rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (int)(hlID + 1); + rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (hlID + 1); } /// "empty({expr})" function @@ -1727,23 +1522,22 @@ static void f_escape(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf[NUMBUFLEN]; - rettv->vval.v_string = (char *)vim_strsave_escaped((const char_u *)tv_get_string(&argvars[0]), - (const char_u *)tv_get_string_buf(&argvars[1], - buf)); + rettv->vval.v_string = vim_strsave_escaped(tv_get_string(&argvars[0]), + tv_get_string_buf(&argvars[1], buf)); rettv->v_type = VAR_STRING; } /// "getenv()" function static void f_getenv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - char_u *p = (char_u *)vim_getenv(tv_get_string(&argvars[0])); + char *p = vim_getenv(tv_get_string(&argvars[0])); if (p == NULL) { rettv->v_type = VAR_SPECIAL; rettv->vval.v_special = kSpecialVarNull; return; } - rettv->vval.v_string = (char *)p; + rettv->vval.v_string = p; rettv->v_type = VAR_STRING; } @@ -1804,7 +1598,7 @@ static char *get_list_line(int c, void *cookie, int indent, bool do_concat) return s == NULL ? NULL : xstrdup(s); } -static void execute_common(typval_T *argvars, typval_T *rettv, int arg_off) +void execute_common(typval_T *argvars, typval_T *rettv, int arg_off) { const int save_msg_silent = msg_silent; const int save_emsg_silent = emsg_silent; @@ -1888,21 +1682,6 @@ static void f_execute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) execute_common(argvars, rettv, 0); } -/// "win_execute(win_id, command)" function -static void f_win_execute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - // Return an empty string if something fails. - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - int id = (int)tv_get_number(argvars); - tabpage_T *tp; - win_T *wp = win_id2wp_tp(id, &tp); - if (wp != NULL && tp != NULL) { - WIN_EXECUTE(wp, tp, execute_common(argvars, rettv, 1)); - } -} - /// "exepath()" function static void f_exepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -1916,7 +1695,7 @@ static void f_exepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) #ifdef BACKSLASH_IN_FILENAME if (path != NULL) { - slash_adjust((char_u *)path); + slash_adjust(path); } #endif @@ -1943,14 +1722,9 @@ static void f_exists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) xfree(exp); } } else if (*p == '&' || *p == '+') { // Option. - bool working = (*p == '+'); // whether option needs to be working - int opt_flags; - - if (find_option_end(&p, &opt_flags) != NULL) { - int opt_idx = findoption(p); - n = (opt_idx >= 0 && (!working || get_varp_scope(get_option(opt_idx), opt_flags) != NULL)); - } else { - n = false; + n = (get_option_tv(&p, NULL, true) == OK); + if (*skipwhite(p) != NUL) { + n = false; // Trailing garbage. } } else if (*p == '*') { // Internal or user defined function. n = function_exists(p + 1, false); @@ -1975,7 +1749,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) int options = WILD_SILENT|WILD_USE_NL|WILD_LIST_NOTFOUND; bool error = false; #ifdef BACKSLASH_IN_FILENAME - char_u *p_csl_save = p_csl; + char *p_csl_save = p_csl; // avoid using 'completeslash' here p_csl = empty_option; @@ -1996,7 +1770,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } size_t len; char *errormsg = NULL; - char_u *result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL, false); + char *result = eval_vars((char *)s, s, &len, NULL, &errormsg, NULL, false); if (p_verbose == 0) { emsg_off--; } else if (errormsg != NULL) { @@ -2009,7 +1783,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } XFREE_CLEAR(result); } else { - rettv->vval.v_string = (char *)result; + rettv->vval.v_string = result; } } else { // When the optional second argument is non-zero, don't remove matches @@ -2062,6 +1836,12 @@ static void f_menu_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) static void f_expandcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char *errormsg = NULL; + bool emsgoff = true; + + if (argvars[1].v_type == VAR_DICT + && tv_dict_get_bool(argvars[1].vval.v_dict, "errmsg", kBoolVarFalse)) { + emsgoff = false; + } rettv->v_type = VAR_STRING; char *cmdstr = xstrdup(tv_get_string(&argvars[0])); @@ -2075,9 +1855,17 @@ static void f_expandcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) }; eap.argt |= EX_NOSPC; - emsg_off++; - expand_filename(&eap, &cmdstr, &errormsg); - emsg_off--; + if (emsgoff) { + emsg_off++; + } + if (expand_filename(&eap, &cmdstr, &errormsg) == FAIL) { + if (!emsgoff && errormsg != NULL && *errormsg != NUL) { + emsg(errormsg); + } + } + if (emsgoff) { + emsg_off--; + } rettv->vval.v_string = cmdstr; } @@ -2108,9 +1896,9 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) list_T *list = argvars[0].vval.v_list; if (list != NULL - && !var_check_lock(tv_list_locked(list), - N_("flatten() argument"), - TV_TRANSLATE) + && !value_check_lock(tv_list_locked(list), + N_("flatten() argument"), + TV_TRANSLATE) && tv_list_flatten(list, maxdepth) == OK) { tv_copy(&argvars[0], rettv); } @@ -2127,7 +1915,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) list_T *const l1 = argvars[0].vval.v_list; list_T *const l2 = argvars[1].vval.v_list; - if (!var_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) { + if (!value_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) { listitem_T *item; if (argvars[2].v_type != VAR_UNKNOWN) { long before = (long)tv_get_number_chk(&argvars[2], &error); @@ -2156,13 +1944,13 @@ static void f_extend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) dict_T *const d1 = argvars[0].vval.v_dict; dict_T *const d2 = argvars[1].vval.v_dict; if (d1 == NULL) { - const bool locked = var_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE); + const bool locked = value_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE); (void)locked; assert(locked == true); } else if (d2 == NULL) { // Do nothing tv_copy(&argvars[0], rettv); - } else if (!var_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) { + } else if (!value_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) { const char *action = "force"; // Check the third argument. if (argvars[2].v_type != VAR_UNKNOWN) { @@ -2233,8 +2021,8 @@ static void f_filewritable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) { - char_u *fresult = NULL; - char_u *path = *curbuf->b_p_path == NUL ? p_path : (char_u *)curbuf->b_p_path; + char *fresult = NULL; + char *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; int count = 1; bool first = true; bool error = false; @@ -2251,7 +2039,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) error = true; } else { if (*p != NUL) { - path = (char_u *)p; + path = (char *)p; } if (argvars[2].v_type != VAR_UNKNOWN) { @@ -2269,13 +2057,13 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST) { xfree(fresult); } - fresult = find_file_in_path_option(first ? (char_u *)fname : NULL, + fresult = find_file_in_path_option(first ? (char *)fname : NULL, first ? strlen(fname) : 0, 0, first, path, - find_what, (char_u *)curbuf->b_ffname, + find_what, curbuf->b_ffname, (find_what == FINDFILE_DIR - ? (char_u *)"" - : (char_u *)curbuf->b_p_sua)); + ? "" + : curbuf->b_p_sua)); first = false; if (fresult != NULL && rettv->v_type == VAR_LIST) { @@ -2285,7 +2073,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) } if (rettv->v_type == VAR_STRING) { - rettv->vval.v_string = (char *)fresult; + rettv->vval.v_string = fresult; } } @@ -2312,14 +2100,16 @@ static void f_float2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { float_T f; - if (tv_get_float_chk(argvars, &f)) { - if (f <= (float_T) - VARNUMBER_MAX + DBL_EPSILON) { - rettv->vval.v_number = -VARNUMBER_MAX; - } else if (f >= (float_T)VARNUMBER_MAX - DBL_EPSILON) { - rettv->vval.v_number = VARNUMBER_MAX; - } else { - rettv->vval.v_number = (varnumber_T)f; - } + if (!tv_get_float_chk(argvars, &f)) { + return; + } + + if (f <= (float_T) - VARNUMBER_MAX + DBL_EPSILON) { + rettv->vval.v_number = -VARNUMBER_MAX; + } else if (f >= (float_T)VARNUMBER_MAX - DBL_EPSILON) { + rettv->vval.v_number = VARNUMBER_MAX; + } else { + rettv->vval.v_number = (varnumber_T)f; } } @@ -2347,7 +2137,7 @@ static void f_fnameescape(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "fnamemodify({fname}, {mods})" function static void f_fnamemodify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - char_u *fbuf = NULL; + char *fbuf = NULL; size_t len = 0; char buf[NUMBUFLEN]; const char *fname = tv_get_string_chk(&argvars[0]); @@ -2359,7 +2149,7 @@ static void f_fnamemodify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (*mods != NUL) { size_t usedlen = 0; (void)modify_fname((char *)mods, false, &usedlen, - (char **)&fname, (char **)&fbuf, &len); + (char **)&fname, &fbuf, &len); } } @@ -2454,13 +2244,17 @@ static void f_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) const char *const what = tv_get_string(&argvars[1]); if (strcmp(what, "func") == 0 || strcmp(what, "name") == 0) { + const char *name = partial_name(pt); rettv->v_type = (*what == 'f' ? VAR_FUNC : VAR_STRING); - const char *const n = (const char *)partial_name(pt); - assert(n != NULL); - rettv->vval.v_string = xstrdup(n); + assert(name != NULL); if (rettv->v_type == VAR_FUNC) { - func_ref((char_u *)rettv->vval.v_string); + func_ref((char *)name); } + if (*what == 'n' && pt->pt_name == NULL && pt->pt_func != NULL) { + // use <SNR> instead of the byte code + name = printable_func_name(pt->pt_func); + } + rettv->vval.v_string = xstrdup(name); } else if (strcmp(what, "dict") == 0) { what_is_dict = true; if (pt->pt_dict != NULL) { @@ -2495,117 +2289,6 @@ static void f_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } -/// "getbufinfo()" function -static void f_getbufinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - buf_T *argbuf = NULL; - bool filtered = false; - bool sel_buflisted = false; - bool sel_bufloaded = false; - bool sel_bufmodified = false; - - tv_list_alloc_ret(rettv, kListLenMayKnow); - - // List of all the buffers or selected buffers - if (argvars[0].v_type == VAR_DICT) { - dict_T *sel_d = argvars[0].vval.v_dict; - - if (sel_d != NULL) { - dictitem_T *di; - - filtered = true; - - di = tv_dict_find(sel_d, S_LEN("buflisted")); - if (di != NULL && tv_get_number(&di->di_tv)) { - sel_buflisted = true; - } - - di = tv_dict_find(sel_d, S_LEN("bufloaded")); - if (di != NULL && tv_get_number(&di->di_tv)) { - sel_bufloaded = true; - } - di = tv_dict_find(sel_d, S_LEN("bufmodified")); - if (di != NULL && tv_get_number(&di->di_tv)) { - sel_bufmodified = true; - } - } - } else if (argvars[0].v_type != VAR_UNKNOWN) { - // Information about one buffer. Argument specifies the buffer - argbuf = tv_get_buf_from_arg(&argvars[0]); - if (argbuf == NULL) { - return; - } - } - - // Return information about all the buffers or a specified buffer - FOR_ALL_BUFFERS(buf) { - if (argbuf != NULL && argbuf != buf) { - continue; - } - if (filtered && ((sel_bufloaded && buf->b_ml.ml_mfp == NULL) - || (sel_buflisted && !buf->b_p_bl) - || (sel_bufmodified && !buf->b_changed))) { - continue; - } - - dict_T *const d = get_buffer_info(buf); - tv_list_append_dict(rettv->vval.v_list, d); - if (argbuf != NULL) { - return; - } - } -} - -/// 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); - rettv->vval.v_string = NULL; - - if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0 || end < start) { - if (retlist) { - tv_list_alloc_ret(rettv, 0); - } - return; - } - - if (retlist) { - if (start < 1) { - start = 1; - } - if (end > buf->b_ml.ml_line_count) { - end = buf->b_ml.ml_line_count; - } - tv_list_alloc_ret(rettv, end - start + 1); - while (start <= end) { - tv_list_append_string(rettv->vval.v_list, - (const char *)ml_get_buf(buf, start++, false), -1); - } - } else { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = ((start >= 1 && start <= buf->b_ml.ml_line_count) - ? xstrdup(ml_get_buf(buf, start, false)) : NULL); - } -} - -/// "getbufline()" function -static void f_getbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); - - const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); - const linenr_T end = (argvars[2].v_type == VAR_UNKNOWN - ? lnum - : tv_get_lnum_buf(&argvars[2], buf)); - - get_buffer_lines(buf, lnum, end, true, rettv); -} - /// "getchangelist()" function static void f_getchangelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -2729,15 +2412,6 @@ static void f_getcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fpt tv_dict_add_nr(dict, S_LEN("until"), last_csearch_until()); } -/// "getcmdwintype()" function -static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - rettv->vval.v_string = xmallocz(1); - rettv->vval.v_string[0] = (char)cmdwin_type; -} - /// `getcwd([{win}[, {tab}]])` function /// /// Every scope not specified implies the currently selected scope object. @@ -2842,13 +2516,13 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } FALLTHROUGH; // In global directory, just need to get OS CWD. case kCdScopeInvalid: // If called without any arguments, get OS CWD. - if (os_dirname((char_u *)cwd, MAXPATHL) == FAIL) { + if (os_dirname(cwd, MAXPATHL) == FAIL) { from = ""; // Return empty string on failure. } } if (from) { - STRLCPY(cwd, from, MAXPATHL); + xstrlcpy(cwd, from, MAXPATHL); } rettv->vval.v_string = xstrdup(cwd); @@ -2870,7 +2544,7 @@ static void f_getfontname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) static void f_getfperm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char *perm = NULL; - char_u flags[] = "rwx"; + char flags[] = "rwx"; const char *filename = tv_get_string(&argvars[0]); int32_t file_perm = os_getperm(filename); @@ -2878,7 +2552,7 @@ static void f_getfperm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) perm = xstrdup("---------"); for (int i = 0; i < 9; i++) { if (file_perm & (1 << (8 - i))) { - perm[i] = (char)flags[i % 3]; + perm[i] = flags[i % 3]; } } } @@ -2989,24 +2663,6 @@ static void f_getjumplist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } -/// "getline(lnum, [end])" function -static void f_getline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - linenr_T end; - bool retlist; - - const linenr_T lnum = tv_get_lnum(argvars); - if (argvars[1].v_type == VAR_UNKNOWN) { - end = lnum; - retlist = false; - } else { - end = tv_get_lnum(&argvars[1]); - retlist = true; - } - - get_buffer_lines(curbuf, lnum, end, retlist, rettv); -} - /// "getmarklist()" function static void f_getmarklist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -3094,19 +2750,19 @@ static void f_getpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// Returns zero on error. static int getreg_get_regname(typval_T *argvars) { - const char_u *strregname; + const char *strregname; if (argvars[0].v_type != VAR_UNKNOWN) { - strregname = (const char_u *)tv_get_string_chk(&argvars[0]); + strregname = tv_get_string_chk(&argvars[0]); if (strregname == NULL) { // type error; errmsg already given return 0; } } else { // Default to v:register - strregname = (char_u *)get_vim_var_str(VV_REG); + strregname = get_vim_var_str(VV_REG); } - return *strregname == 0 ? '"' : *strregname; + return *strregname == 0 ? '"' : (uint8_t)(*strregname); } /// "getreg()" function @@ -3165,38 +2821,6 @@ static void f_getregtype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_string = xstrdup(buf); } -/// "gettabinfo()" function -static void f_gettabinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - tabpage_T *tparg = NULL; - - tv_list_alloc_ret(rettv, (argvars[0].v_type == VAR_UNKNOWN - ? 1 - : kListLenMayKnow)); - - if (argvars[0].v_type != VAR_UNKNOWN) { - // Information about one tab page - tparg = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); - if (tparg == NULL) { - return; - } - } - - // Get information about a specific tab page or all tab pages - int tpnr = 0; - FOR_ALL_TABS(tp) { - tpnr++; - if (tparg != NULL && tp != tparg) { - continue; - } - dict_T *const d = get_tabpage_info(tp, tpnr); - tv_list_append_dict(rettv->vval.v_list, d); - if (tparg != NULL) { - return; - } - } -} - /// "gettagstack()" function static void f_gettagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -3214,41 +2838,6 @@ static void f_gettagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) get_tagstack(wp, rettv->vval.v_dict); } -/// "getwininfo()" function -static void f_getwininfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - win_T *wparg = NULL; - - tv_list_alloc_ret(rettv, kListLenMayKnow); - - if (argvars[0].v_type != VAR_UNKNOWN) { - wparg = win_id2wp((int)tv_get_number(&argvars[0])); - if (wparg == NULL) { - return; - } - } - - // Collect information about either all the windows across all the tab - // pages or one particular window. - int16_t tabnr = 0; - FOR_ALL_TABS(tp) { - tabnr++; - int16_t winnr = 0; - FOR_ALL_WINDOWS_IN_TAB(wp, tp) { - winnr++; - if (wparg != NULL && wp != wparg) { - continue; - } - dict_T *const d = get_win_info(wp, tabnr, winnr); - tv_list_append_dict(rettv->vval.v_list, d); - if (wparg != NULL) { - // found information about a specific window - return; - } - } - } -} - /// Dummy timer callback. Used by f_wait(). static void dummy_timer_due_cb(TimeWatcher *tw, void *data) {} @@ -3313,111 +2902,6 @@ static void f_wait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) time_watcher_close(tw, dummy_timer_close_cb); } -/// "win_screenpos()" function -static void f_win_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - tv_list_alloc_ret(rettv, 2); - const win_T *const wp = find_win_by_nr_or_id(&argvars[0]); - tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_winrow + 1); - tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1); -} - -/// Move the window wp into a new split of targetwin in a given direction -static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags) -{ - int height = wp->w_height; - win_T *oldwin = curwin; - - if (wp == targetwin || wp == aucmd_win) { - return; - } - - // Jump to the target window - if (curwin != targetwin) { - win_goto(targetwin); - } - - // Remove the old window and frame from the tree of frames - int dir; - (void)winframe_remove(wp, &dir, NULL); - win_remove(wp, NULL); - last_status(false); // may need to remove last status line - (void)win_comp_pos(); // recompute window positions - - // Split a window on the desired side and put the old window there - (void)win_split_ins(size, flags, wp, dir); - - // If splitting horizontally, try to preserve height - if (size == 0 && !(flags & WSP_VERT)) { - win_setheight_win(height, wp); - if (p_ea) { - win_equal(wp, true, 'v'); - } - } - - if (oldwin != curwin) { - win_goto(oldwin); - } -} - -/// "win_splitmove()" function -static void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - win_T *targetwin = find_win_by_nr_or_id(&argvars[1]); - - if (wp == NULL || targetwin == NULL || wp == targetwin - || !win_valid(wp) || !win_valid(targetwin) - || win_valid_floating(wp) || win_valid_floating(targetwin)) { - emsg(_(e_invalwindow)); - rettv->vval.v_number = -1; - return; - } - - int flags = 0, size = 0; - - if (argvars[2].v_type != VAR_UNKNOWN) { - dict_T *d; - dictitem_T *di; - - if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) { - emsg(_(e_invarg)); - return; - } - - d = argvars[2].vval.v_dict; - if (tv_dict_get_number(d, "vertical")) { - flags |= WSP_VERT; - } - if ((di = tv_dict_find(d, "rightbelow", -1)) != NULL) { - flags |= tv_get_number(&di->di_tv) ? WSP_BELOW : WSP_ABOVE; - } - size = (int)tv_dict_get_number(d, "size"); - } - - win_move_into_split(wp, targetwin, size, flags); -} - -/// "getwinpos({timeout})" function -static void f_getwinpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - tv_list_alloc_ret(rettv, 2); - tv_list_append_number(rettv->vval.v_list, -1); - tv_list_append_number(rettv->vval.v_list, -1); -} - -/// "getwinposx()" function -static void f_getwinposx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = -1; -} - -/// "getwinposy()" function -static void f_getwinposy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = -1; -} - /// "glob()" function static void f_glob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -3498,7 +2982,7 @@ static void f_globpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) const char *const file = tv_get_string_buf_chk(&argvars[1], buf1); if (file != NULL && !error) { garray_T ga; - ga_init(&ga, (int)sizeof(char_u *), 10); + ga_init(&ga, (int)sizeof(char *), 10); globpath((char *)tv_get_string(&argvars[0]), (char *)file, &ga, flags); if (rettv->v_type == VAR_STRING) { @@ -3526,6 +3010,19 @@ static void f_glob2regpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_string = (pat == NULL) ? NULL : file_pat_to_reg_pat(pat, NULL, NULL, false); } +/// "gettext()" function +static void f_gettext(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + if (argvars[0].v_type != VAR_STRING + || argvars[0].vval.v_string == NULL + || *argvars[0].vval.v_string == NUL) { + semsg(_(e_invarg2), tv_get_string(&argvars[0])); + } else { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = xstrdup(_(argvars[0].vval.v_string)); + } +} + /// "has()" function static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -3542,7 +3039,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) #ifdef UNIX "unix", #endif -#if defined(MSWIN) +#ifdef MSWIN "win32", #endif #ifdef _WIN64 @@ -3565,7 +3062,6 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) "cmdwin", "comments", "conceal", - "cscope", "cursorbind", "cursorshape", #ifdef DEBUG @@ -3586,9 +3082,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) "fork", #endif "gettext", -#if defined(HAVE_ICONV) "iconv", -#endif "insert_expand", "jumplist", "keymap", @@ -3616,8 +3110,6 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) "packages", "path_extra", "persistent_undo", - "postscript", - "printer", "profile", "pythonx", "reltime", @@ -3634,7 +3126,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) "spell", "syntax", #if !defined(UNIX) - "system", // TODO(SplinterOfChaos): This IS defined for UNIX! + "system", #endif "tablineat", "tag_binary", @@ -3870,13 +3362,11 @@ static void f_iconv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) const char *const str = tv_get_string(&argvars[0]); char buf1[NUMBUFLEN]; - char_u *const from = - (char_u *)enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[1], buf1))); + char *const from = enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[1], buf1))); char buf2[NUMBUFLEN]; - char_u *const to = - (char_u *)enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[2], buf2))); + char *const to = enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[2], buf2))); vimconv.vc_type = CONV_NONE; - convert_setup(&vimconv, (char *)from, (char *)to); + convert_setup(&vimconv, from, to); // If the encodings are equal, no conversion needed. if (vimconv.vc_type == CONV_NONE) { @@ -3942,33 +3432,36 @@ static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) emsg(_(e_listblobreq)); return; } + list_T *const l = argvars[0].vval.v_list; - if (l != NULL) { - listitem_T *item = tv_list_first(l); - if (argvars[2].v_type != VAR_UNKNOWN) { - bool error = false; + if (l == NULL) { + return; + } - // Start at specified item. - idx = tv_list_uidx(l, (int)tv_get_number_chk(&argvars[2], &error)); - if (error || idx == -1) { + listitem_T *item = tv_list_first(l); + if (argvars[2].v_type != VAR_UNKNOWN) { + bool error = false; + + // Start at specified item. + idx = tv_list_uidx(l, (int)tv_get_number_chk(&argvars[2], &error)); + if (error || idx == -1) { + item = NULL; + } else { + item = tv_list_find(l, (int)idx); + assert(item != NULL); + } + if (argvars[3].v_type != VAR_UNKNOWN) { + ic = !!tv_get_number_chk(&argvars[3], &error); + if (error) { item = NULL; - } else { - item = tv_list_find(l, (int)idx); - assert(item != NULL); - } - if (argvars[3].v_type != VAR_UNKNOWN) { - ic = !!tv_get_number_chk(&argvars[3], &error); - if (error) { - item = NULL; - } } } + } - for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) { - if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic, false)) { - rettv->vval.v_number = idx; - break; - } + for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) { + if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic, false)) { + rettv->vval.v_number = idx; + break; } } } @@ -4061,8 +3554,8 @@ static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) blob_T *const b = argvars[0].vval.v_blob; if (b == NULL - || var_check_lock(b->bv_lock, N_("insert() argument"), - TV_TRANSLATE)) { + || value_check_lock(b->bv_lock, N_("insert() argument"), + TV_TRANSLATE)) { return; } @@ -4089,16 +3582,16 @@ static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } ga_grow(&b->bv_ga, 1); - char_u *const p = (char_u *)b->bv_ga.ga_data; + uint8_t *const p = (uint8_t *)b->bv_ga.ga_data; memmove(p + before + 1, p + before, (size_t)(len - before)); - *(p + before) = (char_u)val; + *(p + before) = (uint8_t)val; b->bv_ga.ga_len++; tv_copy(&argvars[0], rettv); } else if (argvars[0].v_type != VAR_LIST) { semsg(_(e_listblobarg), "insert()"); - } else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - N_("insert() argument"), TV_TRANSLATE)) { + } else if (!value_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + N_("insert() argument"), TV_TRANSLATE)) { long before = 0; if (argvars[2].v_type != VAR_UNKNOWN) { before = tv_get_number_chk(&argvars[2], &error); @@ -4148,11 +3641,11 @@ static void f_islocked(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) lval_T lv; rettv->vval.v_number = -1; - const char_u *const end = (char_u *)get_lval((char *)tv_get_string(&argvars[0]), - NULL, - &lv, false, false, - GLV_NO_AUTOLOAD|GLV_READ_ONLY, - FNE_CHECK_START); + const char *const end = get_lval((char *)tv_get_string(&argvars[0]), + NULL, + &lv, false, false, + GLV_NO_AUTOLOAD|GLV_READ_ONLY, + FNE_CHECK_START); if (end != NULL && lv.ll_name != NULL) { if (*end != NUL) { semsg(_(e_trailing_arg), end); @@ -4880,9 +4373,9 @@ static void f_map(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) static void find_some_match(typval_T *const argvars, typval_T *const rettv, const SomeMatchType type) { - char_u *str = NULL; + char *str = NULL; long len = 0; - char_u *expr = NULL; + char *expr = NULL; regmatch_T regmatch; long start = 0; long nth = 1; @@ -4890,7 +4383,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, bool match = false; list_T *l = NULL; long idx = 0; - char_u *tofree = NULL; + char *tofree = NULL; // Make 'cpoptions' empty, the 'l' flag should not be used here. char *save_cpo = p_cpo; @@ -4927,8 +4420,8 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, } li = tv_list_first(l); } else { - expr = str = (char_u *)tv_get_string(&argvars[0]); - len = (long)STRLEN(str); + expr = str = (char *)tv_get_string(&argvars[0]); + len = (long)strlen(str); } char patbuf[NUMBUFLEN]; @@ -4987,8 +4480,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, break; } xfree(tofree); - tofree = expr = str = (char_u *)encode_tv2echo(TV_LIST_ITEM_TV(li), - NULL); + tofree = expr = str = encode_tv2echo(TV_LIST_ITEM_TV(li), NULL); if (str == NULL) { break; } @@ -5008,9 +4500,9 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, li = TV_LIST_ITEM_NEXT(l, li); idx++; } else { - startcol = (colnr_T)((char_u *)regmatch.startp[0] + startcol = (colnr_T)(regmatch.startp[0] + utfc_ptr2len(regmatch.startp[0]) - str); - if (startcol > (colnr_T)len || str + startcol <= (char_u *)regmatch.startp[0]) { + if (startcol > (colnr_T)len || str + startcol <= regmatch.startp[0]) { match = false; break; } @@ -5028,9 +4520,9 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, xfree(TV_LIST_ITEM_TV(li1)->vval.v_string); const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]); - TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz((const char *)regmatch.startp[0], rd); - TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)((char_u *)regmatch.startp[0] - expr); - TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)(regmatch.endp[0] - (char *)expr); + TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz(regmatch.startp[0], rd); + TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)(regmatch.startp[0] - expr); + TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)(regmatch.endp[0] - expr); if (l != NULL) { TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx; } @@ -5064,11 +4556,9 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, rettv->vval.v_number = idx; } else { if (type == kSomeMatch) { - rettv->vval.v_number = - (varnumber_T)((char_u *)regmatch.startp[0] - str); + rettv->vval.v_number = (varnumber_T)(regmatch.startp[0] - str); } else { - rettv->vval.v_number = - (varnumber_T)(regmatch.endp[0] - (char *)str); + rettv->vval.v_number = (varnumber_T)(regmatch.endp[0] - str); } rettv->vval.v_number += (varnumber_T)(str - expr); } @@ -5142,7 +4632,7 @@ static void max_min(const typval_T *const tv, typval_T *const rettv, const bool TV_LIST_ITER_CONST(tv->vval.v_list, li, { const varnumber_T i = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error); if (error) { - return; + return; // type error; errmsg already given } if (domax ? i > n : i < n) { n = i; @@ -5155,7 +4645,7 @@ static void max_min(const typval_T *const tv, typval_T *const rettv, const bool TV_DICT_ITER(tv->vval.v_dict, di, { const varnumber_T i = tv_get_number_chk(&di->di_tv, &error); if (error) { - return; + return; // type error; errmsg already given } if (domax ? i > n : i < n) { n = i; @@ -5165,6 +4655,7 @@ static void max_min(const typval_T *const tv, typval_T *const rettv, const bool semsg(_(e_listdictarg), domax ? "max()" : "min()"); return; } + rettv->vval.v_number = n; } @@ -5216,10 +4707,9 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) xfree(failed_dir); rettv->vval.v_number = FAIL; return; - } else { - rettv->vval.v_number = OK; - return; } + rettv->vval.v_number = OK; + return; } } rettv->vval.v_number = vim_mkdir_emsg(dir, prot); @@ -5470,7 +4960,7 @@ static void f_pathshorten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_string = NULL; } else { rettv->vval.v_string = xstrdup(p); - shorten_dir_len((char_u *)rettv->vval.v_string, trim_len); + shorten_dir_len(rettv->vval.v_string, trim_len); } } @@ -5657,10 +5147,11 @@ static void init_srand(uint32_t *const x) } } if (dev_urandom_state != OK) { - // Reading /dev/urandom doesn't work, fall back to time(). + // Reading /dev/urandom doesn't work, fall back to os_hrtime() XOR with process ID #endif // uncrustify:off - *x = (uint32_t)time(NULL); + *x = (uint32_t)os_hrtime(); + *x ^= (uint32_t)os_get_pid(); #ifndef MSWIN } #endif @@ -5820,13 +5311,16 @@ static void f_range(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } if (stride == 0) { emsg(_("E726: Stride is zero")); - } else if (stride > 0 ? end + 1 < start : end - 1 > start) { + return; + } + if (stride > 0 ? end + 1 < start : end - 1 > start) { emsg(_("E727: Start past end")); - } else { - tv_list_alloc_ret(rettv, (end - start) / stride); - for (varnumber_T i = start; stride > 0 ? i <= end : i >= end; i += stride) { - tv_list_append_number(rettv->vval.v_list, i); - } + return; + } + + tv_list_alloc_ret(rettv, (end - start) / stride); + for (varnumber_T i = start; stride > 0 ? i <= end : i >= end; i += stride) { + tv_list_append_number(rettv->vval.v_list, i); } } @@ -5885,17 +5379,17 @@ static void f_readdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) ga_clear_strings(&ga); } -/// "readfile()" function -static void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +/// "readfile()" or "readblob()" function +static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_blob) { bool binary = false; - bool blob = false; + bool blob = always_blob; FILE *fd; char buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1 int io_size = sizeof(buf); char *prev = NULL; // previously read bytes, if any - long prevlen = 0; // length of data in prev - long prevsize = 0; // size of prev buffer + ptrdiff_t prevlen = 0; // length of data in prev + ptrdiff_t prevsize = 0; // size of prev buffer long maxline = MAXLNUM; if (argvars[1].v_type != VAR_UNKNOWN) { @@ -5949,7 +5443,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) for (p = buf, start = buf; p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); p++) { - if (*p == '\n' || readlen <= 0) { + if (readlen <= 0 || *p == '\n') { char *s = NULL; size_t len = (size_t)(p - start); @@ -6024,8 +5518,8 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) int adjust_prevlen = 0; if (dest < buf) { // -V782 - adjust_prevlen = (int)(buf - dest); // -V782 // adjust_prevlen must be 1 or 2. + adjust_prevlen = (int)(buf - dest); // -V782 dest = buf; } if (readlen > p - buf + 1) { @@ -6050,17 +5544,17 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // small, to avoid repeatedly 'allocing' large and // 'reallocing' small. if (prevsize == 0) { - prevsize = (long)(p - start); + prevsize = p - start; } else { - long grow50pc = (prevsize * 3) / 2; - long growmin = (long)((p - start) * 2 + prevlen); + ptrdiff_t grow50pc = (prevsize * 3) / 2; + ptrdiff_t growmin = (p - start) * 2 + prevlen; prevsize = grow50pc > growmin ? grow50pc : growmin; } prev = xrealloc(prev, (size_t)prevsize); } // Add the line part to end of "prev". memmove(prev + prevlen, start, (size_t)(p - start)); - prevlen += (long)(p - start); + prevlen += p - start; } } // while @@ -6068,6 +5562,18 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) fclose(fd); } +/// "readblob()" function +static void f_readblob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + read_file_or_blob(argvars, rettv, true); +} + +/// "readfile()" function +static void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + read_file_or_blob(argvars, rettv, false); +} + /// "getreginfo()" function static void f_getreginfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -6377,7 +5883,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) q[-1] = NUL; q = path_tail(p); } - if (q > p && !path_is_absolute((const char_u *)buf)) { + if (q > p && !path_is_absolute(buf)) { // Symlink is relative to directory of argument. Replace the // symlink with the resolved name in the same directory. const size_t p_len = strlen(p); @@ -6457,7 +5963,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) # endif #endif - simplify_filename((char_u *)rettv->vval.v_string); + simplify_filename(rettv->vval.v_string); } /// "reverse({list})" function @@ -6468,7 +5974,7 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) const int len = tv_blob_len(b); for (int i = 0; i < len / 2; i++) { - const char_u tmp = tv_blob_get(b, i); + const uint8_t tmp = tv_blob_get(b, i); tv_blob_set(b, i, tv_blob_get(b, len - i - 1)); tv_blob_set(b, len - i - 1, tmp); } @@ -6477,8 +5983,8 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) semsg(_(e_listblobarg), "reverse()"); } else { list_T *const l = argvars[0].vval.v_list; - if (!var_check_lock(tv_list_locked(l), N_("reverse() argument"), - TV_TRANSLATE)) { + if (!value_check_lock(tv_list_locked(l), N_("reverse() argument"), + TV_TRANSLATE)) { tv_list_reverse(l); tv_list_set_ret(rettv, l); } @@ -6598,55 +6104,57 @@ static int get_search_arg(typval_T *varp, int *flagsp) { int dir = FORWARD; - if (varp->v_type != VAR_UNKNOWN) { - char nbuf[NUMBUFLEN]; - const char *flags = tv_get_string_buf_chk(varp, nbuf); - if (flags == NULL) { - return 0; // Type error; errmsg already given. - } - int mask; - while (*flags != NUL) { - switch (*flags) { - case 'b': - dir = BACKWARD; break; - case 'w': - p_ws = true; break; - case 'W': - p_ws = false; break; - default: - mask = 0; - if (flagsp != NULL) { - switch (*flags) { - case 'c': - mask = SP_START; break; - case 'e': - mask = SP_END; break; - case 'm': - mask = SP_RETCOUNT; break; - case 'n': - mask = SP_NOMOVE; break; - case 'p': - mask = SP_SUBPAT; break; - case 'r': - mask = SP_REPEAT; break; - case 's': - mask = SP_SETPCMARK; break; - case 'z': - mask = SP_COLUMN; break; - } - } - if (mask == 0) { - semsg(_(e_invarg2), flags); - dir = 0; - } else { - *flagsp |= mask; + if (varp->v_type == VAR_UNKNOWN) { + return FORWARD; + } + + char nbuf[NUMBUFLEN]; + const char *flags = tv_get_string_buf_chk(varp, nbuf); + if (flags == NULL) { + return 0; // Type error; errmsg already given. + } + int mask; + while (*flags != NUL) { + switch (*flags) { + case 'b': + dir = BACKWARD; break; + case 'w': + p_ws = true; break; + case 'W': + p_ws = false; break; + default: + mask = 0; + if (flagsp != NULL) { + switch (*flags) { + case 'c': + mask = SP_START; break; + case 'e': + mask = SP_END; break; + case 'm': + mask = SP_RETCOUNT; break; + case 'n': + mask = SP_NOMOVE; break; + case 'p': + mask = SP_SUBPAT; break; + case 'r': + mask = SP_REPEAT; break; + case 's': + mask = SP_SETPCMARK; break; + case 'z': + mask = SP_COLUMN; break; } } - if (dir == 0) { - break; + if (mask == 0) { + semsg(_(e_invarg2), flags); + dir = 0; + } else { + *flagsp |= mask; } - flags++; } + if (dir == 0) { + break; + } + flags++; } return dir; } @@ -6718,7 +6226,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) // Repeat until {skip} returns false. for (;;) { subpatnum - = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1, options, RE_SEARCH, &sia); + = searchit(curwin, curbuf, &pos, NULL, dir, (char *)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)) { @@ -6729,7 +6237,9 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) // didn't find it or no skip argument break; } - firstpos = pos; + if (firstpos.lnum == 0) { + firstpos = pos; + } // If the skip expression matches, ignore this match. { @@ -6811,12 +6321,15 @@ static void f_rpcnotify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) ADD(args, vim_to_object(tv)); } - if (!rpc_send_event((uint64_t)argvars[0].vval.v_number, - tv_get_string(&argvars[1]), args)) { + bool ok = rpc_send_event((uint64_t)argvars[0].vval.v_number, + tv_get_string(&argvars[1]), args); + + api_free_array(args); + + if (!ok) { semsg(_(e_invarg2), "Channel doesn't exist"); return; } - rettv->vval.v_number = 1; } @@ -6955,7 +6468,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // Allocate extra memory for the argument vector and the NULL pointer int argvl = argsl + 2; - char **argv = xmalloc(sizeof(char_u *) * (size_t)argvl); + char **argv = xmalloc(sizeof(char *) * (size_t)argvl); // Copy program name argv[0] = xstrdup(argvars[0].vval.v_string); @@ -7131,7 +6644,7 @@ static void f_searchdecl(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } if (!error && name != NULL) { - rettv->vval.v_number = find_decl((char_u *)name, strlen(name), locally, + rettv->vval.v_number = find_decl((char *)name, strlen(name), locally, thisblock, SEARCH_KEEP) == FAIL; } } @@ -7266,14 +6779,14 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir // Make two search patterns: start/end (pat2, for in nested pairs) and // start/middle/end (pat3, for the top pair). const size_t pat2_len = strlen(spat) + strlen(epat) + 17; - char_u *pat2 = xmalloc(pat2_len); + char *pat2 = xmalloc(pat2_len); const size_t pat3_len = strlen(spat) + strlen(mpat) + strlen(epat) + 25; - char_u *pat3 = xmalloc(pat3_len); - snprintf((char *)pat2, pat2_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat); + char *pat3 = xmalloc(pat3_len); + snprintf(pat2, pat2_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat); if (*mpat == NUL) { STRCPY(pat3, pat2); } else { - snprintf((char *)pat3, pat3_len, + snprintf(pat3, pat3_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat, mpat); } if (flags & SP_START) { @@ -7290,7 +6803,7 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir clearpos(&firstpos); pos_T foundpos; clearpos(&foundpos); - char_u *pat = pat3; + char *pat = pat3; for (;;) { searchit_arg_T sia = { .sa_stop_lnum = lnum_stop, @@ -7449,9 +6962,8 @@ static void f_serverstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (argvars[0].v_type != VAR_STRING) { emsg(_(e_invarg)); return; - } else { - address = xstrdup(tv_get_string(argvars)); } + address = xstrdup(tv_get_string(argvars)); } else { address = server_address_new(NULL); } @@ -7498,21 +7010,6 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } -/// "setbufline()" function -static void f_setbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - linenr_T lnum; - buf_T *buf; - - buf = tv_get_buf(&argvars[0], false); - if (buf == NULL) { - rettv->vval.v_number = 1; // FAIL - } else { - lnum = tv_get_lnum_buf(&argvars[1], buf); - set_buffer_lines(buf, lnum, false, &argvars[2], rettv); - } -} - /// 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. @@ -7522,31 +7019,35 @@ static void set_position(typval_T *argvars, typval_T *rettv, bool charpos) rettv->vval.v_number = -1; const char *const name = tv_get_string_chk(argvars); - if (name != NULL) { - pos_T pos; - int fnum; - 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, NULL) == OK) { - rettv->vval.v_number = 0; - } - } else { - emsg(_(e_invarg)); - } + if (name == NULL) { + return; + } + + pos_T pos; + int fnum; + if (list2fpos(&argvars[1], &pos, &fnum, &curswant, charpos) != OK) { + return; + } + + 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, NULL) == OK) { + rettv->vval.v_number = 0; } + } else { + emsg(_(e_invarg)); } } @@ -7564,23 +7065,25 @@ static void f_setcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fpt } dict_T *d = argvars[0].vval.v_dict; - if (d != NULL) { - char_u *const csearch = (char_u *)tv_dict_get_string(d, "char", false); - if (csearch != NULL) { - int pcc[MAX_MCO]; - const int c = utfc_ptr2char((char *)csearch, pcc); - set_last_csearch(c, csearch, utfc_ptr2len((char *)csearch)); - } + if (d == NULL) { + return; + } - dictitem_T *di = tv_dict_find(d, S_LEN("forward")); - if (di != NULL) { - set_csearch_direction(tv_get_number(&di->di_tv) ? FORWARD : BACKWARD); - } + char *const csearch = tv_dict_get_string(d, "char", false); + if (csearch != NULL) { + int pcc[MAX_MCO]; + const int c = utfc_ptr2char(csearch, pcc); + set_last_csearch(c, csearch, utfc_ptr2len(csearch)); + } - di = tv_dict_find(d, S_LEN("until")); - if (di != NULL) { - set_csearch_until(!!tv_get_number(&di->di_tv)); - } + dictitem_T *di = tv_dict_find(d, S_LEN("forward")); + if (di != NULL) { + set_csearch_direction(tv_get_number(&di->di_tv) ? FORWARD : BACKWARD); + } + + di = tv_dict_find(d, S_LEN("until")); + if (di != NULL) { + set_csearch_until(!!tv_get_number(&di->di_tv)); } } @@ -7636,13 +7139,6 @@ static void f_setfperm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_number = os_setperm(fname, mode) == OK; } -/// "setline()" function -static void f_setline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - linenr_T lnum = tv_get_lnum(&argvars[0]); - set_buffer_lines(curbuf, lnum, false, &argvars[1], rettv); -} - /// "setpos()" function static void f_setpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -7888,8 +7384,7 @@ static void f_shellescape(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) const bool do_special = non_zero_arg(&argvars[1]); rettv->vval.v_string = - (char *)vim_strsave_shellescape((const char_u *)tv_get_string(&argvars[0]), do_special, - do_special); + vim_strsave_shellescape(tv_get_string(&argvars[0]), do_special, do_special); rettv->v_type = VAR_STRING; } @@ -7914,7 +7409,7 @@ static void f_simplify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const p = tv_get_string(&argvars[0]); rettv->vval.v_string = xstrdup(p); - simplify_filename((char_u *)rettv->vval.v_string); // Simplify in place. + simplify_filename(rettv->vval.v_string); // Simplify in place. rettv->v_type = VAR_STRING; } @@ -8058,7 +7553,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, EvalFuncData fptr if (str != NULL) { // Check the argument for spelling. while (*str != NUL) { - len = spell_check(curwin, (char_u *)str, &attr, &capcol, false); + len = spell_check(curwin, (char *)str, &attr, &capcol, false); if (attr != HLF_COUNT) { word = str; break; @@ -8117,7 +7612,7 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr maxcount = 25; } - spell_suggest_list(&ga, (char_u *)str, maxcount, need_capital, false); + spell_suggest_list(&ga, (char *)str, maxcount, need_capital, false); f_spellsuggest_return: tv_list_alloc_ret(rettv, (ptrdiff_t)ga.ga_len); @@ -8148,7 +7643,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) typeerr = true; } if (argvars[2].v_type != VAR_UNKNOWN) { - keepempty = (bool)tv_get_number_chk(&argvars[2], &typeerr); + keepempty = (bool)tv_get_bool_chk(&argvars[2], &typeerr); } } if (pat == NULL || *pat == NUL) { @@ -8173,7 +7668,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (*str == NUL) { match = false; // Empty item at the end. } else { - match = vim_regexec_nl(®match, (char_u *)str, col); + match = vim_regexec_nl(®match, (char *)str, col); } const char *end; if (match) { @@ -8259,10 +7754,10 @@ static void f_str2float(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) static void f_str2list(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, kListLenUnknown); - const char_u *p = (const char_u *)tv_get_string(&argvars[0]); + const char *p = tv_get_string(&argvars[0]); - for (; *p != NUL; p += utf_ptr2len((char *)p)) { - tv_list_append_number(rettv->vval.v_list, utf_ptr2char((char *)p)); + for (; *p != NUL; p += utf_ptr2len(p)) { + tv_list_append_number(rettv->vval.v_list, utf_ptr2char(p)); } } @@ -8278,15 +7773,15 @@ static void f_str2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) emsg(_(e_invarg)); return; } - if (argvars[2].v_type != VAR_UNKNOWN && tv_get_number(&argvars[2])) { + if (argvars[2].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[2])) { what |= STR2NR_QUOTE; } } - char_u *p = (char_u *)skipwhite(tv_get_string(&argvars[0])); + char *p = skipwhite(tv_get_string(&argvars[0])); bool isneg = (*p == '-'); if (*p == '+' || *p == '-') { - p = (char_u *)skipwhite((char *)p + 1); + p = skipwhite(p + 1); } switch (base) { case 2: @@ -8300,7 +7795,7 @@ static void f_str2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) break; } varnumber_T n; - vim_str2nr((char *)p, NULL, NULL, what, &n, NULL, 0, false); + vim_str2nr(p, NULL, NULL, what, &n, NULL, 0, false); // Text after the number is silently ignored. if (isneg) { rettv->vval.v_number = -n; @@ -8332,15 +7827,13 @@ static void f_strftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) vimconv_T conv; conv.vc_type = CONV_NONE; - char *enc = (char *)enc_locale(); + char *enc = enc_locale(); convert_setup(&conv, p_enc, enc); if (conv.vc_type != CONV_NONE) { p = string_convert(&conv, p, NULL); } char result_buf[256]; - if (p != NULL) { - (void)strftime(result_buf, sizeof(result_buf), p, curtime_ptr); - } else { + if (p == NULL || strftime(result_buf, sizeof(result_buf), p, curtime_ptr) == 0) { result_buf[0] = NUL; } @@ -8433,26 +7926,38 @@ static void f_strlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0])); } -/// "strchars()" function -static void f_strchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +static void strchar_common(typval_T *argvars, typval_T *rettv, bool skipcc) { const char *s = tv_get_string(&argvars[0]); - int skipcc = 0; varnumber_T len = 0; - int (*func_mb_ptr2char_adv)(const char_u **pp); + int (*func_mb_ptr2char_adv)(const char **pp); + + func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv; + while (*s != NUL) { + func_mb_ptr2char_adv(&s); + len++; + } + rettv->vval.v_number = len; +} + +/// "strcharlen()" function +static void f_strcharlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + strchar_common(argvars, rettv, true); +} + +/// "strchars()" function +static void f_strchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + int skipcc = false; if (argvars[1].v_type != VAR_UNKNOWN) { - skipcc = (int)tv_get_number_chk(&argvars[1], NULL); + skipcc = (int)tv_get_bool(&argvars[1]); } if (skipcc < 0 || skipcc > 1) { - emsg(_(e_invarg)); + semsg(_(e_using_number_as_bool_nr), skipcc); } else { - func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv; - while (*s != NUL) { - func_mb_ptr2char_adv((const char_u **)&s); - len++; - } - rettv->vval.v_number = len; + strchar_common(argvars, rettv, skipcc); } } @@ -8592,7 +8097,7 @@ static void f_strptime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) vimconv_T conv = { .vc_type = CONV_NONE, }; - char *enc = (char *)enc_locale(); + char *enc = enc_locale(); convert_setup(&conv, p_enc, enc); if (conv.vc_type != CONV_NONE) { fmt = string_convert(&conv, fmt, NULL); @@ -8866,7 +8371,7 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, EvalFuncData fptr int syntax_flags = 0; int cchar; int matchid = 0; - char_u str[NUMBUFLEN]; + char str[NUMBUFLEN]; tv_list_set_ret(rettv, NULL); @@ -8890,7 +8395,7 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, EvalFuncData fptr : curwin->w_p_lcs_chars.conceal; } if (cchar != NUL) { - utf_char2bytes(cchar, (char *)str); + utf_char2bytes(cchar, str); } } } @@ -8898,7 +8403,7 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, EvalFuncData fptr tv_list_alloc_ret(rettv, 3); tv_list_append_number(rettv->vval.v_list, (syntax_flags & HL_CONCEAL) != 0); // -1 to auto-determine strlen - tv_list_append_string(rettv->vval.v_list, (const char *)str, -1); + tv_list_append_string(rettv->vval.v_list, str, -1); tv_list_append_number(rettv->vval.v_list, matchid); } @@ -8959,105 +8464,6 @@ static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, EvalFuncData fp } } -/// "tabpagenr()" function -static void f_tabpagenr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - int nr = 1; - - if (argvars[0].v_type != VAR_UNKNOWN) { - const char *const arg = tv_get_string_chk(&argvars[0]); - nr = 0; - if (arg != NULL) { - if (strcmp(arg, "$") == 0) { - nr = tabpage_index(NULL) - 1; - } else if (strcmp(arg, "#") == 0) { - nr = valid_tabpage(lastused_tabpage) ? tabpage_index(lastused_tabpage) : 0; - } else { - semsg(_(e_invexpr2), arg); - } - } - } else { - nr = tabpage_index(curtab); - } - rettv->vval.v_number = nr; -} - -/// Common code for tabpagewinnr() and winnr(). -static int get_winnr(tabpage_T *tp, typval_T *argvar) -{ - int nr = 1; - - win_T *twin = (tp == curtab) ? curwin : tp->tp_curwin; - if (argvar->v_type != VAR_UNKNOWN) { - bool invalid_arg = false; - const char *const arg = tv_get_string_chk(argvar); - if (arg == NULL) { - nr = 0; // Type error; errmsg already given. - } else if (strcmp(arg, "$") == 0) { - twin = (tp == curtab) ? lastwin : tp->tp_lastwin; - } else if (strcmp(arg, "#") == 0) { - twin = (tp == curtab) ? prevwin : tp->tp_prevwin; - if (twin == NULL) { - nr = 0; - } - } else { - // Extract the window count (if specified). e.g. winnr('3j') - char *endp; - long count = strtol((char *)arg, &endp, 10); - if (count <= 0) { - // if count is not specified, default to 1 - count = 1; - } - if (endp != NULL && *endp != '\0') { - if (strequal(endp, "j")) { - twin = win_vert_neighbor(tp, twin, false, count); - } else if (strequal(endp, "k")) { - twin = win_vert_neighbor(tp, twin, true, count); - } else if (strequal(endp, "h")) { - twin = win_horz_neighbor(tp, twin, true, count); - } else if (strequal(endp, "l")) { - twin = win_horz_neighbor(tp, twin, false, count); - } else { - invalid_arg = true; - } - } else { - invalid_arg = true; - } - } - - if (invalid_arg) { - semsg(_(e_invexpr2), arg); - nr = 0; - } - } - - if (nr > 0) { - for (win_T *wp = (tp == curtab) ? firstwin : tp->tp_firstwin; - wp != twin; wp = wp->w_next) { - if (wp == NULL) { - // didn't find it in this tabpage - nr = 0; - break; - } - nr++; - } - } - return nr; -} - -/// "tabpagewinnr()" function -static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - int nr = 1; - tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0])); - if (tp == NULL) { - nr = 0; - } else { - nr = get_winnr(tp, &argvars[1]); - } - rettv->vval.v_number = nr; -} - /// "tagfiles()" function static void f_tagfiles(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -9090,7 +8496,7 @@ static void f_taglist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) fname = tv_get_string(&argvars[1]); } (void)get_tags(tv_list_alloc_ret(rettv, kListLenUnknown), - (char_u *)tag_pattern, (char_u *)fname); + (char *)tag_pattern, (char *)fname); } /// "tempname()" function @@ -9184,9 +8590,9 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) int pid = chan->stream.pty.process.pid; // "./…" => "/home/foo/…" - vim_FullName(cwd, (char *)NameBuff, sizeof(NameBuff), false); + vim_FullName(cwd, NameBuff, sizeof(NameBuff), false); // "/home/foo/…" => "~/…" - size_t len = home_replace(NULL, (char *)NameBuff, (char *)IObuff, sizeof(IObuff), true); + size_t len = home_replace(NULL, NameBuff, IObuff, sizeof(IObuff), true); // Trim slash. if (len != 1 && (IObuff[len - 1] == '\\' || IObuff[len - 1] == '/')) { IObuff[len - 1] = '\0'; @@ -9199,14 +8605,14 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } // Terminal URI: "term://$CWD//$PID:$CMD" - snprintf((char *)NameBuff, sizeof(NameBuff), "term://%s//%d:%s", - (char *)IObuff, pid, cmd); + snprintf(NameBuff, sizeof(NameBuff), "term://%s//%d:%s", + IObuff, pid, cmd); // at this point the buffer has no terminal instance associated yet, so unset // the 'swapfile' option to ensure no swap file will be created curbuf->b_p_swf = false; apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, false, curbuf); - (void)setfname(curbuf, (char *)NameBuff, NULL, true); + (void)setfname(curbuf, NameBuff, NULL, true); apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf); // Save the job id and pid in b:terminal_job_{id,pid} @@ -9357,7 +8763,7 @@ static void f_tr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) int fromlen; for (const char *p = fromstr; *p != NUL; p += fromlen) { fromlen = utfc_ptr2len(p); - if (fromlen == inlen && STRNCMP(in_str, p, inlen) == 0) { + if (fromlen == inlen && strncmp(in_str, p, (size_t)inlen) == 0) { int tolen; for (p = tostr; *p != NUL; p += tolen) { tolen = utfc_ptr2len(p); @@ -9612,276 +9018,6 @@ static void f_wildmenumode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr } } -/// "win_findbuf()" function -static void f_win_findbuf(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - tv_list_alloc_ret(rettv, kListLenMayKnow); - win_findbuf(argvars, rettv->vval.v_list); -} - -/// "win_getid()" function -static void f_win_getid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = win_getid(argvars); -} - -/// "win_gettype(nr)" function -static void f_win_gettype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - win_T *wp = curwin; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (argvars[0].v_type != VAR_UNKNOWN) { - wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - rettv->vval.v_string = xstrdup("unknown"); - return; - } - } - if (wp == aucmd_win) { - rettv->vval.v_string = xstrdup("autocmd"); - } else if (wp->w_p_pvw) { - rettv->vval.v_string = xstrdup("preview"); - } else if (wp->w_floating) { - rettv->vval.v_string = xstrdup("popup"); - } else if (wp == curwin && cmdwin_type != 0) { - rettv->vval.v_string = xstrdup("command"); - } else if (bt_quickfix(wp->w_buffer)) { - rettv->vval.v_string = xstrdup((wp->w_llist_ref != NULL ? "loclist" : "quickfix")); - } -} - -/// "win_gotoid()" function -static void f_win_gotoid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - int id = (int)tv_get_number(&argvars[0]); - - if (cmdwin_type != 0) { - emsg(_(e_cmdwin)); - return; - } - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->handle == id) { - goto_tabpage_win(tp, wp); - rettv->vval.v_number = 1; - return; - } - } -} - -/// "win_id2tabwin()" function -static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - win_id2tabwin(argvars, rettv); -} - -/// "win_id2win()" function -static void f_win_id2win(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = win_id2win(argvars); -} - -/// "win_move_separator()" function -static void f_win_move_separator(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = false; - - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL || wp->w_floating) { - return; - } - - int 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, EvalFuncData 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, EvalFuncData fptr) -{ - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - rettv->vval.v_number = -1; - } else { - rettv->vval.v_number = wp->w_buffer->b_fnum; - } -} - -/// "wincol()" function -static void f_wincol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - validate_cursor(); - rettv->vval.v_number = curwin->w_wcol + 1; -} - -/// "winheight(nr)" function -static void f_winheight(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - rettv->vval.v_number = -1; - } else { - rettv->vval.v_number = wp->w_height_inner; - } -} - -/// "winlayout()" function -static void f_winlayout(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - tabpage_T *tp; - - tv_list_alloc_ret(rettv, 2); - - if (argvars[0].v_type == VAR_UNKNOWN) { - tp = curtab; - } else { - tp = find_tabpage((int)tv_get_number(&argvars[0])); - if (tp == NULL) { - return; - } - } - - get_framelayout(tp->tp_topframe, rettv->vval.v_list, true); -} - -/// "winline()" function -static void f_winline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - validate_cursor(); - rettv->vval.v_number = curwin->w_wrow + 1; -} - -/// "winnr()" function -static void f_winnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = get_winnr(curtab, &argvars[0]); -} - -/// "winrestcmd()" function -static void f_winrestcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - char_u buf[50]; - - garray_T ga; - ga_init(&ga, (int)sizeof(char), 70); - - // Do this twice to handle some window layouts properly. - for (int i = 0; i < 2; i++) { - int winnr = 1; - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - snprintf((char *)buf, sizeof(buf), "%dresize %d|", winnr, - wp->w_height); - ga_concat(&ga, (char *)buf); - snprintf((char *)buf, sizeof(buf), "vert %dresize %d|", winnr, - wp->w_width); - ga_concat(&ga, (char *)buf); - winnr++; - } - } - ga_append(&ga, NUL); - - rettv->vval.v_string = ga.ga_data; - rettv->v_type = VAR_STRING; -} - -/// "winrestview()" function -static void f_winrestview(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - dict_T *dict = argvars[0].vval.v_dict; - - if (argvars[0].v_type != VAR_DICT || dict == NULL) { - emsg(_(e_invarg)); - } else { - dictitem_T *di; - if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) { - curwin->w_cursor.lnum = (linenr_T)tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) { - curwin->w_cursor.col = (colnr_T)tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) { - curwin->w_cursor.coladd = (colnr_T)tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) { - curwin->w_curswant = (colnr_T)tv_get_number(&di->di_tv); - curwin->w_set_curswant = false; - } - if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) { - set_topline(curwin, (linenr_T)tv_get_number(&di->di_tv)); - } - if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) { - curwin->w_topfill = (int)tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) { - curwin->w_leftcol = (colnr_T)tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) { - curwin->w_skipcol = (colnr_T)tv_get_number(&di->di_tv); - } - - check_cursor(); - win_new_height(curwin, curwin->w_height); - win_new_width(curwin, curwin->w_width); - changed_window_setting(); - - if (curwin->w_topline <= 0) { - curwin->w_topline = 1; - } - if (curwin->w_topline > curbuf->b_ml.ml_line_count) { - curwin->w_topline = curbuf->b_ml.ml_line_count; - } - check_topfill(curwin, true); - } -} - -/// "winsaveview()" function -static void f_winsaveview(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - tv_dict_alloc_ret(rettv); - dict_T *dict = rettv->vval.v_dict; - - tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)curwin->w_cursor.lnum); - tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)curwin->w_cursor.col); - tv_dict_add_nr(dict, S_LEN("coladd"), (varnumber_T)curwin->w_cursor.coladd); - update_curswant(); - tv_dict_add_nr(dict, S_LEN("curswant"), (varnumber_T)curwin->w_curswant); - - tv_dict_add_nr(dict, S_LEN("topline"), (varnumber_T)curwin->w_topline); - tv_dict_add_nr(dict, S_LEN("topfill"), (varnumber_T)curwin->w_topfill); - tv_dict_add_nr(dict, S_LEN("leftcol"), (varnumber_T)curwin->w_leftcol); - tv_dict_add_nr(dict, S_LEN("skipcol"), (varnumber_T)curwin->w_skipcol); -} - -/// "winwidth(nr)" function -static void f_winwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - rettv->vval.v_number = -1; - } else { - rettv->vval.v_number = wp->w_width_inner; - } -} - /// "windowsversion()" function static void f_windowsversion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -9920,6 +9056,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) bool binary = false; bool append = false; bool do_fsync = !!p_fs; + bool mkdir_p = false; if (argvars[2].v_type != VAR_UNKNOWN) { const char *const flags = tv_get_string_chk(&argvars[2]); if (flags == NULL) { @@ -9935,6 +9072,8 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) do_fsync = true; break; case 'S': do_fsync = false; break; + case 'p': + mkdir_p = true; break; default: // Using %s, p and not %c, *p to preserve multibyte characters semsg(_("E5060: Unknown flag: %s"), p); @@ -9954,6 +9093,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) emsg(_("E482: Can't open file with an empty name")); } else if ((error = file_open(&fp, fname, ((append ? kFileAppend : kFileTruncate) + | (mkdir_p ? kFileMkDir : kFileCreate) | kFileCreate), 0666)) != 0) { semsg(_("E482: Can't open file %s for writing: %s"), fname, os_strerror(error)); diff --git a/src/nvim/eval/funcs.h b/src/nvim/eval/funcs.h index adff0b2441..1ae031a952 100644 --- a/src/nvim/eval/funcs.h +++ b/src/nvim/eval/funcs.h @@ -1,9 +1,14 @@ #ifndef NVIM_EVAL_FUNCS_H #define NVIM_EVAL_FUNCS_H +#include <stdbool.h> +#include <stdint.h> + #include "nvim/api/private/dispatch.h" #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/types.h" /// Prototype of C function that implements VimL function typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, EvalFuncData data); diff --git a/src/nvim/eval/gc.c b/src/nvim/eval/gc.c index 633e6abacf..6a54c4ddc1 100644 --- a/src/nvim/eval/gc.c +++ b/src/nvim/eval/gc.c @@ -1,11 +1,12 @@ // 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 <stddef.h> + #include "nvim/eval/gc.h" -#include "nvim/eval/typval.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "eval/gc.c.generated.h" +# include "eval/gc.c.generated.h" // IWYU pragma: export #endif /// Head of list of all dictionaries diff --git a/src/nvim/eval/gc.h b/src/nvim/eval/gc.h index c2e862e469..3185750c3b 100644 --- a/src/nvim/eval/gc.h +++ b/src/nvim/eval/gc.h @@ -2,6 +2,7 @@ #define NVIM_EVAL_GC_H #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" extern dict_T *gc_first_dict; extern list_T *gc_first_list; diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index a0b06aaaf4..05b4737206 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -28,9 +28,9 @@ #include "nvim/lua/executor.h" #include "nvim/macros.h" #include "nvim/mbyte.h" +#include "nvim/mbyte_defs.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" @@ -43,9 +43,11 @@ static char e_string_required_for_argument_nr[] = N_("E1174: String required for argument %d"); static char e_non_empty_string_required_for_argument_nr[] - = N_("E1142: Non-empty string required for argument %d"); + = N_("E1175: Non-empty string required for argument %d"); static char e_number_required_for_argument_nr[] = N_("E1210: Number required for argument %d"); +static char e_string_or_list_required_for_argument_nr[] + = N_("E1222: String or List required for argument %d"); bool tv_in_free_unref_items = false; @@ -324,10 +326,12 @@ void tv_list_free_list(list_T *const l) void tv_list_free(list_T *const l) FUNC_ATTR_NONNULL_ALL { - if (!tv_in_free_unref_items) { - tv_list_free_contents(l); - tv_list_free_list(l); + if (tv_in_free_unref_items) { + return; } + + tv_list_free_contents(l); + tv_list_free_list(l); } /// Unreference a list @@ -896,8 +900,8 @@ void tv_list_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) list_T *l; bool error = false; - if (var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - arg_errmsg, TV_TRANSLATE)) { + if (value_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + arg_errmsg, TV_TRANSLATE)) { return; } @@ -1094,7 +1098,9 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) tv_clear(&argv[1]); if (res == FAIL) { + // XXX: ITEM_COMPARE_FAIL is unused res = ITEM_COMPARE_FAIL; + sortinfo->item_compare_func_err = true; } else { res = (int)tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err); if (res > 0) { @@ -1150,7 +1156,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) semsg(_(e_listarg), sort ? "sort()" : "uniq()"); } else { list_T *const l = argvars[0].vval.v_list; - if (var_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { + if (value_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { goto theend; } tv_list_set_ret(rettv, l); @@ -1257,7 +1263,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) } else { li = TV_LIST_ITEM_NEXT(l, li); } - if (info.item_compare_func_err) { // -V547 + if (info.item_compare_func_err) { emsg(_("E882: Uniq compare function failed")); break; } @@ -1361,7 +1367,7 @@ void tv_list_reverse(list_T *const l) /// true list will not be modified. Must be initialized to false /// by the caller. void tv_list_item_sort(list_T *const l, ListSortItem *const ptrs, - const ListSorter item_compare_func, bool *errp) + const ListSorter item_compare_func, const bool *errp) FUNC_ATTR_NONNULL_ARG(3, 4) { const int len = tv_list_len(l); @@ -1593,7 +1599,7 @@ void callback_free(Callback *callback) { switch (callback->type) { case kCallbackFuncref: - func_unref((char_u *)callback->data.funcref); + func_unref(callback->data.funcref); xfree(callback->data.funcref); break; case kCallbackPartial: @@ -1622,7 +1628,7 @@ void callback_put(Callback *cb, typval_T *tv) case kCallbackFuncref: tv->v_type = VAR_FUNC; tv->vval.v_string = xstrdup(cb->data.funcref); - func_ref((char_u *)cb->data.funcref); + func_ref(cb->data.funcref); break; case kCallbackLua: // TODO(tjdevries): Unified Callback. @@ -1648,7 +1654,7 @@ void callback_copy(Callback *dest, Callback *src) break; case kCallbackFuncref: dest->data.funcref = xstrdup(src->data.funcref); - func_ref((char_u *)src->data.funcref); + func_ref(src->data.funcref); break; case kCallbackLua: dest->data.luaref = api_new_luaref(src->data.luaref); @@ -1745,9 +1751,8 @@ static bool tv_dict_watcher_matches(DictWatcher *watcher, const char *const key) const size_t len = watcher->key_pattern_len; if (len && watcher->key_pattern[len - 1] == '*') { return strncmp(key, watcher->key_pattern, len - 1) == 0; - } else { - return strcmp(key, watcher->key_pattern) == 0; } + return strcmp(key, watcher->key_pattern) == 0; } /// Send a change notification to all dictionary watchers that match given key @@ -1839,6 +1844,7 @@ dictitem_T *tv_dict_item_alloc_len(const char *const key, const size_t key_len) di->di_key[key_len] = NUL; di->di_flags = DI_FLAGS_ALLOC; di->di_tv.v_lock = VAR_UNLOCKED; + di->di_tv.v_type = VAR_UNKNOWN; return di; } @@ -2058,9 +2064,24 @@ int tv_dict_get_tv(dict_T *d, const char *const key, typval_T *rettv) varnumber_T tv_dict_get_number(const dict_T *const d, const char *const key) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { + return tv_dict_get_number_def(d, key, 0); +} + +/// Get a number item from a dictionary. +/// +/// Returns "def" if the entry doesn't exist. +/// +/// @param[in] d Dictionary to get item from. +/// @param[in] key Key to find in dictionary. +/// @param[in] def Default value. +/// +/// @return Dictionary item. +varnumber_T tv_dict_get_number_def(const dict_T *const d, const char *const key, const int def) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ dictitem_T *const di = tv_dict_find(d, key, -1); if (di == NULL) { - return 0; + return def; } return tv_get_number(&di->di_tv); } @@ -2079,7 +2100,7 @@ char **tv_dict_to_env(dict_T *denv) TV_DICT_ITER(denv, var, { const char *str = tv_get_string(&var->di_tv); assert(str); - size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1; + size_t len = strlen((char *)var->di_key) + strlen(str) + strlen("=") + 1; env[i] = xmalloc(len); snprintf(env[i], len, "%s=%s", (char *)var->di_key, str); i++; @@ -2188,6 +2209,15 @@ bool tv_dict_get_callback(dict_T *const d, const char *const key, const ptrdiff_ return res; } +/// Check for adding a function to g: or l:. +/// If the name is wrong give an error message and return true. +int tv_dict_wrong_func_name(dict_T *d, typval_T *tv, const char *name) +{ + return (d == &globvardict || &d->dv_hashtab == get_funccal_local_ht()) + && tv_is_func(*tv) + && var_wrong_func_name(name, true); +} + //{{{2 dict_add* /// Add item to dictionary @@ -2199,7 +2229,10 @@ bool tv_dict_get_callback(dict_T *const d, const char *const key, const ptrdiff_ int tv_dict_add(dict_T *const d, dictitem_T *const item) FUNC_ATTR_NONNULL_ALL { - return hash_add(&d->dv_hashtab, item->di_key); + if (tv_dict_wrong_func_name(d, &item->di_tv, (const char *)item->di_key)) { + return FAIL; + } + return hash_add(&d->dv_hashtab, (char *)item->di_key); } /// Add a list entry to dictionary @@ -2415,10 +2448,10 @@ void tv_dict_clear(dict_T *const d) /// /// @param d1 Dictionary to extend. /// @param[in] d2 Dictionary to extend with. -/// @param[in] action "error", "force", "keep": -/// +/// @param[in] action "error", "force", "move", "keep": /// e*, including "error": duplicate key gives an error. /// f*, including "force": duplicate d2 keys override d1. +/// m*, including "move": move items instead of copying. /// other, including "keep": duplicate d2 keys ignored. void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action) FUNC_ATTR_NONNULL_ALL @@ -2427,27 +2460,33 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action const char *const arg_errmsg = _("extend() argument"); const size_t arg_errmsg_len = strlen(arg_errmsg); - TV_DICT_ITER(d2, di2, { + if (*action == 'm') { + hash_lock(&d2->dv_hashtab); // don't rehash on hash_remove() + } + + HASHTAB_ITER(&d2->dv_hashtab, hi2, { + dictitem_T *const di2 = TV_DICT_HI2DI(hi2); dictitem_T *const di1 = tv_dict_find(d1, (const char *)di2->di_key, -1); - if (d1->dv_scope != VAR_NO_SCOPE) { - // Disallow replacing a builtin function in l: and g:. - // Check the key to be valid when adding to any scope. - if (d1->dv_scope == VAR_DEF_SCOPE - && tv_is_func(di2->di_tv) - && !var_check_func_name((const char *)di2->di_key, di1 == NULL)) { - break; - } - if (!valid_varname((const char *)di2->di_key)) { - break; - } + // Check the key to be valid when adding to any scope. + if (d1->dv_scope != VAR_NO_SCOPE && !valid_varname((const char *)di2->di_key)) { + break; } if (di1 == NULL) { - dictitem_T *const new_di = tv_dict_item_copy(di2); - if (tv_dict_add(d1, new_di) == FAIL) { - tv_dict_item_free(new_di); - } else if (watched) { - tv_dict_watcher_notify(d1, (const char *)new_di->di_key, &new_di->di_tv, - NULL); + if (*action == 'm') { + // Cheap way to move a dict item from "d2" to "d1". + // If dict_add() fails then "d2" won't be empty. + dictitem_T *const new_di = di2; + if (tv_dict_add(d1, new_di) == OK) { + hash_remove(&d2->dv_hashtab, hi2); + tv_dict_watcher_notify(d1, (const char *)new_di->di_key, &new_di->di_tv, NULL); + } + } else { + dictitem_T *const new_di = tv_dict_item_copy(di2); + if (tv_dict_add(d1, new_di) == FAIL) { + tv_dict_item_free(new_di); + } else if (watched) { + tv_dict_watcher_notify(d1, (const char *)new_di->di_key, &new_di->di_tv, NULL); + } } } else if (*action == 'e') { semsg(_("E737: Key already exists: %s"), di2->di_key); @@ -2455,10 +2494,14 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action } else if (*action == 'f' && di2 != di1) { typval_T oldtv; - if (var_check_lock(di1->di_tv.v_lock, arg_errmsg, arg_errmsg_len) + if (value_check_lock(di1->di_tv.v_lock, arg_errmsg, arg_errmsg_len) || var_check_ro(di1->di_flags, arg_errmsg, arg_errmsg_len)) { break; } + // Disallow replacing a builtin function. + if (tv_dict_wrong_func_name(d1, &di2->di_tv, (const char *)di2->di_key)) { + break; + } if (watched) { tv_copy(&di1->di_tv, &oldtv); @@ -2474,6 +2517,10 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action } } }); + + if (*action == 'm') { + hash_unlock(&d2->dv_hashtab); + } } /// Compare two dictionaries @@ -2488,10 +2535,14 @@ bool tv_dict_equal(dict_T *const d1, dict_T *const d2, const bool ic, const bool if (d1 == d2) { return true; } - if (d1 == NULL || d2 == NULL) { + if (tv_dict_len(d1) != tv_dict_len(d2)) { return false; } - if (tv_dict_len(d1) != tv_dict_len(d2)) { + if (tv_dict_len(d1) == 0) { + // empty and NULL dicts are considered equal + return true; + } + if (d1 == NULL || d2 == NULL) { return false; } @@ -2537,7 +2588,7 @@ dict_T *tv_dict_copy(const vimconv_T *const conv, dict_T *const orig, const bool if (conv == NULL || conv->vc_type == CONV_NONE) { new_di = tv_dict_item_alloc((const char *)di->di_key); } else { - size_t len = STRLEN(di->di_key); + size_t len = strlen((char *)di->di_key); char *const key = (char *)string_convert(conv, (char *)di->di_key, &len); if (key == NULL) { new_di = tv_dict_item_alloc_len((const char *)di->di_key, len); @@ -2659,7 +2710,7 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) { blob_T *const b = argvars[0].vval.v_blob; - if (b != NULL && var_check_lock(b->bv_lock, arg_errmsg, TV_TRANSLATE)) { + if (b != NULL && value_check_lock(b->bv_lock, arg_errmsg, TV_TRANSLATE)) { return; } @@ -2679,7 +2730,7 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) } if (argvars[2].v_type == VAR_UNKNOWN) { // Remove one item, return its value. - char_u *const p = (char_u *)b->bv_ga.ga_data; + uint8_t *const p = (uint8_t *)b->bv_ga.ga_data; rettv->vval.v_number = (varnumber_T)(*(p + idx)); memmove(p + idx, p + idx + 1, (size_t)(len - idx - 1)); b->bv_ga.ga_len--; @@ -2701,9 +2752,8 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) blob->bv_ga.ga_len = (int)(end - idx + 1); ga_grow(&blob->bv_ga, (int)(end - idx + 1)); - char_u *const p = (char_u *)b->bv_ga.ga_data; - memmove((char_u *)blob->bv_ga.ga_data, p + idx, - (size_t)(end - idx + 1)); + uint8_t *const p = (uint8_t *)b->bv_ga.ga_data; + memmove(blob->bv_ga.ga_data, p + idx, (size_t)(end - idx + 1)); tv_blob_set_ret(rettv, blob); if (len - end - 1 > 0) { @@ -2851,7 +2901,7 @@ void tv_dict_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) if (argvars[2].v_type != VAR_UNKNOWN) { semsg(_(e_toomanyarg), "remove()"); } else if ((d = argvars[0].vval.v_dict) != NULL - && !var_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) { + && !value_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) { const char *key = tv_get_string_chk(&argvars[1]); if (key != NULL) { dictitem_T *di = tv_dict_find(d, key, -1); @@ -2956,7 +3006,7 @@ void tv_blob_copy(typval_T *const from, typval_T *const to) (tv)->v_lock = VAR_UNLOCKED; \ } while (0) -static inline int _nothing_conv_func_start(typval_T *const tv, char_u *const fun) +static inline int _nothing_conv_func_start(typval_T *const tv, char *const fun) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(1) { tv->v_lock = VAR_UNLOCKED; @@ -3119,6 +3169,7 @@ static inline void _nothing_conv_dict_end(typval_T *const tv, dict_T **const dic #define TYPVAL_ENCODE_FIRST_ARG_TYPE const void *const #define TYPVAL_ENCODE_FIRST_ARG_NAME ignored #include "nvim/eval/typval_encode.c.h" + #undef TYPVAL_ENCODE_SCOPE #undef TYPVAL_ENCODE_NAME #undef TYPVAL_ENCODE_FIRST_ARG_TYPE @@ -3183,7 +3234,7 @@ void tv_free(typval_T *tv) partial_unref(tv->vval.v_partial); break; case VAR_FUNC: - func_unref((char_u *)tv->vval.v_string); + func_unref(tv->vval.v_string); FALLTHROUGH; case VAR_STRING: xfree(tv->vval.v_string); @@ -3236,7 +3287,7 @@ void tv_copy(const typval_T *const from, typval_T *const to) if (from->vval.v_string != NULL) { to->vval.v_string = xstrdup(from->vval.v_string); if (from->v_type == VAR_FUNC) { - func_ref((char_u *)to->vval.v_string); + func_ref(to->vval.v_string); } } break; @@ -3408,12 +3459,12 @@ bool tv_check_lock(const typval_T *tv, const char *name, size_t name_len) default: break; } - return var_check_lock(tv->v_lock, name, name_len) - || (lock != VAR_UNLOCKED && var_check_lock(lock, name, name_len)); + return value_check_lock(tv->v_lock, name, name_len) + || (lock != VAR_UNLOCKED && value_check_lock(lock, name, name_len)); } -/// @return true if variable "name" is locked (immutable) -bool var_check_lock(VarLockStatus lock, const char *name, size_t name_len) +/// @return true if variable "name" has a locked (immutable) value +bool value_check_lock(VarLockStatus lock, const char *name, size_t name_len) { const char *error_message = NULL; switch (lock) { @@ -3588,13 +3639,13 @@ bool tv_check_str_or_nr(const typval_T *const tv) #define FUNC_ERROR "E703: Using a Funcref as a Number" static const char *const num_errors[] = { - [VAR_PARTIAL]=N_(FUNC_ERROR), - [VAR_FUNC]=N_(FUNC_ERROR), - [VAR_LIST]=N_("E745: Using a List as a Number"), - [VAR_DICT]=N_("E728: Using a Dictionary as a Number"), - [VAR_FLOAT]=N_("E805: Using a Float as a Number"), - [VAR_BLOB]=N_("E974: Using a Blob as a Number"), - [VAR_UNKNOWN]=N_("E685: using an invalid value as a Number"), + [VAR_PARTIAL]= N_(FUNC_ERROR), + [VAR_FUNC]= N_(FUNC_ERROR), + [VAR_LIST]= N_("E745: Using a List as a Number"), + [VAR_DICT]= N_("E728: Using a Dictionary as a Number"), + [VAR_FLOAT]= N_("E805: Using a Float as a Number"), + [VAR_BLOB]= N_("E974: Using a Blob as a Number"), + [VAR_UNKNOWN]= N_("E685: using an invalid value as a Number"), }; #undef FUNC_ERROR @@ -3633,13 +3684,13 @@ bool tv_check_num(const typval_T *const tv) #define FUNC_ERROR "E729: using Funcref as a String" static const char *const str_errors[] = { - [VAR_PARTIAL]=N_(FUNC_ERROR), - [VAR_FUNC]=N_(FUNC_ERROR), - [VAR_LIST]=N_("E730: using List as a String"), - [VAR_DICT]=N_("E731: using Dictionary as a String"), - [VAR_FLOAT]=((const char *)e_float_as_string), - [VAR_BLOB]=N_("E976: using Blob as a String"), - [VAR_UNKNOWN]=N_("E908: using an invalid value as a String"), + [VAR_PARTIAL]= N_(FUNC_ERROR), + [VAR_FUNC]= N_(FUNC_ERROR), + [VAR_LIST]= N_("E730: using List as a String"), + [VAR_DICT]= N_("E731: using Dictionary as a String"), + [VAR_FLOAT]= ((const char *)e_float_as_string), + [VAR_BLOB]= N_("E976: using Blob as a String"), + [VAR_UNKNOWN]= N_("E908: using an invalid value as a String"), }; #undef FUNC_ERROR @@ -3851,6 +3902,17 @@ int tv_check_for_opt_number_arg(const typval_T *const args, const int idx) || tv_check_for_number_arg(args, idx) != FAIL) ? OK : FAIL; } +/// Give an error and return FAIL unless "args[idx]" is a string or a list. +int tv_check_for_string_or_list_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (args[idx].v_type != VAR_STRING && args[idx].v_type != VAR_LIST) { + semsg(_(e_string_or_list_required_for_argument_nr), idx + 1); + return FAIL; + } + return OK; +} + /// Get the string value of a "stringish" VimL object. /// /// @param[in] tv Object to get value of. diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 0a4adc1f53..3f59cd3547 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -4,14 +4,19 @@ #include <assert.h> #include <stdbool.h> #include <stddef.h> +#include <stdint.h> #include <string.h> #include "nvim/eval/typval_defs.h" #include "nvim/func_attr.h" +#include "nvim/garray.h" #include "nvim/gettext.h" +#include "nvim/hashtab.h" +#include "nvim/lib/queue.h" #include "nvim/macros.h" #include "nvim/mbyte_defs.h" #include "nvim/message.h" +#include "nvim/types.h" #ifdef LOG_LIST_ACTIONS # include "nvim/memory.h" @@ -43,10 +48,8 @@ static inline ListLog *list_log_new(const size_t size) return ret; } -static inline void list_log(const list_T *const l, - const listitem_T *const li1, - const listitem_T *const li2, - const char *const action) +static inline void list_log(const list_T *const l, const listitem_T *const li1, + const listitem_T *const li2, const char *const action) REAL_FATTR_ALWAYS_INLINE; /// Add new entry to log @@ -90,7 +93,7 @@ static inline void list_log(const list_T *const l, const listitem_T *const li1, #define TV_DICT_HI2DI(hi) \ ((dictitem_T *)((hi)->hi_key - offsetof(dictitem_T, di_key))) -static inline void tv_list_ref(list_T *const l) +static inline void tv_list_ref(list_T *l) REAL_FATTR_ALWAYS_INLINE; /// Increase reference count for a given list @@ -106,7 +109,7 @@ static inline void tv_list_ref(list_T *const l) l->lv_refcount++; } -static inline void tv_list_set_ret(typval_T *const tv, list_T *const l) +static inline void tv_list_set_ret(typval_T *tv, list_T *l) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1); /// Set a list as the return value. Increments the reference count. @@ -120,7 +123,7 @@ static inline void tv_list_set_ret(typval_T *const tv, list_T *const l) tv_list_ref(l); } -static inline VarLockStatus tv_list_locked(const list_T *const l) +static inline VarLockStatus tv_list_locked(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get list lock status @@ -163,7 +166,7 @@ static inline void tv_list_set_copyid(list_T *const l, const int copyid) l->lv_copyID = copyid; } -static inline int tv_list_len(const list_T *const l) +static inline int tv_list_len(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get the number of items in a list @@ -178,7 +181,7 @@ static inline int tv_list_len(const list_T *const l) return l->lv_len; } -static inline int tv_list_copyid(const list_T *const l) +static inline int tv_list_copyid(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; /// Get list copyID @@ -191,7 +194,7 @@ static inline int tv_list_copyid(const list_T *const l) return l->lv_copyID; } -static inline list_T *tv_list_latest_copy(const list_T *const l) +static inline list_T *tv_list_latest_copy(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; /// Get latest list copy @@ -206,7 +209,7 @@ static inline list_T *tv_list_latest_copy(const list_T *const l) return l->lv_copylist; } -static inline int tv_list_uidx(const list_T *const l, int n) +static inline int tv_list_uidx(const list_T *l, int n) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Normalize index: that is, return either -1 or non-negative index @@ -229,7 +232,7 @@ static inline int tv_list_uidx(const list_T *const l, int n) return n; } -static inline bool tv_list_has_watchers(const list_T *const l) +static inline bool tv_list_has_watchers(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Check whether list has watchers @@ -244,7 +247,7 @@ static inline bool tv_list_has_watchers(const list_T *const l) return l && l->lv_watch; } -static inline listitem_T *tv_list_first(const list_T *const l) +static inline listitem_T *tv_list_first(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get first list item @@ -262,7 +265,7 @@ static inline listitem_T *tv_list_first(const list_T *const l) return l->lv_first; } -static inline listitem_T *tv_list_last(const list_T *const l) +static inline listitem_T *tv_list_last(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get last list item @@ -280,7 +283,7 @@ static inline listitem_T *tv_list_last(const list_T *const l) return l->lv_last; } -static inline void tv_dict_set_ret(typval_T *const tv, dict_T *const d) +static inline void tv_dict_set_ret(typval_T *tv, dict_T *d) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1); /// Set a dictionary as the return value @@ -296,7 +299,7 @@ static inline void tv_dict_set_ret(typval_T *const tv, dict_T *const d) } } -static inline long tv_dict_len(const dict_T *const d) +static inline long tv_dict_len(const dict_T *d) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get the number of items in a Dictionary @@ -310,7 +313,7 @@ static inline long tv_dict_len(const dict_T *const d) return (long)d->dv_hashtab.ht_used; } -static inline bool tv_dict_is_watched(const dict_T *const d) +static inline bool tv_dict_is_watched(const dict_T *d) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Check if dictionary is watched @@ -323,7 +326,7 @@ static inline bool tv_dict_is_watched(const dict_T *const d) return d && !QUEUE_EMPTY(&d->watchers); } -static inline void tv_blob_set_ret(typval_T *const tv, blob_T *const b) +static inline void tv_blob_set_ret(typval_T *tv, blob_T *b) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1); /// Set a blob as the return value. @@ -341,7 +344,7 @@ static inline void tv_blob_set_ret(typval_T *const tv, blob_T *const b) } } -static inline int tv_blob_len(const blob_T *const b) +static inline int tv_blob_len(const blob_T *b) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get the length of the data in the blob, in bytes. @@ -355,7 +358,7 @@ static inline int tv_blob_len(const blob_T *const b) return b->bv_ga.ga_len; } -static inline char_u tv_blob_get(const blob_T *const b, int idx) +static inline uint8_t tv_blob_get(const blob_T *b, int idx) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT; /// Get the byte at index `idx` in the blob. @@ -364,12 +367,12 @@ static inline char_u tv_blob_get(const blob_T *const b, int idx) /// @param[in] idx Index in a blob. Must be valid. /// /// @return Byte value at the given index. -static inline char_u tv_blob_get(const blob_T *const b, int idx) +static inline uint8_t tv_blob_get(const blob_T *const b, int idx) { - return ((char_u *)b->bv_ga.ga_data)[idx]; + return ((uint8_t *)b->bv_ga.ga_data)[idx]; } -static inline void tv_blob_set(blob_T *const b, int idx, char_u c) +static inline void tv_blob_set(blob_T *b, int idx, uint8_t c) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; /// Store the byte `c` at index `idx` in the blob. @@ -377,9 +380,9 @@ static inline void tv_blob_set(blob_T *const b, int idx, char_u c) /// @param[in] b Blob to index. Cannot be NULL. /// @param[in] idx Index in a blob. Must be valid. /// @param[in] c Value to store. -static inline void tv_blob_set(blob_T *const b, int idx, char_u c) +static inline void tv_blob_set(blob_T *const b, int idx, uint8_t c) { - ((char_u *)b->bv_ga.ga_data)[idx] = c; + ((uint8_t *)b->bv_ga.ga_data)[idx] = c; } /// Initialize VimL object @@ -431,7 +434,7 @@ extern bool tv_in_free_unref_items; /// @param li Name of the variable with current listitem_T entry. /// @param code Cycle body. #define TV_LIST_ITER(l, li, code) \ - _TV_LIST_ITER_MOD( , l, li, code) + _TV_LIST_ITER_MOD( , l, li, code) // NOLINT(whitespace/parens) /// Iterate over a list /// @@ -488,8 +491,7 @@ extern bool tv_in_free_unref_items; } \ }) -static inline bool tv_get_float_chk(const typval_T *const tv, - float_T *const ret_f) +static inline bool tv_get_float_chk(const typval_T *tv, float_T *ret_f) REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT; /// Get the float value @@ -527,7 +529,7 @@ static inline DictWatcher *tv_dict_watcher_node_data(QUEUE *q) return QUEUE_DATA(q, DictWatcher, node); } -static inline bool tv_is_func(const typval_T tv) +static inline bool tv_is_func(typval_T tv) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST; /// Check whether given typval_T contains a function @@ -562,4 +564,9 @@ EXTERN const size_t kTVTranslate INIT(= TV_TRANSLATE); #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/typval.h.generated.h" #endif + +#define tv_get_bool tv_get_number +#define tv_get_bool_chk tv_get_number_chk +#define tv_dict_get_bool tv_dict_get_number_def + #endif // NVIM_EVAL_TYPVAL_H diff --git a/src/nvim/eval/typval_defs.h b/src/nvim/eval/typval_defs.h index 1c0a438751..4615198441 100644 --- a/src/nvim/eval/typval_defs.h +++ b/src/nvim/eval/typval_defs.h @@ -206,7 +206,7 @@ typedef struct { struct { \ typval_T di_tv; /* Structure that holds scope dictionary itself. */ \ uint8_t di_flags; /* Flags. */ \ - char_u di_key[__VA_ARGS__]; /* Key value. */ \ + char_u di_key[__VA_ARGS__]; /* Key value. */ /* NOLINT(runtime/arrays)*/ \ } /// Structure to hold a scope dictionary @@ -337,9 +337,9 @@ struct ufunc { ///< used for s: variables int uf_refcount; ///< reference count, see func_name_refcount() funccall_T *uf_scoped; ///< l: local variables for closure - char_u *uf_name_exp; ///< if "uf_name[]" starts with SNR the name with + char *uf_name_exp; ///< if "uf_name[]" starts with SNR the name with ///< "<SNR>" as a string, otherwise NULL - char_u uf_name[]; ///< Name of function (actual size equals name); + char uf_name[]; ///< Name of function (actual size equals name); ///< can start with <SNR>123_ ///< (<SNR> is K_SPECIAL KS_EXTRA KE_SNR) }; diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index ff4f92e40b..6d29286a58 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -266,7 +266,7 @@ static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE( const MPConvStack *const mpstack, const int copyID, const MPConvStackValType conv_type, const char *const objname) -REAL_FATTR_NONNULL_ARG(2, 3, 4, 7) REAL_FATTR_WARN_UNUSED_RESULT + REAL_FATTR_NONNULL_ARG(2, 3, 4, 7) REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE; /// Function for checking whether container references itself @@ -301,7 +301,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( MPConvStack *const mpstack, MPConvStackVal *const cur_mpsv, typval_T *const tv, const int copyID, const char *const objname) -REAL_FATTR_NONNULL_ARG(2, 4, 6) REAL_FATTR_WARN_UNUSED_RESULT; + REAL_FATTR_NONNULL_ARG(2, 4, 6) REAL_FATTR_WARN_UNUSED_RESULT; /// Convert single value /// @@ -339,7 +339,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( tv_blob_len(tv->vval.v_blob)); break; case VAR_FUNC: - TYPVAL_ENCODE_CONV_FUNC_START(tv, (char_u *)tv->vval.v_string); + TYPVAL_ENCODE_CONV_FUNC_START(tv, tv->vval.v_string); TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, 0); TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, -1); TYPVAL_ENCODE_CONV_FUNC_END(tv); @@ -347,7 +347,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( case VAR_PARTIAL: { partial_T *const pt = tv->vval.v_partial; (void)pt; - TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : (char_u *)partial_name(pt))); // -V547 + TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : partial_name(pt))); // -V547 _mp_push(*mpstack, ((MPConvStackVal) { // -V779 .type = kMPConvPartial, .tv = tv, @@ -358,7 +358,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( .pt = tv->vval.v_partial, }, }, - })); + })); break; } case VAR_LIST: { @@ -381,7 +381,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( .li = tv_list_first(tv->vval.v_list), }, }, - })); + })); TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, _mp_last(*mpstack)); break; } @@ -459,8 +459,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( const listitem_T *const highest_bits_li = ( TV_LIST_ITEM_NEXT(val_list, sign_li)); if (TV_LIST_ITEM_TV(highest_bits_li)->v_type != VAR_NUMBER - || ((highest_bits - = TV_LIST_ITEM_TV(highest_bits_li)->vval.v_number) + || ((highest_bits = TV_LIST_ITEM_TV(highest_bits_li)->vval.v_number) < 0)) { goto _convert_one_value_regular_dict; } @@ -536,7 +535,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( .li = tv_list_first(val_di->di_tv.vval.v_list), }, }, - })); + })); break; } case kMPMap: { @@ -571,7 +570,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( .li = tv_list_first(val_list), }, }, - })); + })); break; } case kMPExt: { @@ -581,8 +580,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( || tv_list_len((val_list = val_di->di_tv.vval.v_list)) != 2 || (TV_LIST_ITEM_TV(tv_list_first(val_list))->v_type != VAR_NUMBER) - || ((type - = TV_LIST_ITEM_TV(tv_list_first(val_list))->vval.v_number) + || ((type = TV_LIST_ITEM_TV(tv_list_first(val_list))->vval.v_number) > INT8_MAX) || type < INT8_MIN || (TV_LIST_ITEM_TV(tv_list_last(val_list))->v_type @@ -622,7 +620,7 @@ _convert_one_value_regular_dict: {} .todo = tv->vval.v_dict->dv_hashtab.ht_used, }, }, - })); + })); TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, tv->vval.v_dict, _mp_last(*mpstack)); break; @@ -640,7 +638,7 @@ typval_encode_stop_converting_one_item: TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, typval_T *const tv, const char *const objname) -REAL_FATTR_NONNULL_ARG(2, 3) REAL_FATTR_WARN_UNUSED_RESULT; + REAL_FATTR_NONNULL_ARG(2, 3) REAL_FATTR_WARN_UNUSED_RESULT; /// Convert the whole typval /// @@ -758,7 +756,7 @@ typval_encode_stop_converting_one_item: .todo = (size_t)pt->pt_argc, }, }, - })); + })); } break; case kMPConvPartialSelf: { @@ -797,7 +795,7 @@ typval_encode_stop_converting_one_item: .todo = dict->dv_hashtab.ht_used, }, }, - })); + })); TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(NULL, pt->pt_dict, _mp_last(mpstack)); } else { diff --git a/src/nvim/eval/typval_encode.h b/src/nvim/eval/typval_encode.h index 33e19c531c..2f19144da3 100644 --- a/src/nvim/eval/typval_encode.h +++ b/src/nvim/eval/typval_encode.h @@ -71,7 +71,7 @@ typedef kvec_withinit_t(MPConvStackVal, 8) MPConvStack; #define _mp_pop kv_pop #define _mp_last kv_last -static inline size_t tv_strlen(const typval_T *const tv) +static inline size_t tv_strlen(const typval_T *tv) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index b0a56c4440..22c5b1954d 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -3,28 +3,49 @@ // User defined function support +#include <assert.h> +#include <ctype.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "lauxlib.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/debugger.h" -#include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" #include "nvim/eval/funcs.h" +#include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" -#include "nvim/fileio.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/insexpand.h" +#include "nvim/keycodes.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/memline_defs.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option_defs.h" #include "nvim/os/input.h" +#include "nvim/path.h" #include "nvim/profile.h" #include "nvim/regexp.h" #include "nvim/runtime.h" #include "nvim/search.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -48,6 +69,8 @@ static char *e_funcexts = N_("E122: Function %s already exists, add ! to replace static char *e_funcdict = N_("E717: Dictionary entry already exists"); static char *e_funcref = N_("E718: Funcref required"); static char *e_nofunc = N_("E130: Unknown function: %s"); +static char e_no_white_space_allowed_before_str_str[] + = N_("E1068: No white space allowed before '%s': %s"); void func_init(void) { @@ -61,7 +84,7 @@ hashtab_T *func_tbl_get(void) } /// Get function arguments. -static int get_function_args(char **argp, char_u endchar, garray_T *newargs, int *varargs, +static int get_function_args(char **argp, char endchar, garray_T *newargs, int *varargs, garray_T *default_args, bool skip) { bool mustend = false; @@ -71,10 +94,10 @@ static int get_function_args(char **argp, char_u endchar, garray_T *newargs, int int i; if (newargs != NULL) { - ga_init(newargs, (int)sizeof(char_u *), 3); + ga_init(newargs, (int)sizeof(char *), 3); } if (default_args != NULL) { - ga_init(default_args, (int)sizeof(char_u *), 3); + ga_init(default_args, (int)sizeof(char *), 3); } if (varargs != NULL) { @@ -83,7 +106,7 @@ static int get_function_args(char **argp, char_u endchar, garray_T *newargs, int // Isolate the arguments: "arg1, arg2, ...)" bool any_default = false; - while (*p != (char)endchar) { + while (*p != endchar) { if (p[0] == '.' && p[1] == '.' && p[2] == '.') { if (varargs != NULL) { *varargs = true; @@ -95,9 +118,9 @@ static int get_function_args(char **argp, char_u endchar, garray_T *newargs, int while (ASCII_ISALNUM(*p) || *p == '_') { p++; } - if (arg == p || isdigit(*arg) - || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0) - || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) { + if (arg == p || isdigit((uint8_t)(*arg)) + || (p - arg == 9 && strncmp(arg, "firstline", 9) == 0) + || (p - arg == 8 && strncmp(arg, "lastline", 8) == 0)) { if (!skip) { semsg(_("E125: Illegal argument: %s"), arg); } @@ -149,6 +172,15 @@ static int get_function_args(char **argp, char_u endchar, garray_T *newargs, int emsg(_("E989: Non-default argument follows default argument")); mustend = true; } + + if (ascii_iswhite(*p) && *skipwhite(p) == ',') { + // Be tolerant when skipping + if (!skip) { + semsg(_(e_no_white_space_allowed_before_str_str), ",", p); + goto err_ret; + } + p = skipwhite(p); + } if (*p == ',') { p++; } else { @@ -156,14 +188,14 @@ static int get_function_args(char **argp, char_u endchar, garray_T *newargs, int } } p = skipwhite(p); - if (mustend && *p != (char)endchar) { + if (mustend && *p != endchar) { if (!skip) { semsg(_(e_invarg2), *argp); } break; } } - if (*p != (char)endchar) { + if (*p != endchar) { goto err_ret; } p++; // skip "endchar" @@ -197,21 +229,21 @@ static void register_closure(ufunc_T *fp) } /// @return a name for a lambda. Returned in static memory. -char_u *get_lambda_name(void) +char *get_lambda_name(void) { - static char_u name[30]; + static char name[30]; static int lambda_no = 0; - snprintf((char *)name, sizeof(name), "<lambda>%d", ++lambda_no); + snprintf(name, sizeof(name), "<lambda>%d", ++lambda_no); return name; } -static void set_ufunc_name(ufunc_T *fp, char_u *name) +static void set_ufunc_name(ufunc_T *fp, char *name) { STRCPY(fp->uf_name, name); - if (name[0] == K_SPECIAL) { - fp->uf_name_exp = xmalloc(STRLEN(name) + 3); + if ((uint8_t)name[0] == K_SPECIAL) { + fp->uf_name_exp = xmalloc(strlen(name) + 3); STRCPY(fp->uf_name_exp, "<SNR>"); STRCAT(fp->uf_name_exp, fp->uf_name + 3); } @@ -228,13 +260,13 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate) partial_T *pt = NULL; int varargs; int ret; - char_u *start = (char_u *)skipwhite(*arg + 1); - char_u *s, *e; + char *start = skipwhite(*arg + 1); + char *s, *e; bool *old_eval_lavars = eval_lavars_used; bool eval_lavars = false; // First, check if this is a lambda expression. "->" must exists. - ret = get_function_args((char **)&start, '-', NULL, NULL, NULL, true); + ret = get_function_args(&start, '-', NULL, NULL, NULL, true); if (ret == FAIL || *start != '>') { return NOTDONE; } @@ -258,38 +290,39 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate) // Get the start and the end of the expression. *arg = skipwhite((*arg) + 1); - s = (char_u *)(*arg); + s = *arg; ret = skip_expr(arg); if (ret == FAIL) { goto errret; } - e = (char_u *)(*arg); + e = *arg; *arg = skipwhite(*arg); if (**arg != '}') { + semsg(_("E451: Expected }: %s"), *arg); goto errret; } (*arg)++; if (evaluate) { int flags = 0; - char_u *p; + char *p; garray_T newlines; - char_u *name = get_lambda_name(); + char *name = get_lambda_name(); - fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); + fp = xcalloc(1, offsetof(ufunc_T, uf_name) + strlen(name) + 1); pt = xcalloc(1, sizeof(partial_T)); - ga_init(&newlines, (int)sizeof(char_u *), 1); + ga_init(&newlines, (int)sizeof(char *), 1); ga_grow(&newlines, 1); // Add "return " before the expression. size_t len = (size_t)(7 + e - s + 1); - p = (char_u *)xmalloc(len); - ((char **)(newlines.ga_data))[newlines.ga_len++] = (char *)p; + p = xmalloc(len); + ((char **)(newlines.ga_data))[newlines.ga_len++] = p; STRCPY(p, "return "); - STRLCPY(p + 7, s, e - s + 1); - if (strstr((char *)p + 7, "a:") == NULL) { + xstrlcpy(p + 7, s, (size_t)(e - s) + 1); + if (strstr(p + 7, "a:") == NULL) { // No a: variables are used for sure. flags |= FC_NOARGS; } @@ -298,7 +331,7 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate) set_ufunc_name(fp, name); hash_add(&func_hashtab, UF2HIKEY(fp)); fp->uf_args = newargs; - ga_init(&fp->uf_def_args, (int)sizeof(char_u *), 1); + ga_init(&fp->uf_def_args, (int)sizeof(char *), 1); fp->uf_lines = newlines; if (current_funccal != NULL && eval_lavars) { flags |= FC_CLOSURE; @@ -351,7 +384,7 @@ errret: /// was not found. /// /// @return name of the function. -char_u *deref_func_name(const char *name, int *lenp, partial_T **const partialp, bool no_autoload) +char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, bool no_autoload) FUNC_ATTR_NONNULL_ARG(1, 2) { if (partialp != NULL) { @@ -362,10 +395,10 @@ char_u *deref_func_name(const char *name, int *lenp, partial_T **const partialp, if (v != NULL && v->di_tv.v_type == VAR_FUNC) { if (v->di_tv.vval.v_string == NULL) { // just in case *lenp = 0; - return (char_u *)""; + return ""; } *lenp = (int)strlen(v->di_tv.vval.v_string); - return (char_u *)v->di_tv.vval.v_string; + return v->di_tv.vval.v_string; } if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) { @@ -373,31 +406,31 @@ char_u *deref_func_name(const char *name, int *lenp, partial_T **const partialp, if (pt == NULL) { // just in case *lenp = 0; - return (char_u *)""; + return ""; } if (partialp != NULL) { *partialp = pt; } - char_u *s = (char_u *)partial_name(pt); - *lenp = (int)STRLEN(s); + char *s = partial_name(pt); + *lenp = (int)strlen(s); return s; } - return (char_u *)name; + return (char *)name; } /// Give an error message with a function name. Handle <SNR> things. /// /// @param ermsg must be passed without translation (use N_() instead of _()). /// @param name function name -void emsg_funcname(char *ermsg, const char_u *name) +void emsg_funcname(char *ermsg, const char *name) { - char_u *p; + char *p; - if (*name == K_SPECIAL) { - p = (char_u *)concat_str("<SNR>", (char *)name + 3); + if ((uint8_t)(*name) == K_SPECIAL) { + p = concat_str("<SNR>", name + 3); } else { - p = (char_u *)name; + p = (char *)name; } semsg(_(ermsg), p); @@ -415,7 +448,7 @@ void emsg_funcname(char *ermsg, const char_u *name) /// @param funcexe various values /// /// @return OK or FAIL. -int get_func_tv(const char_u *name, int len, typval_T *rettv, char **arg, funcexe_T *funcexe) +int get_func_tv(const char *name, int len, typval_T *rettv, char **arg, funcexe_T *funcexe) { char *argp; int ret = OK; @@ -449,7 +482,7 @@ int get_func_tv(const char_u *name, int len, typval_T *rettv, char **arg, funcex int i = 0; if (get_vim_var_nr(VV_TESTING)) { - // Prepare for calling garbagecollect_for_testing(), need to know + // Prepare for calling test_garbagecollect_now(), need to know // what variables are used on the call stack. if (funcargs.ga_itemsize == 0) { ga_init(&funcargs, (int)sizeof(typval_T *), 50); @@ -459,7 +492,7 @@ int get_func_tv(const char_u *name, int len, typval_T *rettv, char **arg, funcex ((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i]; } } - ret = call_func((char *)name, len, rettv, argcount, argvars, funcexe); + ret = call_func(name, len, rettv, argcount, argvars, funcexe); funcargs.ga_len -= i; } else if (!aborting()) { @@ -549,9 +582,9 @@ static char *fname_trans_sid(const char *const name, char *const fname_buf, char /// Find a function by name, return pointer to it in ufuncs. /// /// @return NULL for unknown function. -ufunc_T *find_func(const char_u *name) +ufunc_T *find_func(const char *name) { - hashitem_T *hi = hash_find(&func_hashtab, (char *)name); + hashitem_T *hi = hash_find(&func_hashtab, name); if (!HASHITEM_EMPTY(hi)) { return HI2UF(hi); } @@ -561,9 +594,9 @@ ufunc_T *find_func(const char_u *name) /// 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) +static void cat_func_name(char *buf, ufunc_T *fp) { - if (fp->uf_name[0] == K_SPECIAL) { + if ((uint8_t)fp->uf_name[0] == K_SPECIAL) { STRCPY(buf, "<SNR>"); STRCAT(buf, fp->uf_name + 3); } else { @@ -578,7 +611,7 @@ static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr) STRCPY(v->di_key, name); #endif v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - tv_dict_add(dp, v); + hash_add(&dp->dv_hashtab, (char *)v->di_key); v->di_tv.v_type = VAR_NUMBER; v->di_tv.v_lock = VAR_FIXED; v->di_tv.vval.v_number = nr; @@ -813,7 +846,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett int fixvar_idx = 0; // index in fixvar[] int ai; bool islambda = false; - char_u numbuf[NUMBUFLEN]; + char numbuf[NUMBUFLEN]; char *name; typval_T *tv_to_free[MAX_FUNC_ARGS]; int tv_to_free_len = 0; @@ -855,7 +888,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1); func_ptr_ref(fp); - if (STRNCMP(fp->uf_name, "<lambda>", 8) == 0) { + if (strncmp(fp->uf_name, "<lambda>", 8) == 0) { islambda = true; } @@ -874,7 +907,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett STRCPY(name, "self"); #endif v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - tv_dict_add(&fc->l_vars, v); + hash_add(&fc->l_vars.dv_hashtab, (char *)v->di_key); v->di_tv.v_type = VAR_DICT; v->di_tv.v_lock = VAR_UNLOCKED; v->di_tv.vval.v_dict = selfdict; @@ -900,7 +933,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett STRCPY(name, "000"); #endif v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - tv_dict_add(&fc->l_avars, v); + hash_add(&fc->l_avars.dv_hashtab, (char *)v->di_key); v->di_tv.v_type = VAR_LIST; v->di_tv.v_lock = VAR_FIXED; v->di_tv.vval.v_list = &fc->l_varlist; @@ -952,8 +985,8 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett break; } // "..." argument a:1, a:2, etc. - snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1); - name = (char *)numbuf; + snprintf(numbuf, sizeof(numbuf), "%d", ai + 1); + name = numbuf; } if (fixvar_idx < FIXVAR_CNT && strlen(name) <= VAR_SHORT_LEN) { v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; @@ -978,16 +1011,16 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // Named arguments can be accessed without the "a:" prefix in lambda // expressions. Add to the l: dict. tv_copy(&v->di_tv, &v->di_tv); - tv_dict_add(&fc->l_vars, v); + hash_add(&fc->l_vars.dv_hashtab, (char *)v->di_key); } else { - tv_dict_add(&fc->l_avars, v); + hash_add(&fc->l_avars.dv_hashtab, (char *)v->di_key); } if (ai >= 0 && ai < MAX_FUNC_ARGS) { listitem_T *li = &fc->l_listitems[ai]; *TV_LIST_ITEM_TV(li) = argvars[i]; - TV_LIST_ITEM_TV(li)->v_lock = VAR_FIXED; + TV_LIST_ITEM_TV(li)->v_lock = VAR_FIXED; tv_list_append(&fc->l_varlist, li); } } @@ -1193,9 +1226,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett /// For the first we only count the name stored in func_hashtab as a reference, /// using function() does not count as a reference, because the function is /// looked up by name. -static bool func_name_refcount(char_u *name) +static bool func_name_refcount(const char *name) { - return isdigit(*name) || *name == '<'; + return isdigit((uint8_t)(*name)) || *name == '<'; } /// Call a user function after checking the arguments. @@ -1265,7 +1298,7 @@ void free_all_functions(void) ufunc_T *fp; uint64_t skipped = 0; uint64_t todo = 1; - uint64_t used; + int changed; // Clean up the current_funccal chain and the funccal stack. while (current_funccal != NULL) { @@ -1289,9 +1322,9 @@ void free_all_functions(void) if (func_name_refcount(fp->uf_name)) { skipped++; } else { - used = func_hashtab.ht_used; + changed = func_hashtab.ht_changed; func_clear(fp, true); - if (used != func_hashtab.ht_used) { + if (changed != func_hashtab.ht_changed) { skipped = 0; break; } @@ -1335,10 +1368,10 @@ void free_all_functions(void) /// @param[in] len length of "name", or -1 for NUL terminated. /// /// @return true if "name" looks like a builtin function name: starts with a -/// lower case letter and doesn't contain AUTOLOAD_CHAR. +/// lower case letter and doesn't contain AUTOLOAD_CHAR or ':'. static bool builtin_function(const char *name, int len) { - if (!ASCII_ISLOWER(name[0])) { + if (!ASCII_ISLOWER(name[0]) || name[1] == ':') { return false; } @@ -1349,7 +1382,7 @@ static bool builtin_function(const char *name, int len) return p == NULL; } -int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict, typval_T *rettv) +int func_call(char *name, typval_T *args, partial_T *partial, dict_T *selfdict, typval_T *rettv) { typval_T argv[MAX_FUNC_ARGS + 1]; int argc = 0; @@ -1371,7 +1404,7 @@ int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict funcexe.fe_evaluate = true; funcexe.fe_partial = partial; funcexe.fe_selfdict = selfdict; - r = call_func((char *)name, -1, rettv, argc, argv, &funcexe); + r = call_func(name, -1, rettv, argc, argv, &funcexe); func_call_skip_call: // Free the arguments. @@ -1382,9 +1415,27 @@ func_call_skip_call: return r; } +/// call the 'callback' function and return the result as a number. +/// Returns -2 when calling the function fails. Uses argv[0] to argv[argc - 1] +/// for the function arguments. argv[argc] should have type VAR_UNKNOWN. +/// +/// @param argcount number of "argvars" +/// @param argvars vars for arguments, must have "argcount" PLUS ONE elements! +varnumber_T callback_call_retnr(Callback *callback, int argcount, typval_T *argvars) +{ + typval_T rettv; + if (!callback_call(callback, argcount, argvars, &rettv)) { + return -2; + } + + varnumber_T retval = tv_get_number_chk(&rettv, NULL); + tv_clear(&rettv); + return retval; +} + /// 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) +static void user_func_error(int error, const char *name) FUNC_ATTR_NONNULL_ALL { switch (error) { @@ -1401,16 +1452,13 @@ static void user_func_error(int error, const char_u *name) emsg_funcname(_(e_toomanyarg), name); break; case FCERR_TOOFEW: - emsg_funcname(N_("E119: Not enough arguments for function: %s"), - name); + emsg_funcname(N_("E119: Not enough arguments for function: %s"), name); break; case FCERR_SCRIPT: - emsg_funcname(N_("E120: Using <SID> not in a script context: %s"), - name); + emsg_funcname(N_("E120: Using <SID> not in a script context: %s"), name); break; case FCERR_DICT: - emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"), - name); + emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"), name); break; } } @@ -1477,7 +1525,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t // Make a copy of the name, if it comes from a funcref variable it could // be changed or deleted in the called function. name = xstrnsave(funcname, (size_t)len); - fname = fname_trans_sid(name, (char *)fname_buf, &tofree, &error); + fname = fname_trans_sid(name, fname_buf, &tofree, &error); } if (funcexe->fe_doesrange != NULL) { @@ -1532,7 +1580,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t } else if (fp != NULL || !builtin_function((const char *)rfname, -1)) { // User defined function. if (fp == NULL) { - fp = find_func((char_u *)rfname); + fp = find_func(rfname); } // Trigger FuncUndefined event, may load the function. @@ -1540,13 +1588,13 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t && apply_autocmds(EVENT_FUNCUNDEFINED, rfname, rfname, true, NULL) && !aborting()) { // executed an autocommand, search for the function again - fp = find_func((char_u *)rfname); + fp = find_func(rfname); } // Try loading a package. if (fp == NULL && script_autoload((const char *)rfname, strlen(rfname), true) && !aborting()) { // Loaded a package, search for the function again. - fp = find_func((char_u *)rfname); + fp = find_func(rfname); } if (fp != NULL && (fp->uf_flags & FC_DELETED)) { @@ -1564,11 +1612,11 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t } else if (funcexe->fe_basetv != NULL) { // expr->method(): Find the method name in the table, call its // implementation with the base as one of the arguments. - error = call_internal_method((char_u *)fname, argcount, argvars, rettv, + error = call_internal_method(fname, argcount, argvars, rettv, funcexe->fe_basetv); } else { // Find the function name in the table, call its implementation. - error = call_internal_func((char_u *)fname, argcount, argvars, rettv); + error = call_internal_func(fname, argcount, argvars, rettv); } // The function call (or "FuncUndefined" autocommand sequence) might // have been aborted by an error, an interrupt, or an explicitly thrown @@ -1588,7 +1636,7 @@ theend: // Report an error unless the argument evaluation or function call has been // cancelled due to an aborting error, an interrupt, or an exception. if (!aborting()) { - user_func_error(error, (name != NULL) ? (char_u *)name : (char_u *)funcname); + user_func_error(error, (name != NULL) ? name : funcname); } // clear the copies made from the partial @@ -1602,6 +1650,11 @@ theend: return ret; } +char *printable_func_name(ufunc_T *fp) +{ + return fp->uf_name_exp != NULL ? fp->uf_name_exp : fp->uf_name; +} + /// List the head of the function: "name(arg1, arg2)". /// /// @param[in] fp Function pointer. @@ -1671,12 +1724,12 @@ static void list_func_head(ufunc_T *fp, int indent, bool force) /// @param partial return: partial of a FuncRef /// /// @return the function name in allocated memory, or NULL for failure. -char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, partial_T **partial) +char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, partial_T **partial) FUNC_ATTR_NONNULL_ARG(1) { char *name = NULL; - const char_u *start; - const char_u *end; + const char *start; + const char *end; int lead; int len; lval_T lv; @@ -1684,7 +1737,7 @@ char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, pa if (fdp != NULL) { CLEAR_POINTER(fdp); } - start = (char_u *)(*pp); + start = *pp; // Check for hard coded <SNR>: already translated function ID (from a user // command). @@ -1692,19 +1745,19 @@ char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, pa && (*pp)[2] == KE_SNR) { *pp += 3; len = get_id_len((const char **)pp) + 3; - return (char_u *)xmemdupz(start, (size_t)len); + return xmemdupz(start, (size_t)len); } // A name starting with "<SID>" or "<SNR>" is local to a script. But // don't skip over "s:", get_lval() needs it for "s:dict.func". - lead = eval_fname_script((const char *)start); + lead = eval_fname_script(start); if (lead > 2) { start += lead; } // Note that TFN_ flags use the same values as GLV_ flags. - end = (char_u *)get_lval((char *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY, - lead > 2 ? 0 : FNE_CHECK_START); + end = get_lval((char *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY, + lead > 2 ? 0 : FNE_CHECK_START); if (end == start) { if (!skip) { emsg(_("E129: Function name required")); @@ -1728,7 +1781,7 @@ char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, pa if (lv.ll_tv != NULL) { if (fdp != NULL) { fdp->fd_dict = lv.ll_dict; - fdp->fd_newkey = (char_u *)lv.ll_newkey; + fdp->fd_newkey = lv.ll_newkey; lv.ll_newkey = NULL; fdp->fd_di = lv.ll_di; } @@ -1738,7 +1791,7 @@ char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, pa } else if (lv.ll_tv->v_type == VAR_PARTIAL && lv.ll_tv->vval.v_partial != NULL) { if (is_luafunc(lv.ll_tv->vval.v_partial) && *end == '.') { - len = check_luafunc_name((const char *)end + 1, true); + len = check_luafunc_name(end + 1, true); if (len == 0) { semsg(e_invexpr2, "v:lua"); goto theend; @@ -1775,15 +1828,14 @@ char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, pa // Check if the name is a Funcref. If so, use the value. if (lv.ll_exp_name != NULL) { len = (int)strlen(lv.ll_exp_name); - name = (char *)deref_func_name(lv.ll_exp_name, &len, partial, - flags & TFN_NO_AUTOLOAD); + name = deref_func_name(lv.ll_exp_name, &len, partial, + flags & TFN_NO_AUTOLOAD); if ((const char *)name == lv.ll_exp_name) { name = NULL; } } else if (!(flags & TFN_NO_DEREF)) { - len = (int)(end - (char_u *)(*pp)); - name = (char *)deref_func_name((const char *)(*pp), &len, partial, - flags & TFN_NO_AUTOLOAD); + len = (int)(end - *pp); + name = deref_func_name(*pp, &len, partial, flags & TFN_NO_AUTOLOAD); if (name == *pp) { name = NULL; } @@ -1791,7 +1843,7 @@ char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, pa if (name != NULL) { name = xstrdup(name); *pp = (char *)end; - if (STRNCMP(name, "<SNR>", 5) == 0) { + if (strncmp(name, "<SNR>", 5) == 0) { // Change "<SNR>" to the byte sequence. name[0] = (char)K_SPECIAL; name[1] = (char)KS_EXTRA; @@ -1818,7 +1870,7 @@ char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, pa lv.ll_name += 2; lv.ll_name_len -= 2; } - len = (int)((const char *)end - lv.ll_name); + len = (int)(end - lv.ll_name); } size_t sid_buf_len = 0; @@ -1849,7 +1901,7 @@ char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, pa } if (!skip && !(flags & TFN_QUIET) && !(flags & TFN_NO_DEREF)) { - char_u *cp = xmemrchr(lv.ll_name, ':', lv.ll_name_len); + char *cp = xmemrchr(lv.ll_name, ':', lv.ll_name_len); if (cp != NULL && cp < end) { semsg(_("E884: Function name cannot contain a colon: %s"), start); @@ -1872,11 +1924,96 @@ char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, pa theend: clear_lval(&lv); - return (char_u *)name; + return name; +} + +/// If the "funcname" starts with "s:" or "<SID>", then expands it to the +/// current script ID and returns the expanded function name. The caller should +/// free the returned name. If not called from a script context or the function +/// name doesn't start with these prefixes, then returns NULL. +/// This doesn't check whether the script-local function exists or not. +char *get_scriptlocal_funcname(char *funcname) +{ + if (funcname == NULL) { + return NULL; + } + + if (strncmp(funcname, "s:", 2) != 0 + && strncmp(funcname, "<SID>", 5) != 0) { + // The function name is not a script-local function name + return NULL; + } + + if (!SCRIPT_ID_VALID(current_sctx.sc_sid)) { + emsg(_(e_usingsid)); + return NULL; + } + + char sid_buf[25]; + // Expand s: and <SID> prefix into <SNR>nr_<name> + snprintf(sid_buf, sizeof(sid_buf), "<SNR>%" PRId64 "_", + (int64_t)current_sctx.sc_sid); + const int off = *funcname == 's' ? 2 : 5; + char *newname = xmalloc(strlen(sid_buf) + strlen(funcname + off) + 1); + STRCPY(newname, sid_buf); + STRCAT(newname, funcname + off); + + return newname; +} + +/// Call trans_function_name(), except that a lambda is returned as-is. +/// Returns the name in allocated memory. +char *save_function_name(char **name, bool skip, int flags, funcdict_T *fudi) +{ + char *p = *name; + char *saved; + + if (strncmp(p, "<lambda>", 8) == 0) { + p += 8; + (void)getdigits(&p, false, 0); + saved = xstrndup(*name, (size_t)(p - *name)); + if (fudi != NULL) { + CLEAR_POINTER(fudi); + } + } else { + saved = trans_function_name(&p, skip, flags, fudi, NULL); + } + *name = p; + return saved; } #define MAX_FUNC_NESTING 50 +/// List functions. +/// +/// @param regmatch When NULL, all of them. +/// Otherwise functions matching "regmatch". +static void list_functions(regmatch_T *regmatch) +{ + const int changed = func_hashtab.ht_changed; + size_t todo = func_hashtab.ht_used; + const hashitem_T *const ht_array = func_hashtab.ht_array; + + for (const hashitem_T *hi = ht_array; todo > 0 && !got_int; hi++) { + if (!HASHITEM_EMPTY(hi)) { + ufunc_T *fp = HI2UF(hi); + todo--; + if ((fp->uf_flags & FC_DEAD) == 0 + && (regmatch == NULL + ? (!message_filtered((char *)fp->uf_name) + && !func_name_refcount(fp->uf_name)) + : (!isdigit((uint8_t)(*fp->uf_name)) + && vim_regexec(regmatch, (char *)fp->uf_name, 0)))) { + list_func_head(fp, false, false); + if (changed != func_hashtab.ht_changed) { + emsg(_("E454: function list was modified")); + return; + } + } + } + } +} + /// ":function" void ex_function(exarg_T *eap) { @@ -1903,7 +2040,6 @@ void ex_function(exarg_T *eap) static int func_nr = 0; // number for nameless function int paren; hashtab_T *ht; - int todo; hashitem_T *hi; linenr_T sourcing_lnum_off; linenr_T sourcing_lnum_top; @@ -1916,19 +2052,7 @@ void ex_function(exarg_T *eap) // ":function" without argument: list functions. if (ends_excmd(*eap->arg)) { if (!eap->skip) { - todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) { - if (!HASHITEM_EMPTY(hi)) { - todo--; - fp = HI2UF(hi); - if (message_filtered((char *)fp->uf_name)) { - continue; - } - if (!func_name_refcount(fp->uf_name)) { - list_func_head(fp, false, false); - } - } - } + list_functions(NULL); } eap->nextcmd = check_nextcmd(eap->arg); return; @@ -1936,7 +2060,7 @@ void ex_function(exarg_T *eap) // ":function /pat": list functions matching pattern. if (*eap->arg == '/') { - p = skip_regexp(eap->arg + 1, '/', true, NULL); + p = skip_regexp(eap->arg + 1, '/', true); if (!eap->skip) { regmatch_T regmatch; @@ -1946,18 +2070,7 @@ void ex_function(exarg_T *eap) *p = c; if (regmatch.regprog != NULL) { regmatch.rm_ic = p_ic; - - todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) { - if (!HASHITEM_EMPTY(hi)) { - todo--; - fp = HI2UF(hi); - if (!isdigit(*fp->uf_name) - && vim_regexec(®match, (char *)fp->uf_name, 0)) { - list_func_head(fp, false, false); - } - } - } + list_functions(®match); vim_regfree(regmatch.regprog); } } @@ -1983,14 +2096,7 @@ void ex_function(exarg_T *eap) // s:func script-local function name // g:func global function name, same as "func" p = eap->arg; - if (strncmp(p, "<lambda>", 8) == 0) { - p += 8; - (void)getdigits(&p, false, 0); - name = xstrndup(eap->arg, (size_t)(p - eap->arg)); - CLEAR_FIELD(fudi); - } else { - name = (char *)trans_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL); - } + name = save_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi); paren = (vim_strchr(p, '(') != NULL); if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { // Return on an invalid expression in braces, unless the expression @@ -2002,9 +2108,8 @@ void ex_function(exarg_T *eap) } xfree(fudi.fd_newkey); return; - } else { - eap->skip = true; } + eap->skip = true; } // An error in a function call during evaluation of an expression in magic @@ -2028,7 +2133,7 @@ void ex_function(exarg_T *eap) *p = NUL; } if (!eap->skip && !got_int) { - fp = find_func((char_u *)name); + fp = find_func(name); if (fp != NULL) { list_func_head(fp, !eap->forceit, eap->forceit); for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) { @@ -2054,7 +2159,7 @@ void ex_function(exarg_T *eap) msg_puts(eap->forceit ? "endfunction" : " endfunction"); } } else { - emsg_funcname(N_("E123: Undefined function: %s"), (char_u *)name); + emsg_funcname(N_("E123: Undefined function: %s"), name); } } goto ret_free; @@ -2074,8 +2179,8 @@ void ex_function(exarg_T *eap) } p = skipwhite(p + 1); - ga_init(&newargs, (int)sizeof(char_u *), 3); - ga_init(&newlines, (int)sizeof(char_u *), 3); + ga_init(&newargs, (int)sizeof(char *), 3); + ga_init(&newlines, (int)sizeof(char *), 3); if (!eap->skip) { // Check the name of the function. Unless it's a dictionary function @@ -2083,7 +2188,7 @@ void ex_function(exarg_T *eap) if (name != NULL) { arg = name; } else { - arg = (char *)fudi.fd_newkey; + arg = fudi.fd_newkey; } if (arg != NULL && (fudi.fd_di == NULL || !tv_is_func(fudi.fd_di->di_tv))) { int j = ((uint8_t)(*arg) == K_SPECIAL) ? 3 : 0; @@ -2091,7 +2196,7 @@ void ex_function(exarg_T *eap) j++; } if (arg[j] != NUL) { - emsg_funcname((char *)e_invarg2, (char_u *)arg); + emsg_funcname((char *)e_invarg2, arg); } } // Disallow using the g: dict. @@ -2113,21 +2218,21 @@ void ex_function(exarg_T *eap) // find extra arguments "range", "dict", "abort" and "closure" for (;;) { p = skipwhite(p); - if (STRNCMP(p, "range", 5) == 0) { + if (strncmp(p, "range", 5) == 0) { flags |= FC_RANGE; p += 5; - } else if (STRNCMP(p, "dict", 4) == 0) { + } else if (strncmp(p, "dict", 4) == 0) { flags |= FC_DICT; p += 4; - } else if (STRNCMP(p, "abort", 5) == 0) { + } else if (strncmp(p, "abort", 5) == 0) { flags |= FC_ABORT; p += 5; - } else if (STRNCMP(p, "closure", 7) == 0) { + } else if (strncmp(p, "closure", 7) == 0) { flags |= FC_CLOSURE; p += 7; if (current_funccal == NULL) { emsg_funcname(N_("E932: Closure function should not be at top level: %s"), - name == NULL ? (char_u *)"" : (char_u *)name); + name == NULL ? "" : name); goto erret; } } else { @@ -2151,8 +2256,8 @@ void ex_function(exarg_T *eap) if (!eap->skip && !eap->forceit) { if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL) { emsg(_(e_funcdict)); - } else if (name != NULL && find_func((char_u *)name) != NULL) { - emsg_funcname(e_funcexts, (char_u *)name); + } else if (name != NULL && find_func(name) != NULL) { + emsg_funcname(e_funcexts, name); } } @@ -2191,7 +2296,7 @@ void ex_function(exarg_T *eap) } else { xfree(line_to_free); if (eap->getline == NULL) { - theline = (char *)getcmdline(':', 0L, indent, do_concat); + theline = getcmdline(':', 0L, indent, do_concat); } else { theline = eap->getline(':', eap->cookie, indent, do_concat); } @@ -2224,7 +2329,7 @@ void ex_function(exarg_T *eap) // * ":let {var-name} =<< [trim] {marker}" and "{marker}" if (heredoc_trimmed == NULL || (is_heredoc && skipwhite(theline) == theline) - || STRNCMP(theline, heredoc_trimmed, + || strncmp(theline, heredoc_trimmed, strlen(heredoc_trimmed)) == 0) { if (heredoc_trimmed == NULL) { p = theline; @@ -2274,12 +2379,12 @@ void ex_function(exarg_T *eap) // Increase indent inside "if", "while", "for" and "try", decrease // at "end". - if (indent > 2 && STRNCMP(p, "end", 3) == 0) { + if (indent > 2 && strncmp(p, "end", 3) == 0) { indent -= 2; - } else if (STRNCMP(p, "if", 2) == 0 - || STRNCMP(p, "wh", 2) == 0 - || STRNCMP(p, "for", 3) == 0 - || STRNCMP(p, "try", 3) == 0) { + } else if (strncmp(p, "if", 2) == 0 + || strncmp(p, "wh", 2) == 0 + || strncmp(p, "for", 3) == 0 + || strncmp(p, "try", 3) == 0) { indent += 2; } @@ -2307,7 +2412,7 @@ void ex_function(exarg_T *eap) && (!ASCII_ISALPHA(p[1]) || (p[1] == 'h' && (!ASCII_ISALPHA(p[2]) || (p[2] == 'a' - && (STRNCMP(&p[3], "nge", 3) != 0 + && (strncmp(&p[3], "nge", 3) != 0 || !ASCII_ISALPHA(p[6]))))))) || (p[0] == 'i' && (!ASCII_ISALPHA(p[1]) || (p[1] == 'n' @@ -2358,7 +2463,7 @@ void ex_function(exarg_T *eap) && (!ASCII_ISALNUM(p[2]) || (p[2] == 't' && !ASCII_ISALNUM(p[3]))))) { p = skipwhite(arg + 3); - if (STRNCMP(p, "trim", 4) == 0) { + if (strncmp(p, "trim", 4) == 0) { // Ignore leading white space. p = skipwhite(p + 4); heredoc_trimmed = xstrnsave(theline, (size_t)(skipwhite(theline) - theline)); @@ -2401,24 +2506,22 @@ void ex_function(exarg_T *eap) if (fudi.fd_dict == NULL) { v = find_var((const char *)name, strlen(name), &ht, false); if (v != NULL && v->di_tv.v_type == VAR_FUNC) { - emsg_funcname(N_("E707: Function name conflicts with variable: %s"), - (char_u *)name); + emsg_funcname(N_("E707: Function name conflicts with variable: %s"), name); goto erret; } - fp = find_func((char_u *)name); + fp = find_func(name); if (fp != NULL) { // Function can be replaced with "function!" and when sourcing the // same script again, but only once. if (!eap->forceit && (fp->uf_script_ctx.sc_sid != current_sctx.sc_sid || fp->uf_script_ctx.sc_seq == current_sctx.sc_seq)) { - emsg_funcname(e_funcexts, (char_u *)name); + emsg_funcname(e_funcexts, name); goto erret; } if (fp->uf_calls > 0) { - emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"), - (char_u *)name); + emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"), name); goto erret; } if (fp->uf_refcount > 1) { @@ -2429,7 +2532,7 @@ void ex_function(exarg_T *eap) fp = NULL; overwrite = true; } else { - char_u *exp_name = fp->uf_name_exp; + char *exp_name = fp->uf_name_exp; // redefine existing function, keep the expanded name XFREE_CLEAR(name); fp->uf_name_exp = NULL; @@ -2448,13 +2551,13 @@ void ex_function(exarg_T *eap) goto erret; } if (fudi.fd_di == NULL) { - if (var_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg, - TV_CSTRING)) { + if (value_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg, + TV_CSTRING)) { // Can't add a function to a locked dictionary goto erret; } - } else if (var_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg, - TV_CSTRING)) { + } else if (value_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg, + TV_CSTRING)) { // Can't change an existing function if it is locked goto erret; } @@ -2469,15 +2572,15 @@ void ex_function(exarg_T *eap) if (fp == NULL) { if (fudi.fd_dict == NULL && vim_strchr(name, AUTOLOAD_CHAR) != NULL) { int slen, plen; - char_u *scriptname; + char *scriptname; // Check that the autoload name matches the script name. int j = FAIL; if (SOURCING_NAME != NULL) { - scriptname = (char_u *)autoload_name((const char *)name, strlen(name)); - p = vim_strchr((char *)scriptname, '/'); - plen = (int)STRLEN(p); - slen = (int)STRLEN(SOURCING_NAME); + scriptname = autoload_name(name, strlen(name)); + p = vim_strchr(scriptname, '/'); + plen = (int)strlen(p); + slen = (int)strlen(SOURCING_NAME); if (slen > plen && path_fnamecmp(p, SOURCING_NAME + slen - plen) == 0) { j = OK; } @@ -2513,10 +2616,10 @@ void ex_function(exarg_T *eap) } // insert the new function in the function list - set_ufunc_name(fp, (char_u *)name); + set_ufunc_name(fp, name); if (overwrite) { hi = hash_find(&func_hashtab, name); - hi->hi_key = UF2HIKEY(fp); + hi->hi_key = (char *)UF2HIKEY(fp); } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { xfree(fp); goto erret; @@ -2587,7 +2690,7 @@ bool translated_function_exists(const char *name) if (builtin_function(name, -1)) { return find_internal_func((char *)name) != NULL; } - return find_func((const char_u *)name) != NULL; + return find_func(name) != NULL; } /// Check whether function with the given name exists @@ -2598,15 +2701,15 @@ bool translated_function_exists(const char *name) /// @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; + const char *nm = name; bool n = false; int flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD; if (no_deref) { flag |= TFN_NO_DEREF; } - char *const p = (char *)trans_function_name((char **)&nm, false, flag, NULL, NULL); - nm = (char_u *)skipwhite((char *)nm); + char *const p = trans_function_name((char **)&nm, false, flag, NULL, NULL); + nm = skipwhite(nm); // Only accept "funcname", "funcname ", "funcname (..." and // "funcname(...", not "funcname!...". @@ -2622,15 +2725,17 @@ bool function_exists(const char *const name, bool no_deref) char *get_user_func_name(expand_T *xp, int idx) { static size_t done; + static int changed; static hashitem_T *hi; ufunc_T *fp; if (idx == 0) { done = 0; hi = func_hashtab.ht_array; + changed = func_hashtab.ht_changed; } assert(hi); - if (done < func_hashtab.ht_used) { + if (changed == func_hashtab.ht_changed && done < func_hashtab.ht_used) { if (done++ > 0) { hi++; } @@ -2640,22 +2745,22 @@ char *get_user_func_name(expand_T *xp, int idx) fp = HI2UF(hi); if ((fp->uf_flags & FC_DICT) - || STRNCMP(fp->uf_name, "<lambda>", 8) == 0) { + || strncmp(fp->uf_name, "<lambda>", 8) == 0) { return ""; // don't show dict and lambda functions } - if (STRLEN(fp->uf_name) + 4 >= IOSIZE) { + if (strlen(fp->uf_name) + 4 >= IOSIZE) { return (char *)fp->uf_name; // Prevent overflow. } - cat_func_name((char_u *)IObuff, fp); + cat_func_name(IObuff, fp); if (xp->xp_context != EXPAND_USER_FUNC) { STRCAT(IObuff, "("); if (!fp->uf_varargs && GA_EMPTY(&fp->uf_args)) { STRCAT(IObuff, ")"); } } - return (char *)IObuff; + return IObuff; } return NULL; } @@ -2664,12 +2769,12 @@ char *get_user_func_name(expand_T *xp, int idx) void ex_delfunction(exarg_T *eap) { ufunc_T *fp = NULL; - char_u *p; - char_u *name; + char *p; + char *name; funcdict_T fudi; - p = (char_u *)eap->arg; - name = trans_function_name((char **)&p, eap->skip, 0, &fudi, NULL); + p = eap->arg; + name = trans_function_name(&p, eap->skip, 0, &fudi, NULL); xfree(fudi.fd_newkey); if (name == NULL) { if (fudi.fd_dict != NULL && !eap->skip) { @@ -2677,16 +2782,23 @@ void ex_delfunction(exarg_T *eap) } return; } - if (!ends_excmd(*skipwhite((char *)p))) { + if (!ends_excmd(*skipwhite(p))) { xfree(name); semsg(_(e_trailing_arg), p); return; } - eap->nextcmd = check_nextcmd((char *)p); + eap->nextcmd = check_nextcmd(p); if (eap->nextcmd != NULL) { *p = NUL; } + if (isdigit((uint8_t)(*name)) && fudi.fd_dict == NULL) { + if (!eap->skip) { + semsg(_(e_invarg2), eap->arg); + } + xfree(name); + return; + } if (!eap->skip) { fp = find_func(name); } @@ -2737,7 +2849,7 @@ void ex_delfunction(exarg_T *eap) /// Unreference a Function: decrement the reference count and free it when it /// becomes zero. -void func_unref(char_u *name) +void func_unref(char *name) { ufunc_T *fp = NULL; @@ -2746,7 +2858,7 @@ void func_unref(char_u *name) } fp = find_func(name); - if (fp == NULL && isdigit(*name)) { + if (fp == NULL && isdigit((uint8_t)(*name))) { #ifdef EXITFREE if (!entered_free_all_mem) { internal_error("func_unref()"); @@ -2779,7 +2891,7 @@ void func_ptr_unref(ufunc_T *fp) } /// Count a reference to a Function. -void func_ref(char_u *name) +void func_ref(char *name) { ufunc_T *fp; @@ -2789,7 +2901,7 @@ void func_ref(char_u *name) fp = find_func(name); if (fp != NULL) { (fp->uf_refcount)++; - } else if (isdigit(*name)) { + } else if (isdigit((uint8_t)(*name))) { // Only give an error for a numbered function. // Fail silently, when named or lambda function isn't found. internal_error("func_ref()"); @@ -2833,7 +2945,7 @@ static int can_free_funccal(funccall_T *fc, int copyID) /// ":return [expr]" void ex_return(exarg_T *eap) { - char_u *arg = (char_u *)eap->arg; + char *arg = eap->arg; typval_T rettv; int returning = false; @@ -2848,7 +2960,7 @@ void ex_return(exarg_T *eap) eap->nextcmd = NULL; if ((*arg != NUL && *arg != '|' && *arg != '\n') - && eval0((char *)arg, &rettv, &eap->nextcmd, !eap->skip) != FAIL) { + && eval0(arg, &rettv, &eap->nextcmd, !eap->skip) != FAIL) { if (!eap->skip) { returning = do_return(eap, false, true, &rettv); } else { @@ -2871,7 +2983,7 @@ void ex_return(exarg_T *eap) if (returning) { eap->nextcmd = NULL; } else if (eap->nextcmd == NULL) { // no argument - eap->nextcmd = check_nextcmd((char *)arg); + eap->nextcmd = check_nextcmd(arg); } if (eap->skip) { @@ -2879,15 +2991,13 @@ void ex_return(exarg_T *eap) } } -// TODO(ZyX-I): move to eval/ex_cmds - /// ":1,25call func(arg1, arg2)" function call. void ex_call(exarg_T *eap) { - char_u *arg = (char_u *)eap->arg; - char_u *startarg; - char_u *name; - char_u *tofree; + char *arg = eap->arg; + char *startarg; + char *name; + char *tofree; int len; typval_T rettv; linenr_T lnum; @@ -2908,7 +3018,7 @@ void ex_call(exarg_T *eap) return; } - tofree = trans_function_name((char **)&arg, false, TFN_INT, &fudi, &partial); + tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial); if (fudi.fd_newkey != NULL) { // Still need to give an error message for missing key. semsg(_(e_dictkey), fudi.fd_newkey); @@ -2927,13 +3037,12 @@ void ex_call(exarg_T *eap) // If it is the name of a variable of type VAR_FUNC or VAR_PARTIAL use its // contents. For VAR_PARTIAL get its partial, unless we already have one // from trans_function_name(). - len = (int)STRLEN(tofree); - name = deref_func_name((const char *)tofree, &len, - partial != NULL ? NULL : &partial, false); + len = (int)strlen(tofree); + name = deref_func_name(tofree, &len, partial != NULL ? NULL : &partial, false); // Skip white space to allow ":call func ()". Not good, but required for // backward compatibility. - startarg = (char_u *)skipwhite((char *)arg); + startarg = skipwhite(arg); rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this. if (*startarg != '(') { @@ -2963,7 +3072,7 @@ void ex_call(exarg_T *eap) funcexe.fe_evaluate = true; funcexe.fe_partial = partial; funcexe.fe_selfdict = fudi.fd_dict; - if (get_func_tv(name, -1, &rettv, (char **)&arg, &funcexe) == FAIL) { + if (get_func_tv(name, -1, &rettv, &arg, &funcexe) == FAIL) { failed = true; break; } @@ -3000,7 +3109,7 @@ void ex_call(exarg_T *eap) semsg(_(e_trailing_arg), arg); } } else { - eap->nextcmd = check_nextcmd((char *)arg); + eap->nextcmd = check_nextcmd(arg); } } @@ -3100,12 +3209,12 @@ char *get_return_cmd(void *rettv) } STRCPY(IObuff, ":return "); - STRLCPY(IObuff + 8, s, IOSIZE - 8); + xstrlcpy(IObuff + 8, s, IOSIZE - 8); if (strlen(s) + 8 >= IOSIZE) { STRCPY(IObuff + IOSIZE - 4, "..."); } xfree(tofree); - return xstrdup((char *)IObuff); + return xstrdup(IObuff); } /// Get next function line. @@ -3182,20 +3291,20 @@ int func_has_abort(void *cookie) /// Changes "rettv" in-place. void make_partial(dict_T *const selfdict, typval_T *const rettv) { - char_u *fname; - char_u *tofree = NULL; + char *fname; + char *tofree = NULL; ufunc_T *fp; - char_u fname_buf[FLEN_FIXED + 1]; + char fname_buf[FLEN_FIXED + 1]; int error; if (rettv->v_type == VAR_PARTIAL && rettv->vval.v_partial->pt_func != NULL) { fp = rettv->vval.v_partial->pt_func; } else { fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING - ? (char_u *)rettv->vval.v_string - : (char_u *)rettv->vval.v_partial->pt_name; + ? rettv->vval.v_string + : rettv->vval.v_partial->pt_name; // Translate "s:func" to the stored function name. - fname = (char_u *)fname_trans_sid((char *)fname, (char *)fname_buf, (char **)&tofree, &error); + fname = fname_trans_sid(fname, fname_buf, &tofree, &error); fp = find_func(fname); xfree(tofree); } @@ -3219,7 +3328,7 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv) // be referenced elsewhere. if (ret_pt->pt_name != NULL) { pt->pt_name = xstrdup(ret_pt->pt_name); - func_ref((char_u *)pt->pt_name); + func_ref(pt->pt_name); } else { pt->pt_func = ret_pt->pt_func; func_ptr_ref(pt->pt_func); @@ -3240,7 +3349,7 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv) } /// @return the name of the executed function. -char_u *func_name(void *cookie) +char *func_name(void *cookie) { return ((funccall_T *)cookie)->func->uf_name; } @@ -3527,21 +3636,21 @@ bool set_ref_in_func_args(int copyID) /// "ht_stack" is used to add hashtabs to be marked. Can be NULL. /// /// @return true if setting references failed somehow. -bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) +bool set_ref_in_func(char *name, ufunc_T *fp_in, int copyID) { ufunc_T *fp = fp_in; funccall_T *fc; int error = FCERR_NONE; - char_u fname_buf[FLEN_FIXED + 1]; - char_u *tofree = NULL; - char_u *fname; + char fname_buf[FLEN_FIXED + 1]; + char *tofree = NULL; + char *fname; bool abort = false; if (name == NULL && fp_in == NULL) { return false; } if (fp_in == NULL) { - fname = (char_u *)fname_trans_sid((char *)name, (char *)fname_buf, (char **)&tofree, &error); + fname = fname_trans_sid(name, fname_buf, &tofree, &error); fp = find_func(fname); } if (fp != NULL) { @@ -3554,10 +3663,10 @@ bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) } /// Registers a luaref as a lambda. -char_u *register_luafunc(LuaRef ref) +char *register_luafunc(LuaRef ref) { - char_u *name = get_lambda_name(); - ufunc_T *fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); + char *name = get_lambda_name(); + ufunc_T *fp = xcalloc(1, offsetof(ufunc_T, uf_name) + strlen(name) + 1); fp->uf_refcount = 1; fp->uf_varargs = true; diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h index 4098622a14..c8583f232c 100644 --- a/src/nvim/eval/userfunc.h +++ b/src/nvim/eval/userfunc.h @@ -1,8 +1,18 @@ #ifndef NVIM_EVAL_USERFUNC_H #define NVIM_EVAL_USERFUNC_H +#include <stdbool.h> +#include <stddef.h> + #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" +#include "nvim/garray.h" +#include "nvim/hashtab.h" +#include "nvim/pos.h" +#include "nvim/types.h" + +struct funccal_entry; // From user function to hashitem and back. #define UF2HIKEY(fp) ((fp)->uf_name) @@ -23,10 +33,10 @@ #define FC_VIM9 0x400 // defined in vim9 script file #define FC_LUAREF 0x800 // luaref callback -///< Structure used by trans_function_name() +/// Structure used by trans_function_name() typedef struct { dict_T *fd_dict; ///< Dictionary used. - char_u *fd_newkey; ///< New key in "dict" in allocated memory. + char *fd_newkey; ///< New key in "dict" in allocated memory. dictitem_T *fd_di; ///< Dictionary item used. } funcdict_T; diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 4d7214205d..3e593151fc 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -3,24 +3,43 @@ // eval/vars.c: functions for dealing with variables +#include <assert.h> +#include <ctype.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> + #include "nvim/ascii.h" #include "nvim/autocmd.h" -#include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" #include "nvim/eval/funcs.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" +#include "nvim/eval/window.h" #include "nvim/ex_cmds.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/hashtab.h" +#include "nvim/macros.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/ops.h" #include "nvim/option.h" -#include "nvim/screen.h" +#include "nvim/os/os.h" #include "nvim/search.h" +#include "nvim/strings.h" +#include "nvim/types.h" +#include "nvim/vim.h" #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -62,7 +81,7 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd) // Check for the optional 'trim' word before the marker cmd = skipwhite(cmd); - if (STRNCMP(cmd, "trim", 4) == 0 + if (strncmp(cmd, "trim", 4) == 0 && (cmd[4] == NUL || ascii_iswhite(cmd[4]))) { cmd = skipwhite(cmd + 4); @@ -87,7 +106,7 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd) return NULL; } *p = NUL; - if (islower(*marker)) { + if (islower((uint8_t)(*marker))) { emsg(_("E221: Marker cannot start with lower case letter")); return NULL; } @@ -110,7 +129,7 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd) // with "trim": skip the indent matching the :let line to find the // marker if (marker_indent_len > 0 - && STRNCMP(theline, *eap->cmdlinep, marker_indent_len) == 0) { + && strncmp(theline, *eap->cmdlinep, (size_t)marker_indent_len) == 0) { mi = marker_indent_len; } if (strcmp(marker, theline + mi) == 0) { @@ -189,8 +208,8 @@ static void ex_let_const(exarg_T *eap, const bool is_const) argend--; } expr = skipwhite(argend); - if (*expr != '=' && !((vim_strchr("+-*/%.", *expr) != NULL - && expr[1] == '=') || STRNCMP(expr, "..=", 3) == 0)) { + if (*expr != '=' && !((vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL + && expr[1] == '=') || strncmp(expr, "..=", 3) == 0)) { // ":let" without "=": list variables if (*arg == '[') { emsg(_(e_invarg)); @@ -225,17 +244,19 @@ static void ex_let_const(exarg_T *eap, const bool is_const) op[0] = '='; op[1] = NUL; if (*expr != '=') { - if (vim_strchr("+-*/%.", *expr) != NULL) { + if (vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL) { op[0] = *expr; // +=, -=, *=, /=, %= or .= if (expr[0] == '.' && expr[1] == '.') { // ..= expr++; } } - expr = skipwhite(expr + 2); + expr += 2; } else { - expr = skipwhite(expr + 1); + expr += 1; } + expr = skipwhite(expr); + if (eap->skip) { emsg_skip++; } @@ -378,9 +399,8 @@ const char *skip_var_list(const char *arg, int *var_count, int *semicolon) } } return p + 1; - } else { - return skip_var_one((char *)arg); } + return skip_var_one((char *)arg); } /// Skip one (assignable) variable name, including @r, $VAR, &option, d.key, @@ -539,8 +559,6 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first) return arg; } -// TODO(ZyX-I): move to eval/ex_cmds - /// Set one item of `:let var = expr` or `:let [v1, v2] = list` to its value /// /// @param[in] arg Start of the variable name. @@ -558,8 +576,6 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo { char *arg_end = NULL; int len; - int opt_flags; - char *tofree = NULL; // ":let $VAR = expr": Set environment variable. if (*arg == '$') { @@ -574,18 +590,18 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo if (len == 0) { semsg(_(e_invarg2), name - 1); } else { - if (op != NULL && vim_strchr("+-*/%", *op) != NULL) { + if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) { semsg(_(e_letwrong), op); } else if (endchars != NULL - && vim_strchr(endchars, *skipwhite(arg)) == NULL) { + && vim_strchr(endchars, (uint8_t)(*skipwhite(arg))) == NULL) { emsg(_(e_letunexp)); } else if (!check_secure()) { + char *tofree = NULL; const char c1 = name[len]; name[len] = NUL; const char *p = tv_get_string_chk(tv); if (p != NULL && op != NULL && *op == '.') { char *s = vim_getenv(name); - if (s != NULL) { tofree = concat_str(s, p); p = (const char *)tofree; @@ -609,10 +625,11 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo return NULL; } // Find the end of the name. - char *const p = (char *)find_option_end((const char **)&arg, &opt_flags); + int scope; + char *const p = (char *)find_option_end((const char **)&arg, &scope); if (p == NULL || (endchars != NULL - && vim_strchr(endchars, *skipwhite(p)) == NULL)) { + && vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL)) { emsg(_(e_letunexp)); } else { varnumber_T n = 0; @@ -621,11 +638,13 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo char *stringval = NULL; const char *s = NULL; bool failed = false; + uint32_t opt_p_flags; + char *tofree = NULL; const char c1 = *p; *p = NUL; - opt_type = get_option_value(arg, &numval, &stringval, opt_flags); + opt_type = get_option_value(arg, &numval, &stringval, &opt_p_flags, scope); if (opt_type == gov_bool || opt_type == gov_number || opt_type == gov_hidden_bool @@ -634,8 +653,13 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo n = (long)tv_get_number(tv); } - // Avoid setting a string option to the text "v:false" or similar. - if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) { + if ((opt_p_flags & P_FUNC) && tv_is_func(*tv)) { + // If the option can be set to a function reference or a lambda + // and the passed value is a function reference, then convert it to + // the name (string) of the function reference. + s = tofree = encode_tv2string(tv, NULL); + } else if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) { + // Avoid setting a string option to the text "v:false" or similar. s = tv_get_string_chk(tv); } @@ -672,7 +696,7 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo if (!failed) { if (opt_type != gov_string || s != NULL) { - char *err = set_option_value(arg, n, s, opt_flags); + char *err = set_option_value(arg, n, s, scope); arg_end = p; if (err != NULL) { emsg(_(err)); @@ -683,6 +707,7 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo } *p = c1; xfree(stringval); + xfree(tofree); } // ":let @r = expr": Set register contents. } else if (*arg == '@') { @@ -691,10 +716,10 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo return NULL; } arg++; - if (op != NULL && vim_strchr("+-*/%", *op) != NULL) { + if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) { semsg(_(e_letwrong), op); } else if (endchars != NULL - && vim_strchr(endchars, *skipwhite(arg + 1)) == NULL) { + && vim_strchr(endchars, (uint8_t)(*skipwhite(arg + 1))) == NULL) { emsg(_(e_letunexp)); } else { char *s; @@ -722,7 +747,7 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo char *const p = get_lval(arg, tv, &lv, false, false, 0, FNE_CHECK_START); if (p != NULL && lv.ll_name != NULL) { - if (endchars != NULL && vim_strchr(endchars, *skipwhite(p)) == NULL) { + if (endchars != NULL && vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL) { emsg(_(e_letunexp)); } else { set_var_lval(&lv, p, tv, copy, is_const, op); @@ -743,8 +768,6 @@ void ex_unlet(exarg_T *eap) ex_unletlock(eap, eap->arg, 0, do_unlet_var); } -// TODO(ZyX-I): move to eval/ex_cmds - /// ":lockvar" and ":unlockvar" commands void ex_lockvar(exarg_T *eap) { @@ -761,8 +784,6 @@ void ex_lockvar(exarg_T *eap) ex_unletlock(eap, arg, deep, do_lock_var); } -// TODO(ZyX-I): move to eval/ex_cmds - /// Common parsing logic for :unlet, :lockvar and :unlockvar. /// /// Invokes `callback` afterwards if successful and `eap->skip == false`. @@ -827,8 +848,6 @@ static void ex_unletlock(exarg_T *eap, char *argstart, int deep, ex_unletlock_ca eap->nextcmd = check_nextcmd(arg); } -// TODO(ZyX-I): move to eval/ex_cmds - /// Unlet a variable indicated by `lp`. /// /// @param[in] lp The lvalue. @@ -845,7 +864,7 @@ static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_ int cc; if (lp->ll_tv == NULL) { - cc = (char_u)(*name_end); + cc = (uint8_t)(*name_end); *name_end = NUL; // Environment variable, normal name or expanded name. @@ -858,13 +877,13 @@ static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_ } else if ((lp->ll_list != NULL // ll_list is not NULL when lvalue is not in a list, NULL lists // yield E689. - && var_check_lock(tv_list_locked(lp->ll_list), - lp->ll_name, - lp->ll_name_len)) + && value_check_lock(tv_list_locked(lp->ll_list), + lp->ll_name, + lp->ll_name_len)) || (lp->ll_dict != NULL - && var_check_lock(lp->ll_dict->dv_lock, - lp->ll_name, - lp->ll_name_len))) { + && value_check_lock(lp->ll_dict->dv_lock, + lp->ll_name, + lp->ll_name_len))) { return FAIL; } else if (lp->ll_range) { assert(lp->ll_list != NULL); @@ -873,18 +892,17 @@ static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_ listitem_T *last_li = first_li; for (;;) { listitem_T *const li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); - if (var_check_lock(TV_LIST_ITEM_TV(lp->ll_li)->v_lock, - lp->ll_name, - lp->ll_name_len)) { + if (value_check_lock(TV_LIST_ITEM_TV(lp->ll_li)->v_lock, + lp->ll_name, + lp->ll_name_len)) { return false; } lp->ll_li = li; lp->ll_n1++; if (lp->ll_li == NULL || (!lp->ll_empty2 && lp->ll_n2 < lp->ll_n1)) { break; - } else { - last_li = lp->ll_li; } + last_li = lp->ll_li; } tv_list_remove_items(lp->ll_list, first_li, last_li); } else { @@ -919,8 +937,6 @@ static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_ return ret; } -// TODO(ZyX-I): move to eval/ex_cmds - /// unlet a variable /// /// @param[in] name Variable name to unlet. @@ -960,11 +976,11 @@ int do_unlet(const char *const name, const size_t name_len, const bool forceit) dictitem_T *const di = TV_DICT_HI2DI(hi); if (var_check_fixed(di->di_flags, name, TV_CSTRING) || var_check_ro(di->di_flags, name, TV_CSTRING) - || var_check_lock(d->dv_lock, name, TV_CSTRING)) { + || value_check_lock(d->dv_lock, name, TV_CSTRING)) { return FAIL; } - if (var_check_lock(d->dv_lock, name, TV_CSTRING)) { + if (value_check_lock(d->dv_lock, name, TV_CSTRING)) { return FAIL; } @@ -991,8 +1007,6 @@ int do_unlet(const char *const name, const size_t name_len, const bool forceit) return FAIL; } -// TODO(ZyX-I): move to eval/ex_cmds - /// Lock or unlock variable indicated by `lp`. /// /// Locks if `eap->cmdidx == CMD_lockvar`, unlocks otherwise. @@ -1009,10 +1023,6 @@ static int do_lock_var(lval_T *lp, char *name_end FUNC_ATTR_UNUSED, exarg_T *eap bool lock = eap->cmdidx == CMD_lockvar; int ret = OK; - if (deep == 0) { // Nothing to do. - return OK; - } - if (lp->ll_tv == NULL) { if (*lp->ll_name == '$') { semsg(_(e_lock_unlock), lp->ll_name); @@ -1036,9 +1046,13 @@ static int do_lock_var(lval_T *lp, char *name_end FUNC_ATTR_UNUSED, exarg_T *eap } else { di->di_flags &= (uint8_t)(~DI_FLAGS_LOCK); } - tv_item_lock(&di->di_tv, deep, lock, false); + if (deep != 0) { + tv_item_lock(&di->di_tv, deep, lock, false); + } } } + } else if (deep == 0) { + // nothing to do } else if (lp->ll_range) { listitem_T *li = lp->ll_li; @@ -1098,7 +1112,7 @@ int get_var_tv(const char *name, int len, typval_T *rettv, dictitem_T **dip, boo /// 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) +char *get_var_value(const char *const name) { dictitem_T *v; @@ -1106,7 +1120,7 @@ char_u *get_var_value(const char *const name) if (v == NULL) { return NULL; } - return (char_u *)tv_get_string(&v->di_tv); + return (char *)tv_get_string(&v->di_tv); } /// Clean up a list of internal variables. @@ -1161,7 +1175,7 @@ void delete_var(hashtab_T *ht, hashitem_T *hi) static void list_one_var(dictitem_T *v, const char *prefix, int *first) { char *const s = encode_tv2echo(&v->di_tv, NULL); - list_one_var_a(prefix, (const char *)v->di_key, (ptrdiff_t)STRLEN(v->di_key), + list_one_var_a(prefix, (const char *)v->di_key, (ptrdiff_t)strlen((char *)v->di_key), v->di_tv.v_type, (s == NULL ? "" : s), first); xfree(s); } @@ -1257,7 +1271,7 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, v = find_var_in_scoped_ht(name, name_len, true); } - if (tv_is_func(*tv) && !var_check_func_name(name, v == NULL)) { + if (tv_is_func(*tv) && var_wrong_func_name(name, v == NULL)) { return; } @@ -1268,12 +1282,18 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, return; } - // existing variable, need to clear the value + // Check in this order for backwards compatibility: + // - Whether the variable is read-only + // - Whether the variable value is locked + // - Whether the variable is locked if (var_check_ro(v->di_flags, name, name_len) - || var_check_lock(v->di_tv.v_lock, name, name_len)) { + || value_check_lock(v->di_tv.v_lock, name, name_len) + || var_check_lock(v->di_flags, name, name_len)) { return; } + // existing variable, need to clear the value + // Handle setting internal v: variables separately where needed to // prevent changing the type. if (is_vimvarht(ht)) { @@ -1329,7 +1349,7 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, v = xmalloc(sizeof(dictitem_T) + strlen(varname)); STRCPY(v->di_key, varname); - if (tv_dict_add(dict, v) == FAIL) { + if (hash_add(ht, (char *)v->di_key) == FAIL) { xfree(v); return; } @@ -1404,6 +1424,26 @@ bool var_check_ro(const int flags, const char *name, size_t name_len) return true; } +/// Return true if di_flags "flags" indicates variable "name" is locked. +/// Also give an error message. +bool var_check_lock(const int flags, const char *name, size_t name_len) +{ + if (!(flags & DI_FLAGS_LOCK)) { + return false; + } + + if (name_len == TV_TRANSLATE) { + name = _(name); + name_len = strlen(name); + } else if (name_len == TV_CSTRING) { + name_len = strlen(name); + } + + semsg(_("E1122: Variable is locked: %*s"), (int)name_len, name); + + return true; +} + /// Check whether variable is fixed (DI_FLAGS_FIX) /// /// Also gives an error message. @@ -1438,37 +1478,34 @@ bool var_check_fixed(const int flags, const char *name, size_t name_len) return false; } -// TODO(ZyX-I): move to eval/expressions - /// Check if name is a valid name to assign funcref to /// /// @param[in] name Possible function/funcref name. /// @param[in] new_var True if it is a name for a variable. /// -/// @return false in case of error, true in case of success. Also gives an +/// @return false in case of success, true in case of failure. Also gives an /// error message if appropriate. -bool var_check_func_name(const char *const name, const bool new_var) +bool var_wrong_func_name(const char *const name, const bool new_var) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { // Allow for w: b: s: and t:. - if (!(vim_strchr("wbst", name[0]) != NULL && name[1] == ':') - && !ASCII_ISUPPER((name[0] != NUL && name[1] == ':') - ? name[2] : name[0])) { + // Allow autoload variable. + if (!(vim_strchr("wbst", (uint8_t)name[0]) != NULL && name[1] == ':') + && !ASCII_ISUPPER((name[0] != NUL && name[1] == ':') ? name[2] : name[0]) + && vim_strchr(name, '#') == NULL) { semsg(_("E704: Funcref variable name must start with a capital: %s"), name); - return false; + return true; } // Don't allow hiding a function. When "v" is not NULL we might be // assigning another function to the same var, the type is checked // below. if (new_var && function_exists(name, false)) { semsg(_("E705: Variable name conflicts with existing function: %s"), name); - return false; + return true; } - return true; + return false; } -// TODO(ZyX-I): move to eval/expressions - /// Check if a variable name is valid /// /// @param[in] varname Variable name to check. @@ -1643,25 +1680,27 @@ static void setwinvar(typval_T *argvars, typval_T *rettv, int off) const char *varname = tv_get_string_chk(&argvars[off + 1]); typval_T *varp = &argvars[off + 2]; - if (win != NULL && varname != NULL && varp != NULL) { - bool need_switch_win = !(tp == curtab && win == curwin); - switchwin_T switchwin; - if (!need_switch_win || switch_win(&switchwin, win, tp, true) == OK) { - if (*varname == '&') { - set_option_from_tv(varname + 1, varp); - } else { - const size_t varname_len = strlen(varname); - char *const winvarname = xmalloc(varname_len + 3); - memcpy(winvarname, "w:", 2); - memcpy(winvarname + 2, varname, varname_len + 1); - set_var(winvarname, varname_len + 2, varp, true); - xfree(winvarname); - } - } - if (need_switch_win) { - restore_win(&switchwin, true); + if (win == NULL || varname == NULL) { + return; + } + + bool need_switch_win = !(tp == curtab && win == curwin); + switchwin_T switchwin; + if (!need_switch_win || switch_win(&switchwin, win, tp, true) == OK) { + if (*varname == '&') { + set_option_from_tv(varname + 1, varp); + } else { + const size_t varname_len = strlen(varname); + char *const winvarname = xmalloc(varname_len + 3); + memcpy(winvarname, "w:", 2); + memcpy(winvarname + 2, varname, varname_len + 1); + set_var(winvarname, varname_len + 2, varp, true); + xfree(winvarname); } } + if (need_switch_win) { + restore_win(&switchwin, true); + } } bool var_exists(const char *var) @@ -1744,21 +1783,23 @@ void f_settabvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) const char *const varname = tv_get_string_chk(&argvars[1]); typval_T *const varp = &argvars[2]; - if (varname != NULL && tp != NULL) { - tabpage_T *const save_curtab = curtab; - goto_tabpage_tp(tp, false, false); + if (varname == NULL || tp == NULL) { + return; + } - const size_t varname_len = strlen(varname); - char *const tabvarname = xmalloc(varname_len + 3); - memcpy(tabvarname, "t:", 2); - memcpy(tabvarname + 2, varname, varname_len + 1); - set_var(tabvarname, varname_len + 2, varp, true); - xfree(tabvarname); - - // Restore current tabpage. - if (valid_tabpage(save_curtab)) { - goto_tabpage_tp(save_curtab, false, false); - } + tabpage_T *const save_curtab = curtab; + goto_tabpage_tp(tp, false, false); + + const size_t varname_len = strlen(varname); + char *const tabvarname = xmalloc(varname_len + 3); + memcpy(tabvarname, "t:", 2); + memcpy(tabvarname + 2, varname, varname_len + 1); + set_var(tabvarname, varname_len + 2, varp, true); + xfree(tabvarname); + + // Restore current tabpage. + if (valid_tabpage(save_curtab)) { + goto_tabpage_tp(save_curtab, false, false); } } @@ -1785,27 +1826,29 @@ void f_setbufvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) buf_T *const buf = tv_get_buf(&argvars[0], false); typval_T *varp = &argvars[2]; - if (buf != NULL && varname != NULL) { - if (*varname == '&') { - aco_save_T aco; + if (buf == NULL || varname == NULL) { + return; + } - // set curbuf to be our buf, temporarily - aucmd_prepbuf(&aco, buf); + if (*varname == '&') { + aco_save_T aco; - set_option_from_tv(varname + 1, varp); + // Set curbuf to be our buf, temporarily. + aucmd_prepbuf(&aco, buf); - // reset notion of buffer - aucmd_restbuf(&aco); - } else { - const size_t varname_len = strlen(varname); - char *const bufvarname = xmalloc(varname_len + 3); - buf_T *const save_curbuf = curbuf; - curbuf = buf; - memcpy(bufvarname, "b:", 2); - memcpy(bufvarname + 2, varname, varname_len + 1); - set_var(bufvarname, varname_len + 2, varp, true); - xfree(bufvarname); - curbuf = save_curbuf; - } + set_option_from_tv(varname + 1, varp); + + // reset notion of buffer + aucmd_restbuf(&aco); + } else { + const size_t varname_len = strlen(varname); + char *const bufvarname = xmalloc(varname_len + 3); + buf_T *const save_curbuf = curbuf; + curbuf = buf; + memcpy(bufvarname, "b:", 2); + memcpy(bufvarname + 2, varname, varname_len + 1); + set_var(bufvarname, varname_len + 2, varp, true); + xfree(bufvarname); + curbuf = save_curbuf; } } diff --git a/src/nvim/eval/vars.h b/src/nvim/eval/vars.h index 73efc4938a..b87c9d62cb 100644 --- a/src/nvim/eval/vars.h +++ b/src/nvim/eval/vars.h @@ -1,7 +1,7 @@ #ifndef NVIM_EVAL_VARS_H #define NVIM_EVAL_VARS_H -#include "nvim/ex_cmds_defs.h" // For exarg_T +#include "nvim/ex_cmds_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/vars.h.generated.h" diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c new file mode 100644 index 0000000000..f58a0c488a --- /dev/null +++ b/src/nvim/eval/window.c @@ -0,0 +1,954 @@ +// 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 + +// eval/window.c: Window related builtin functions + +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "nvim/ascii.h" +#include "nvim/autocmd.h" +#include "nvim/buffer.h" +#include "nvim/buffer_defs.h" +#include "nvim/cursor.h" +#include "nvim/eval/funcs.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/eval/window.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/macros.h" +#include "nvim/memline_defs.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/move.h" +#include "nvim/option_defs.h" +#include "nvim/pos.h" +#include "nvim/types.h" +#include "nvim/vim.h" +#include "nvim/window.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/window.c.generated.h" +#endif + +static char *e_invalwindow = N_("E957: Invalid window number"); +static char e_cannot_resize_window_in_another_tab_page[] + = N_("E1308: Cannot resize a window in another tab page"); + +static int win_getid(typval_T *argvars) +{ + if (argvars[0].v_type == VAR_UNKNOWN) { + return curwin->handle; + } + int winnr = (int)tv_get_number(&argvars[0]); + win_T *wp; + if (winnr <= 0) { + return 0; + } + + if (argvars[1].v_type == VAR_UNKNOWN) { + wp = firstwin; + } else { + tabpage_T *tp = NULL; + int tabnr = (int)tv_get_number(&argvars[1]); + FOR_ALL_TABS(tp2) { + if (--tabnr == 0) { + tp = tp2; + break; + } + } + if (tp == NULL) { + return -1; + } + if (tp == curtab) { + wp = firstwin; + } else { + wp = tp->tp_firstwin; + } + } + for (; wp != NULL; wp = wp->w_next) { + if (--winnr == 0) { + return wp->handle; + } + } + return 0; +} + +static void win_id2tabwin(typval_T *const argvars, typval_T *const rettv) +{ + handle_T id = (handle_T)tv_get_number(&argvars[0]); + + int winnr = 1; + int tabnr = 1; + win_get_tabwin(id, &tabnr, &winnr); + + list_T *const list = tv_list_alloc_ret(rettv, 2); + tv_list_append_number(list, tabnr); + tv_list_append_number(list, winnr); +} + +win_T *win_id2wp(int id) +{ + return win_id2wp_tp(id, NULL); +} + +/// Return the window and tab pointer of window "id". +win_T *win_id2wp_tp(int id, tabpage_T **tpp) +{ + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->handle == id) { + if (tpp != NULL) { + *tpp = tp; + } + return wp; + } + } + + return NULL; +} + +static int win_id2win(typval_T *argvars) +{ + int nr = 1; + int id = (int)tv_get_number(&argvars[0]); + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->handle == id) { + return nr; + } + nr++; + } + return 0; +} + +void win_findbuf(typval_T *argvars, list_T *list) +{ + int bufnr = (int)tv_get_number(&argvars[0]); + + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer->b_fnum == bufnr) { + tv_list_append_number(list, wp->handle); + } + } +} + +/// Find window specified by "vp" in tabpage "tp". +/// +/// @param tp NULL for current tab page +/// @return current window if "vp" is number zero. +/// NULL if not found. +win_T *find_win_by_nr(typval_T *vp, tabpage_T *tp) +{ + int nr = (int)tv_get_number_chk(vp, NULL); + + if (nr < 0) { + return NULL; + } + + if (nr == 0) { + return curwin; + } + + // This method accepts NULL as an alias for curtab. + if (tp == NULL) { + tp = curtab; + } + + FOR_ALL_WINDOWS_IN_TAB(wp, tp) { + if (nr >= LOWEST_WIN_ID) { + if (wp->handle == nr) { + return wp; + } + } else if (--nr <= 0) { + return wp; + } + } + return NULL; +} + +/// 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) +{ + int nr = (int)tv_get_number_chk(vp, NULL); + + if (nr >= LOWEST_WIN_ID) { + return win_id2wp((int)tv_get_number(vp)); + } + + return find_win_by_nr(vp, NULL); +} + +/// Find window specified by "wvp" in tabpage "tvp". +win_T *find_tabwin(typval_T *wvp, typval_T *tvp) +{ + win_T *wp = NULL; + tabpage_T *tp = NULL; + + if (wvp->v_type != VAR_UNKNOWN) { + if (tvp->v_type != VAR_UNKNOWN) { + long n = tv_get_number(tvp); + if (n >= 0) { + tp = find_tabpage((int)n); + } + } else { + tp = curtab; + } + + if (tp != NULL) { + wp = find_win_by_nr(wvp, tp); + } + } else { + wp = curwin; + } + + return wp; +} + +/// Get the layout of the given tab page for winlayout(). +static void get_framelayout(const frame_T *fr, list_T *l, bool outer) +{ + if (fr == NULL) { + return; + } + + list_T *fr_list; + if (outer) { + // outermost call from f_winlayout() + fr_list = l; + } else { + fr_list = tv_list_alloc(2); + tv_list_append_list(l, fr_list); + } + + if (fr->fr_layout == FR_LEAF) { + if (fr->fr_win != NULL) { + tv_list_append_string(fr_list, "leaf", -1); + tv_list_append_number(fr_list, fr->fr_win->handle); + } + } else { + tv_list_append_string(fr_list, fr->fr_layout == FR_ROW ? "row" : "col", -1); + + list_T *const win_list = tv_list_alloc(kListLenUnknown); + tv_list_append_list(fr_list, win_list); + const frame_T *child = fr->fr_child; + while (child != NULL) { + get_framelayout(child, win_list, false); + child = child->fr_next; + } + } +} + +/// Common code for tabpagewinnr() and winnr(). +static int get_winnr(tabpage_T *tp, typval_T *argvar) +{ + int nr = 1; + + win_T *twin = (tp == curtab) ? curwin : tp->tp_curwin; + if (argvar->v_type != VAR_UNKNOWN) { + bool invalid_arg = false; + const char *const arg = tv_get_string_chk(argvar); + if (arg == NULL) { + nr = 0; // Type error; errmsg already given. + } else if (strcmp(arg, "$") == 0) { + twin = (tp == curtab) ? lastwin : tp->tp_lastwin; + } else if (strcmp(arg, "#") == 0) { + twin = (tp == curtab) ? prevwin : tp->tp_prevwin; + if (twin == NULL) { + nr = 0; + } + } else { + // Extract the window count (if specified). e.g. winnr('3j') + char *endp; + long count = strtol((char *)arg, &endp, 10); + if (count <= 0) { + // if count is not specified, default to 1 + count = 1; + } + if (endp != NULL && *endp != '\0') { + if (strequal(endp, "j")) { + twin = win_vert_neighbor(tp, twin, false, count); + } else if (strequal(endp, "k")) { + twin = win_vert_neighbor(tp, twin, true, count); + } else if (strequal(endp, "h")) { + twin = win_horz_neighbor(tp, twin, true, count); + } else if (strequal(endp, "l")) { + twin = win_horz_neighbor(tp, twin, false, count); + } else { + invalid_arg = true; + } + } else { + invalid_arg = true; + } + } + + if (invalid_arg) { + semsg(_(e_invexpr2), arg); + nr = 0; + } + } + + if (nr <= 0) { + return 0; + } + + for (win_T *wp = (tp == curtab) ? firstwin : tp->tp_firstwin; + wp != twin; wp = wp->w_next) { + if (wp == NULL) { + // didn't find it in this tabpage + nr = 0; + break; + } + nr++; + } + return nr; +} + +/// @return information about a window as a dictionary. +static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) +{ + dict_T *const dict = tv_dict_alloc(); + + // make sure w_botline is valid + validate_botline(wp); + + tv_dict_add_nr(dict, S_LEN("tabnr"), tpnr); + tv_dict_add_nr(dict, S_LEN("winnr"), winnr); + tv_dict_add_nr(dict, S_LEN("winid"), wp->handle); + tv_dict_add_nr(dict, S_LEN("height"), wp->w_height_inner); + tv_dict_add_nr(dict, S_LEN("winrow"), wp->w_winrow + 1); + tv_dict_add_nr(dict, S_LEN("topline"), wp->w_topline); + tv_dict_add_nr(dict, S_LEN("botline"), wp->w_botline - 1); + tv_dict_add_nr(dict, S_LEN("winbar"), wp->w_winbar_height); + tv_dict_add_nr(dict, S_LEN("width"), wp->w_width_inner); + 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"), + (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL)); + + // Add a reference to window variables + tv_dict_add_dict(dict, S_LEN("variables"), wp->w_vars); + + return dict; +} + +/// @return information (variables, options, etc.) about a tab page +/// as a dictionary. +static dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) +{ + dict_T *const dict = tv_dict_alloc(); + + tv_dict_add_nr(dict, S_LEN("tabnr"), tp_idx); + + list_T *const l = tv_list_alloc(kListLenMayKnow); + FOR_ALL_WINDOWS_IN_TAB(wp, tp) { + tv_list_append_number(l, (varnumber_T)wp->handle); + } + tv_dict_add_list(dict, S_LEN("windows"), l); + + // Make a reference to tabpage variables + tv_dict_add_dict(dict, S_LEN("variables"), tp->tp_vars); + + return dict; +} + +/// "gettabinfo()" function +void f_gettabinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tabpage_T *tparg = NULL; + + tv_list_alloc_ret(rettv, (argvars[0].v_type == VAR_UNKNOWN + ? 1 + : kListLenMayKnow)); + + if (argvars[0].v_type != VAR_UNKNOWN) { + // Information about one tab page + tparg = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); + if (tparg == NULL) { + return; + } + } + + // Get information about a specific tab page or all tab pages + int tpnr = 0; + FOR_ALL_TABS(tp) { + tpnr++; + if (tparg != NULL && tp != tparg) { + continue; + } + dict_T *const d = get_tabpage_info(tp, tpnr); + tv_list_append_dict(rettv->vval.v_list, d); + if (tparg != NULL) { + return; + } + } +} + +/// "getwininfo()" function +void f_getwininfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + win_T *wparg = NULL; + + tv_list_alloc_ret(rettv, kListLenMayKnow); + + if (argvars[0].v_type != VAR_UNKNOWN) { + wparg = win_id2wp((int)tv_get_number(&argvars[0])); + if (wparg == NULL) { + return; + } + } + + // Collect information about either all the windows across all the tab + // pages or one particular window. + int16_t tabnr = 0; + FOR_ALL_TABS(tp) { + tabnr++; + int16_t winnr = 0; + FOR_ALL_WINDOWS_IN_TAB(wp, tp) { + winnr++; + if (wparg != NULL && wp != wparg) { + continue; + } + dict_T *const d = get_win_info(wp, tabnr, winnr); + tv_list_append_dict(rettv->vval.v_list, d); + if (wparg != NULL) { + // found information about a specific window + return; + } + } + } +} + +/// "getwinpos({timeout})" function +void f_getwinpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tv_list_alloc_ret(rettv, 2); + tv_list_append_number(rettv->vval.v_list, -1); + tv_list_append_number(rettv->vval.v_list, -1); +} + +/// "getwinposx()" function +void f_getwinposx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = -1; +} + +/// "getwinposy()" function +void f_getwinposy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = -1; +} + +/// "tabpagenr()" function +void f_tabpagenr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + int nr = 1; + + if (argvars[0].v_type != VAR_UNKNOWN) { + const char *const arg = tv_get_string_chk(&argvars[0]); + nr = 0; + if (arg != NULL) { + if (strcmp(arg, "$") == 0) { + nr = tabpage_index(NULL) - 1; + } else if (strcmp(arg, "#") == 0) { + nr = valid_tabpage(lastused_tabpage) ? tabpage_index(lastused_tabpage) : 0; + } else { + semsg(_(e_invexpr2), arg); + } + } + } else { + nr = tabpage_index(curtab); + } + rettv->vval.v_number = nr; +} + +/// "tabpagewinnr()" function +void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + int nr = 1; + tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0])); + if (tp == NULL) { + nr = 0; + } else { + nr = get_winnr(tp, &argvars[1]); + } + rettv->vval.v_number = nr; +} + +/// "win_execute(win_id, command)" function +void f_win_execute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + // Return an empty string if something fails. + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + int id = (int)tv_get_number(argvars); + tabpage_T *tp; + win_T *wp = win_id2wp_tp(id, &tp); + if (wp == NULL || tp == NULL) { + return; + } + + WIN_EXECUTE(wp, tp, execute_common(argvars, rettv, 1)); +} + +/// "win_findbuf()" function +void f_win_findbuf(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tv_list_alloc_ret(rettv, kListLenMayKnow); + win_findbuf(argvars, rettv->vval.v_list); +} + +/// "win_getid()" function +void f_win_getid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = win_getid(argvars); +} + +/// "win_gotoid()" function +void f_win_gotoid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + int id = (int)tv_get_number(&argvars[0]); + + if (cmdwin_type != 0) { + emsg(_(e_cmdwin)); + return; + } + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->handle == id) { + goto_tabpage_win(tp, wp); + rettv->vval.v_number = 1; + return; + } + } +} + +/// "win_id2tabwin()" function +void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + win_id2tabwin(argvars, rettv); +} + +/// "win_id2win()" function +void f_win_id2win(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = win_id2win(argvars); +} + +/// "win_move_separator()" function +void f_win_move_separator(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = false; + + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL || wp->w_floating) { + return; + } + if (!win_valid(wp)) { + emsg(_(e_cannot_resize_window_in_another_tab_page)); + return; + } + + int offset = (int)tv_get_number(&argvars[1]); + win_drag_vsep_line(wp, offset); + rettv->vval.v_number = true; +} + +/// "win_move_statusline()" function +void f_win_move_statusline(typval_T *argvars, typval_T *rettv, EvalFuncData 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; + } + if (!win_valid(wp)) { + emsg(_(e_cannot_resize_window_in_another_tab_page)); + return; + } + + offset = (int)tv_get_number(&argvars[1]); + win_drag_status_line(wp, offset); + rettv->vval.v_number = true; +} + +/// "win_screenpos()" function +void f_win_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tv_list_alloc_ret(rettv, 2); + const win_T *const wp = find_win_by_nr_or_id(&argvars[0]); + tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_winrow + 1); + tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1); +} + +/// Move the window wp into a new split of targetwin in a given direction +static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags) +{ + int height = wp->w_height; + win_T *oldwin = curwin; + + if (wp == targetwin || is_aucmd_win(wp)) { + return; + } + + // Jump to the target window + if (curwin != targetwin) { + win_goto(targetwin); + } + + // Remove the old window and frame from the tree of frames + int dir; + (void)winframe_remove(wp, &dir, NULL); + win_remove(wp, NULL); + last_status(false); // may need to remove last status line + (void)win_comp_pos(); // recompute window positions + + // Split a window on the desired side and put the old window there + (void)win_split_ins(size, flags, wp, dir); + + // If splitting horizontally, try to preserve height + if (size == 0 && !(flags & WSP_VERT)) { + win_setheight_win(height, wp); + if (p_ea) { + win_equal(wp, true, 'v'); + } + } + + if (oldwin != curwin) { + win_goto(oldwin); + } +} + +/// "win_splitmove()" function +void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + win_T *targetwin = find_win_by_nr_or_id(&argvars[1]); + + if (wp == NULL || targetwin == NULL || wp == targetwin + || !win_valid(wp) || !win_valid(targetwin) + || win_valid_floating(wp) || win_valid_floating(targetwin)) { + emsg(_(e_invalwindow)); + rettv->vval.v_number = -1; + return; + } + + int flags = 0, size = 0; + + if (argvars[2].v_type != VAR_UNKNOWN) { + dict_T *d; + dictitem_T *di; + + if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) { + emsg(_(e_invarg)); + return; + } + + d = argvars[2].vval.v_dict; + if (tv_dict_get_number(d, "vertical")) { + flags |= WSP_VERT; + } + if ((di = tv_dict_find(d, "rightbelow", -1)) != NULL) { + flags |= tv_get_number(&di->di_tv) ? WSP_BELOW : WSP_ABOVE; + } + size = (int)tv_dict_get_number(d, "size"); + } + + win_move_into_split(wp, targetwin, size, flags); +} + +/// "win_gettype(nr)" function +void f_win_gettype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + win_T *wp = curwin; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (argvars[0].v_type != VAR_UNKNOWN) { + wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + rettv->vval.v_string = xstrdup("unknown"); + return; + } + } + if (is_aucmd_win(wp)) { + rettv->vval.v_string = xstrdup("autocmd"); + } else if (wp->w_p_pvw) { + rettv->vval.v_string = xstrdup("preview"); + } else if (wp->w_floating) { + rettv->vval.v_string = xstrdup("popup"); + } else if (wp == curwin && cmdwin_type != 0) { + rettv->vval.v_string = xstrdup("command"); + } else if (bt_quickfix(wp->w_buffer)) { + rettv->vval.v_string = xstrdup((wp->w_llist_ref != NULL ? "loclist" : "quickfix")); + } +} + +/// "getcmdwintype()" function +void f_getcmdwintype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + rettv->vval.v_string = xmallocz(1); + rettv->vval.v_string[0] = (char)cmdwin_type; +} + +/// "winbufnr(nr)" function +void f_winbufnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + rettv->vval.v_number = -1; + } else { + rettv->vval.v_number = wp->w_buffer->b_fnum; + } +} + +/// "wincol()" function +void f_wincol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + validate_cursor(); + rettv->vval.v_number = curwin->w_wcol + 1; +} + +/// "winheight(nr)" function +void f_winheight(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + rettv->vval.v_number = -1; + } else { + rettv->vval.v_number = wp->w_height_inner; + } +} + +/// "winlayout()" function +void f_winlayout(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tabpage_T *tp; + + tv_list_alloc_ret(rettv, 2); + + if (argvars[0].v_type == VAR_UNKNOWN) { + tp = curtab; + } else { + tp = find_tabpage((int)tv_get_number(&argvars[0])); + if (tp == NULL) { + return; + } + } + + get_framelayout(tp->tp_topframe, rettv->vval.v_list, true); +} + +/// "winline()" function +void f_winline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + validate_cursor(); + rettv->vval.v_number = curwin->w_wrow + 1; +} + +/// "winnr()" function +void f_winnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = get_winnr(curtab, &argvars[0]); +} + +/// "winrestcmd()" function +void f_winrestcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + char buf[50]; + + garray_T ga; + ga_init(&ga, (int)sizeof(char), 70); + + // Do this twice to handle some window layouts properly. + for (int i = 0; i < 2; i++) { + int winnr = 1; + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + snprintf(buf, sizeof(buf), "%dresize %d|", winnr, + wp->w_height); + ga_concat(&ga, buf); + snprintf(buf, sizeof(buf), "vert %dresize %d|", winnr, + wp->w_width); + ga_concat(&ga, buf); + winnr++; + } + } + ga_append(&ga, NUL); + + rettv->vval.v_string = ga.ga_data; + rettv->v_type = VAR_STRING; +} + +/// "winrestview()" function +void f_winrestview(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + dict_T *dict = argvars[0].vval.v_dict; + + if (argvars[0].v_type != VAR_DICT || dict == NULL) { + emsg(_(e_invarg)); + } else { + dictitem_T *di; + if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) { + curwin->w_cursor.lnum = (linenr_T)tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) { + curwin->w_cursor.col = (colnr_T)tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) { + curwin->w_cursor.coladd = (colnr_T)tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) { + curwin->w_curswant = (colnr_T)tv_get_number(&di->di_tv); + curwin->w_set_curswant = false; + } + if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) { + set_topline(curwin, (linenr_T)tv_get_number(&di->di_tv)); + } + if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) { + curwin->w_topfill = (int)tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) { + curwin->w_leftcol = (colnr_T)tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) { + curwin->w_skipcol = (colnr_T)tv_get_number(&di->di_tv); + } + + check_cursor(); + win_new_height(curwin, curwin->w_height); + win_new_width(curwin, curwin->w_width); + changed_window_setting(); + + if (curwin->w_topline <= 0) { + curwin->w_topline = 1; + } + if (curwin->w_topline > curbuf->b_ml.ml_line_count) { + curwin->w_topline = curbuf->b_ml.ml_line_count; + } + check_topfill(curwin, true); + } +} + +/// "winsaveview()" function +void f_winsaveview(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tv_dict_alloc_ret(rettv); + dict_T *dict = rettv->vval.v_dict; + + tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)curwin->w_cursor.lnum); + tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)curwin->w_cursor.col); + tv_dict_add_nr(dict, S_LEN("coladd"), (varnumber_T)curwin->w_cursor.coladd); + update_curswant(); + tv_dict_add_nr(dict, S_LEN("curswant"), (varnumber_T)curwin->w_curswant); + + tv_dict_add_nr(dict, S_LEN("topline"), (varnumber_T)curwin->w_topline); + tv_dict_add_nr(dict, S_LEN("topfill"), (varnumber_T)curwin->w_topfill); + tv_dict_add_nr(dict, S_LEN("leftcol"), (varnumber_T)curwin->w_leftcol); + tv_dict_add_nr(dict, S_LEN("skipcol"), (varnumber_T)curwin->w_skipcol); +} + +/// "winwidth(nr)" function +void f_winwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + rettv->vval.v_number = -1; + } else { + rettv->vval.v_number = wp->w_width_inner; + } +} + +/// Set "win" to be the curwin and "tp" to be the current tab page. +/// restore_win() MUST be called to undo, also when FAIL is returned. +/// No autocommands will be executed until restore_win() is called. +/// +/// @param no_display if true the display won't be affected, no redraw is +/// triggered, another tabpage access is limited. +/// +/// @return FAIL if switching to "win" failed. +int switch_win(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display) +{ + block_autocmds(); + return switch_win_noblock(switchwin, win, tp, no_display); +} + +// As switch_win() but without blocking autocommands. +int switch_win_noblock(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display) +{ + CLEAR_POINTER(switchwin); + 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) { + switchwin->sw_curtab = curtab; + if (no_display) { + curtab->tp_firstwin = firstwin; + curtab->tp_lastwin = lastwin; + curtab = tp; + firstwin = curtab->tp_firstwin; + lastwin = curtab->tp_lastwin; + } else { + goto_tabpage_tp(tp, false, false); + } + } + if (!win_valid(win)) { + return FAIL; + } + curwin = win; + curbuf = curwin->w_buffer; + return OK; +} + +// 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(switchwin_T *switchwin, bool no_display) +{ + restore_win_noblock(switchwin, no_display); + unblock_autocmds(); +} + +// As restore_win() but without unblocking autocommands. +void restore_win_noblock(switchwin_T *switchwin, bool no_display) +{ + if (switchwin->sw_curtab != NULL && valid_tabpage(switchwin->sw_curtab)) { + if (no_display) { + curtab->tp_firstwin = firstwin; + curtab->tp_lastwin = lastwin; + curtab = switchwin->sw_curtab; + firstwin = curtab->tp_firstwin; + lastwin = curtab->tp_lastwin; + } else { + goto_tabpage_tp(switchwin->sw_curtab, false, false); + } + } + + 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; + } +} diff --git a/src/nvim/eval/window.h b/src/nvim/eval/window.h new file mode 100644 index 0000000000..995f0a55a9 --- /dev/null +++ b/src/nvim/eval/window.h @@ -0,0 +1,78 @@ +#ifndef NVIM_EVAL_WINDOW_H +#define NVIM_EVAL_WINDOW_H + +#include <stdbool.h> +#include <string.h> + +#include "nvim/buffer.h" +#include "nvim/buffer_defs.h" +#include "nvim/cursor.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/globals.h" +#include "nvim/mark.h" +#include "nvim/option_defs.h" +#include "nvim/os/os.h" +#include "nvim/pos.h" +#include "nvim/vim.h" +#include "nvim/window.h" + +/// 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 cwd_[MAXPATHL]; \ + char 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(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 "eval/window.h.generated.h" +#endif +#endif // NVIM_EVAL_WINDOW_H diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index f012bacdd9..e528d21a71 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -2,16 +2,18 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> +#include <stdint.h> #include <uv.h> +#include "nvim/eval/typval.h" #include "nvim/event/libuv_process.h" #include "nvim/event/loop.h" #include "nvim/event/process.h" -#include "nvim/event/rstream.h" -#include "nvim/event/wstream.h" +#include "nvim/event/stream.h" #include "nvim/log.h" #include "nvim/macros.h" #include "nvim/os/os.h" +#include "nvim/ui_client.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "event/libuv_process.c.generated.h" @@ -40,11 +42,19 @@ int libuv_process_spawn(LibuvProcess *uvproc) #endif uvproc->uvopts.exit_cb = exit_cb; uvproc->uvopts.cwd = proc->cwd; + uvproc->uvopts.stdio = uvproc->uvstdio; uvproc->uvopts.stdio_count = 3; uvproc->uvstdio[0].flags = UV_IGNORE; uvproc->uvstdio[1].flags = UV_IGNORE; uvproc->uvstdio[2].flags = UV_IGNORE; + + if (ui_client_forward_stdin) { + assert(UI_CLIENT_STDIN_FD == 3); + uvproc->uvopts.stdio_count = 4; + uvproc->uvstdio[3].data.fd = 0; + uvproc->uvstdio[3].flags = UV_INHERIT_FD; + } uvproc->uv.data = proc; if (proc->env) { @@ -77,6 +87,9 @@ int libuv_process_spawn(LibuvProcess *uvproc) uvproc->uvstdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; uvproc->uvstdio[2].data.stream = STRUCT_CAST(uv_stream_t, &proc->err.uv.pipe); + } else if (proc->fwd_err) { + uvproc->uvstdio[2].flags = UV_INHERIT_FD; + uvproc->uvstdio[2].data.fd = STDERR_FILENO; } int status; diff --git a/src/nvim/event/libuv_process.h b/src/nvim/event/libuv_process.h index 1132ce79ca..4472839944 100644 --- a/src/nvim/event/libuv_process.h +++ b/src/nvim/event/libuv_process.h @@ -3,13 +3,14 @@ #include <uv.h> +#include "nvim/event/loop.h" #include "nvim/event/process.h" typedef struct libuv_process { Process process; uv_process_t uv; uv_process_options_t uvopts; - uv_stdio_container_t uvstdio[3]; + uv_stdio_container_t uvstdio[4]; } LibuvProcess; static inline LibuvProcess libuv_process_init(Loop *loop, void *data) diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c index 3329cbd574..ab2524c1a9 100644 --- a/src/nvim/event/loop.c +++ b/src/nvim/event/loop.c @@ -1,13 +1,16 @@ // 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 <stdarg.h> +#include <stdbool.h> #include <stdint.h> +#include <stdlib.h> #include <uv.h> +#include "nvim/event/defs.h" #include "nvim/event/loop.h" -#include "nvim/event/process.h" #include "nvim/log.h" +#include "nvim/memory.h" +#include "nvim/os/time.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "event/loop.c.generated.h" @@ -40,7 +43,7 @@ void loop_init(Loop *loop, void *data) /// @param once true: process at most one `Loop.uv` event. /// false: process until `ms` timeout (only has effect if `ms` > 0). /// @return true if `ms` > 0 and was reached -bool loop_uv_run(Loop *loop, int ms, bool once) +bool loop_uv_run(Loop *loop, int64_t ms, bool once) { if (loop->recursive++) { abort(); // Should not re-enter uv_run @@ -79,7 +82,7 @@ bool loop_uv_run(Loop *loop, int ms, bool once) /// > 0: timeout after `ms`. /// < 0: wait forever. /// @return true if `ms` > 0 and was reached -bool loop_poll_events(Loop *loop, int ms) +bool loop_poll_events(Loop *loop, int64_t ms) { bool timeout_expired = loop_uv_run(loop, ms, true); multiqueue_process_events(loop->fast_events); diff --git a/src/nvim/event/loop.h b/src/nvim/event/loop.h index c0bcda40ce..b2265a726d 100644 --- a/src/nvim/event/loop.h +++ b/src/nvim/event/loop.h @@ -58,7 +58,7 @@ typedef struct loop { // Poll for events until a condition or timeout #define LOOP_PROCESS_EVENTS_UNTIL(loop, multiqueue, timeout, condition) \ do { \ - int remaining = timeout; \ + int64_t remaining = timeout; \ uint64_t before = (remaining > 0) ? os_hrtime() : 0; \ while (!(condition)) { \ LOOP_PROCESS_EVENTS(loop, multiqueue, remaining); \ @@ -66,7 +66,7 @@ typedef struct loop { break; \ } else if (remaining > 0) { \ uint64_t now = os_hrtime(); \ - remaining -= (int)((now - before) / 1000000); \ + remaining -= (int64_t)((now - before) / 1000000); \ before = now; \ if (remaining <= 0) { \ break; \ diff --git a/src/nvim/event/multiqueue.c b/src/nvim/event/multiqueue.c index 40d20033e0..e05084b656 100644 --- a/src/nvim/event/multiqueue.c +++ b/src/nvim/event/multiqueue.c @@ -46,14 +46,14 @@ // other sources and focus on a specific channel. #include <assert.h> -#include <stdarg.h> #include <stdbool.h> -#include <stdint.h> +#include <stddef.h> #include <uv.h> +#include "nvim/event/defs.h" #include "nvim/event/multiqueue.h" +#include "nvim/lib/queue.h" #include "nvim/memory.h" -#include "nvim/os/time.h" typedef struct multiqueue_item MultiQueueItem; struct multiqueue_item { diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index e029f778f6..1a524a56ca 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -2,20 +2,25 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> +#include <inttypes.h> +#include <signal.h> #include <stdlib.h> #include <uv.h> +#include "klib/klist.h" #include "nvim/event/libuv_process.h" #include "nvim/event/loop.h" #include "nvim/event/process.h" -#include "nvim/event/rstream.h" -#include "nvim/event/wstream.h" #include "nvim/globals.h" #include "nvim/log.h" #include "nvim/macros.h" +#include "nvim/main.h" #include "nvim/os/process.h" #include "nvim/os/pty_process.h" #include "nvim/os/shell.h" +#include "nvim/os/time.h" +#include "nvim/rbuffer.h" +#include "nvim/ui_client.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "event/process.c.generated.h" @@ -32,10 +37,16 @@ void __gcov_flush(void); static bool process_is_tearing_down = false; +// Delay exit until handles are closed, to avoid deadlocks +static int exit_need_delay = 0; + /// @returns zero on success, or negative error code int process_spawn(Process *proc, bool in, bool out, bool err) FUNC_ATTR_NONNULL_ALL { + // forwarding stderr contradicts with processing it internally + assert(!(err && proc->fwd_err)); + if (in) { uv_pipe_init(&proc->loop->uv, &proc->in.uv.pipe, 0); } else { @@ -395,12 +406,41 @@ static void process_close_handles(void **argv) exit_need_delay--; } +static void exit_delay_cb(uv_timer_t *handle) +{ + uv_timer_stop(&main_loop.exit_delay_timer); + multiqueue_put(main_loop.fast_events, exit_event, 1, main_loop.exit_delay_timer.data); +} + +static void exit_event(void **argv) +{ + int status = (int)(intptr_t)argv[0]; + if (exit_need_delay) { + main_loop.exit_delay_timer.data = argv[0]; + uv_timer_start(&main_loop.exit_delay_timer, exit_delay_cb, 0, 0); + return; + } + + if (!exiting) { + os_exit(status); + } +} + +void exit_from_channel(int status) +{ + multiqueue_put(main_loop.fast_events, exit_event, 1, status); +} + static void on_process_exit(Process *proc) { Loop *loop = proc->loop; ILOG("exited: pid=%d status=%d stoptime=%" PRIu64, proc->pid, proc->status, proc->stopped_time); + if (ui_client_channel_id) { + exit_from_channel(proc->status); + } + // Process has terminated, but there could still be data to be read from the // OS. We are still in the libuv loop, so we cannot call code that polls for // more data directly. Instead delay the reading after the libuv loop by diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h index 30254bfe07..e0057faffb 100644 --- a/src/nvim/event/process.h +++ b/src/nvim/event/process.h @@ -1,11 +1,20 @@ #ifndef NVIM_EVENT_PROCESS_H #define NVIM_EVENT_PROCESS_H +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" #include "nvim/event/rstream.h" +#include "nvim/event/stream.h" #include "nvim/event/wstream.h" +struct process; + typedef enum { kProcessTypeUv, kProcessTypePty, @@ -29,7 +38,7 @@ struct process { /// Exit handler. If set, user must call process_free(). process_exit_cb cb; internal_process_cb internal_exit_cb, internal_close_cb; - bool closed, detach, overlapped; + bool closed, detach, overlapped, fwd_err; MultiQueue *events; }; @@ -53,7 +62,8 @@ static inline Process process_init(Loop *loop, ProcessType type, void *data) .closed = false, .internal_close_cb = NULL, .internal_exit_cb = NULL, - .detach = false + .detach = false, + .fwd_err = false, }; } diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c index 2847788ef8..a88d62fd6b 100644 --- a/src/nvim/event/rstream.c +++ b/src/nvim/event/rstream.c @@ -3,17 +3,18 @@ #include <assert.h> #include <stdbool.h> +#include <stddef.h> #include <stdint.h> -#include <stdlib.h> #include <uv.h> -#include "nvim/ascii.h" #include "nvim/event/loop.h" #include "nvim/event/rstream.h" +#include "nvim/event/stream.h" #include "nvim/log.h" +#include "nvim/macros.h" #include "nvim/main.h" -#include "nvim/memory.h" -#include "nvim/vim.h" +#include "nvim/os/os_defs.h" +#include "nvim/rbuffer.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "event/rstream.c.generated.h" diff --git a/src/nvim/event/signal.c b/src/nvim/event/signal.c index 4a45a2ec2f..8256ca2091 100644 --- a/src/nvim/event/signal.c +++ b/src/nvim/event/signal.c @@ -1,6 +1,7 @@ // 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 <stddef.h> #include <uv.h> #include "nvim/event/loop.h" diff --git a/src/nvim/event/signal.h b/src/nvim/event/signal.h index 7fe352edef..f9adf62c20 100644 --- a/src/nvim/event/signal.h +++ b/src/nvim/event/signal.h @@ -4,6 +4,9 @@ #include <uv.h> #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" + +struct signal_watcher; typedef struct signal_watcher SignalWatcher; typedef void (*signal_cb)(SignalWatcher *watcher, int signum, void *data); diff --git a/src/nvim/event/socket.c b/src/nvim/event/socket.c index 9ca3dcc276..10756015ad 100644 --- a/src/nvim/event/socket.c +++ b/src/nvim/event/socket.c @@ -2,23 +2,24 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> -#include <stdint.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> #include <uv.h> #include "nvim/ascii.h" #include "nvim/charset.h" #include "nvim/event/loop.h" -#include "nvim/event/rstream.h" #include "nvim/event/socket.h" -#include "nvim/event/wstream.h" +#include "nvim/event/stream.h" +#include "nvim/gettext.h" #include "nvim/log.h" #include "nvim/macros.h" #include "nvim/main.h" #include "nvim/memory.h" #include "nvim/os/os.h" #include "nvim/path.h" -#include "nvim/strings.h" -#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "event/socket.c.generated.h" diff --git a/src/nvim/event/socket.h b/src/nvim/event/socket.h index d30ae45502..c6fcdec4bb 100644 --- a/src/nvim/event/socket.h +++ b/src/nvim/event/socket.h @@ -4,9 +4,12 @@ #include <uv.h> #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" #include "nvim/event/rstream.h" #include "nvim/event/wstream.h" +struct socket_watcher; + #define ADDRESS_MAX_SIZE 256 typedef struct socket_watcher SocketWatcher; diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c index bfb7a551b9..0a4918636a 100644 --- a/src/nvim/event/stream.c +++ b/src/nvim/event/stream.c @@ -3,9 +3,11 @@ #include <assert.h> #include <stdbool.h> -#include <stdio.h> +#include <stddef.h> #include <uv.h> +#include <uv/version.h> +#include "nvim/event/loop.h" #include "nvim/event/stream.h" #include "nvim/log.h" #include "nvim/macros.h" diff --git a/src/nvim/event/stream.h b/src/nvim/event/stream.h index b580d3c33d..33d2d6e775 100644 --- a/src/nvim/event/stream.h +++ b/src/nvim/event/stream.h @@ -6,8 +6,11 @@ #include <uv.h> #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" #include "nvim/rbuffer.h" +struct stream; + typedef struct stream Stream; /// Type of function called when the Stream buffer is filled with data /// @@ -16,8 +19,7 @@ typedef struct stream Stream; /// @param count Number of bytes that was read. /// @param data User-defined data /// @param eof If the stream reached EOF. -typedef void (*stream_read_cb)(Stream *stream, RBuffer *buf, size_t count, - void *data, bool eof); +typedef void (*stream_read_cb)(Stream *stream, RBuffer *buf, size_t count, void *data, bool eof); /// Type of function called when the Stream has information about a write /// request. diff --git a/src/nvim/event/time.h b/src/nvim/event/time.h index a6de89ad6e..e84488fdd6 100644 --- a/src/nvim/event/time.h +++ b/src/nvim/event/time.h @@ -1,9 +1,13 @@ #ifndef NVIM_EVENT_TIME_H #define NVIM_EVENT_TIME_H +#include <stdbool.h> #include <uv.h> #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" + +struct time_watcher; typedef struct time_watcher TimeWatcher; typedef void (*time_cb)(TimeWatcher *watcher, void *data); diff --git a/src/nvim/event/wstream.c b/src/nvim/event/wstream.c index d81ffa5c15..65391ba5cf 100644 --- a/src/nvim/event/wstream.c +++ b/src/nvim/event/wstream.c @@ -3,15 +3,13 @@ #include <assert.h> #include <stdbool.h> -#include <stdint.h> -#include <stdlib.h> #include <uv.h> #include "nvim/event/loop.h" +#include "nvim/event/stream.h" #include "nvim/event/wstream.h" -#include "nvim/log.h" +#include "nvim/macros.h" #include "nvim/memory.h" -#include "nvim/vim.h" #define DEFAULT_MAXMEM 1024 * 1024 * 2000 diff --git a/src/nvim/event/wstream.h b/src/nvim/event/wstream.h index d599d29913..ef1311c619 100644 --- a/src/nvim/event/wstream.h +++ b/src/nvim/event/wstream.h @@ -2,12 +2,15 @@ #define NVIM_EVENT_WSTREAM_H #include <stdbool.h> +#include <stddef.h> #include <stdint.h> #include <uv.h> #include "nvim/event/loop.h" #include "nvim/event/stream.h" +struct wbuffer; + typedef struct wbuffer WBuffer; typedef void (*wbuffer_data_finalizer)(void *data); diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index ea51cac163..437a05f61d 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -4,20 +4,27 @@ // ex_cmds.c: some functions for command line commands #include <assert.h> +#include <ctype.h> #include <float.h> #include <inttypes.h> #include <math.h> #include <stdbool.h> +#include <stddef.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> +#include <time.h> -#include "nvim/api/buffer.h" -#include "nvim/api/private/defs.h" +#include "auto/config.h" +#include "klib/kvec.h" #include "nvim/arglist.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" +#include "nvim/channel.h" #include "nvim/charset.h" #include "nvim/cmdhist.h" #include "nvim/cursor.h" @@ -27,22 +34,27 @@ #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" #include "nvim/fileio.h" #include "nvim/fold.h" -#include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/grid_defs.h" #include "nvim/help.h" -#include "nvim/highlight.h" +#include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/input.h" -#include "nvim/log.h" +#include "nvim/lua/executor.h" +#include "nvim/macros.h" #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -55,21 +67,22 @@ #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/optionstr.h" +#include "nvim/os/fs_defs.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/shell.h" #include "nvim/os/time.h" -#include "nvim/os_unix.h" #include "nvim/path.h" #include "nvim/plines.h" #include "nvim/profile.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/spell.h" #include "nvim/strings.h" -#include "nvim/syntax.h" -#include "nvim/tag.h" +#include "nvim/terminal.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -145,14 +158,14 @@ void do_ascii(const exarg_T *const eap) char buf2[20]; buf2[0] = NUL; - dig = (char *)get_digraph_for_char(cval); + dig = get_digraph_for_char(cval); if (dig != NULL) { - iobuff_len += (size_t)vim_snprintf((char *)IObuff + iobuff_len, + iobuff_len += (size_t)vim_snprintf(IObuff + iobuff_len, sizeof(IObuff) - iobuff_len, _("<%s>%s%s %d, Hex %02x, Oct %03o, Digr %s"), transchar(c), buf1, buf2, cval, cval, cval, dig); } else { - iobuff_len += (size_t)vim_snprintf((char *)IObuff + iobuff_len, + iobuff_len += (size_t)vim_snprintf(IObuff + iobuff_len, sizeof(IObuff) - iobuff_len, _("<%s>%s%s %d, Hex %02x, Octal %03o"), transchar(c), buf1, buf2, cval, cval, cval); @@ -191,18 +204,18 @@ void do_ascii(const exarg_T *const eap) if (utf_iscomposing(c)) { IObuff[iobuff_len++] = ' '; // Draw composing char on top of a space. } - iobuff_len += (size_t)utf_char2bytes(c, (char *)IObuff + iobuff_len); + iobuff_len += (size_t)utf_char2bytes(c, IObuff + iobuff_len); - dig = (char *)get_digraph_for_char(c); + dig = get_digraph_for_char(c); if (dig != NULL) { - iobuff_len += (size_t)vim_snprintf((char *)IObuff + iobuff_len, + iobuff_len += (size_t)vim_snprintf(IObuff + iobuff_len, sizeof(IObuff) - iobuff_len, (c < 0x10000 ? _("> %d, Hex %04x, Oct %o, Digr %s") : _("> %d, Hex %08x, Oct %o, Digr %s")), c, c, c, dig); } else { - iobuff_len += (size_t)vim_snprintf((char *)IObuff + iobuff_len, + iobuff_len += (size_t)vim_snprintf(IObuff + iobuff_len, sizeof(IObuff) - iobuff_len, (c < 0x10000 ? _("> %d, Hex %04x, Octal %o") @@ -215,10 +228,10 @@ void do_ascii(const exarg_T *const eap) c = cc[ci++]; } if (ci != MAX_MCO && c != 0) { - xstrlcpy((char *)IObuff + iobuff_len, " ...", sizeof(IObuff) - iobuff_len); + xstrlcpy(IObuff + iobuff_len, " ...", sizeof(IObuff) - iobuff_len); } - msg((char *)IObuff); + msg(IObuff); } /// ":left", ":center" and ":right": align text. @@ -266,13 +279,12 @@ void ex_align(exarg_T *eap) } for (curwin->w_cursor.lnum = eap->line1; - curwin->w_cursor.lnum <= eap->line2; ++curwin->w_cursor.lnum) { + curwin->w_cursor.lnum <= eap->line2; curwin->w_cursor.lnum++) { if (eap->cmdidx == CMD_left) { // left align new_indent = indent; } else { has_tab = false; // avoid uninit warnings - len = linelen(eap->cmdidx == CMD_right ? &has_tab - : NULL) - get_indent(); + len = linelen(eap->cmdidx == CMD_right ? &has_tab : NULL) - get_indent(); if (len <= 0) { // skip blank lines continue; @@ -335,7 +347,7 @@ static int linelen(int *has_tab) char save = *last; *last = NUL; // Get line length. - len = linetabsize((char_u *)line); + len = linetabsize(line); // Check for embedded TAB. if (has_tab != NULL) { *has_tab = vim_strchr(first, TAB) != NULL; @@ -505,9 +517,8 @@ void ex_sort(exarg_T *eap) eap->nextcmd = check_nextcmd(p); break; } else if (!ASCII_ISALPHA(*p) && regmatch.regprog == NULL) { - s = skip_regexp(p + 1, *p, true, NULL); - if (*s != *p) { - emsg(_(e_invalpat)); + s = skip_regexp_err(p + 1, *p, true); + if (s == NULL) { goto sortend; } *s = NUL; @@ -517,7 +528,7 @@ void ex_sort(exarg_T *eap) emsg(_(e_noprevre)); goto sortend; } - regmatch.regprog = vim_regcomp((char *)last_search_pat(), RE_MAGIC); + regmatch.regprog = vim_regcomp(last_search_pat(), RE_MAGIC); } else { regmatch.regprog = vim_regcomp(p + 1, RE_MAGIC); } @@ -708,187 +719,6 @@ sortend: } } -/// ":retab". -void ex_retab(exarg_T *eap) -{ - linenr_T lnum; - bool got_tab = false; - long num_spaces = 0; - long num_tabs; - long len; - long col; - long vcol; - long start_col = 0; // For start of white-space string - long start_vcol = 0; // For start of white-space string - long old_len; - char *ptr; - char *new_line = (char *)1; // init to non-NULL - bool did_undo; // called u_save for current line - long *new_vts_array = NULL; - char *new_ts_str; // string value of tab argument - - int save_list; - linenr_T first_line = 0; // first changed line - linenr_T last_line = 0; // last changed line - - save_list = curwin->w_p_list; - curwin->w_p_list = 0; // don't want list mode here - - new_ts_str = eap->arg; - if (!tabstop_set(eap->arg, &new_vts_array)) { - return; - } - while (ascii_isdigit(*(eap->arg)) || *(eap->arg) == ',') { - (eap->arg)++; - } - - // This ensures that either new_vts_array and new_ts_str are freshly - // allocated, or new_vts_array points to an existing array and new_ts_str - // is null. - if (new_vts_array == NULL) { - new_vts_array = curbuf->b_p_vts_array; - new_ts_str = NULL; - } else { - new_ts_str = xstrnsave(new_ts_str, (size_t)(eap->arg - new_ts_str)); - } - for (lnum = eap->line1; !got_int && lnum <= eap->line2; lnum++) { - ptr = ml_get(lnum); - col = 0; - vcol = 0; - did_undo = false; - for (;;) { - if (ascii_iswhite(ptr[col])) { - if (!got_tab && num_spaces == 0) { - // First consecutive white-space - start_vcol = vcol; - start_col = col; - } - if (ptr[col] == ' ') { - num_spaces++; - } else { - got_tab = true; - } - } else { - if (got_tab || (eap->forceit && num_spaces > 1)) { - // Retabulate this string of white-space - - // len is virtual length of white string - len = num_spaces = vcol - start_vcol; - num_tabs = 0; - if (!curbuf->b_p_et) { - int t, s; - - tabstop_fromto((colnr_T)start_vcol, (colnr_T)vcol, - curbuf->b_p_ts, new_vts_array, &t, &s); - num_tabs = t; - num_spaces = s; - } - if (curbuf->b_p_et || got_tab - || (num_spaces + num_tabs < len)) { - if (did_undo == false) { - did_undo = true; - if (u_save((linenr_T)(lnum - 1), - (linenr_T)(lnum + 1)) == FAIL) { - new_line = NULL; // flag out-of-memory - break; - } - } - - // len is actual number of white characters used - len = num_spaces + num_tabs; - old_len = (long)strlen(ptr); - 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((size_t)new_len); - - if (start_col > 0) { - memmove(new_line, ptr, (size_t)start_col); - } - memmove(new_line + start_col + len, - ptr + col, (size_t)(old_len - col + 1)); - ptr = new_line + start_col; - for (col = 0; col < len; col++) { - ptr[col] = (col < num_tabs) ? '\t' : ' '; - } - if (ml_replace(lnum, new_line, false) == OK) { - // "new_line" may have been copied - new_line = (char *)curbuf->b_ml.ml_line_ptr; - extmark_splice_cols(curbuf, lnum - 1, 0, (colnr_T)old_len, - (colnr_T)new_len - 1, kExtmarkUndo); - } - if (first_line == 0) { - first_line = lnum; - } - last_line = lnum; - ptr = new_line; - col = start_col + len; - } - } - got_tab = false; - num_spaces = 0; - } - if (ptr[col] == NUL) { - 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 - break; - } - line_breakcheck(); - } - if (got_int) { - emsg(_(e_interr)); - } - - // If a single value was given then it can be considered equal to - // either the value of 'tabstop' or the value of 'vartabstop'. - if (tabstop_count(curbuf->b_p_vts_array) == 0 - && tabstop_count(new_vts_array) == 1 - && curbuf->b_p_ts == tabstop_first(new_vts_array)) { - // not changed - } else if (tabstop_count(curbuf->b_p_vts_array) > 0 - && tabstop_eq(curbuf->b_p_vts_array, new_vts_array)) { - // not changed - } else { - redraw_curbuf_later(UPD_NOT_VALID); - } - if (first_line != 0) { - changed_lines(first_line, 0, last_line + 1, 0L, true); - } - - curwin->w_p_list = save_list; // restore 'list' - - if (new_ts_str != NULL) { // set the new tabstop - // If 'vartabstop' is in use or if the value given to retab has more - // than one tabstop then update 'vartabstop'. - long *old_vts_ary = curbuf->b_p_vts_array; - - if (tabstop_count(old_vts_ary) > 0 || tabstop_count(new_vts_array) > 1) { - set_string_option_direct("vts", -1, new_ts_str, OPT_FREE | OPT_LOCAL, 0); - curbuf->b_p_vts_array = new_vts_array; - xfree(old_vts_ary); - } else { - // 'vartabstop' wasn't in use and a single value was given to - // retab then update 'tabstop'. - curbuf->b_p_ts = tabstop_first(new_vts_array); - xfree(new_vts_array); - } - xfree(new_ts_str); - } - coladvance(curwin->w_curswant); - - u_clearline(); -} - /// :move command - move lines line1-line2 to line dest /// /// @return FAIL for failure, OK otherwise @@ -1008,7 +838,9 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) ml_delete(line1 + extra, true); } if (!global_busy && num_lines > p_report) { - smsg(NGETTEXT("1 line moved", "%" PRId64 " lines moved", num_lines), (int64_t)num_lines); + smsg(NGETTEXT("%" PRId64 " line moved", + "%" PRId64 " lines moved", num_lines), + (int64_t)num_lines); } extmark_move_region(curbuf, line1 - 1, 0, start_byte, @@ -1124,8 +956,7 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out int scroll_save = msg_scroll; // - // Disallow shell commands from .exrc and .vimrc in current directory for - // security reasons. + // Disallow shell commands in secure mode // if (check_secure()) { return; @@ -1137,10 +968,12 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out msg_scroll = scroll_save; } - // Try to find an embedded bang, like in :!<cmd> ! [args] - // (:!! is indicated by the 'forceit' variable) + // Try to find an embedded bang, like in ":!<cmd> ! [args]" + // ":!!" is indicated by the 'forceit' variable. bool ins_prevcmd = forceit; - trailarg = arg; + + // Skip leading white space to avoid a strange error with some shells. + trailarg = skipwhite(arg); do { len = strlen(trailarg) + 1; if (newcmd != NULL) { @@ -1192,7 +1025,7 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out // If % or # appears in the command, it must have been escaped. // Reescape them, so that redoing them does not substitute them by the // buffername. - char *cmd = (char *)vim_strsave_escaped((char_u *)prevcmd, (char_u *)"%#"); + char *cmd = vim_strsave_escaped(prevcmd, "%#"); AppendToRedobuffLit(cmd, -1); xfree(cmd); @@ -1201,7 +1034,7 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out } // Add quotes around the command, for shells that need them. if (*p_shq != NUL) { - newcmd = xmalloc(strlen(prevcmd) + 2 * STRLEN(p_shq) + 1); + newcmd = xmalloc(strlen(prevcmd) + 2 * strlen(p_shq) + 1); STRCPY(newcmd, p_shq); STRCAT(newcmd, prevcmd); STRCAT(newcmd, p_shq); @@ -1346,7 +1179,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b read_linecount = curbuf->b_ml.ml_line_count; // Pass on the kShellOptDoOut flag when the output is being redirected. - call_shell((char_u *)cmd_buf, (ShellOpts)(kShellOptFilter | shell_flags), NULL); + call_shell(cmd_buf, (ShellOpts)(kShellOptFilter | shell_flags), NULL); xfree(cmd_buf); did_check_timestamps = false; @@ -1465,8 +1298,7 @@ filterend: /// @param flags may be SHELL_DOOUT when output is redirected void do_shell(char *cmd, int flags) { - // Disallow shell commands from .exrc and .vimrc in current directory for - // security reasons. + // Disallow shell commands in secure mode if (check_secure()) { msg_end(); return; @@ -1492,7 +1324,7 @@ void do_shell(char *cmd, int flags) // This ui_cursor_goto is required for when the '\n' resulted in a "delete line // 1" command to the terminal. ui_cursor_goto(msg_row, msg_col); - (void)call_shell((char_u *)cmd, (ShellOpts)flags, NULL); + (void)call_shell(cmd, (ShellOpts)flags, NULL); msg_didout = true; did_check_timestamps = false; need_check_timestamps = true; @@ -1512,11 +1344,11 @@ static char *find_pipe(const char *cmd) for (const char *p = cmd; *p != NUL; p++) { if (!inquote && *p == '|') { - return p; + return (char *)p; } if (*p == '"') { inquote = !inquote; - } else if (rem_backslash((const char_u *)p)) { + } else if (rem_backslash(p)) { p++; } } @@ -1535,12 +1367,13 @@ char *make_filter_cmd(char *cmd, char *itmp, char *otmp) { bool is_fish_shell = #if defined(UNIX) - STRNCMP(invocation_path_tail((char_u *)p_sh, NULL), "fish", 4) == 0; + strncmp((char *)invocation_path_tail(p_sh, NULL), "fish", 4) == 0; #else false; #endif - bool is_pwsh = STRNCMP(invocation_path_tail((char_u *)p_sh, NULL), "pwsh", 4) == 0 - || STRNCMP(invocation_path_tail((char_u *)p_sh, NULL), "powershell", 10) == 0; + bool is_pwsh = strncmp((char *)invocation_path_tail(p_sh, NULL), "pwsh", 4) == 0 + || strncmp((char *)invocation_path_tail(p_sh, NULL), "powershell", + 10) == 0; size_t len = strlen(cmd) + 1; // At least enough space for cmd + NULL. @@ -1549,7 +1382,7 @@ char *make_filter_cmd(char *cmd, char *itmp, char *otmp) : 0; if (itmp != NULL) { - len += is_pwsh ? strlen(itmp) + sizeof("& { Get-Content " " | & " " }") - 1 + len += is_pwsh ? strlen(itmp) + sizeof("& { Get-Content " " | & " " }") - 1 + 6 // +6: #20530 : strlen(itmp) + sizeof(" { " " < " " } ") - 1; } if (otmp != NULL) { @@ -1587,7 +1420,7 @@ char *make_filter_cmd(char *cmd, char *itmp, char *otmp) #else // For shells that don't understand braces around commands, at least allow // the use of commands in a pipe. - xstrlcpy(buf, (char *)cmd, len); + xstrlcpy(buf, cmd, len); if (itmp != NULL) { // If there is a pipe, we have to put the '<' in front of it. // Don't do this when 'shellquote' is not empty, otherwise the @@ -1670,7 +1503,7 @@ void print_line(linenr_T lnum, int use_number, int list) msg_start(); silent_mode = false; - info_message = true; // use mch_msg(), not mch_errmsg() + info_message = true; // use os_msg(), not os_errmsg() print_line_no_prefix(lnum, use_number, list); if (save_silent) { msg_putchar('\n'); @@ -1774,12 +1607,23 @@ void ex_write(exarg_T *eap) } } -/// write current buffer to file 'eap->arg' -/// if 'eap->append' is true, append to the file +#ifdef UNIX +static int check_writable(const char *fname) +{ + if (os_nodetype(fname) == NODE_OTHER) { + semsg(_("E503: \"%s\" is not a file or writable device"), fname); + return FAIL; + } + return OK; +} +#endif + +/// Write current buffer to file "eap->arg". +/// If "eap->append" is true, append to the file. /// -/// if *eap->arg == NUL write to current file +/// If "*eap->arg == NUL" write to current file. /// -/// @return FAIL for failure, OK otherwise +/// @return FAIL for failure, OK otherwise. int do_write(exarg_T *eap) { int other; @@ -1831,7 +1675,11 @@ int do_write(exarg_T *eap) // Writing to the current file is not allowed in readonly mode // and a file name is required. // "nofile" and "nowrite" buffers cannot be written implicitly either. - if (!other && (bt_dontwrite_msg(curbuf) || check_fname() == FAIL + if (!other && (bt_dontwrite_msg(curbuf) + || check_fname() == FAIL +#ifdef UNIX + || check_writable(curbuf->b_ffname) == FAIL +#endif || check_readonly(&eap->forceit, curbuf))) { goto theend; } @@ -1909,6 +1757,13 @@ int do_write(exarg_T *eap) fname = curbuf->b_sfname; } + if (eap->mkdir_p) { + if (os_file_mkdir(fname, 0755) < 0) { + retval = FAIL; + goto theend; + } + } + name_was_missing = curbuf->b_ffname == NULL; retval = buf_write(curbuf, ffname, fname, eap->line1, eap->line2, eap, eap->append, eap->forceit, true, false); @@ -1999,7 +1854,7 @@ int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, int oth p = p_dir; copy_option_part(&p, dir, MAXPATHL, ","); } - swapname = (char *)makeswapname((char_u *)fname, (char_u *)ffname, curbuf, (char_u *)dir); + swapname = makeswapname(fname, ffname, curbuf, dir); xfree(dir); if (os_path_exists(swapname)) { if (p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) { @@ -2139,9 +1994,8 @@ static int check_readonly(int *forceit, buf_T *buf) // Set forceit, to force the writing of a readonly file *forceit = true; return false; - } else { - return true; } + return true; } else if (buf->b_p_ro) { emsg(_(e_readonly)); } else { @@ -3182,8 +3036,7 @@ void ex_z(exarg_T *eap) ex_no_reprint = true; } -/// @return true if the secure flag is set (.exrc or .vimrc in current directory) -/// and also give an error message. +/// @return true if the secure flag is set and also give an error message. /// Otherwise, return false. bool check_secure(void) { @@ -3240,7 +3093,7 @@ void sub_set_replacement(SubReplacementString sub) /// @param[in] save Save pattern to options, history /// /// @returns true if :substitute can be replaced with a join command -static bool sub_joining_lines(exarg_T *eap, char *pat, char *sub, char *cmd, bool save) +static bool sub_joining_lines(exarg_T *eap, char *pat, const char *sub, const char *cmd, bool save) FUNC_ATTR_NONNULL_ARG(1, 3, 4) { // TODO(vim): find a generic solution to make line-joining operations more @@ -3279,7 +3132,7 @@ static bool sub_joining_lines(exarg_T *eap, char *pat, char *sub, char *cmd, boo if (save) { if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) == 0) { - save_re_pat(RE_SUBST, pat, p_magic); + save_re_pat(RE_SUBST, pat, magic_isset()); } // put pattern in history add_to_history(HIST_SEARCH, pat, true, NUL); @@ -3410,6 +3263,30 @@ static int check_regexp_delim(int c) /// @return 0, 1 or 2. See show_cmdpreview() for more information on what the return value means. static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T cmdpreview_bufnr) { +#define ADJUST_SUB_FIRSTLNUM() \ + do { \ + /* For a multi-line match, make a copy of the last matched */ \ + /* line and continue in that one. */ \ + if (nmatch > 1) { \ + sub_firstlnum += (linenr_T)nmatch - 1; \ + xfree(sub_firstline); \ + sub_firstline = xstrdup(ml_get(sub_firstlnum)); \ + /* When going beyond the last line, stop substituting. */ \ + if (sub_firstlnum <= line2) { \ + do_again = true; \ + } else { \ + subflags.do_all = false; \ + } \ + } \ + if (skip_match) { \ + /* Already hit end of the buffer, sub_firstlnum is one */ \ + /* less than what it ought to be. */ \ + xfree(sub_firstline); \ + sub_firstline = xstrdup(""); \ + copycol = 0; \ + } \ + } while (0) + long i = 0; regmmatch_T regmatch; static subflags_T subflags = { @@ -3455,7 +3332,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T } // new pattern and substitution if (eap->cmd[0] == 's' && *cmd != NUL && !ascii_iswhite(*cmd) - && vim_strchr("0123456789cegriIp|\"", *cmd) == NULL) { + && vim_strchr("0123456789cegriIp|\"", (uint8_t)(*cmd)) == NULL) { // don't accept alphanumeric for separator if (check_regexp_delim(*cmd) == FAIL) { return 0; @@ -3466,7 +3343,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T // //sub/r). "\&sub&" use last substitute pattern (like //sub/). if (*cmd == '\\') { cmd++; - if (vim_strchr("/?&", *cmd) == NULL) { + if (vim_strchr("/?&", (uint8_t)(*cmd)) == NULL) { emsg(_(e_backslash)); return 0; } @@ -3474,13 +3351,13 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T which_pat = RE_SEARCH; // use last '/' pattern } pat = ""; // empty search pattern - delimiter = (char_u)(*cmd++); // remember delimiter character + delimiter = (uint8_t)(*cmd++); // remember delimiter character has_second_delim = true; } else { // find the end of the regexp which_pat = RE_LAST; // use last used regexp - delimiter = (char_u)(*cmd++); // remember delimiter character + delimiter = (uint8_t)(*cmd++); // remember delimiter character pat = cmd; // remember start of search pat - cmd = skip_regexp(cmd, delimiter, p_magic, &eap->arg); + cmd = skip_regexp_ex(cmd, delimiter, magic_isset(), &eap->arg, NULL, NULL); if (cmd[0] == delimiter) { // end delimiter found *cmd++ = NUL; // replace it with a NUL has_second_delim = true; @@ -3566,8 +3443,8 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T return 0; } - if (search_regcomp((char_u *)pat, RE_SUBST, which_pat, (cmdpreview ? 0 : SEARCH_HIS), - ®match) == FAIL) { + if (search_regcomp(pat, NULL, RE_SUBST, which_pat, + (cmdpreview ? 0 : SEARCH_HIS), ®match) == FAIL) { if (subflags.do_error) { emsg(_(e_invcmd)); } @@ -3596,7 +3473,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T sub = xstrdup(sub); sub_copy = sub; } else { - char *newsub = regtilde(sub, p_magic, cmdpreview); + char *newsub = regtilde(sub, magic_isset(), cmdpreview); if (newsub != sub) { // newsub was allocated, free it later. sub_copy = newsub; @@ -3820,7 +3697,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T msg_putchar('\n'); xfree(prompt); if (resp != NULL) { - typed = (char_u)(*resp); + typed = (uint8_t)(*resp); xfree(resp); } else { // getcmdline_prompt() returns NULL if there is no command line to return. @@ -3981,30 +3858,6 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T skip_match = true; } -#define ADJUST_SUB_FIRSTLNUM() \ - do { \ - /* For a multi-line match, make a copy of the last matched */ \ - /* line and continue in that one. */ \ - if (nmatch > 1) { \ - sub_firstlnum += (linenr_T)nmatch - 1; \ - xfree(sub_firstline); \ - sub_firstline = xstrdup(ml_get(sub_firstlnum)); \ - /* When going beyond the last line, stop substituting. */ \ - if (sub_firstlnum <= line2) { \ - do_again = true; \ - } else { \ - subflags.do_all = false; \ - } \ - } \ - if (skip_match) { \ - /* Already hit end of the buffer, sub_firstlnum is one */ \ - /* less than what it ought to be. */ \ - xfree(sub_firstline); \ - sub_firstline = xstrdup(""); \ - copycol = 0; \ - } \ - } while (0) - // Save the line numbers for the preview buffer // NOTE: If the pattern matches a final newline, the next line will // be shown also, but should not be highlighted. Intentional for now. @@ -4042,8 +3895,9 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T // When it fails sublen is zero. sublen = vim_regsub_multi(®match, sub_firstlnum - regmatch.startpos[0].lnum, - (char_u *)sub, (char_u *)sub_firstline, 0, - REGSUB_BACKSLASH | (p_magic ? REGSUB_MAGIC : 0)); + sub, sub_firstline, 0, + REGSUB_BACKSLASH + | (magic_isset() ? REGSUB_MAGIC : 0)); textlock--; // If getting the substitute string caused an error, don't do @@ -4084,8 +3938,9 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T textlock++; (void)vim_regsub_multi(®match, sub_firstlnum - regmatch.startpos[0].lnum, - (char_u *)sub, (char_u *)new_end, sublen, - REGSUB_COPY | REGSUB_BACKSLASH | (p_magic ? REGSUB_MAGIC : 0)); + sub, new_end, sublen, + REGSUB_COPY | REGSUB_BACKSLASH + | (magic_isset() ? REGSUB_MAGIC : 0)); textlock--; sub_nsubs++; did_sub = true; @@ -4517,7 +4372,7 @@ void ex_global(exarg_T *eap) // "\&": use previous substitute pattern. if (*cmd == '\\') { cmd++; - if (vim_strchr("/?&", *cmd) == NULL) { + if (vim_strchr("/?&", (uint8_t)(*cmd)) == NULL) { emsg(_(e_backslash)); return; } @@ -4537,13 +4392,15 @@ void ex_global(exarg_T *eap) delim = *cmd; // get the delimiter cmd++; // skip delimiter if there is one pat = cmd; // remember start of pattern - cmd = skip_regexp(cmd, delim, p_magic, &eap->arg); + cmd = skip_regexp_ex(cmd, delim, magic_isset(), &eap->arg, NULL, NULL); if (cmd[0] == delim) { // end delimiter found *cmd++ = NUL; // replace it with a NUL } } - if (search_regcomp((char_u *)pat, RE_BOTH, which_pat, SEARCH_HIS, ®match) == FAIL) { + char *used_pat; + if (search_regcomp(pat, &used_pat, RE_BOTH, which_pat, + SEARCH_HIS, ®match) == FAIL) { emsg(_(e_invcmd)); return; } @@ -4574,9 +4431,9 @@ void ex_global(exarg_T *eap) msg(_(e_interr)); } else if (ndone == 0) { if (type == 'v') { - smsg(_("Pattern found in every line: %s"), pat); + smsg(_("Pattern found in every line: %s"), used_pat); } else { - smsg(_("Pattern not found: %s"), pat); + smsg(_("Pattern not found: %s"), used_pat); } } else { global_exe(cmd); @@ -4653,32 +4510,30 @@ void free_old_sub(void) /// @return true when it was created. bool prepare_tagpreview(bool undo_sync) { + if (curwin->w_p_pvw) { + return false; + } + // If there is already a preview window open, use that one. - if (!curwin->w_p_pvw) { - bool found_win = false; - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_p_pvw) { - win_enter(wp, undo_sync); - found_win = true; - break; - } - } - if (!found_win) { - // There is no preview window open yet. Create one. - if (win_split(g_do_tagpreview > 0 ? g_do_tagpreview : 0, 0) - == FAIL) { - return false; - } - curwin->w_p_pvw = true; - curwin->w_p_wfh = true; - RESET_BINDING(curwin); // don't take over 'scrollbind' and 'cursorbind' - curwin->w_p_diff = false; // no 'diff' - set_string_option_direct("fdc", -1, // no 'foldcolumn' - "0", OPT_FREE, SID_NONE); - return true; + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_p_pvw) { + win_enter(wp, undo_sync); + return false; } } - return false; + + // There is no preview window open yet. Create one. + if (win_split(g_do_tagpreview > 0 ? g_do_tagpreview : 0, 0) + == FAIL) { + return false; + } + curwin->w_p_pvw = true; + curwin->w_p_wfh = true; + RESET_BINDING(curwin); // don't take over 'scrollbind' and 'cursorbind' + curwin->w_p_diff = false; // no 'diff' + set_string_option_direct("fdc", -1, // no 'foldcolumn' + "0", OPT_FREE, SID_NONE); + return true; } /// Shows the effects of the :substitute command being typed ('inccommand'). @@ -4835,7 +4690,7 @@ char *skip_vimgrep_pat(char *p, char **s, int *flags) { int c; - if (vim_isIDc(*p)) { + if (vim_isIDc((uint8_t)(*p))) { // ":vimgrep pattern fname" if (s != NULL) { *s = p; @@ -4849,8 +4704,8 @@ char *skip_vimgrep_pat(char *p, char **s, int *flags) if (s != NULL) { *s = p + 1; } - c = (char_u)(*p); - p = skip_regexp(p + 1, c, true, NULL); + c = (uint8_t)(*p); + p = skip_regexp(p + 1, c, true); if (*p != c) { return NULL; } @@ -4886,45 +4741,72 @@ void ex_oldfiles(exarg_T *eap) if (l == NULL) { msg(_("No old files")); - } else { - msg_start(); - msg_scroll = true; - TV_LIST_ITER(l, li, { - if (got_int) { - break; - } - nr++; - const char *fname = tv_get_string(TV_LIST_ITEM_TV(li)); - if (!message_filtered((char *)fname)) { - msg_outnum(nr); - msg_puts(": "); - msg_outtrans((char *)tv_get_string(TV_LIST_ITEM_TV(li))); - msg_clr_eos(); - msg_putchar('\n'); - os_breakcheck(); - } - }); - - // Assume "got_int" was set to truncate the listing. - got_int = false; - - // File selection prompt on ":browse oldfiles" - if (cmdmod.cmod_flags & CMOD_BROWSE) { - quit_more = false; - nr = prompt_for_number(false); - msg_starthere(); - if (nr > 0 && nr <= tv_list_len(l)) { - const char *const p = tv_list_find_str(l, (int)nr - 1); - if (p == NULL) { - return; - } - char *const s = expand_env_save((char *)p); - eap->arg = s; - eap->cmdidx = CMD_edit; - cmdmod.cmod_flags &= ~CMOD_BROWSE; - do_exedit(eap, NULL); - xfree(s); + return; + } + + msg_start(); + msg_scroll = true; + TV_LIST_ITER(l, li, { + if (got_int) { + break; + } + nr++; + const char *fname = tv_get_string(TV_LIST_ITEM_TV(li)); + if (!message_filtered((char *)fname)) { + msg_outnum(nr); + msg_puts(": "); + msg_outtrans((char *)tv_get_string(TV_LIST_ITEM_TV(li))); + msg_clr_eos(); + msg_putchar('\n'); + os_breakcheck(); + } + }); + + // Assume "got_int" was set to truncate the listing. + got_int = false; + + // File selection prompt on ":browse oldfiles" + if (cmdmod.cmod_flags & CMOD_BROWSE) { + quit_more = false; + nr = prompt_for_number(false); + msg_starthere(); + if (nr > 0 && nr <= tv_list_len(l)) { + const char *const p = tv_list_find_str(l, (int)nr - 1); + if (p == NULL) { + return; } + char *const s = expand_env_save((char *)p); + eap->arg = s; + eap->cmdidx = CMD_edit; + cmdmod.cmod_flags &= ~CMOD_BROWSE; + do_exedit(eap, NULL); + xfree(s); } } } + +void ex_trust(exarg_T *eap) +{ + const char *const p = skiptowhite(eap->arg); + char *arg1 = xmemdupz(eap->arg, (size_t)(p - eap->arg)); + const char *action = "allow"; + const char *path = skipwhite(p); + + if (strcmp(arg1, "++deny") == 0) { + action = "deny"; + } else if (strcmp(arg1, "++remove") == 0) { + action = "remove"; + } else if (*arg1 != '\0') { + semsg(e_invarg2, arg1); + goto theend; + } + + if (path[0] == '\0') { + path = NULL; + } + + nlua_trust(action, path); + +theend: + xfree(arg1); +} diff --git a/src/nvim/ex_cmds.h b/src/nvim/ex_cmds.h index 3aaba9ce42..39bff3e35d 100644 --- a/src/nvim/ex_cmds.h +++ b/src/nvim/ex_cmds.h @@ -5,6 +5,7 @@ #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/os/time.h" #include "nvim/pos.h" diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 04b8832428..c8b6ceab69 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -673,18 +673,6 @@ module.cmds = { func='ex_cc', }, { - command='cscope', - flags=bit.bor(EXTRA, NOTRLCOM, XFILE), - addr_type='ADDR_NONE', - func='ex_cscope', - }, - { - command='cstag', - flags=bit.bor(BANG, TRLBAR, WORD1), - addr_type='ADDR_NONE', - func='ex_cstag', - }, - { command='cunmap', flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', @@ -1117,12 +1105,6 @@ module.cmds = { func='ex_helptags', }, { - command='hardcopy', - flags=bit.bor(RANGE, COUNT, EXTRA, TRLBAR, DFLALL, BANG), - addr_type='ADDR_LINES', - func='ex_hardcopy', - }, - { command='highlight', flags=bit.bor(BANG, EXTRA, TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', @@ -1405,12 +1387,6 @@ module.cmds = { func='ex_cclose', }, { - command='lcscope', - flags=bit.bor(EXTRA, NOTRLCOM, XFILE), - addr_type='ADDR_NONE', - func='ex_cscope', - }, - { command='ldo', flags=bit.bor(BANG, NEEDARG, EXTRA, NOTRLCOM, RANGE, DFLALL), addr_type='ADDR_QUICKFIX_VALID', @@ -2411,7 +2387,7 @@ module.cmds = { }, { command='scriptnames', - flags=bit.bor(BANG, RANGE, COUNT, TRLBAR, CMDWIN, LOCK_OK), + flags=bit.bor(BANG, FILES, RANGE, COUNT, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_scriptnames', }, @@ -2422,12 +2398,6 @@ module.cmds = { func='ex_scriptencoding', }, { - command='scscope', - flags=bit.bor(EXTRA, NOTRLCOM), - addr_type='ADDR_NONE', - func='ex_scscope', - }, - { command='set', flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, LOCK_OK, SBOXOK), addr_type='ADDR_NONE', @@ -2958,6 +2928,12 @@ module.cmds = { func='ex_tag', }, { + command='trust', + flags=bit.bor(EXTRA, FILE1, TRLBAR, LOCK_OK), + addr_type='ADDR_NONE', + func='ex_trust', + }, + { command='try', flags=bit.bor(TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 1248251556..d08823bc30 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -6,9 +6,9 @@ /// Some more functions for command line commands #include <assert.h> -#include <fcntl.h> #include <inttypes.h> #include <stdbool.h> +#include <stdio.h> #include <string.h> #include "nvim/arglist.h" @@ -16,30 +16,33 @@ #include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/change.h" -#include "nvim/charset.h" +#include "nvim/channel.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/vars.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" -#include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" +#include "nvim/gettext.h" #include "nvim/globals.h" +#include "nvim/highlight_defs.h" +#include "nvim/macros.h" #include "nvim/mark.h" -#include "nvim/mbyte.h" +#include "nvim/memline_defs.h" #include "nvim/memory.h" #include "nvim/message.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_unix.h" +#include "nvim/os/os_defs.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/quickfix.h" #include "nvim/runtime.h" -#include "nvim/strings.h" #include "nvim/undo.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -702,54 +705,56 @@ void ex_compiler(exarg_T *eap) // List all compiler scripts. do_cmdline_cmd("echo globpath(&rtp, 'compiler/*.vim')"); // NOLINT do_cmdline_cmd("echo globpath(&rtp, 'compiler/*.lua')"); // NOLINT + return; + } + + size_t bufsize = strlen(eap->arg) + 14; + buf = xmalloc(bufsize); + + if (eap->forceit) { + // ":compiler! {name}" sets global options + do_cmdline_cmd("command -nargs=* CompilerSet set <args>"); } else { - size_t bufsize = strlen(eap->arg) + 14; - buf = xmalloc(bufsize); - if (eap->forceit) { - // ":compiler! {name}" sets global options - do_cmdline_cmd("command -nargs=* CompilerSet set <args>"); - } else { - // ":compiler! {name}" sets local options. - // To remain backwards compatible "current_compiler" is always - // used. A user's compiler plugin may set it, the distributed - // plugin will then skip the settings. Afterwards set - // "b:current_compiler" and restore "current_compiler". - // Explicitly prepend "g:" to make it work in a function. - old_cur_comp = (char *)get_var_value("g:current_compiler"); - if (old_cur_comp != NULL) { - old_cur_comp = xstrdup(old_cur_comp); - } - do_cmdline_cmd("command -nargs=* -keepscript CompilerSet setlocal <args>"); + // ":compiler! {name}" sets local options. + // To remain backwards compatible "current_compiler" is always + // used. A user's compiler plugin may set it, the distributed + // plugin will then skip the settings. Afterwards set + // "b:current_compiler" and restore "current_compiler". + // Explicitly prepend "g:" to make it work in a function. + old_cur_comp = get_var_value("g:current_compiler"); + if (old_cur_comp != NULL) { + old_cur_comp = xstrdup(old_cur_comp); } - do_unlet(S_LEN("g:current_compiler"), true); - do_unlet(S_LEN("b:current_compiler"), true); + 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); - snprintf(buf, bufsize, "compiler/%s.vim", eap->arg); + snprintf(buf, bufsize, "compiler/%s.vim", eap->arg); + if (source_runtime(buf, DIP_ALL) == FAIL) { + // Try lua compiler + snprintf(buf, bufsize, "compiler/%s.lua", eap->arg); if (source_runtime(buf, DIP_ALL) == FAIL) { - // Try lua compiler - snprintf(buf, bufsize, "compiler/%s.lua", eap->arg); - if (source_runtime(buf, DIP_ALL) == FAIL) { - semsg(_("E666: compiler not supported: %s"), eap->arg); - } + semsg(_("E666: compiler not supported: %s"), eap->arg); } - xfree(buf); + } + xfree(buf); - do_cmdline_cmd(":delcommand CompilerSet"); + do_cmdline_cmd(":delcommand CompilerSet"); - // Set "b:current_compiler" from "current_compiler". - p = (char *)get_var_value("g:current_compiler"); - if (p != NULL) { - set_internal_string_var("b:current_compiler", p); - } + // Set "b:current_compiler" from "current_compiler". + p = get_var_value("g:current_compiler"); + if (p != NULL) { + set_internal_string_var("b:current_compiler", p); + } - // Restore "current_compiler" for ":compiler {name}". - if (!eap->forceit) { - if (old_cur_comp != NULL) { - set_internal_string_var("g:current_compiler", old_cur_comp); - xfree(old_cur_comp); - } else { - do_unlet(S_LEN("g:current_compiler"), true); - } + // Restore "current_compiler" for ":compiler {name}". + if (!eap->forceit) { + if (old_cur_comp != NULL) { + set_internal_string_var("g:current_compiler", old_cur_comp); + xfree(old_cur_comp); + } else { + do_unlet(S_LEN("g:current_compiler"), true); } } } @@ -844,45 +849,46 @@ void ex_drop(exarg_T *eap) // edited in a window yet. It's like ":tab all" but without closing // windows or tabs. ex_all(eap); - } else { - // ":drop file ...": Edit the first argument. Jump to an existing - // window if possible, edit in current window if the current buffer - // can be abandoned, otherwise open a new window. - buf = buflist_findnr(ARGLIST[0].ae_fnum); + return; + } - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer == buf) { - goto_tabpage_win(tp, wp); - curwin->w_arg_idx = 0; - if (!bufIsChanged(curbuf)) { - const int save_ar = curbuf->b_p_ar; - - // reload the file if it is newer - curbuf->b_p_ar = 1; - buf_check_timestamp(curbuf); - curbuf->b_p_ar = save_ar; - } - return; + // ":drop file ...": Edit the first argument. Jump to an existing + // window if possible, edit in current window if the current buffer + // can be abandoned, otherwise open a new window. + buf = buflist_findnr(ARGLIST[0].ae_fnum); + + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer == buf) { + goto_tabpage_win(tp, wp); + curwin->w_arg_idx = 0; + if (!bufIsChanged(curbuf)) { + const int save_ar = curbuf->b_p_ar; + + // reload the file if it is newer + curbuf->b_p_ar = 1; + buf_check_timestamp(curbuf); + curbuf->b_p_ar = save_ar; } + return; } + } - // Check whether the current buffer is changed. If so, we will need - // to split the current window or data could be lost. - // Skip the check if the 'hidden' option is set, as in this case the - // buffer won't be lost. - if (!buf_hide(curbuf)) { - emsg_off++; - split = check_changed(curbuf, CCGD_AW | CCGD_EXCMD); - emsg_off--; - } + // Check whether the current buffer is changed. If so, we will need + // to split the current window or data could be lost. + // Skip the check if the 'hidden' option is set, as in this case the + // buffer won't be lost. + if (!buf_hide(curbuf)) { + emsg_off++; + split = check_changed(curbuf, CCGD_AW | CCGD_EXCMD); + emsg_off--; + } - // Fake a ":sfirst" or ":first" command edit the first argument. - if (split) { - eap->cmdidx = CMD_sfirst; - eap->cmd[0] = 's'; - } else { - eap->cmdidx = CMD_first; - } - ex_rewind(eap); + // Fake a ":sfirst" or ":first" command edit the first argument. + if (split) { + eap->cmdidx = CMD_sfirst; + eap->cmd[0] = 's'; + } else { + eap->cmdidx = CMD_first; } + ex_rewind(eap); } diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index 71956b2246..629aaf14cf 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -6,7 +6,7 @@ #include "nvim/eval/typval.h" #include "nvim/normal.h" -#include "nvim/pos.h" // for linenr_T +#include "nvim/pos.h" #include "nvim/regexp_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -62,7 +62,7 @@ #define EX_FLAGS 0x200000u // allow flags after count in argument #define EX_LOCK_OK 0x1000000u // command can be executed when textlock is // set; when missing disallows editing another - // buffer when current buffer is locked + // buffer when curbuf->b_ro_locked is set #define EX_KEEPSCRIPT 0x4000000u // keep sctx of where command was invoked #define EX_PREVIEW 0x8000000u // allow incremental command preview #define EX_FILES (EX_XFILE | EX_EXTRA) // multiple extra files allowed @@ -202,6 +202,7 @@ struct exarg { int regname; ///< register name (NUL if none) int force_bin; ///< 0, FORCE_BIN or FORCE_NOBIN int read_edit; ///< ++edit argument + int mkdir_p; ///< ++p argument int force_ff; ///< ++ff= argument (first char of argument) int force_enc; ///< ++enc= argument (index in cmd[]) int bad_char; ///< BAD_KEEP, BAD_DROP or replacement byte diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 5591b5f55d..a24e8458a6 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -4,59 +4,57 @@ // ex_docmd.c: functions for executing an Ex command line. #include <assert.h> +#include <ctype.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stddef.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> +#include "auto/config.h" #include "nvim/arglist.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" -#include "nvim/cmdhist.h" #include "nvim/cursor.h" #include "nvim/debugger.h" -#include "nvim/diff.h" #include "nvim/digraph.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" -#include "nvim/eval/vars.h" -#include "nvim/event/rstream.h" -#include "nvim/event/wstream.h" +#include "nvim/event/loop.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" -#include "nvim/ex_session.h" #include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/fold.h" -#include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/globals.h" -#include "nvim/hardcopy.h" -#include "nvim/help.h" +#include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" -#include "nvim/if_cscope.h" #include "nvim/input.h" #include "nvim/keycodes.h" -#include "nvim/locale.h" -#include "nvim/lua/executor.h" +#include "nvim/macros.h" #include "nvim/main.h" -#include "nvim/mapping.h" #include "nvim/mark.h" -#include "nvim/match.h" #include "nvim/mbyte.h" +#include "nvim/memfile_defs.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/menu.h" #include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" @@ -66,28 +64,25 @@ #include "nvim/optionstr.h" #include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os/time.h" -#include "nvim/os_unix.h" +#include "nvim/os/shell.h" #include "nvim/path.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" +#include "nvim/runtime.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/shada.h" -#include "nvim/sign.h" -#include "nvim/spell.h" -#include "nvim/spellfile.h" #include "nvim/state.h" +#include "nvim/statusline.h" #include "nvim/strings.h" -#include "nvim/syntax.h" #include "nvim/tag.h" -#include "nvim/terminal.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" -#include "nvim/undo_defs.h" #include "nvim/usercmd.h" -#include "nvim/version.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -374,7 +369,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // Get the function or script name and the address where the next breakpoint // line and the debug tick for a function or script are stored. if (getline_is_func) { - fname = (char *)func_name(real_cookie); + fname = func_name(real_cookie); breakpoint = func_breakpoint(real_cookie); dbg_tick = func_dbg_tick(real_cookie); } else if (getline_equal(fgetline, cookie, getsourceline)) { @@ -727,7 +722,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) if (cstack.cs_idx >= 0) { // If a sourced file or executed function ran to its end, report the // unclosed conditional. - if (!got_int && !did_throw + if (!got_int && !did_throw && !aborting() && ((getline_equal(fgetline, cookie, getsourceline) && !source_finished(fgetline, cookie)) || (getline_equal(fgetline, cookie, get_func_line) @@ -771,52 +766,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // of interrupts or errors to exceptions, and ensure that no more // commands are executed. if (did_throw) { - assert(current_exception != NULL); - char *p = NULL; - msglist_T *messages = NULL; - msglist_T *next; - - // If the uncaught exception is a user exception, report it as an - // error. If it is an error exception, display the saved error - // message now. For an interrupt exception, do nothing; the - // interrupt message is given elsewhere. - switch (current_exception->type) { - case ET_USER: - vim_snprintf((char *)IObuff, IOSIZE, - _("E605: Exception not caught: %s"), - current_exception->value); - p = xstrdup((char *)IObuff); - break; - case ET_ERROR: - messages = current_exception->messages; - current_exception->messages = NULL; - break; - case ET_INTERRUPT: - break; - } - - estack_push(ETYPE_EXCEPT, current_exception->throw_name, current_exception->throw_lnum); - current_exception->throw_name = NULL; - - discard_current_exception(); // uses IObuff if 'verbose' - suppress_errthrow = true; - force_abort = true; - msg_ext_set_kind("emsg"); // kind=emsg for :throw, exceptions. #9993 - - if (messages != NULL) { - do { - next = messages->next; - emsg(messages->msg); - xfree(messages->msg); - xfree(messages); - messages = next; - } while (messages != NULL); - } else if (p != NULL) { - emsg(p); - xfree(p); - } - xfree(SOURCING_NAME); - estack_pop(); + handle_did_throw(); } else if (got_int || (did_emsg && force_abort)) { // On an interrupt or an aborting error not converted to an exception, // disable the conversion of errors to exceptions. (Interrupts are not @@ -906,6 +856,57 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) return retval; } +/// Handle when "did_throw" is set after executing commands. +void handle_did_throw(void) +{ + assert(current_exception != NULL); + char *p = NULL; + msglist_T *messages = NULL; + + // If the uncaught exception is a user exception, report it as an + // error. If it is an error exception, display the saved error + // message now. For an interrupt exception, do nothing; the + // interrupt message is given elsewhere. + switch (current_exception->type) { + case ET_USER: + vim_snprintf(IObuff, IOSIZE, + _("E605: Exception not caught: %s"), + current_exception->value); + p = xstrdup(IObuff); + break; + case ET_ERROR: + messages = current_exception->messages; + current_exception->messages = NULL; + break; + case ET_INTERRUPT: + break; + } + + estack_push(ETYPE_EXCEPT, current_exception->throw_name, current_exception->throw_lnum); + current_exception->throw_name = NULL; + + discard_current_exception(); // uses IObuff if 'verbose' + suppress_errthrow = true; + force_abort = true; + msg_ext_set_kind("emsg"); // kind=emsg for :throw, exceptions. #9993 + + if (messages != NULL) { + do { + msglist_T *next = messages->next; + emsg(messages->msg); + xfree(messages->msg); + xfree(messages->sfile); + xfree(messages); + messages = next; + } while (messages != NULL); + } else if (p != NULL) { + emsg(p); + xfree(p); + } + xfree(SOURCING_NAME); + estack_pop(); +} + /// Obtain a line when inside a ":while" or ":for" loop. static char *get_loop_line(int c, void *cookie, int indent, bool do_concat) { @@ -918,7 +919,7 @@ static char *get_loop_line(int c, void *cookie, int indent, bool do_concat) char *line; // First time inside the ":while"/":for": get line normally. if (cp->getline == NULL) { - line = (char *)getcmdline(c, 0L, indent, do_concat); + line = getcmdline(c, 0L, indent, do_concat); } else { line = cp->getline(c, cp->cookie, indent, do_concat); } @@ -1060,7 +1061,7 @@ static int current_tab_nr(tabpage_T *tab) #define LAST_TAB_NR current_tab_nr(NULL) /// Figure out the address type for ":wincmd". -static void get_wincmd_addr_type(char *arg, exarg_T *eap) +static void get_wincmd_addr_type(const char *arg, exarg_T *eap) { switch (*arg) { case 'S': @@ -1444,7 +1445,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er // If the modifier was parsed OK the error must be in the following command char *cmdname = after_modifier ? after_modifier : cmdline; append_command(cmdname); - *errormsg = (char *)IObuff; + *errormsg = IObuff; goto end; } @@ -1638,6 +1639,7 @@ int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview) char *errormsg = NULL; int retv = 0; +#undef ERROR #define ERROR(msg) \ do { \ errormsg = msg; \ @@ -1830,6 +1832,7 @@ static bool skip_cmd(const exarg_T *eap) case CMD_throw: case CMD_tilde: case CMD_topleft: + case CMD_trust: case CMD_unlet: case CMD_unlockvar: case CMD_verbose: @@ -1928,9 +1931,11 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter profile_cmd(&ea, cstack, fgetline, cookie); - // May go to debug mode. If this happens and the ">quit" debug command is - // used, throw an interrupt exception and skip the next command. - dbg_check_breakpoint(&ea); + if (!exiting) { + // May go to debug mode. If this happens and the ">quit" debug command is + // used, throw an interrupt exception and skip the next command. + dbg_check_breakpoint(&ea); + } if (!ea.skip && got_int) { ea.skip = true; (void)do_intthrow(cstack); @@ -2033,7 +2038,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter if (!(flags & DOCMD_VERBOSE)) { append_command(cmdname); } - errormsg = (char *)IObuff; + errormsg = IObuff; did_emsg_syntax = true; verify_command(cmdname); } @@ -2302,9 +2307,9 @@ doend: if (errormsg != NULL && *errormsg != NUL && !did_emsg) { if (flags & DOCMD_VERBOSE) { - if (errormsg != (char *)IObuff) { + if (errormsg != IObuff) { STRCPY(IObuff, errormsg); - errormsg = (char *)IObuff; + errormsg = IObuff; } append_command(*ea.cmdlinep); } @@ -2838,7 +2843,7 @@ theend: /// @param pp start of command /// @param cmd name of command /// @param len required length -bool checkforcmd(char **pp, char *cmd, int len) +bool checkforcmd(char **pp, const char *cmd, int len) { int i; @@ -2847,7 +2852,7 @@ bool checkforcmd(char **pp, char *cmd, int len) break; } } - if (i >= len && !isalpha((*pp)[i])) { + if (i >= len && !ASCII_ISALPHA((*pp)[i])) { *pp = skipwhite(*pp + i); return true; } @@ -2865,14 +2870,14 @@ static void append_command(char *cmd) if (len > IOSIZE - 100) { // Not enough space, truncate and put in "...". - d = (char *)IObuff + IOSIZE - 100; - d -= utf_head_off((char *)IObuff, d); + d = IObuff + IOSIZE - 100; + d -= utf_head_off(IObuff, d); STRCPY(d, "..."); } STRCAT(IObuff, ": "); - d = (char *)IObuff + STRLEN(IObuff); + d = IObuff + strlen(IObuff); while (*s != NUL && d - IObuff + 5 < IOSIZE) { - if ((char_u)s[0] == 0xc2 && (char_u)s[1] == 0xa0) { + if ((uint8_t)s[0] == 0xc2 && (uint8_t)s[1] == 0xa0) { s += 2; STRCPY(d, "<a0>"); d += 4; @@ -2885,6 +2890,33 @@ static void append_command(char *cmd) *d = NUL; } +/// Return true and set "*idx" if "p" points to a one letter command. +/// - The 'k' command can directly be followed by any character. +/// - The 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' +/// but :sre[wind] is another command, as are :scr[iptnames], +/// :scs[cope], :sim[alt], :sig[ns] and :sil[ent]. +static int one_letter_cmd(const char *p, cmdidx_T *idx) +{ + if (*p == 'k') { + *idx = CMD_k; + return true; + } + if (p[0] == 's' + && ((p[1] == 'c' + && (p[2] == NUL + || (p[2] != 's' && p[2] != 'r' + && (p[3] == NUL + || (p[3] != 'i' && p[4] != 'p'))))) + || p[1] == 'g' + || (p[1] == 'i' && p[2] != 'm' && p[2] != 'l' && p[2] != 'g') + || p[1] == 'I' + || (p[1] == 'r' && p[2] != 'e'))) { + *idx = CMD_substitute; + return true; + } + return false; +} + /// 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. @@ -2895,27 +2927,8 @@ char *find_ex_command(exarg_T *eap, int *full) FUNC_ATTR_NONNULL_ARG(1) { // Isolate the command and search for it in the command table. - // Exceptions: - // - the 'k' command can directly be followed by any character. - // - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' - // but :sre[wind] is another command, as are :scr[iptnames], - // :scs[cope], :sim[alt], :sig[ns] and :sil[ent]. - // - the "d" command can directly be followed by 'l' or 'p' flag. char *p = eap->cmd; - if (*p == 'k') { - eap->cmdidx = CMD_k; - p++; - } else if (p[0] == 's' - && ((p[1] == 'c' - && (p[2] == NUL - || (p[2] != 's' && p[2] != 'r' - && (p[3] == NUL - || (p[3] != 'i' && p[4] != 'p'))))) - || p[1] == 'g' - || (p[1] == 'i' && p[2] != 'm' && p[2] != 'l' && p[2] != 'g') - || p[1] == 'I' - || (p[1] == 'r' && p[2] != 'e'))) { - eap->cmdidx = CMD_substitute; + if (one_letter_cmd(p, &eap->cmdidx)) { p++; } else { while (ASCII_ISALPHA(*p)) { @@ -2929,10 +2942,11 @@ char *find_ex_command(exarg_T *eap, int *full) } // check for non-alpha command - if (p == eap->cmd && vim_strchr("@!=><&~#", *p) != NULL) { + if (p == eap->cmd && vim_strchr("@!=><&~#", (uint8_t)(*p)) != NULL) { p++; } int len = (int)(p - eap->cmd); + // The "d" command can directly be followed by 'l' or 'p' flag. if (*eap->cmd == 'd' && (p[-1] == 'l' || p[-1] == 'p')) { // Check for ":dl", ":dell", etc. to ":deletel": that's // :delete with the 'l' flag. Same for 'p'. @@ -2953,7 +2967,7 @@ char *find_ex_command(exarg_T *eap, int *full) } if (ASCII_ISLOWER(eap->cmd[0])) { - const int c1 = (char_u)eap->cmd[0]; + const int c1 = (uint8_t)eap->cmd[0]; const int c2 = len == 1 ? NUL : eap->cmd[1]; if (command_count != CMD_SIZE) { @@ -2976,7 +2990,7 @@ char *find_ex_command(exarg_T *eap, int *full) for (; (int)eap->cmdidx < CMD_SIZE; eap->cmdidx = (cmdidx_T)((int)eap->cmdidx + 1)) { - if (STRNCMP(cmdnames[(int)eap->cmdidx].cmd_name, eap->cmd, + if (strncmp(cmdnames[(int)eap->cmdidx].cmd_name, eap->cmd, (size_t)len) == 0) { if (full != NULL && cmdnames[(int)eap->cmdidx].cmd_name[len] == NUL) { @@ -3130,9 +3144,11 @@ cmdidx_T excmd_get_cmdidx(const char *cmd, size_t len) { cmdidx_T idx; - for (idx = (cmdidx_T)0; (int)idx < CMD_SIZE; idx = (cmdidx_T)((int)idx + 1)) { - if (strncmp(cmdnames[(int)idx].cmd_name, cmd, len) == 0) { - break; + if (!one_letter_cmd(cmd, &idx)) { + for (idx = (cmdidx_T)0; (int)idx < CMD_SIZE; idx = (cmdidx_T)((int)idx + 1)) { + if (strncmp(cmdnames[(int)idx].cmd_name, cmd, len) == 0) { + break; + } } } @@ -3156,7 +3172,7 @@ uint32_t excmd_get_argt(cmdidx_T idx) /// @return the "cmd" pointer advanced to beyond the range. char *skip_range(const char *cmd, int *ctx) { - while (vim_strchr(" \t0123456789.$%'/?-+,;\\", *cmd) != NULL) { + while (vim_strchr(" \t0123456789.$%'/?-+,;\\", (uint8_t)(*cmd)) != NULL) { if (*cmd == '\\') { if (cmd[1] == '?' || cmd[1] == '/' || cmd[1] == '&') { cmd++; @@ -3345,14 +3361,14 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int case '/': case '?': // '/' or '?' - search - c = (char_u)(*cmd++); + c = (uint8_t)(*cmd++); if (addr_type != ADDR_LINES) { addr_error(addr_type); cmd = NULL; goto error; } if (skip) { // skip "/pat/" - cmd = skip_regexp(cmd, c, p_magic, NULL); + cmd = skip_regexp(cmd, c, magic_isset()); if (*cmd == c) { cmd++; } @@ -3381,7 +3397,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int } searchcmdlen = 0; flags = silent ? 0 : SEARCH_HIS | SEARCH_MSG; - if (!do_search(NULL, c, c, (char_u *)cmd, 1L, flags, NULL)) { + if (!do_search(NULL, c, c, cmd, 1L, flags, NULL)) { curwin->w_cursor = pos; cmd = NULL; goto error; @@ -3418,7 +3434,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int pos.coladd = 0; if (searchit(curwin, curbuf, &pos, NULL, *cmd == '?' ? BACKWARD : FORWARD, - (char_u *)"", 1L, SEARCH_MSG, i, NULL) != FAIL) { + "", 1L, SEARCH_MSG, i, NULL) != FAIL) { lnum = pos.lnum; } else { cmd = NULL; @@ -3479,11 +3495,12 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int if (ascii_isdigit(*cmd)) { i = '+'; // "number" is same as "+number" } else { - i = (char_u)(*cmd++); + i = (uint8_t)(*cmd++); } - if (!ascii_isdigit(*cmd)) { // '+' is '+1', but '+0' is not '+1' + if (!ascii_isdigit(*cmd)) { // '+' is '+1' n = 1; } else { + // "number", "+number" or "-number" n = getdigits_int32(&cmd, false, MAXLNUM); if (n == MAXLNUM) { emsg(_(e_line_number_out_of_range)); @@ -3498,8 +3515,8 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int } else if (addr_type == ADDR_LOADED_BUFFERS || addr_type == ADDR_BUFFERS) { lnum = compute_buffer_local_count(addr_type, lnum, (i == '-') ? -1 * n : n); } else { - // Relative line addressing, need to adjust for folded lines - // now, but only do it after the first address. + // Relative line addressing: need to adjust for lines in a + // closed fold after the first address. if (addr_type == ADDR_LINES && (i == '-' || i == '+') && address_count >= 2) { (void)hasFolding(lnum, NULL, &lnum); @@ -3525,7 +3542,7 @@ error: /// Get flags from an Ex command argument. static void get_flags(exarg_T *eap) { - while (vim_strchr("lp#", *eap->arg) != NULL) { + while (vim_strchr("lp#", (uint8_t)(*eap->arg)) != NULL) { if (*eap->arg == 'l') { eap->flags |= EXFLAG_LIST; } else if (*eap->arg == 'p') { @@ -3693,7 +3710,7 @@ char *replace_makeprg(exarg_T *eap, char *arg, char **cmdlinep) if ((eap->cmdidx == CMD_make || eap->cmdidx == CMD_lmake || isgrep) && !grep_internal(eap->cmdidx)) { const char *program = isgrep ? (*curbuf->b_p_gp == NUL ? p_gp : curbuf->b_p_gp) - : (*curbuf->b_p_mp == NUL ? (char *)p_mp : curbuf->b_p_mp); + : (*curbuf->b_p_mp == NUL ? p_mp : curbuf->b_p_mp); arg = skipwhite(arg); @@ -3742,7 +3759,7 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) } // Quick check if this cannot be the start of a special string. // Also removes backslash before '%', '#' and '<'. - if (vim_strchr("%#<", *p) == NULL) { + if (vim_strchr("%#<", (uint8_t)(*p)) == NULL) { p++; continue; } @@ -3750,8 +3767,8 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) // Try to find a match at this position. size_t srclen; int escaped; - char *repl = (char *)eval_vars((char_u *)p, (char_u *)eap->arg, &srclen, &(eap->do_ecmd_lnum), - errormsgp, &escaped, true); + char *repl = eval_vars(p, eap->arg, &srclen, &(eap->do_ecmd_lnum), + errormsgp, &escaped, true); if (*errormsgp != NULL) { // error detected return FAIL; } @@ -3778,7 +3795,6 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) && eap->cmdidx != CMD_bang && eap->cmdidx != CMD_grep && eap->cmdidx != CMD_grepadd - && eap->cmdidx != CMD_hardcopy && eap->cmdidx != CMD_lgrep && eap->cmdidx != CMD_lgrepadd && eap->cmdidx != CMD_lmake @@ -3796,8 +3812,8 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) #endif for (l = repl; *l; l++) { - if (vim_strchr((char *)ESCAPE_CHARS, *l) != NULL) { - l = (char *)vim_strsave_escaped((char_u *)repl, ESCAPE_CHARS); + if (vim_strchr(ESCAPE_CHARS, (uint8_t)(*l)) != NULL) { + l = vim_strsave_escaped(repl, ESCAPE_CHARS); xfree(repl); repl = l; break; @@ -3812,7 +3828,7 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) && strpbrk(repl, "!") != NULL) { char *l; - l = (char *)vim_strsave_escaped((char_u *)repl, (char_u *)"!"); + l = vim_strsave_escaped(repl, "!"); xfree(repl); repl = l; } @@ -3833,9 +3849,9 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) // if there are still wildcards present. if (vim_strchr(eap->arg, '$') != NULL || vim_strchr(eap->arg, '~') != NULL) { - expand_env_esc((char_u *)eap->arg, (char_u *)NameBuff, MAXPATHL, true, true, NULL); + expand_env_esc(eap->arg, NameBuff, MAXPATHL, true, true, NULL); has_wildcards = path_has_wildcard(NameBuff); - p = (char *)NameBuff; + p = NameBuff; } else { p = NULL; } @@ -3852,7 +3868,7 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) backslash_halve(eap->arg); } #else - backslash_halve((char_u *)eap->arg); + backslash_halve(eap->arg); #endif if (has_wildcards) { @@ -3982,7 +3998,7 @@ void separate_nextcmd(exarg_T *eap) } if (!(eap->argt & EX_NOTRLCOM)) { // remove trailing spaces - del_trailing_spaces((char_u *)eap->arg); + del_trailing_spaces(eap->arg); } } @@ -4028,15 +4044,15 @@ char *skip_cmd_arg(char *p, int rembs) return p; } -int get_bad_opt(const char_u *p, exarg_T *eap) +int get_bad_opt(const char *p, exarg_T *eap) FUNC_ATTR_NONNULL_ALL { if (STRICMP(p, "keep") == 0) { eap->bad_char = BAD_KEEP; } else if (STRICMP(p, "drop") == 0) { eap->bad_char = BAD_DROP; - } else if (MB_BYTE2LEN(*p) == 1 && p[1] == NUL) { - eap->bad_char = *p; + } else if (MB_BYTE2LEN((uint8_t)(*p)) == 1 && p[1] == NUL) { + eap->bad_char = (uint8_t)(*p); } else { return FAIL; } @@ -4053,7 +4069,7 @@ static int getargopt(exarg_T *eap) int bad_char_idx; // ":edit ++[no]bin[ary] file" - if (STRNCMP(arg, "bin", 3) == 0 || STRNCMP(arg, "nobin", 5) == 0) { + if (strncmp(arg, "bin", 3) == 0 || strncmp(arg, "nobin", 5) == 0) { if (*arg == 'n') { arg += 2; eap->force_bin = FORCE_NOBIN; @@ -4068,26 +4084,33 @@ static int getargopt(exarg_T *eap) } // ":read ++edit file" - if (STRNCMP(arg, "edit", 4) == 0) { + if (strncmp(arg, "edit", 4) == 0) { eap->read_edit = true; eap->arg = skipwhite(arg + 4); return OK; } - if (STRNCMP(arg, "ff", 2) == 0) { + // ":write ++p foo/bar/file + if (strncmp(arg, "p", 1) == 0) { + eap->mkdir_p = true; + eap->arg = skipwhite(arg + 1); + return OK; + } + + if (strncmp(arg, "ff", 2) == 0) { arg += 2; pp = &eap->force_ff; - } else if (STRNCMP(arg, "fileformat", 10) == 0) { + } else if (strncmp(arg, "fileformat", 10) == 0) { arg += 10; pp = &eap->force_ff; - } else if (STRNCMP(arg, "enc", 3) == 0) { - if (STRNCMP(arg, "encoding", 8) == 0) { + } else if (strncmp(arg, "enc", 3) == 0) { + if (strncmp(arg, "encoding", 8) == 0) { arg += 8; } else { arg += 3; } pp = &eap->force_enc; - } else if (STRNCMP(arg, "bad", 3) == 0) { + } else if (strncmp(arg, "bad", 3) == 0) { arg += 3; pp = &bad_char_idx; } @@ -4106,7 +4129,7 @@ static int getargopt(exarg_T *eap) if (check_ff_value(eap->cmd + eap->force_ff) == FAIL) { return FAIL; } - eap->force_ff = (char_u)eap->cmd[eap->force_ff]; + eap->force_ff = (uint8_t)eap->cmd[eap->force_ff]; } else if (pp == &eap->force_enc) { // Make 'fileencoding' lower case. for (char *p = eap->cmd + eap->force_enc; *p != NUL; p++) { @@ -4115,7 +4138,7 @@ static int getargopt(exarg_T *eap) } else { // Check ++bad= argument. Must be a single-byte character, "keep" or // "drop". - if (get_bad_opt((char_u *)eap->cmd + bad_char_idx, eap) == FAIL) { + if (get_bad_opt(eap->cmd + bad_char_idx, eap) == FAIL) { return FAIL; } } @@ -4218,13 +4241,12 @@ theend: static void ex_autocmd(exarg_T *eap) { - // Disallow autocommands from .exrc and .vimrc in current - // directory for security reasons. + // Disallow autocommands in secure mode. if (secure) { secure = 2; eap->errmsg = _(e_curdir); } else if (eap->cmdidx == CMD_autocmd) { - do_autocmd(eap->arg, eap->forceit); + do_autocmd(eap, eap->arg, eap->forceit); } else { do_augroup(eap->arg, eap->forceit); } @@ -4357,9 +4379,8 @@ char *check_nextcmd(char *p) if (*s == '|' || *s == '\n') { return s + 1; - } else { - return NULL; } + return NULL; } /// - if there are more files to edit @@ -4382,14 +4403,14 @@ static int check_more(int message, bool forceit) vim_snprintf((char *)buff, DIALOG_MSG_SIZE, NGETTEXT("%d more file to edit. Quit anyway?", - "%d more files to edit. Quit anyway?", (unsigned long)n), n); + "%d more files to edit. Quit anyway?", n), n); if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 1) == VIM_YES) { return OK; } return FAIL; } semsg(NGETTEXT("E173: %" PRId64 " more file to edit", - "E173: %" PRId64 " more files to edit", (unsigned long)n), (int64_t)n); + "E173: %" PRId64 " more files to edit", n), (int64_t)n); quitmore = 2; // next try to quit is allowed } return FAIL; @@ -4422,7 +4443,7 @@ static void ex_colorscheme(exarg_T *eap) } else { msg("default"); } - } else if (load_colors((char_u *)eap->arg) == FAIL) { + } else if (load_colors(eap->arg) == FAIL) { semsg(_("E185: Cannot find color scheme '%s'"), eap->arg); } } @@ -4617,7 +4638,7 @@ static void ex_pclose(exarg_T *eap) void ex_win_close(int forceit, win_T *win, tabpage_T *tp) { // Never close the autocommand window. - if (win == aucmd_win) { + if (is_aucmd_win(win)) { emsg(_(e_autocmd_close)); return; } @@ -4654,23 +4675,29 @@ static void ex_tabclose(exarg_T *eap) { if (cmdwin_type != 0) { cmdwin_result = K_IGNORE; - } else if (first_tabpage->tp_next == NULL) { + return; + } + + if (first_tabpage->tp_next == NULL) { emsg(_("E784: Cannot close last tab page")); - } else { - int tab_number = get_tabpage_arg(eap); - if (eap->errmsg == NULL) { - tabpage_T *tp = find_tabpage(tab_number); - if (tp == NULL) { - beep_flush(); - return; - } - if (tp != curtab) { - tabpage_close_other(tp, eap->forceit); - return; - } else if (!text_locked() && !curbuf_locked()) { - tabpage_close(eap->forceit); - } - } + return; + } + + int tab_number = get_tabpage_arg(eap); + if (eap->errmsg != NULL) { + return; + } + + tabpage_T *tp = find_tabpage(tab_number); + if (tp == NULL) { + beep_flush(); + return; + } + if (tp != curtab) { + tabpage_close_other(tp, eap->forceit); + return; + } else if (!text_locked() && !curbuf_locked()) { + tabpage_close(eap->forceit); } } @@ -4679,32 +4706,38 @@ static void ex_tabonly(exarg_T *eap) { if (cmdwin_type != 0) { cmdwin_result = K_IGNORE; - } else if (first_tabpage->tp_next == NULL) { + return; + } + + if (first_tabpage->tp_next == NULL) { msg(_("Already only one tab page")); - } else { - int tab_number = get_tabpage_arg(eap); - if (eap->errmsg == NULL) { - goto_tabpage(tab_number); - // Repeat this up to a 1000 times, because autocommands may - // mess up the lists. - for (int done = 0; done < 1000; done++) { - FOR_ALL_TABS(tp) { - if (tp->tp_topframe != topframe) { - tabpage_close_other(tp, eap->forceit); - // if we failed to close it quit - if (valid_tabpage(tp)) { - done = 1000; - } - // start over, "tp" is now invalid - break; - } - } - assert(first_tabpage); - if (first_tabpage->tp_next == NULL) { - break; + return; + } + + int tab_number = get_tabpage_arg(eap); + if (eap->errmsg != NULL) { + return; + } + + goto_tabpage(tab_number); + // Repeat this up to a 1000 times, because autocommands may + // mess up the lists. + for (int done = 0; done < 1000; done++) { + FOR_ALL_TABS(tp) { + if (tp->tp_topframe != topframe) { + tabpage_close_other(tp, eap->forceit); + // if we failed to close it quit + if (valid_tabpage(tp)) { + done = 1000; } + // start over, "tp" is now invalid + break; } } + assert(first_tabpage); + if (first_tabpage->tp_next == NULL) { + break; + } } } @@ -4758,9 +4791,8 @@ static void ex_only(exarg_T *eap) for (wp = firstwin; --wnr > 0;) { if (wp->w_next == NULL) { break; - } else { - wp = wp->w_next; } + wp = wp->w_next; } } else { wp = curwin; @@ -4774,25 +4806,27 @@ static void ex_only(exarg_T *eap) 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, eap->forceit); // don't free buffer - } else { - int winnr = 0; - win_T *win = NULL; + if (eap->skip) { + return; + } - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - winnr++; - if (winnr == eap->line2) { - win = wp; - break; - } - } - if (win == NULL) { - win = lastwin; + if (eap->addr_count == 0) { + win_close(curwin, false, eap->forceit); // don't free buffer + } else { + int winnr = 0; + win_T *win = NULL; + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + winnr++; + if (winnr == eap->line2) { + win = wp; + break; } - win_close(win, false, eap->forceit); } + if (win == NULL) { + win = lastwin; + } + win_close(win, false, eap->forceit); } } @@ -4942,8 +4976,8 @@ void ex_splitview(exarg_T *eap) } if (eap->cmdidx == CMD_sfind || eap->cmdidx == CMD_tabfind) { - fname = (char *)find_file_in_path((char_u *)eap->arg, strlen(eap->arg), - FNAME_MESS, true, (char_u *)curbuf->b_ffname); + fname = find_file_in_path(eap->arg, strlen(eap->arg), + FNAME_MESS, true, curbuf->b_ffname); if (fname == NULL) { goto theend; } @@ -4953,7 +4987,7 @@ void ex_splitview(exarg_T *eap) // Either open new tab page or split the window. if (use_tab) { if (win_new_tabpage(cmdmod.cmod_tab != 0 ? cmdmod.cmod_tab : eap->addr_count == 0 - ? 0 : (int)eap->line2 + 1, (char_u *)eap->arg) != FAIL) { + ? 0 : (int)eap->line2 + 1, eap->arg) != FAIL) { do_exedit(eap, old_curwin); apply_autocmds(EVENT_TABNEWENTERED, NULL, NULL, false, curbuf); @@ -5066,8 +5100,8 @@ static void ex_tabs(exarg_T *eap) } msg_putchar('\n'); - vim_snprintf((char *)IObuff, IOSIZE, _("Tab page %d"), tabcount++); - msg_outtrans_attr((char *)IObuff, HL_ATTR(HLF_T)); + vim_snprintf(IObuff, IOSIZE, _("Tab page %d"), tabcount++); + msg_outtrans_attr(IObuff, HL_ATTR(HLF_T)); os_breakcheck(); FOR_ALL_WINDOWS_IN_TAB(wp, tp) { @@ -5081,11 +5115,11 @@ static void ex_tabs(exarg_T *eap) msg_putchar(bufIsChanged(wp->w_buffer) ? '+' : ' '); msg_putchar(' '); if (buf_spname(wp->w_buffer) != NULL) { - STRLCPY(IObuff, buf_spname(wp->w_buffer), IOSIZE); + xstrlcpy(IObuff, buf_spname(wp->w_buffer), IOSIZE); } else { - home_replace(wp->w_buffer, wp->w_buffer->b_fname, (char *)IObuff, IOSIZE, true); + home_replace(wp->w_buffer, wp->w_buffer->b_fname, IObuff, IOSIZE, true); } - msg_outtrans((char *)IObuff); + msg_outtrans(IObuff); os_breakcheck(); } } @@ -5135,23 +5169,25 @@ static void ex_resize(exarg_T *eap) /// ":find [+command] <file>" command. static void ex_find(exarg_T *eap) { - char *fname = (char *)find_file_in_path((char_u *)eap->arg, strlen(eap->arg), - FNAME_MESS, true, (char_u *)curbuf->b_ffname); + char *fname = find_file_in_path(eap->arg, strlen(eap->arg), + FNAME_MESS, true, curbuf->b_ffname); if (eap->addr_count > 0) { // Repeat finding the file "count" times. This matters when it // appears several times in the path. linenr_T count = eap->line2; while (fname != NULL && --count > 0) { xfree(fname); - fname = (char *)find_file_in_path(NULL, 0, FNAME_MESS, false, (char_u *)curbuf->b_ffname); + fname = find_file_in_path(NULL, 0, FNAME_MESS, false, curbuf->b_ffname); } } - if (fname != NULL) { - eap->arg = fname; - do_exedit(eap, NULL); - xfree(fname); + if (fname == NULL) { + return; } + + eap->arg = fname; + do_exedit(eap, NULL); + xfree(fname); } /// ":edit", ":badd", ":balt", ":visual". @@ -5213,9 +5249,9 @@ void do_exedit(exarg_T *eap, win_T *old_curwin) old_curwin == NULL ? curwin : NULL); } else if ((eap->cmdidx != CMD_split && eap->cmdidx != CMD_vsplit) || *eap->arg != NUL) { - // Can't edit another file when "curbuf->b_ro_lockec" is set. Only ":edit" - // can bring us here, others are stopped earlier. - if (*eap->arg != NUL && curbuf_locked()) { + // Can't edit another file when "textlock" or "curbuf->b_ro_locked" is set. + // Only ":edit" or ":script" can bring us here, others are stopped earlier. + if (*eap->arg != NUL && text_or_buf_locked()) { return; } n = readonlymode; @@ -5376,50 +5412,51 @@ static void ex_read(exarg_T *eap) if (eap->usefilter) { // :r!cmd do_bang(1, eap, false, false, true); - } else { - if (u_save(eap->line2, (linenr_T)(eap->line2 + 1)) == FAIL) { + return; + } + + if (u_save(eap->line2, (linenr_T)(eap->line2 + 1)) == FAIL) { + return; + } + + int i; + if (*eap->arg == NUL) { + if (check_fname() == FAIL) { // check for no file name return; } - int i; - - if (*eap->arg == NUL) { - if (check_fname() == FAIL) { // check for no file name - return; - } - i = readfile(curbuf->b_ffname, curbuf->b_fname, - eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0, false); - } else { - if (vim_strchr(p_cpo, CPO_ALTREAD) != NULL) { - (void)setaltfname(eap->arg, eap->arg, (linenr_T)1); - } - i = readfile(eap->arg, NULL, - eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0, false); + i = readfile(curbuf->b_ffname, curbuf->b_fname, + eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0, false); + } else { + if (vim_strchr(p_cpo, CPO_ALTREAD) != NULL) { + (void)setaltfname(eap->arg, eap->arg, (linenr_T)1); + } + i = readfile(eap->arg, NULL, + eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0, false); + } + if (i != OK) { + if (!aborting()) { + semsg(_(e_notopen), eap->arg); } - if (i != OK) { - if (!aborting()) { - semsg(_(e_notopen), eap->arg); + } else { + if (empty && exmode_active) { + // Delete the empty line that remains. Historically ex does + // this but vi doesn't. + linenr_T lnum; + if (eap->line2 == 0) { + lnum = curbuf->b_ml.ml_line_count; + } else { + lnum = 1; } - } else { - if (empty && exmode_active) { - // Delete the empty line that remains. Historically ex does - // this but vi doesn't. - linenr_T lnum; - if (eap->line2 == 0) { - lnum = curbuf->b_ml.ml_line_count; - } else { - lnum = 1; - } - if (*ml_get(lnum) == NUL && u_savedel(lnum, 1L) == OK) { - ml_delete(lnum, false); - if (curwin->w_cursor.lnum > 1 - && curwin->w_cursor.lnum >= lnum) { - curwin->w_cursor.lnum--; - } - deleted_lines_mark(lnum, 1L); + if (*ml_get(lnum) == NUL && u_savedel(lnum, 1L) == OK) { + ml_delete(lnum, false); + if (curwin->w_cursor.lnum > 1 + && curwin->w_cursor.lnum >= lnum) { + curwin->w_cursor.lnum--; } + deleted_lines_mark(lnum, 1L); } - redraw_curbuf_later(UPD_VALID); } + redraw_curbuf_later(UPD_VALID); } } @@ -5471,7 +5508,7 @@ static void post_chdir(CdScope scope, bool trigger_dirchanged) } char cwd[MAXPATHL]; - if (os_dirname((char_u *)cwd, MAXPATHL) != OK) { + if (os_dirname(cwd, MAXPATHL) != OK) { return; } switch (scope) { @@ -5518,7 +5555,7 @@ bool changedir_func(char *new_dir, CdScope scope) new_dir = pdir; } - if (os_dirname((char_u *)NameBuff, MAXPATHL) == OK) { + if (os_dirname(NameBuff, MAXPATHL) == OK) { pdir = xstrdup(NameBuff); } else { pdir = NULL; @@ -5533,13 +5570,13 @@ bool changedir_func(char *new_dir, CdScope scope) #endif // Use NameBuff for home directory name. expand_env("$HOME", NameBuff, MAXPATHL); - new_dir = (char *)NameBuff; + new_dir = NameBuff; } bool dir_differs = pdir == NULL || pathcmp(pdir, new_dir, -1) != 0; if (dir_differs) { do_autocmd_dirchanged(new_dir, scope, kCdCauseManual, true); - if (vim_chdir((char_u *)new_dir) != 0) { + if (vim_chdir(new_dir) != 0) { emsg(_(e_failed)); xfree(pdir); return false; @@ -5576,6 +5613,7 @@ void ex_cd(exarg_T *eap) return; } #endif + CdScope scope = kCdScopeGlobal; switch (eap->cmdidx) { case CMD_tcd: @@ -5600,7 +5638,7 @@ void ex_cd(exarg_T *eap) /// ":pwd". static void ex_pwd(exarg_T *eap) { - if (os_dirname((char_u *)NameBuff, MAXPATHL) == OK) { + if (os_dirname(NameBuff, MAXPATHL) == OK) { #ifdef BACKSLASH_IN_FILENAME slash_adjust(NameBuff); #endif @@ -5613,9 +5651,9 @@ static void ex_pwd(exarg_T *eap) } else if (curtab->tp_localdir != NULL) { context = "tabpage"; } - smsg("[%s] %s", context, (char *)NameBuff); + smsg("[%s] %s", context, NameBuff); } else { - msg((char *)NameBuff); + msg(NameBuff); } } else { emsg(_("E187: Unknown")); @@ -5650,15 +5688,11 @@ 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 return early on CTRL-C. void do_sleep(long msec) { ui_flush(); // flush before waiting - for (long left = msec; !got_int && left > 0; left -= 1000L) { - int next = left > 1000l ? 1000 : (int)left; - LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, (int)next, got_int); - os_breakcheck(); - } + LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, msec, got_int); // If CTRL-C was typed to interrupt the sleep, drop the CTRL-C from the // input buffer, otherwise a following call to input() fails. @@ -5820,21 +5854,21 @@ void ex_may_print(exarg_T *eap) /// ":smagic" and ":snomagic". static void ex_submagic(exarg_T *eap) { - int magic_save = p_magic; + const optmagic_T saved = magic_overruled; - p_magic = (eap->cmdidx == CMD_smagic); + magic_overruled = eap->cmdidx == CMD_smagic ? OPTION_MAGIC_ON : OPTION_MAGIC_OFF; ex_substitute(eap); - p_magic = magic_save; + magic_overruled = saved; } /// ":smagic" and ":snomagic" preview callback. static int ex_submagic_preview(exarg_T *eap, long cmdpreview_ns, handle_T cmdpreview_bufnr) { - int magic_save = p_magic; + const optmagic_T saved = magic_overruled; - p_magic = (eap->cmdidx == CMD_smagic); + magic_overruled = eap->cmdidx == CMD_smagic ? OPTION_MAGIC_ON : OPTION_MAGIC_OFF; int retv = ex_substitute_preview(eap, cmdpreview_ns, cmdpreview_bufnr); - p_magic = magic_save; + magic_overruled = saved; return retv; } @@ -5875,20 +5909,21 @@ static void ex_at(exarg_T *eap) // Put the register in the typeahead buffer with the "silent" flag. if (do_execreg(c, true, vim_strchr(p_cpo, CPO_EXECBUF) != NULL, true) == FAIL) { beep_flush(); - } else { - bool save_efr = exec_from_reg; + return; + } - exec_from_reg = true; + const bool save_efr = exec_from_reg; - // Execute from the typeahead buffer. - // Continue until the stuff buffer is empty and all added characters - // have been consumed. - while (!stuff_empty() || typebuf.tb_len > prev_len) { - (void)do_cmdline(NULL, getexline, NULL, DOCMD_NOWAIT|DOCMD_VERBOSE); - } + exec_from_reg = true; - exec_from_reg = save_efr; + // Execute from the typeahead buffer. + // Continue until the stuff buffer is empty and all added characters + // have been consumed. + while (!stuff_empty() || typebuf.tb_len > prev_len) { + (void)do_cmdline(NULL, getexline, NULL, DOCMD_NOWAIT|DOCMD_VERBOSE); } + + exec_from_reg = save_efr; } /// ":!". @@ -5937,18 +5972,18 @@ static void ex_undo(exarg_T *eap) static void ex_wundo(exarg_T *eap) { - char hash[UNDO_HASH_SIZE]; + uint8_t hash[UNDO_HASH_SIZE]; - u_compute_hash(curbuf, (char_u *)hash); - u_write_undo(eap->arg, eap->forceit, curbuf, (char_u *)hash); + u_compute_hash(curbuf, hash); + u_write_undo(eap->arg, eap->forceit, curbuf, hash); } static void ex_rundo(exarg_T *eap) { - char hash[UNDO_HASH_SIZE]; + uint8_t hash[UNDO_HASH_SIZE]; - u_compute_hash(curbuf, (char_u *)hash); - u_read_undo(eap->arg, (char_u *)hash, NULL); + u_compute_hash(curbuf, hash); + u_read_undo(eap->arg, hash, NULL); } /// ":redo". @@ -5967,7 +6002,7 @@ static void ex_later(exarg_T *eap) if (*p == NUL) { count = 1; - } else if (isdigit(*p)) { + } else if (isdigit((uint8_t)(*p))) { count = getdigits_long(&p, false, 0); switch (*p) { case 's': @@ -6018,14 +6053,14 @@ static void ex_redir(exarg_T *eap) return; } - redir_fd = open_exfile((char_u *)fname, eap->forceit, mode); + redir_fd = open_exfile(fname, eap->forceit, mode); xfree(fname); } else if (*arg == '@') { // redirect to a register a-z (resp. A-Z for appending) close_redir(); arg++; if (valid_yank_reg(*arg, true) && *arg != '_') { - redir_reg = (char_u)(*arg++); + redir_reg = (uint8_t)(*arg++); if (*arg == '>' && arg[1] == '>') { // append arg += 2; } else { @@ -6191,22 +6226,22 @@ int vim_mkdir_emsg(const char *const name, const int prot) /// @param mode "w" for create new file or "a" for append /// /// @return file descriptor, or NULL on failure. -FILE *open_exfile(char_u *fname, int forceit, char *mode) +FILE *open_exfile(char *fname, int forceit, char *mode) { #ifdef UNIX // with Unix it is possible to open a directory - if (os_isdir((char *)fname)) { + if (os_isdir(fname)) { semsg(_(e_isadir2), fname); return NULL; } #endif - if (!forceit && *mode != 'a' && os_path_exists((char *)fname)) { + if (!forceit && *mode != 'a' && os_path_exists(fname)) { semsg(_("E189: \"%s\" exists (add ! to override)"), fname); return NULL; } FILE *fd; - if ((fd = os_fopen((char *)fname, mode)) == NULL) { + if ((fd = os_fopen(fname, mode)) == NULL) { semsg(_("E190: Cannot open \"%s\" for writing"), fname); } @@ -6218,17 +6253,21 @@ static void ex_mark(exarg_T *eap) { if (*eap->arg == NUL) { // No argument? emsg(_(e_argreq)); - } else if (eap->arg[1] != NUL) { // more than one character? + return; + } + + if (eap->arg[1] != NUL) { // more than one character? semsg(_(e_trailing_arg), eap->arg); - } else { - pos_T pos = curwin->w_cursor; // save curwin->w_cursor - curwin->w_cursor.lnum = eap->line2; - beginline(BL_WHITE | BL_FIX); - if (setmark(*eap->arg) == FAIL) { // set mark - emsg(_("E191: Argument must be a letter or forward/backward quote")); - } - curwin->w_cursor = pos; // restore curwin->w_cursor + return; } + + pos_T pos = curwin->w_cursor; // save curwin->w_cursor + curwin->w_cursor.lnum = eap->line2; + beginline(BL_WHITE | BL_FIX); + if (setmark(*eap->arg) == FAIL) { // set mark + emsg(_("E191: Argument must be a letter or forward/backward quote")); + } + curwin->w_cursor = pos; // restore curwin->w_cursor } /// Update w_topline, w_leftcol and the cursor position. @@ -6359,7 +6398,7 @@ static void ex_normal(exarg_T *eap) check_cursor_moved(curwin); } - exec_normal_cmd((char_u *)(arg != NULL ? arg : eap->arg), + exec_normal_cmd((arg != NULL ? arg : eap->arg), eap->forceit ? REMAP_NONE : REMAP_YES, false); } while (eap->addr_count > 0 && eap->line1 <= eap->line2 && !got_int); } @@ -6423,10 +6462,10 @@ static void ex_stopinsert(exarg_T *eap) /// Execute normal mode command "cmd". /// "remap" can be REMAP_NONE or REMAP_YES. -void exec_normal_cmd(char_u *cmd, int remap, bool silent) +void exec_normal_cmd(char *cmd, int remap, bool silent) { // Stuff the argument into the typeahead buffer. - ins_typebuf((char *)cmd, remap, 0, true, silent); + ins_typebuf(cmd, remap, 0, true, silent); exec_normal(false); } @@ -6495,7 +6534,7 @@ static void ex_findpat(exarg_T *eap) if (*eap->arg == '/') { // Match regexp, not just whole words whole = false; eap->arg++; - char *p = skip_regexp(eap->arg, '/', p_magic, NULL); + char *p = skip_regexp(eap->arg, '/', magic_isset()); if (*p) { *p++ = NUL; p = skipwhite(p); @@ -6509,7 +6548,7 @@ static void ex_findpat(exarg_T *eap) } } if (!eap->skip) { - find_pattern_in_path((char_u *)eap->arg, 0, strlen(eap->arg), whole, !eap->forceit, + find_pattern_in_path(eap->arg, 0, strlen(eap->arg), whole, !eap->forceit, *eap->cmd == 'd' ? FIND_DEFINE : FIND_ANY, n, action, eap->line1, eap->line2); } @@ -6560,7 +6599,7 @@ static void ex_tag(exarg_T *eap) ex_tag_cmd(eap, cmdnames[eap->cmdidx].cmd_name); } -static void ex_tag_cmd(exarg_T *eap, char *name) +static void ex_tag_cmd(exarg_T *eap, const char *name) { int cmd; @@ -6589,10 +6628,6 @@ static void ex_tag_cmd(exarg_T *eap, char *name) cmd = DT_LAST; // ":tlast" break; default: // ":tag" - if (p_cst && *eap->arg != NUL) { - ex_cstag(eap); - return; - } cmd = DT_TAG; break; } @@ -6627,7 +6662,7 @@ enum { /// 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) +ssize_t find_cmdline_var(const char *src, size_t *usedlen) FUNC_ATTR_NONNULL_ALL { static char *(spec_str[]) = { @@ -6651,7 +6686,7 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) for (size_t i = 0; i < ARRAY_SIZE(spec_str); i++) { size_t len = strlen(spec_str[i]); - if (STRNCMP(src, spec_str[i], len) == 0) { + if (strncmp(src, spec_str[i], len) == 0) { *usedlen = len; assert(i <= SSIZE_MAX); return (ssize_t)i; @@ -6690,8 +6725,8 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) /// @return an allocated string if a valid match was found. /// Returns NULL if no match was found. "usedlen" then still contains the /// number of characters to skip. -char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnump, char **errormsg, - int *escaped, bool empty_is_error) +char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnump, char **errormsg, + int *escaped, bool empty_is_error) { char *result; char *resultbuf = NULL; @@ -6717,7 +6752,7 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum // Note: In "\\%" the % is also not recognized! if (src > srcstart && src[-1] == '\\') { *usedlen = 0; - STRMOVE(src - 1, src); // remove backslash + STRMOVE(src - 1, (char *)src); // remove backslash return NULL; } @@ -6765,16 +6800,16 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum skip_mod = true; break; } - char *s = (char *)src + 1; + char *s = src + 1; if (*s == '<') { // "#<99" uses v:oldfiles. s++; } int i = getdigits_int(&s, false, 0); - if ((char_u *)s == src + 2 && src[1] == '-') { + if (s == src + 2 && src[1] == '-') { // just a minus sign, don't skip over it s--; } - *usedlen = (size_t)((char_u *)s - src); // length of what we expand + *usedlen = (size_t)(s - src); // length of what we expand if (src[1] == '<' && i != 0) { if (*usedlen < 2) { @@ -6810,7 +6845,7 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum break; case SPEC_CFILE: // file name under cursor - result = (char *)file_name_at_cursor(FNAME_MESS|FNAME_HYP, 1L, NULL); + result = file_name_at_cursor(FNAME_MESS|FNAME_HYP, 1L, NULL); if (result == NULL) { *errormsg = ""; return NULL; @@ -6820,14 +6855,14 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum case SPEC_AFILE: // file name for autocommand if (autocmd_fname != NULL - && !path_is_absolute((char_u *)autocmd_fname) + && !path_is_absolute(autocmd_fname) // For CmdlineEnter and related events, <afile> is not a path! #9348 && !strequal("/", autocmd_fname)) { // Still need to turn the fname into a full path. It was // postponed to avoid a delay when <afile> is not used. result = FullName_save(autocmd_fname, false); // Copy into `autocmd_fname`, don't reassign it. #8165 - STRLCPY(autocmd_fname, result, MAXPATHL); + xstrlcpy(autocmd_fname, result, MAXPATHL); xfree(result); } result = autocmd_fname; @@ -6835,7 +6870,7 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum *errormsg = _("E495: no autocommand file name to substitute for \"<afile>\""); return NULL; } - result = (char *)path_try_shorten_fname((char_u *)result); + result = path_try_shorten_fname(result); break; case SPEC_ABUF: // buffer number for autocommand @@ -6927,7 +6962,7 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum resultlen = (size_t)(s - result); } } else if (!skip_mod) { - valid |= modify_fname((char *)src, tilde_file, usedlen, &result, + valid |= modify_fname(src, tilde_file, usedlen, &result, &resultbuf, &resultlen); if (result == NULL) { *errormsg = ""; @@ -6950,7 +6985,7 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum result = xstrnsave(result, resultlen); } xfree(resultbuf); - return (char_u *)result; + return result; } /// Expand the <sfile> string in "arg". @@ -6961,14 +6996,13 @@ char *expand_sfile(char *arg) char *result = xstrdup(arg); for (char *p = result; *p;) { - if (STRNCMP(p, "<sfile>", 7) != 0) { + if (strncmp(p, "<sfile>", 7) != 0) { p++; } else { // replace "<sfile>" with the sourced file name, and do ":" stuff size_t srclen; char *errormsg; - char *repl = (char *)eval_vars((char_u *)p, (char_u *)result, &srclen, NULL, &errormsg, NULL, - true); + char *repl = eval_vars(p, result, &srclen, NULL, &errormsg, NULL, true); if (errormsg != NULL) { if (*errormsg) { emsg(errormsg); @@ -7067,12 +7101,12 @@ static void ex_filetype(exarg_T *eap) // Accept "plugin" and "indent" in any order. for (;;) { - if (STRNCMP(arg, "plugin", 6) == 0) { + if (strncmp(arg, "plugin", 6) == 0) { plugin = true; arg = skipwhite(arg + 6); continue; } - if (STRNCMP(arg, "indent", 6) == 0) { + if (strncmp(arg, "indent", 6) == 0) { indent = true; arg = skipwhite(arg + 6); continue; @@ -7146,17 +7180,18 @@ void filetype_maybe_enable(void) /// ":setfiletype [FALLBACK] {name}" static void ex_setfiletype(exarg_T *eap) { - if (!did_filetype) { - char *arg = eap->arg; + if (did_filetype) { + return; + } - if (STRNCMP(arg, "FALLBACK ", 9) == 0) { - arg += 9; - } + char *arg = eap->arg; + if (strncmp(arg, "FALLBACK ", 9) == 0) { + arg += 9; + } - set_option_value_give_err("filetype", 0L, arg, OPT_LOCAL); - if (arg != eap->arg) { - did_filetype = false; - } + set_option_value_give_err("filetype", 0L, arg, OPT_LOCAL); + if (arg != eap->arg) { + did_filetype = false; } } @@ -7238,7 +7273,7 @@ static void ex_terminal(exarg_T *eap) char ex_cmd[1024]; if (*eap->arg != NUL) { // Run {cmd} in 'shell'. - char *name = (char *)vim_strsave_escaped((char_u *)eap->arg, (char_u *)"\"\\"); + char *name = vim_strsave_escaped(eap->arg, "\"\\"); snprintf(ex_cmd, sizeof(ex_cmd), ":enew%s | call termopen(\"%s\")", eap->forceit ? "!" : "", name); @@ -7271,7 +7306,7 @@ static void ex_terminal(exarg_T *eap) void verify_command(char *cmd) { - if (strcmp("smile", cmd)) { + if (strcmp("smile", cmd) != 0) { return; // acceptable non-existing command } msg(" #xxn` #xnxx` ,+x@##@Mz;` .xxx" diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h index 283501cf76..19dd9e96ca 100644 --- a/src/nvim/ex_docmd.h +++ b/src/nvim/ex_docmd.h @@ -1,6 +1,9 @@ #ifndef NVIM_EX_DOCMD_H #define NVIM_EX_DOCMD_H +#include <stdbool.h> + +#include "nvim/buffer_defs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/globals.h" diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index c2648b9bfc..f76e60f6c5 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -1,8 +1,6 @@ // 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 -// TODO(ZyX-I): move to eval/executor - /// @file ex_eval.c /// /// Functions for Ex command line for the +eval feature. @@ -10,19 +8,30 @@ #include <inttypes.h> #include <limits.h> #include <stdbool.h> +#include <stdio.h> +#include <string.h> #include "nvim/ascii.h" #include "nvim/charset.h" #include "nvim/debugger.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" +#include "nvim/ex_eval_defs.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/option_defs.h" +#include "nvim/pos.h" #include "nvim/regexp.h" #include "nvim/runtime.h" #include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -245,7 +254,7 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore) // Skip the extra "Vim " prefix for message "E458". tmsg = elem->msg; - if (STRNCMP(tmsg, "Vim E", 5) == 0 + if (strncmp(tmsg, "Vim E", 5) == 0 && ascii_isdigit(tmsg[5]) && ascii_isdigit(tmsg[6]) && ascii_isdigit(tmsg[7]) @@ -439,9 +448,9 @@ static int throw_exception(void *value, except_type_T type, char *cmdname) // would be treated differently from real interrupt or error exceptions // when no active try block is found, see do_cmdline(). if (type == ET_USER) { - if (STRNCMP((char_u *)value, "Vim", 3) == 0 - && (((char_u *)value)[3] == NUL || ((char_u *)value)[3] == ':' - || ((char_u *)value)[3] == '(')) { + if (strncmp(value, "Vim", 3) == 0 + && (((char *)value)[3] == NUL || ((char *)value)[3] == ':' + || ((char *)value)[3] == '(')) { emsg(_("E608: Cannot :throw exceptions with 'Vim' prefix")); goto fail; } @@ -529,7 +538,7 @@ static void discard_exception(except_T *excp, bool was_finished) if (p_verbose >= 13 || debug_break_level > 0) { int save_msg_silent = msg_silent; - saved_IObuff = xstrdup((char *)IObuff); + saved_IObuff = xstrdup(IObuff); if (debug_break_level > 0) { msg_silent = false; // display messages } else { @@ -539,9 +548,7 @@ static void discard_exception(except_T *excp, bool was_finished) if (debug_break_level > 0 || *p_vfile == NUL) { msg_scroll = true; // always scroll up, don't overwrite } - smsg(was_finished ? _("Exception finished: %s") - : _("Exception discarded: %s"), - excp->value); + smsg(was_finished ? _("Exception finished: %s") : _("Exception discarded: %s"), excp->value); msg_puts("\n"); // don't overwrite this either if (debug_break_level > 0 || *p_vfile == NUL) { cmdline_row = msg_row; @@ -552,7 +559,7 @@ static void discard_exception(except_T *excp, bool was_finished) } else { verbose_leave(); } - STRLCPY(IObuff, saved_IObuff, IOSIZE); + xstrlcpy(IObuff, saved_IObuff, IOSIZE); xfree(saved_IObuff); } if (excp->type != ET_INTERRUPT) { @@ -585,12 +592,12 @@ static void catch_exception(except_T *excp) 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), + vim_snprintf(IObuff, IOSIZE, _("%s, line %" PRId64), excp->throw_name, (int64_t)excp->throw_lnum); } else { - vim_snprintf((char *)IObuff, IOSIZE, "%s", excp->throw_name); + vim_snprintf(IObuff, IOSIZE, "%s", excp->throw_name); } - set_vim_var_string(VV_THROWPOINT, (char *)IObuff, -1); + set_vim_var_string(VV_THROWPOINT, IObuff, -1); } else { // throw_name not set on an exception from a command that was typed. set_vim_var_string(VV_THROWPOINT, NULL, -1); @@ -634,14 +641,14 @@ static void finish_exception(except_T *excp) 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, + vim_snprintf(IObuff, IOSIZE, _("%s, line %" PRId64), caught_stack->throw_name, (int64_t)caught_stack->throw_lnum); } else { - vim_snprintf((char *)IObuff, IOSIZE, "%s", + vim_snprintf(IObuff, IOSIZE, "%s", caught_stack->throw_name); } - set_vim_var_string(VV_THROWPOINT, (char *)IObuff, -1); + set_vim_var_string(VV_THROWPOINT, IObuff, -1); } else { // throw_name not set on an exception from a command that was // typed. @@ -707,9 +714,9 @@ static void report_pending(int action, int pending, void *value) default: if (pending & CSTP_THROW) { - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, mesg, _("Exception")); - mesg = concat_str((char *)IObuff, ": %s"); + mesg = concat_str(IObuff, ": %s"); s = ((except_T *)value)->value; } else if ((pending & CSTP_ERROR) && (pending & CSTP_INTERRUPT)) { s = _("Error and interrupt"); @@ -853,7 +860,7 @@ void ex_endif(exarg_T *eap) /// Handle ":else" and ":elseif". void ex_else(exarg_T *eap) { - int result; + bool result = false; cstack_T *const cstack = eap->cstack; bool skip = CHECK_SKIP; @@ -901,13 +908,20 @@ void ex_else(exarg_T *eap) if (eap->cmdidx == CMD_elseif) { bool error; - result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip); + // When skipping we ignore most errors, but a missing expression is + // wrong, perhaps it should have been "else". + // A double quote here is the start of a string, not a comment. + if (skip && *eap->arg != '"' && ends_excmd(*eap->arg)) { + semsg(_(e_invexpr2), eap->arg); + } else { + result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip); + } + // When throwing error exceptions, we want to throw always the first // of several errors in a row. This is what actually happens when // a conditional error was detected above and there is another failure // when parsing the expression. Since the skip flag is set in this // case, the parsing error will be ignored by emsg(). - if (!skip && !error) { if (result) { cstack->cs_flags[cstack->cs_idx] = CSF_ACTIVE | CSF_TRUE; @@ -1085,7 +1099,7 @@ void ex_endwhile(exarg_T *eap) } // Try to find the matching ":while" and report what's missing. for (idx = cstack->cs_idx; idx > 0; idx--) { - fl = cstack->cs_flags[idx]; + fl = cstack->cs_flags[idx]; if ((fl & CSF_TRY) && !(fl & CSF_FINALLY)) { // Give up at a try conditional not in its finally clause. // Ignore the ":endwhile"/":endfor". @@ -1136,7 +1150,7 @@ void ex_throw(exarg_T *eap) // On error or when an exception is thrown during argument evaluation, do // not throw. if (!eap->skip && value != NULL) { - if (throw_exception((char_u *)value, ET_USER, NULL) == FAIL) { + if (throw_exception(value, ET_USER, NULL) == FAIL) { xfree(value); } else { do_throw(eap->cstack); @@ -1298,7 +1312,10 @@ void ex_catch(exarg_T *eap) eap->nextcmd = find_nextcmd(eap->arg); } else { pat = eap->arg + 1; - end = skip_regexp(pat, *eap->arg, true, NULL); + end = skip_regexp_err(pat, *eap->arg, true); + if (end == NULL) { + give_up = true; + } } if (!give_up) { @@ -1355,8 +1372,7 @@ void ex_catch(exarg_T *eap) // prev_got_int = got_int; got_int = false; - caught = vim_regexec_nl(®match, (char_u *)current_exception->value, - (colnr_T)0); + caught = vim_regexec_nl(®match, current_exception->value, (colnr_T)0); got_int |= prev_got_int; vim_regfree(regmatch.regprog); } @@ -1403,108 +1419,107 @@ void ex_finally(exarg_T *eap) int pending = CSTP_NONE; cstack_T *const cstack = eap->cstack; - if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) { - eap->errmsg = _("E606: :finally without :try"); - } else { - if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { - eap->errmsg = get_end_emsg(cstack); - for (idx = cstack->cs_idx - 1; idx > 0; idx--) { - if (cstack->cs_flags[idx] & CSF_TRY) { - break; - } - } - // Make this error pending, so that the commands in the following - // finally clause can be executed. This overrules also a pending - // ":continue", ":break", ":return", or ":finish". - pending = CSTP_ERROR; - } else { - idx = cstack->cs_idx; + for (idx = cstack->cs_idx; idx >= 0; idx--) { + if (cstack->cs_flags[idx] & CSF_TRY) { + break; } + } + if (cstack->cs_trylevel <= 0 || idx < 0) { + eap->errmsg = _("E606: :finally without :try"); + return; + } - if (cstack->cs_flags[idx] & CSF_FINALLY) { - // Give up for a multiple ":finally" and ignore it. - eap->errmsg = _("E607: multiple :finally"); - return; - } - rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, - &cstack->cs_looplevel); + if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { + eap->errmsg = get_end_emsg(cstack); + // Make this error pending, so that the commands in the following + // finally clause can be executed. This overrules also a pending + // ":continue", ":break", ":return", or ":finish". + pending = CSTP_ERROR; + } - // Don't do something when the corresponding try block never got active - // (because of an inactive surrounding conditional or after an error or - // interrupt or throw) or for a ":finally" without ":try" or a multiple - // ":finally". After every other error (did_emsg or the conditional - // errors detected above) or after an interrupt (got_int) or an - // exception (did_throw), the finally clause must be executed. - skip = !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); + if (cstack->cs_flags[idx] & CSF_FINALLY) { + // Give up for a multiple ":finally" and ignore it. + eap->errmsg = _("E607: multiple :finally"); + return; + } + rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, + &cstack->cs_looplevel); + + // Don't do something when the corresponding try block never got active + // (because of an inactive surrounding conditional or after an error or + // interrupt or throw) or for a ":finally" without ":try" or a multiple + // ":finally". After every other error (did_emsg or the conditional + // errors detected above) or after an interrupt (got_int) or an + // exception (did_throw), the finally clause must be executed. + skip = !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); + + if (!skip) { + // When debugging or a breakpoint was encountered, display the + // debug prompt (if not already done). The user then knows that the + // finally clause is executed. + if (dbg_check_skipped(eap)) { + // Handle a ">quit" debug command as if an interrupt had + // occurred before the ":finally". That is, discard the + // original exception and replace it by an interrupt + // exception. + (void)do_intthrow(cstack); + } - if (!skip) { - // When debugging or a breakpoint was encountered, display the - // debug prompt (if not already done). The user then knows that the - // finally clause is executed. - if (dbg_check_skipped(eap)) { - // Handle a ">quit" debug command as if an interrupt had - // occurred before the ":finally". That is, discard the - // original exception and replace it by an interrupt - // exception. - (void)do_intthrow(cstack); + // If there is a preceding catch clause and it caught the exception, + // finish the exception now. This happens also after errors except + // when this is a multiple ":finally" or one not within a ":try". + // After an error or interrupt, this also discards a pending + // ":continue", ":break", ":finish", or ":return" from the preceding + // try block or catch clause. + cleanup_conditionals(cstack, CSF_TRY, false); + + // Make did_emsg, got_int, did_throw pending. If set, they overrule + // a pending ":continue", ":break", ":return", or ":finish". Then + // we have particularly to discard a pending return value (as done + // by the call to cleanup_conditionals() above when did_emsg or + // got_int is set). The pending values are restored by the + // ":endtry", except if there is a new error, interrupt, exception, + // ":continue", ":break", ":return", or ":finish" in the following + // finally clause. A missing ":endwhile", ":endfor" or ":endif" + // detected here is treated as if did_emsg and did_throw had + // already been set, respectively in case that the error is not + // converted to an exception, did_throw had already been unset. + // We must not set did_emsg here since that would suppress the + // error message. + if (pending == CSTP_ERROR || did_emsg || got_int || did_throw) { + if (cstack->cs_pending[cstack->cs_idx] == CSTP_RETURN) { + report_discard_pending(CSTP_RETURN, + cstack->cs_rettv[cstack->cs_idx]); + discard_pending_return(cstack->cs_rettv[cstack->cs_idx]); } - - // If there is a preceding catch clause and it caught the exception, - // finish the exception now. This happens also after errors except - // when this is a multiple ":finally" or one not within a ":try". - // After an error or interrupt, this also discards a pending - // ":continue", ":break", ":finish", or ":return" from the preceding - // try block or catch clause. - cleanup_conditionals(cstack, CSF_TRY, false); - - // Make did_emsg, got_int, did_throw pending. If set, they overrule - // a pending ":continue", ":break", ":return", or ":finish". Then - // we have particularly to discard a pending return value (as done - // by the call to cleanup_conditionals() above when did_emsg or - // got_int is set). The pending values are restored by the - // ":endtry", except if there is a new error, interrupt, exception, - // ":continue", ":break", ":return", or ":finish" in the following - // finally clause. A missing ":endwhile", ":endfor" or ":endif" - // detected here is treated as if did_emsg and did_throw had - // already been set, respectively in case that the error is not - // converted to an exception, did_throw had already been unset. - // We must not set did_emsg here since that would suppress the - // error message. - if (pending == CSTP_ERROR || did_emsg || got_int || did_throw) { - if (cstack->cs_pending[cstack->cs_idx] == CSTP_RETURN) { - report_discard_pending(CSTP_RETURN, - cstack->cs_rettv[cstack->cs_idx]); - discard_pending_return(cstack->cs_rettv[cstack->cs_idx]); - } - if (pending == CSTP_ERROR && !did_emsg) { - pending |= (THROW_ON_ERROR ? CSTP_THROW : 0); - } else { - pending |= (did_throw ? CSTP_THROW : 0); - } - pending |= did_emsg ? CSTP_ERROR : 0; - pending |= got_int ? CSTP_INTERRUPT : 0; - assert(pending >= CHAR_MIN && pending <= CHAR_MAX); - cstack->cs_pending[cstack->cs_idx] = (char)pending; - - // It's mandatory that the current exception is stored in the - // cstack so that it can be rethrown at the ":endtry" or be - // discarded if the finally clause is left by a ":continue", - // ":break", ":return", ":finish", error, interrupt, or another - // exception. When emsg() is called for a missing ":endif" or - // a missing ":endwhile"/":endfor" detected here, the - // exception will be discarded. - if (did_throw && cstack->cs_exception[cstack->cs_idx] != current_exception) { - internal_error("ex_finally()"); - } + if (pending == CSTP_ERROR && !did_emsg) { + pending |= (THROW_ON_ERROR ? CSTP_THROW : 0); + } else { + pending |= (did_throw ? CSTP_THROW : 0); + } + pending |= did_emsg ? CSTP_ERROR : 0; + pending |= got_int ? CSTP_INTERRUPT : 0; + assert(pending >= CHAR_MIN && pending <= CHAR_MAX); + cstack->cs_pending[cstack->cs_idx] = (char)pending; + + // It's mandatory that the current exception is stored in the + // cstack so that it can be rethrown at the ":endtry" or be + // discarded if the finally clause is left by a ":continue", + // ":break", ":return", ":finish", error, interrupt, or another + // exception. When emsg() is called for a missing ":endif" or + // a missing ":endwhile"/":endfor" detected here, the + // exception will be discarded. + if (did_throw && cstack->cs_exception[cstack->cs_idx] != current_exception) { + internal_error("ex_finally()"); } - - // Set CSL_HAD_FINA, so do_cmdline() will reset did_emsg, - // got_int, and did_throw and make the finally clause active. - // This will happen after emsg() has been called for a missing - // ":endif" or a missing ":endwhile"/":endfor" detected here, so - // that the following finally clause will be executed even then. - cstack->cs_lflags |= CSL_HAD_FINA; } + + // Set CSL_HAD_FINA, so do_cmdline() will reset did_emsg, + // got_int, and did_throw and make the finally clause active. + // This will happen after emsg() has been called for a missing + // ":endif" or a missing ":endwhile"/":endfor" detected here, so + // that the following finally clause will be executed even then. + cstack->cs_lflags |= CSL_HAD_FINA; } } @@ -1517,165 +1532,167 @@ void ex_endtry(exarg_T *eap) void *rettv = NULL; cstack_T *const cstack = eap->cstack; - if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) { + for (idx = cstack->cs_idx; idx >= 0; idx--) { + if (cstack->cs_flags[idx] & CSF_TRY) { + break; + } + } + if (cstack->cs_trylevel <= 0 || idx < 0) { eap->errmsg = _("E602: :endtry without :try"); - } else { - // Don't do something after an error, interrupt or throw in the try - // block, catch clause, or finally clause preceding this ":endtry" or - // when an error or interrupt occurred after a ":continue", ":break", - // ":return", or ":finish" in a try block or catch clause preceding this - // ":endtry" or when the try block never got active (because of an - // inactive surrounding conditional or after an error or interrupt or - // throw) or when there is a surrounding conditional and it has been - // 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. - bool skip = did_emsg || got_int || did_throw || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); + return; + } - if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { - eap->errmsg = get_end_emsg(cstack); + // Don't do something after an error, interrupt or throw in the try + // block, catch clause, or finally clause preceding this ":endtry" or + // when an error or interrupt occurred after a ":continue", ":break", + // ":return", or ":finish" in a try block or catch clause preceding this + // ":endtry" or when the try block never got active (because of an + // inactive surrounding conditional or after an error or interrupt or + // throw) or when there is a surrounding conditional and it has been + // 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. + bool skip = did_emsg || got_int || did_throw || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); - // Find the matching ":try" and report what's missing. - idx = cstack->cs_idx; - do { - idx--; - } while (idx > 0 && !(cstack->cs_flags[idx] & CSF_TRY)); - rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, - &cstack->cs_looplevel); - skip = true; + if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { + eap->errmsg = get_end_emsg(cstack); - // If an exception is being thrown, discard it to prevent it from - // being rethrown at the end of this function. It would be - // discarded by the error message, anyway. Resets did_throw. - // This does not affect the script termination due to the error - // since "trylevel" is decremented after emsg() has been called. - if (did_throw) { - discard_current_exception(); - } + // Find the matching ":try" and report what's missing. + rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, + &cstack->cs_looplevel); + skip = true; - // report eap->errmsg, also when there already was an error - did_emsg = false; - } else { - idx = cstack->cs_idx; - - // If we stopped with the exception currently being thrown at this - // try conditional since we didn't know that it doesn't have - // a finally clause, we need to rethrow it after closing the try - // conditional. - if (did_throw - && (cstack->cs_flags[idx] & CSF_TRUE) - && !(cstack->cs_flags[idx] & CSF_FINALLY)) { - rethrow = true; - } + // If an exception is being thrown, discard it to prevent it from + // being rethrown at the end of this function. It would be + // discarded by the error message, anyway. Resets did_throw. + // This does not affect the script termination due to the error + // since "trylevel" is decremented after emsg() has been called. + if (did_throw) { + discard_current_exception(); } - // If there was no finally clause, show the user when debugging or - // a breakpoint was encountered that the end of the try conditional has - // been reached: display the debug prompt (if not already done). Do - // this on normal control flow or when an exception was thrown, but not - // on an interrupt or error not converted to an exception or when - // a ":break", ":continue", ":return", or ":finish" is pending. These - // actions are carried out immediately. - if ((rethrow || (!skip - && !(cstack->cs_flags[idx] & CSF_FINALLY) - && !cstack->cs_pending[idx])) - && dbg_check_skipped(eap)) { - // Handle a ">quit" debug command as if an interrupt had occurred - // before the ":endtry". That is, throw an interrupt exception and - // set "skip" and "rethrow". - if (got_int) { - skip = true; - (void)do_intthrow(cstack); - // The do_intthrow() call may have reset did_throw or - // cstack->cs_pending[idx]. - rethrow = false; - if (did_throw && !(cstack->cs_flags[idx] & CSF_FINALLY)) { - rethrow = true; - } + // report eap->errmsg, also when there already was an error + did_emsg = false; + } else { + idx = cstack->cs_idx; + + // If we stopped with the exception currently being thrown at this + // try conditional since we didn't know that it doesn't have + // a finally clause, we need to rethrow it after closing the try + // conditional. + if (did_throw + && (cstack->cs_flags[idx] & CSF_TRUE) + && !(cstack->cs_flags[idx] & CSF_FINALLY)) { + rethrow = true; + } + } + + // If there was no finally clause, show the user when debugging or + // a breakpoint was encountered that the end of the try conditional has + // been reached: display the debug prompt (if not already done). Do + // this on normal control flow or when an exception was thrown, but not + // on an interrupt or error not converted to an exception or when + // a ":break", ":continue", ":return", or ":finish" is pending. These + // actions are carried out immediately. + if ((rethrow || (!skip + && !(cstack->cs_flags[idx] & CSF_FINALLY) + && !cstack->cs_pending[idx])) + && dbg_check_skipped(eap)) { + // Handle a ">quit" debug command as if an interrupt had occurred + // before the ":endtry". That is, throw an interrupt exception and + // set "skip" and "rethrow". + if (got_int) { + skip = true; + (void)do_intthrow(cstack); + // The do_intthrow() call may have reset did_throw or + // cstack->cs_pending[idx]. + rethrow = false; + if (did_throw && !(cstack->cs_flags[idx] & CSF_FINALLY)) { + rethrow = true; } } + } - // If a ":return" is pending, we need to resume it after closing the - // try conditional; remember the return value. If there was a finally - // clause making an exception pending, we need to rethrow it. Make it - // the exception currently being thrown. - if (!skip) { - pending = cstack->cs_pending[idx]; - cstack->cs_pending[idx] = CSTP_NONE; - if (pending == CSTP_RETURN) { - rettv = cstack->cs_rettv[idx]; - } else if (pending & CSTP_THROW) { - current_exception = cstack->cs_exception[idx]; - } + // If a ":return" is pending, we need to resume it after closing the + // try conditional; remember the return value. If there was a finally + // clause making an exception pending, we need to rethrow it. Make it + // the exception currently being thrown. + if (!skip) { + pending = cstack->cs_pending[idx]; + cstack->cs_pending[idx] = CSTP_NONE; + if (pending == CSTP_RETURN) { + rettv = cstack->cs_rettv[idx]; + } else if (pending & CSTP_THROW) { + current_exception = cstack->cs_exception[idx]; } + } - // Discard anything pending on an error, interrupt, or throw in the - // finally clause. If there was no ":finally", discard a pending - // ":continue", ":break", ":return", or ":finish" if an error or - // interrupt occurred afterwards, but before the ":endtry" was reached. - // If an exception was caught by the last of the catch clauses and there - // was no finally clause, finish the exception now. This happens also - // after errors except when this ":endtry" is not within a ":try". - // Restore "emsg_silent" if it has been reset by this try conditional. - (void)cleanup_conditionals(cstack, CSF_TRY | CSF_SILENT, true); + // Discard anything pending on an error, interrupt, or throw in the + // finally clause. If there was no ":finally", discard a pending + // ":continue", ":break", ":return", or ":finish" if an error or + // interrupt occurred afterwards, but before the ":endtry" was reached. + // If an exception was caught by the last of the catch clauses and there + // was no finally clause, finish the exception now. This happens also + // after errors except when this ":endtry" is not within a ":try". + // Restore "emsg_silent" if it has been reset by this try conditional. + (void)cleanup_conditionals(cstack, CSF_TRY | CSF_SILENT, true); - if (cstack->cs_idx >= 0 && (cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { - cstack->cs_idx--; - } - cstack->cs_trylevel--; + if (cstack->cs_idx >= 0 && (cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { + cstack->cs_idx--; + } + cstack->cs_trylevel--; - if (!skip) { - report_resume_pending(pending, - (pending == CSTP_RETURN) ? rettv : - (pending & CSTP_THROW) ? (void *)current_exception : NULL); - switch (pending) { - case CSTP_NONE: - break; + if (!skip) { + report_resume_pending(pending, + (pending == CSTP_RETURN) ? rettv : + (pending & CSTP_THROW) ? (void *)current_exception : NULL); + switch (pending) { + case CSTP_NONE: + break; - // Reactivate a pending ":continue", ":break", ":return", - // ":finish" from the try block or a catch clause of this try - // conditional. This is skipped, if there was an error in an - // (unskipped) conditional command or an interrupt afterwards - // or if the finally clause is present and executed a new error, - // interrupt, throw, ":continue", ":break", ":return", or - // ":finish". - case CSTP_CONTINUE: - ex_continue(eap); - break; - case CSTP_BREAK: - ex_break(eap); - break; - case CSTP_RETURN: - do_return(eap, false, false, rettv); - break; - case CSTP_FINISH: - do_finish(eap, false); - break; + // Reactivate a pending ":continue", ":break", ":return", + // ":finish" from the try block or a catch clause of this try + // conditional. This is skipped, if there was an error in an + // (unskipped) conditional command or an interrupt afterwards + // or if the finally clause is present and executed a new error, + // interrupt, throw, ":continue", ":break", ":return", or + // ":finish". + case CSTP_CONTINUE: + ex_continue(eap); + break; + case CSTP_BREAK: + ex_break(eap); + break; + case CSTP_RETURN: + do_return(eap, false, false, rettv); + break; + case CSTP_FINISH: + do_finish(eap, false); + break; - // When the finally clause was entered due to an error, - // interrupt or throw (as opposed to a ":continue", ":break", - // ":return", or ":finish"), restore the pending values of - // did_emsg, got_int, and did_throw. This is skipped, if there - // was a new error, interrupt, throw, ":continue", ":break", - // ":return", or ":finish". in the finally clause. - default: - if (pending & CSTP_ERROR) { - did_emsg = true; - } - if (pending & CSTP_INTERRUPT) { - got_int = true; - } - if (pending & CSTP_THROW) { - rethrow = true; - } - break; + // When the finally clause was entered due to an error, + // interrupt or throw (as opposed to a ":continue", ":break", + // ":return", or ":finish"), restore the pending values of + // did_emsg, got_int, and did_throw. This is skipped, if there + // was a new error, interrupt, throw, ":continue", ":break", + // ":return", or ":finish". in the finally clause. + default: + if (pending & CSTP_ERROR) { + did_emsg = true; + } + if (pending & CSTP_INTERRUPT) { + got_int = true; } + if (pending & CSTP_THROW) { + rethrow = true; + } + break; } + } - if (rethrow) { - // Rethrow the current exception (within this cstack). - do_throw(cstack); - } + if (rethrow) { + // Rethrow the current exception (within this cstack). + do_throw(cstack); } } @@ -1950,7 +1967,7 @@ void rewind_conditionals(cstack_T *cstack, int idx, int cond_type, int *cond_lev { while (cstack->cs_idx > idx) { if (cstack->cs_flags[cstack->cs_idx] & cond_type) { - --*cond_level; + (*cond_level)--; } if (cstack->cs_flags[cstack->cs_idx] & CSF_FOR) { free_for_info(cstack->cs_forinfo[cstack->cs_idx]); diff --git a/src/nvim/ex_eval.h b/src/nvim/ex_eval.h index 9e3ac5e9c1..d3053ae0d4 100644 --- a/src/nvim/ex_eval.h +++ b/src/nvim/ex_eval.h @@ -1,7 +1,7 @@ #ifndef NVIM_EX_EVAL_H #define NVIM_EX_EVAL_H -#include "nvim/ex_cmds_defs.h" // for exarg_T +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_eval_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/ex_eval_defs.h b/src/nvim/ex_eval_defs.h index 9da0c9ad12..6b3c426722 100644 --- a/src/nvim/ex_eval_defs.h +++ b/src/nvim/ex_eval_defs.h @@ -1,7 +1,7 @@ #ifndef NVIM_EX_EVAL_DEFS_H #define NVIM_EX_EVAL_DEFS_H -#include "nvim/pos.h" // for linenr_T +#include "nvim/pos.h" /// There is no CSF_IF, the lack of CSF_WHILE, CSF_FOR and CSF_TRY means ":if" /// was used. diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 031226c5a1..76c3680742 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -5,44 +5,45 @@ #include <assert.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> +#include <time.h> #include "klib/kvec.h" #include "nvim/api/extmark.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" #include "nvim/arabic.h" #include "nvim/ascii.h" -#include "nvim/assert.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" #include "nvim/cmdhist.h" #include "nvim/cursor.h" -#include "nvim/cursor_shape.h" #include "nvim/digraph.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" -#include "nvim/event/loop.h" +#include "nvim/eval/typval.h" #include "nvim/ex_cmds.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" -#include "nvim/fileio.h" -#include "nvim/func_attr.h" +#include "nvim/extmark.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/grid.h" -#include "nvim/highlight.h" #include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" -#include "nvim/indent.h" #include "nvim/keycodes.h" -#include "nvim/log.h" -#include "nvim/main.h" +#include "nvim/macros.h" #include "nvim/mapping.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -51,15 +52,18 @@ #include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" +#include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/optionstr.h" #include "nvim/os/input.h" -#include "nvim/os/time.h" +#include "nvim/os/os.h" #include "nvim/path.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/regexp.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/state.h" #include "nvim/strings.h" @@ -94,7 +98,7 @@ typedef struct { pos_T match_end; bool did_incsearch; bool incsearch_postponed; - int magic_save; + optmagic_T magic_overruled_save; } incsearch_state_T; typedef struct command_line_state { @@ -113,7 +117,6 @@ typedef struct command_line_state { incsearch_state_T is_state; int did_wild_list; // did wild_list() recently int wim_index; // index in wim_flags[] - int res; int save_msg_scroll; int save_State; // remember State when called char *save_p_icm; @@ -153,6 +156,14 @@ typedef struct cmdpreview_info { garray_T save_view; } CpInfo; +/// Return value when handling keys in command-line mode. +enum { + CMDLINE_NOT_CHANGED = 1, + CMDLINE_CHANGED = 2, + GOTO_NORMAL_MODE = 3, + PROCESS_NEXT_KEY = 4, +}; + /// 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(). @@ -207,7 +218,7 @@ static void init_incsearch_state(incsearch_state_T *s) s->match_start = curwin->w_cursor; s->did_incsearch = false; s->incsearch_postponed = false; - s->magic_save = p_magic; + s->magic_overruled_save = magic_overruled; clearpos(&s->match_end); s->save_cursor = curwin->w_cursor; // may be restored later s->search_start = curwin->w_cursor; @@ -230,6 +241,7 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s pos_T save_cursor; bool use_last_pat; bool retval = false; + magic_T magic = 0; *skiplen = 0; *patlen = ccline.cmdlen; @@ -262,7 +274,7 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s parse_command_modifiers(&ea, &dummy, &dummy_cmdmod, true); cmd = skip_range(ea.cmd, NULL); - if (vim_strchr("sgvl", *cmd) == NULL) { + if (vim_strchr("sgvl", (uint8_t)(*cmd)) == NULL) { goto theend; } @@ -272,16 +284,16 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s goto theend; } - if (STRNCMP(cmd, "substitute", p - cmd) == 0 - || STRNCMP(cmd, "smagic", p - cmd) == 0 - || STRNCMP(cmd, "snomagic", MAX(p - cmd, 3)) == 0 - || STRNCMP(cmd, "vglobal", p - cmd) == 0) { + if (strncmp(cmd, "substitute", (size_t)(p - cmd)) == 0 + || strncmp(cmd, "smagic", (size_t)(p - cmd)) == 0 + || strncmp(cmd, "snomagic", (size_t)MAX(p - cmd, 3)) == 0 + || strncmp(cmd, "vglobal", (size_t)(p - cmd)) == 0) { if (*cmd == 's' && cmd[1] == 'm') { - p_magic = true; + magic_overruled = OPTION_MAGIC_ON; } else if (*cmd == 's' && cmd[1] == 'n') { - p_magic = false; + magic_overruled = OPTION_MAGIC_OFF; } - } else if (STRNCMP(cmd, "sort", MAX(p - cmd, 3)) == 0) { + } else if (strncmp(cmd, "sort", (size_t)MAX(p - cmd, 3)) == 0) { // skip over ! and flags if (*p == '!') { p = skipwhite(p + 1); @@ -292,11 +304,11 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s if (*p == NUL) { goto theend; } - } else if (STRNCMP(cmd, "vimgrep", MAX(p - cmd, 3)) == 0 - || STRNCMP(cmd, "vimgrepadd", MAX(p - cmd, 8)) == 0 - || STRNCMP(cmd, "lvimgrep", MAX(p - cmd, 2)) == 0 - || STRNCMP(cmd, "lvimgrepadd", MAX(p - cmd, 9)) == 0 - || STRNCMP(cmd, "global", p - cmd) == 0) { + } else if (strncmp(cmd, "vimgrep", (size_t)MAX(p - cmd, 3)) == 0 + || strncmp(cmd, "vimgrepadd", (size_t)MAX(p - cmd, 8)) == 0 + || strncmp(cmd, "lvimgrep", (size_t)MAX(p - cmd, 2)) == 0 + || strncmp(cmd, "lvimgrepadd", (size_t)MAX(p - cmd, 9)) == 0 + || strncmp(cmd, "global", (size_t)(p - cmd)) == 0) { // skip over "!/". if (*p == '!') { p++; @@ -312,9 +324,9 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s } p = skipwhite(p); - delim = (delim_optional && vim_isIDc(*p)) ? ' ' : *p++; + delim = (delim_optional && vim_isIDc((uint8_t)(*p))) ? ' ' : *p++; *search_delim = delim; - end = skip_regexp(p, delim, p_magic, NULL); + end = skip_regexp_ex(p, delim, magic_isset(), NULL, NULL, &magic); use_last_pat = end == p && *end == delim; if (end == p && !use_last_pat) { @@ -324,10 +336,8 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s // Don't do 'hlsearch' highlighting if the pattern matches everything. if (!use_last_pat) { char c = *end; - int empty; - *end = NUL; - empty = empty_pattern(p); + bool empty = empty_pattern_magic(p, strlen(p), magic); *end = c; if (empty) { goto theend; @@ -370,8 +380,8 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat pos_T end_pos; proftime_T tm; int skiplen, patlen; - char_u next_char; - char_u use_last_pat; + char next_char; + bool use_last_pat; int search_delim; // Parsing range may already set the last search pattern. @@ -408,7 +418,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat int found; // do_search() result // Use the previous pattern for ":s//". - next_char = (char_u)ccline.cmdbuff[skiplen + patlen]; + next_char = ccline.cmdbuff[skiplen + patlen]; use_last_pat = patlen == 0 && skiplen > 0 && ccline.cmdbuff[skiplen - 1] == next_char; @@ -435,9 +445,9 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat .sa_tm = &tm, }; found = do_search(NULL, firstc == ':' ? '/' : firstc, search_delim, - (char_u *)ccline.cmdbuff + skiplen, count, + ccline.cmdbuff + skiplen, count, search_flags, &sia); - ccline.cmdbuff[skiplen + patlen] = (char)next_char; + ccline.cmdbuff[skiplen + patlen] = next_char; emsg_off--; if (curwin->w_cursor.lnum < search_first_line || curwin->w_cursor.lnum > search_last_line) { @@ -482,17 +492,17 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat } else { end_pos = curwin->w_cursor; // shutup gcc 4 } - // + // Disable 'hlsearch' highlighting if the pattern matches // everything. Avoids a flash when typing "foo\|". if (!use_last_pat) { - next_char = (char_u)ccline.cmdbuff[skiplen + patlen]; + next_char = ccline.cmdbuff[skiplen + patlen]; ccline.cmdbuff[skiplen + patlen] = NUL; - if (empty_pattern(ccline.cmdbuff) && !no_hlsearch) { + if (empty_pattern(ccline.cmdbuff + skiplen, search_delim) && !no_hlsearch) { redraw_all_later(UPD_SOME_VALID); set_no_hlsearch(true); } - ccline.cmdbuff[skiplen + patlen] = (char)next_char; + ccline.cmdbuff[skiplen + patlen] = next_char; } validate_cursor(); @@ -548,11 +558,11 @@ static int may_add_char_to_search(int firstc, int *c, incsearch_state_T *s) // command line has no uppercase characters, convert // the character to lowercase if (p_ic && p_scs - && !pat_has_uppercase((char_u *)ccline.cmdbuff + skiplen)) { + && !pat_has_uppercase(ccline.cmdbuff + skiplen)) { *c = mb_tolower(*c); } if (*c == search_delim - || vim_strchr((p_magic ? "\\~^$.*[" : "\\^$"), *c) != NULL) { + || vim_strchr((magic_isset() ? "\\~^$.*[" : "\\^$"), *c) != NULL) { // put a backslash before special characters stuffcharReadbuff(*c); *c = '\\'; @@ -565,32 +575,64 @@ static int may_add_char_to_search(int firstc, int *c, incsearch_state_T *s) static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool call_update_screen) { - if (s->did_incsearch) { - s->did_incsearch = false; - if (gotesc) { + if (!s->did_incsearch) { + return; + } + + s->did_incsearch = false; + if (gotesc) { + curwin->w_cursor = s->save_cursor; + } else { + if (!equalpos(s->save_cursor, s->search_start)) { + // put the '" mark at the original position curwin->w_cursor = s->save_cursor; - } else { - if (!equalpos(s->save_cursor, s->search_start)) { - // put the '" mark at the original position - curwin->w_cursor = s->save_cursor; - setpcmark(); - } - curwin->w_cursor = s->search_start; // -V519 + setpcmark(); } - restore_viewstate(curwin, &s->old_viewstate); - highlight_match = false; + curwin->w_cursor = s->search_start; // -V519 + } + restore_viewstate(curwin, &s->old_viewstate); + highlight_match = false; - // by default search all lines - search_first_line = 0; - search_last_line = MAXLNUM; + // by default search all lines + search_first_line = 0; + search_last_line = MAXLNUM; - p_magic = s->magic_save; + magic_overruled = s->magic_overruled_save; - validate_cursor(); // needed for TAB - redraw_all_later(UPD_SOME_VALID); - if (call_update_screen) { - update_screen(); - } + validate_cursor(); // needed for TAB + redraw_all_later(UPD_SOME_VALID); + if (call_update_screen) { + update_screen(); + } +} + +/// Initialize the current command-line info. +static void init_ccline(int firstc, int indent) +{ + ccline.overstrike = false; // always start in insert mode + + assert(indent >= 0); + + // set some variables for redrawcmd() + ccline.cmdfirstc = (firstc == '@' ? 0 : firstc); + ccline.cmdindent = (firstc > 0 ? indent : 0); + + // alloc initial ccline.cmdbuff + alloc_cmdbuff(indent + 50); + ccline.cmdlen = ccline.cmdpos = 0; + ccline.cmdbuff[0] = NUL; + + ccline.last_colors = (ColoredCmdline){ .cmdbuff = NULL, + .colors = KV_INITIAL_VALUE }; + sb_text_start_cmdline(); + + // autoindent for :insert and :append + if (firstc <= 0) { + memset(ccline.cmdbuff, ' ', (size_t)indent); + ccline.cmdbuff[indent] = NUL; + ccline.cmdpos = indent; + ccline.cmdspos = indent; + ccline.cmdlen = indent; } } @@ -598,8 +640,8 @@ static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool /// /// @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) +/// @param clear_ccline clear ccline first +static uint8_t *command_line_enter(int firstc, long count, int indent, bool clear_ccline) { // can be invoked recursively, identify each level static int cmdline_level = 0; @@ -622,14 +664,14 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init bool did_save_ccline = false; if (ccline.cmdbuff != NULL) { - // Currently ccline can never be in use if init_ccline is false. + // Currently ccline can never be in use if clear_ccline is false. // Some changes will be needed if this is no longer the case. - assert(init_ccline); + assert(clear_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) { + } else if (clear_ccline) { CLEAR_FIELD(ccline); } @@ -643,33 +685,9 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init cmd_hkmap = 0; } + init_ccline(s->firstc, s->indent); ccline.prompt_id = last_prompt_id++; ccline.level = cmdline_level; - ccline.overstrike = false; // always start in insert mode - - assert(indent >= 0); - - // set some variables for redrawcmd() - ccline.cmdfirstc = (s->firstc == '@' ? 0 : s->firstc); - ccline.cmdindent = (s->firstc > 0 ? s->indent : 0); - - // alloc initial ccline.cmdbuff - alloc_cmdbuff(indent + 50); - ccline.cmdlen = ccline.cmdpos = 0; - ccline.cmdbuff[0] = NUL; - - ccline.last_colors = (ColoredCmdline){ .cmdbuff = NULL, - .colors = KV_INITIAL_VALUE }; - sb_text_start_cmdline(); - - // autoindent for :insert and :append - if (s->firstc <= 0) { - memset(ccline.cmdbuff, ' ', (size_t)s->indent); - ccline.cmdbuff[s->indent] = NUL; - ccline.cmdpos = s->indent; - ccline.cmdspos = s->indent; - ccline.cmdlen = s->indent; - } if (cmdline_level == 50) { // Somehow got into a loop recursively calling getcmdline(), bail out. @@ -707,7 +725,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init if (ccline.input_fn) { s->xpc.xp_context = ccline.xp_context; s->xpc.xp_pattern = ccline.cmdbuff; - s->xpc.xp_arg = (char *)ccline.xp_arg; + s->xpc.xp_arg = ccline.xp_arg; } // Avoid scrolling when called by a recursive do_cmdline(), e.g. when @@ -735,13 +753,14 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init TryState tstate; Error err = ERROR_INIT; bool tl_ret = true; - 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; if (has_event(EVENT_CMDLINEENTER)) { + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); + // set v:event to a dictionary with information about the commandline tv_dict_add_str(dict, S_LEN("cmdtype"), firstcbuf); tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level); @@ -754,7 +773,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init tl_ret = try_leave(&tstate, &err); if (!tl_ret && ERROR_SET(&err)) { msg_putchar('\n'); - msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg); + msg_scroll = true; + msg_puts_attr(err.msg, HL_ATTR(HLF_E)|MSG_HIST); api_clear_error(&err); redrawcmd(); } @@ -803,6 +823,9 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init state_enter(&s->state); if (has_event(EVENT_CMDLINELEAVE)) { + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); + tv_dict_add_str(dict, S_LEN("cmdtype"), firstcbuf); tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level); tv_dict_set_keys_readonly(dict); @@ -859,7 +882,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init if (!tl_ret && ERROR_SET(&err)) { msg_putchar('\n'); - semsg(e_autocmd_err, err.msg); + emsg(err.msg); did_emsg = false; api_clear_error(&err); } @@ -885,7 +908,7 @@ theend: xfree(ccline.last_colors.cmdbuff); kv_destroy(ccline.last_colors.colors); - char_u *p = (char_u *)ccline.cmdbuff; + char *p = ccline.cmdbuff; if (ui_has(kUICmdline)) { ui_call_cmdline_hide(ccline.level); @@ -900,7 +923,7 @@ theend: ccline.cmdbuff = NULL; } - return p; + return (uint8_t *)p; } static int command_line_check(VimState *state) @@ -919,6 +942,191 @@ static int command_line_check(VimState *state) return 1; } +/// Handle CTRL-\ pressed in Command-line mode: +/// - CTRL-\ CTRL-N or CTRL-\ CTRL-G goes to Normal mode. +/// - CTRL-\ e prompts for an expression. +static int command_line_handle_ctrl_bsl(CommandLineState *s) +{ + no_mapping++; + allow_keys++; + s->c = plain_vgetc(); + no_mapping--; + allow_keys--; + + // CTRL-\ e doesn't work when obtaining an expression, unless it + // is in a mapping. + if (s->c != Ctrl_N + && s->c != Ctrl_G + && (s->c != 'e' + || (ccline.cmdfirstc == '=' && KeyTyped) + || cmdline_star > 0)) { + vungetc(s->c); + return PROCESS_NEXT_KEY; + } + + if (s->c == 'e') { + // Replace the command line with the result of an expression. + // This will call getcmdline() recursively in get_expr_register(). + if (ccline.cmdpos == ccline.cmdlen) { + new_cmdpos = 99999; // keep it at the end + } else { + new_cmdpos = ccline.cmdpos; + } + + s->c = get_expr_register(); + if (s->c == '=') { + // Evaluate the expression. Set "textlock" to avoid nasty things + // like going to another buffer. + textlock++; + char *p = get_expr_line(); + textlock--; + + if (p != NULL) { + int len = (int)strlen(p); + realloc_cmdbuff(len + 1); + ccline.cmdlen = len; + STRCPY(ccline.cmdbuff, p); + xfree(p); + + // Restore the cursor or use the position set with + // set_cmdline_pos(). + if (new_cmdpos > ccline.cmdlen) { + ccline.cmdpos = ccline.cmdlen; + } else { + ccline.cmdpos = new_cmdpos; + } + + KeyTyped = false; // Don't do p_wc completion. + redrawcmd(); + return CMDLINE_CHANGED; + } + } + beep_flush(); + got_int = false; // don't abandon the command line + did_emsg = false; + emsg_on_display = false; + redrawcmd(); + return CMDLINE_NOT_CHANGED; + } + + s->gotesc = true; // will free ccline.cmdbuff after putting it in history + return GOTO_NORMAL_MODE; +} + +/// Completion for 'wildchar' or 'wildcharm' key. +/// - hitting <ESC> twice means: abandon command line. +/// - wildcard expansion is only done when the 'wildchar' key is really +/// typed, not when it comes from a macro +/// @return CMDLINE_CHANGED if command line is changed or CMDLINE_NOT_CHANGED. +static int command_line_wildchar_complete(CommandLineState *s) +{ + int res; + int options = WILD_NO_BEEP; + if (wim_flags[s->wim_index] & WIM_BUFLASTUSED) { + options |= WILD_BUFLASTUSED; + } + if (s->xpc.xp_numfiles > 0) { // typed p_wc at least twice + // if 'wildmode' contains "list" may still need to list + if (s->xpc.xp_numfiles > 1 + && !s->did_wild_list + && ((wim_flags[s->wim_index] & WIM_LIST) + || (p_wmnu && (wim_flags[s->wim_index] & WIM_FULL) != 0))) { + (void)showmatches(&s->xpc, p_wmnu && ((wim_flags[s->wim_index] & WIM_LIST) == 0)); + redrawcmd(); + s->did_wild_list = true; + } + + if (wim_flags[s->wim_index] & WIM_LONGEST) { + res = nextwild(&s->xpc, WILD_LONGEST, options, s->firstc != '@'); + } else if (wim_flags[s->wim_index] & WIM_FULL) { + res = nextwild(&s->xpc, WILD_NEXT, options, s->firstc != '@'); + } else { + res = OK; // don't insert 'wildchar' now + } + } else { // typed p_wc first time + s->wim_index = 0; + int j = ccline.cmdpos; + + // if 'wildmode' first contains "longest", get longest + // common part + if (wim_flags[0] & WIM_LONGEST) { + res = nextwild(&s->xpc, WILD_LONGEST, options, s->firstc != '@'); + } else { + res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options, s->firstc != '@'); + } + + // if interrupted while completing, behave like it failed + if (got_int) { + (void)vpeekc(); // remove <C-C> from input stream + got_int = false; // don't abandon the command line + (void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE); + s->xpc.xp_context = EXPAND_NOTHING; + return CMDLINE_CHANGED; + } + + // when more than one match, and 'wildmode' first contains + // "list", or no change and 'wildmode' contains "longest,list", + // list all matches + if (res == OK && s->xpc.xp_numfiles > 1) { + // a "longest" that didn't do anything is skipped (but not + // "list:longest") + if (wim_flags[0] == WIM_LONGEST && ccline.cmdpos == j) { + s->wim_index = 1; + } + if ((wim_flags[s->wim_index] & WIM_LIST) + || (p_wmnu && (wim_flags[s->wim_index] & WIM_FULL) != 0)) { + if (!(wim_flags[0] & WIM_LONGEST)) { + int p_wmnu_save = p_wmnu; + p_wmnu = 0; + // remove match + nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@'); + p_wmnu = p_wmnu_save; + } + + (void)showmatches(&s->xpc, p_wmnu && ((wim_flags[s->wim_index] & WIM_LIST) == 0)); + redrawcmd(); + s->did_wild_list = true; + + if (wim_flags[s->wim_index] & WIM_LONGEST) { + nextwild(&s->xpc, WILD_LONGEST, options, s->firstc != '@'); + } else if (wim_flags[s->wim_index] & WIM_FULL) { + nextwild(&s->xpc, WILD_NEXT, options, s->firstc != '@'); + } + } else { + vim_beep(BO_WILD); + } + } else if (s->xpc.xp_numfiles == -1) { + s->xpc.xp_context = EXPAND_NOTHING; + } + } + + if (s->wim_index < 3) { + s->wim_index++; + } + + if (s->c == ESC) { + s->gotesc = true; + } + + return (res == OK) ? CMDLINE_CHANGED : CMDLINE_NOT_CHANGED; +} + +static void command_line_end_wildmenu(CommandLineState *s) +{ + if (cmdline_pum_active()) { + cmdline_pum_remove(); + } + if (s->xpc.xp_numfiles != -1) { + (void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE); + } + s->did_wild_list = false; + if (!p_wmnu || (s->c != K_UP && s->c != K_DOWN)) { + s->xpc.xp_context = EXPAND_NOTHING; + } + s->wim_index = 0; + wildmenu_cleanup(&ccline); +} + static int command_line_execute(VimState *state, int key) { if (key == K_IGNORE || key == K_NOP) { @@ -937,6 +1145,19 @@ static int command_line_execute(VimState *state, int key) map_execute_lua(); } + // nvim_select_popupmenu_item() can be called from the handling of + // K_EVENT, K_COMMAND, or K_LUA. + if (pum_want.active) { + if (cmdline_pum_active()) { + nextwild(&s->xpc, WILD_PUM_WANT, 0, s->firstc != '@'); + if (pum_want.finish) { + nextwild(&s->xpc, WILD_APPLY, WILD_NO_BEEP, s->firstc != '@'); + command_line_end_wildmenu(s); + } + } + pum_want.active = false; + } + if (!cmdline_was_last_drawn) { redrawcmdline(); } @@ -1005,100 +1226,49 @@ static int command_line_execute(VimState *state, int key) } if (cmdline_pum_active() || s->did_wild_list) { + // Ctrl-Y: Accept the current selection and close the popup menu. + // Ctrl-E: cancel the cmdline popup menu and return the original text. if (s->c == Ctrl_E || s->c == Ctrl_Y) { const int wild_type = (s->c == Ctrl_E) ? WILD_CANCEL : WILD_APPLY; - s->res = nextwild(&s->xpc, wild_type, WILD_NO_BEEP, s->firstc != '@'); + (void)nextwild(&s->xpc, wild_type, WILD_NO_BEEP, s->firstc != '@'); s->c = Ctrl_E; } } + // The wildmenu is cleared if the pressed key is not used for + // navigating the wild menu (i.e. the key is not 'wildchar' or + // 'wildcharm' or Ctrl-N or Ctrl-P or Ctrl-A or Ctrl-L). + // If the popup menu is displayed, then PageDown and PageUp keys are + // also used to navigate the menu. + bool end_wildmenu = (!(s->c == p_wc && KeyTyped) && s->c != p_wcm && s->c != Ctrl_Z + && s->c != Ctrl_N && s->c != Ctrl_P && s->c != Ctrl_A + && s->c != Ctrl_L); + end_wildmenu = end_wildmenu && (!cmdline_pum_active() + || (s->c != K_PAGEDOWN && s->c != K_PAGEUP + && s->c != K_KPAGEDOWN && s->c != K_KPAGEUP)); + // free expanded names when finished walking through matches - if (!(s->c == p_wc && KeyTyped) && s->c != p_wcm && s->c != Ctrl_Z - && s->c != Ctrl_N && s->c != Ctrl_P && s->c != Ctrl_A - && s->c != Ctrl_L) { - if (cmdline_pum_active()) { - cmdline_pum_remove(); - } - if (s->xpc.xp_numfiles != -1) { - (void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE); - } - s->did_wild_list = false; - if (!p_wmnu || (s->c != K_UP && s->c != K_DOWN)) { - s->xpc.xp_context = EXPAND_NOTHING; - } - s->wim_index = 0; - wildmenu_cleanup(&ccline); + if (end_wildmenu) { + command_line_end_wildmenu(s); } if (p_wmnu) { s->c = wildmenu_process_key(&ccline, s->c, &s->xpc); } - // CTRL-\ CTRL-N goes to Normal mode, CTRL-\ e prompts for an expression. + // CTRL-\ CTRL-N or CTRL-\ CTRL-G goes to Normal mode, + // CTRL-\ e prompts for an expression. if (s->c == Ctrl_BSL) { - no_mapping++; - allow_keys++; - s->c = plain_vgetc(); - no_mapping--; - allow_keys--; - // CTRL-\ e doesn't work when obtaining an expression, unless it - // is in a mapping. - if (s->c != Ctrl_N - && s->c != Ctrl_G - && (s->c != 'e' - || (ccline.cmdfirstc == '=' && KeyTyped) - || cmdline_star > 0)) { - vungetc(s->c); - s->c = Ctrl_BSL; - } else if (s->c == 'e') { - char_u *p = NULL; - int len; - - // Replace the command line with the result of an expression. - // Need to save and restore the current command line, to be - // able to enter a new one... - if (ccline.cmdpos == ccline.cmdlen) { - new_cmdpos = 99999; // keep it at the end - } else { - new_cmdpos = ccline.cmdpos; - } - - s->c = get_expr_register(); - if (s->c == '=') { - textlock++; - p = (char_u *)get_expr_line(); - textlock--; - - if (p != NULL) { - len = (int)STRLEN(p); - realloc_cmdbuff(len + 1); - ccline.cmdlen = len; - STRCPY(ccline.cmdbuff, p); - xfree(p); - - // Restore the cursor or use the position set with - // set_cmdline_pos(). - if (new_cmdpos > ccline.cmdlen) { - ccline.cmdpos = ccline.cmdlen; - } else { - ccline.cmdpos = new_cmdpos; - } - - KeyTyped = false; // Don't do p_wc completion. - redrawcmd(); - return command_line_changed(s); - } - } - beep_flush(); - got_int = false; // don't abandon the command line - did_emsg = false; - emsg_on_display = false; - redrawcmd(); + switch (command_line_handle_ctrl_bsl(s)) { + case CMDLINE_CHANGED: + return command_line_changed(s); + case CMDLINE_NOT_CHANGED: return command_line_not_changed(s); - } else { - s->gotesc = true; // will free ccline.cmdbuff after putting it - // in history - return 0; // back to Normal mode + case GOTO_NORMAL_MODE: + return 0; // back to cmd mode + default: + s->c = Ctrl_BSL; // backslash key not processed by + // command_line_handle_ctrl_bsl() } } @@ -1146,107 +1316,8 @@ static int command_line_execute(VimState *state, int key) } // Completion for 'wildchar' or 'wildcharm' key. - // - hitting <ESC> twice means: abandon command line. - // - wildcard expansion is only done when the 'wildchar' key is really - // typed, not when it comes from a macro - if ((s->c == p_wc && !s->gotesc && KeyTyped) || s->c == p_wcm - || s->c == Ctrl_Z) { - int options = WILD_NO_BEEP; - if (wim_flags[s->wim_index] & WIM_BUFLASTUSED) { - options |= WILD_BUFLASTUSED; - } - if (s->xpc.xp_numfiles > 0) { // typed p_wc at least twice - // if 'wildmode' contains "list" may still need to list - if (s->xpc.xp_numfiles > 1 - && !s->did_wild_list - && ((wim_flags[s->wim_index] & WIM_LIST) - || (p_wmnu && (wim_flags[s->wim_index] & WIM_FULL) != 0))) { - (void)showmatches(&s->xpc, p_wmnu - && ((wim_flags[s->wim_index] & WIM_LIST) == 0)); - redrawcmd(); - s->did_wild_list = true; - } - - if (wim_flags[s->wim_index] & WIM_LONGEST) { - s->res = nextwild(&s->xpc, WILD_LONGEST, options, - s->firstc != '@'); - } else if (wim_flags[s->wim_index] & WIM_FULL) { - s->res = nextwild(&s->xpc, WILD_NEXT, options, - s->firstc != '@'); - } else { - s->res = OK; // don't insert 'wildchar' now - } - } else { // typed p_wc first time - s->wim_index = 0; - int j = ccline.cmdpos; - - // if 'wildmode' first contains "longest", get longest - // common part - if (wim_flags[0] & WIM_LONGEST) { - s->res = nextwild(&s->xpc, WILD_LONGEST, options, - s->firstc != '@'); - } else { - s->res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options, - s->firstc != '@'); - } - - // if interrupted while completing, behave like it failed - if (got_int) { - (void)vpeekc(); // remove <C-C> from input stream - got_int = false; // don't abandon the command line - (void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE); - s->xpc.xp_context = EXPAND_NOTHING; - return command_line_changed(s); - } - - // when more than one match, and 'wildmode' first contains - // "list", or no change and 'wildmode' contains "longest,list", - // list all matches - if (s->res == OK && s->xpc.xp_numfiles > 1) { - // a "longest" that didn't do anything is skipped (but not - // "list:longest") - if (wim_flags[0] == WIM_LONGEST && ccline.cmdpos == j) { - s->wim_index = 1; - } - if ((wim_flags[s->wim_index] & WIM_LIST) - || (p_wmnu && (wim_flags[s->wim_index] & WIM_FULL) != 0)) { - if (!(wim_flags[0] & WIM_LONGEST)) { - int p_wmnu_save = p_wmnu; - p_wmnu = 0; - // remove match - nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@'); - p_wmnu = p_wmnu_save; - } - - (void)showmatches(&s->xpc, p_wmnu - && ((wim_flags[s->wim_index] & WIM_LIST) == 0)); - redrawcmd(); - s->did_wild_list = true; - - if (wim_flags[s->wim_index] & WIM_LONGEST) { - nextwild(&s->xpc, WILD_LONGEST, options, - s->firstc != '@'); - } else if (wim_flags[s->wim_index] & WIM_FULL) { - nextwild(&s->xpc, WILD_NEXT, options, - s->firstc != '@'); - } - } else { - vim_beep(BO_WILD); - } - } else if (s->xpc.xp_numfiles == -1) { - s->xpc.xp_context = EXPAND_NOTHING; - } - } - - if (s->wim_index < 3) { - s->wim_index++; - } - - if (s->c == ESC) { - s->gotesc = true; - } - - if (s->res == OK) { + if ((s->c == p_wc && !s->gotesc && KeyTyped) || s->c == p_wcm || s->c == Ctrl_Z) { + if (command_line_wildchar_complete(s) == CMDLINE_CHANGED) { return command_line_changed(s); } } @@ -1303,9 +1374,9 @@ static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_ ui_flush(); pos_T t; - char_u *pat; + char *pat; int search_flags = SEARCH_NOOF; - char_u save; + char save; if (search_delim == ccline.cmdbuff[skiplen]) { pat = last_search_pattern(); @@ -1314,9 +1385,9 @@ static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_ return FAIL; } skiplen = 0; - patlen = (int)STRLEN(pat); + patlen = (int)strlen(pat); } else { - pat = (char_u *)ccline.cmdbuff + skiplen; + pat = ccline.cmdbuff + skiplen; } if (next_match) { @@ -1389,6 +1460,199 @@ static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_ return FAIL; } +/// Handle backspace, delete and CTRL-W keys in the command-line mode. +static int command_line_erase_chars(CommandLineState *s) +{ + if (s->c == K_KDEL) { + s->c = K_DEL; + } + + // Delete current character is the same as backspace on next + // character, except at end of line + if (s->c == K_DEL && ccline.cmdpos != ccline.cmdlen) { + ccline.cmdpos++; + } + + if (s->c == K_DEL) { + ccline.cmdpos += mb_off_next(ccline.cmdbuff, + ccline.cmdbuff + ccline.cmdpos); + } + + if (ccline.cmdpos > 0) { + char *p; + + int j = ccline.cmdpos; + p = mb_prevptr(ccline.cmdbuff, ccline.cmdbuff + j); + + if (s->c == Ctrl_W) { + while (p > ccline.cmdbuff && ascii_isspace(*p)) { + p = mb_prevptr(ccline.cmdbuff, p); + } + + int i = mb_get_class(p); + while (p > ccline.cmdbuff && mb_get_class(p) == i) { + p = mb_prevptr(ccline.cmdbuff, p); + } + + if (mb_get_class(p) != i) { + p += utfc_ptr2len(p); + } + } + + ccline.cmdpos = (int)(p - ccline.cmdbuff); + ccline.cmdlen -= j - ccline.cmdpos; + int i = ccline.cmdpos; + + while (i < ccline.cmdlen) { + ccline.cmdbuff[i++] = ccline.cmdbuff[j++]; + } + + // Truncate at the end, required for multi-byte chars. + ccline.cmdbuff[ccline.cmdlen] = NUL; + if (ccline.cmdlen == 0) { + s->is_state.search_start = s->is_state.save_cursor; + // save view settings, so that the screen won't be restored at the + // wrong position + s->is_state.old_viewstate = s->is_state.init_viewstate; + } + redrawcmd(); + } else if (ccline.cmdlen == 0 && s->c != Ctrl_W + && ccline.cmdprompt == NULL && s->indent == 0) { + // In ex and debug mode it doesn't make sense to return. + if (exmode_active || ccline.cmdfirstc == '>') { + return CMDLINE_NOT_CHANGED; + } + + XFREE_CLEAR(ccline.cmdbuff); // no commandline to return + if (!cmd_silent && !ui_has(kUICmdline)) { + if (cmdmsg_rl) { + msg_col = Columns; + } else { + msg_col = 0; + } + msg_putchar(' '); // delete ':' + } + s->is_state.search_start = s->is_state.save_cursor; + redraw_cmdline = true; + return GOTO_NORMAL_MODE; + } + return CMDLINE_CHANGED; +} + +/// Handle the CTRL-^ key in the command-line mode and toggle the use of the +/// language :lmap mappings and/or Input Method. +static void command_line_toggle_langmap(CommandLineState *s) +{ + if (map_to_exists_mode("", MODE_LANGMAP, false)) { + // ":lmap" mappings exists, toggle use of mappings. + State ^= MODE_LANGMAP; + if (s->b_im_ptr != NULL) { + if (State & MODE_LANGMAP) { + *s->b_im_ptr = B_IMODE_LMAP; + } else { + *s->b_im_ptr = B_IMODE_NONE; + } + } + } + + if (s->b_im_ptr != NULL) { + if (s->b_im_ptr == &curbuf->b_p_iminsert) { + set_iminsert_global(curbuf); + } else { + set_imsearch_global(curbuf); + } + } + ui_cursor_shape(); // may show different cursor shape + // Show/unshow value of 'keymap' in status lines later. + status_redraw_curbuf(); +} + +/// Handle the CTRL-R key in the command-line mode and insert the contents of a +/// numbered or named register. +static int command_line_insert_reg(CommandLineState *s) +{ + const int save_new_cmdpos = new_cmdpos; + + putcmdline('"', true); + no_mapping++; + allow_keys++; + int i = s->c = plain_vgetc(); // CTRL-R <char> + if (i == Ctrl_O) { + i = Ctrl_R; // CTRL-R CTRL-O == CTRL-R CTRL-R + } + + if (i == Ctrl_R) { + s->c = plain_vgetc(); // CTRL-R CTRL-R <char> + } + no_mapping--; + allow_keys--; + // Insert the result of an expression. + new_cmdpos = -1; + if (s->c == '=') { + if (ccline.cmdfirstc == '=' // can't do this recursively + || cmdline_star > 0) { // or when typing a password + beep_flush(); + s->c = ESC; + } else { + s->c = get_expr_register(); + } + } + + if (s->c != ESC) { // use ESC to cancel inserting register + cmdline_paste(s->c, i == Ctrl_R, false); + + // When there was a serious error abort getting the + // command line. + if (aborting()) { + s->gotesc = true; // will free ccline.cmdbuff after + // putting it in history + return GOTO_NORMAL_MODE; + } + KeyTyped = false; // Don't do p_wc completion. + if (new_cmdpos >= 0) { + // set_cmdline_pos() was used + if (new_cmdpos > ccline.cmdlen) { + ccline.cmdpos = ccline.cmdlen; + } else { + ccline.cmdpos = new_cmdpos; + } + } + } + new_cmdpos = save_new_cmdpos; + + // remove the double quote + ccline.special_char = NUL; + redrawcmd(); + + // The text has been stuffed, the command line didn't change yet. + return CMDLINE_NOT_CHANGED; +} + +/// Handle the Left and Right mouse clicks in the command-line mode. +static void command_line_left_right_mouse(CommandLineState *s) +{ + if (s->c == K_LEFTRELEASE || s->c == K_RIGHTRELEASE) { + s->ignore_drag_release = true; + } else { + s->ignore_drag_release = false; + } + + ccline.cmdspos = cmd_startcol(); + for (ccline.cmdpos = 0; ccline.cmdpos < ccline.cmdlen; + ccline.cmdpos++) { + int cells = cmdline_charsize(ccline.cmdpos); + if (mouse_row <= cmdline_row + ccline.cmdspos / Columns + && mouse_col < ccline.cmdspos % Columns + cells) { + break; + } + + // Count ">" for double-wide char that doesn't fit. + correct_screencol(ccline.cmdpos, cells, &ccline.cmdspos); + ccline.cmdpos += utfc_ptr2len(ccline.cmdbuff + ccline.cmdpos) - 1; + ccline.cmdspos += cells; + } +} + static void command_line_next_histidx(CommandLineState *s, bool next_match) { int j = (int)strlen(s->lookfor); @@ -1434,96 +1698,118 @@ static void command_line_next_histidx(CommandLineState *s, bool next_match) if ((s->c != K_UP && s->c != K_DOWN) || s->hiscnt == s->save_hiscnt - || STRNCMP(get_histentry(s->histype)[s->hiscnt].hisstr, + || strncmp(get_histentry(s->histype)[s->hiscnt].hisstr, s->lookfor, (size_t)j) == 0) { break; } } } -static int command_line_handle_key(CommandLineState *s) +/// Handle the Up, Down, Page Up, Page down, CTRL-N and CTRL-P key in the +/// command-line mode. +static int command_line_browse_history(CommandLineState *s) { - // Big switch for a typed command line character. - switch (s->c) { - case K_BS: - case Ctrl_H: - case K_DEL: - case K_KDEL: - case Ctrl_W: - if (s->c == K_KDEL) { - s->c = K_DEL; - } + if (s->histype == HIST_INVALID || get_hislen() == 0 || s->firstc == NUL) { + // no history + return CMDLINE_NOT_CHANGED; + } - // delete current character is the same as backspace on next - // character, except at end of line - if (s->c == K_DEL && ccline.cmdpos != ccline.cmdlen) { - ccline.cmdpos++; - } + s->save_hiscnt = s->hiscnt; - if (s->c == K_DEL) { - ccline.cmdpos += mb_off_next((char_u *)ccline.cmdbuff, - (char_u *)ccline.cmdbuff + ccline.cmdpos); - } + // save current command string so it can be restored later + if (s->lookfor == NULL) { + s->lookfor = xstrdup(ccline.cmdbuff); + s->lookfor[ccline.cmdpos] = NUL; + } - if (ccline.cmdpos > 0) { - char_u *p; + bool next_match = (s->c == K_DOWN || s->c == K_S_DOWN || s->c == Ctrl_N + || s->c == K_PAGEDOWN || s->c == K_KPAGEDOWN); + command_line_next_histidx(s, next_match); - int j = ccline.cmdpos; - p = mb_prevptr((char_u *)ccline.cmdbuff, (char_u *)ccline.cmdbuff + j); + if (s->hiscnt != s->save_hiscnt) { + // jumped to other entry + char *p; + int len = 0; + int old_firstc; - if (s->c == Ctrl_W) { - while (p > (char_u *)ccline.cmdbuff && ascii_isspace(*p)) { - p = mb_prevptr((char_u *)ccline.cmdbuff, p); - } + XFREE_CLEAR(ccline.cmdbuff); + s->xpc.xp_context = EXPAND_NOTHING; + if (s->hiscnt == get_hislen()) { + p = s->lookfor; // back to the old one + } else { + p = get_histentry(s->histype)[s->hiscnt].hisstr; + } + + if (s->histype == HIST_SEARCH + && p != s->lookfor + && (old_firstc = (uint8_t)p[strlen(p) + 1]) != s->firstc) { + // Correct for the separator character used when + // adding the history entry vs the one used now. + // First loop: count length. + // Second loop: copy the characters. + for (int i = 0; i <= 1; i++) { + len = 0; + for (int j = 0; p[j] != NUL; j++) { + // Replace old sep with new sep, unless it is + // escaped. + if (p[j] == old_firstc + && (j == 0 || p[j - 1] != '\\')) { + if (i > 0) { + ccline.cmdbuff[len] = (char)s->firstc; + } + } else { + // Escape new sep, unless it is already + // escaped. + if (p[j] == s->firstc + && (j == 0 || p[j - 1] != '\\')) { + if (i > 0) { + ccline.cmdbuff[len] = '\\'; + } + len++; + } - int i = mb_get_class(p); - while (p > (char_u *)ccline.cmdbuff && mb_get_class(p) == i) { - p = mb_prevptr((char_u *)ccline.cmdbuff, p); + if (i > 0) { + ccline.cmdbuff[len] = p[j]; + } + } + len++; } - if (mb_get_class(p) != i) { - p += utfc_ptr2len((char *)p); + if (i == 0) { + alloc_cmdbuff(len); } } + ccline.cmdbuff[len] = NUL; + } else { + alloc_cmdbuff((int)strlen(p)); + STRCPY(ccline.cmdbuff, p); + } - ccline.cmdpos = (int)(p - (char_u *)ccline.cmdbuff); - ccline.cmdlen -= j - ccline.cmdpos; - int i = ccline.cmdpos; - - while (i < ccline.cmdlen) { - ccline.cmdbuff[i++] = ccline.cmdbuff[j++]; - } - - // Truncate at the end, required for multi-byte chars. - ccline.cmdbuff[ccline.cmdlen] = NUL; - if (ccline.cmdlen == 0) { - s->is_state.search_start = s->is_state.save_cursor; - // save view settings, so that the screen won't be restored at the - // wrong position - s->is_state.old_viewstate = s->is_state.init_viewstate; - } - redrawcmd(); - } else if (ccline.cmdlen == 0 && s->c != Ctrl_W - && ccline.cmdprompt == NULL && s->indent == 0) { - // In ex and debug mode it doesn't make sense to return. - if (exmode_active || ccline.cmdfirstc == '>') { - return command_line_not_changed(s); - } + ccline.cmdpos = ccline.cmdlen = (int)strlen(ccline.cmdbuff); + redrawcmd(); + return CMDLINE_CHANGED; + } + beep_flush(); + return CMDLINE_NOT_CHANGED; +} - XFREE_CLEAR(ccline.cmdbuff); // no commandline to return - if (!cmd_silent && !ui_has(kUICmdline)) { - if (cmdmsg_rl) { - msg_col = Columns; - } else { - msg_col = 0; - } - msg_putchar(' '); // delete ':' - } - s->is_state.search_start = s->is_state.save_cursor; - redraw_cmdline = true; - return 0; // back to cmd mode +static int command_line_handle_key(CommandLineState *s) +{ + // Big switch for a typed command line character. + switch (s->c) { + case K_BS: + case Ctrl_H: + case K_DEL: + case K_KDEL: + case Ctrl_W: + switch (command_line_erase_chars(s)) { + case CMDLINE_NOT_CHANGED: + return command_line_not_changed(s); + case GOTO_NORMAL_MODE: + return 0; // back to cmd mode + default: + return command_line_changed(s); } - return command_line_changed(s); case K_INS: case K_KINS: @@ -1533,28 +1819,7 @@ static int command_line_handle_key(CommandLineState *s) return command_line_not_changed(s); case Ctrl_HAT: - if (map_to_exists_mode("", MODE_LANGMAP, false)) { - // ":lmap" mappings exists, toggle use of mappings. - State ^= MODE_LANGMAP; - if (s->b_im_ptr != NULL) { - if (State & MODE_LANGMAP) { - *s->b_im_ptr = B_IMODE_LMAP; - } else { - *s->b_im_ptr = B_IMODE_NONE; - } - } - } - - if (s->b_im_ptr != NULL) { - if (s->b_im_ptr == &curbuf->b_p_iminsert) { - set_iminsert_global(); - } else { - set_imsearch_global(); - } - } - ui_cursor_shape(); // may show different cursor shape - // Show/unshow value of 'keymap' in status lines later. - status_redraw_curbuf(); + command_line_toggle_langmap(s); return command_line_not_changed(s); case Ctrl_U: { @@ -1590,58 +1855,15 @@ static int command_line_handle_key(CommandLineState *s) // putting it in history return 0; // back to cmd mode - case Ctrl_R: { // insert register - putcmdline('"', true); - no_mapping++; - allow_keys++; - int i = s->c = plain_vgetc(); // CTRL-R <char> - if (i == Ctrl_O) { - i = Ctrl_R; // CTRL-R CTRL-O == CTRL-R CTRL-R - } - - if (i == Ctrl_R) { - s->c = plain_vgetc(); // CTRL-R CTRL-R <char> - } - no_mapping--; - allow_keys--; - // Insert the result of an expression. - // Need to save the current command line, to be able to enter - // a new one... - new_cmdpos = -1; - if (s->c == '=') { - if (ccline.cmdfirstc == '=' // can't do this recursively - || cmdline_star > 0) { // or when typing a password - beep_flush(); - s->c = ESC; - } else { - s->c = get_expr_register(); - } - } - - if (s->c != ESC) { // use ESC to cancel inserting register - cmdline_paste(s->c, i == Ctrl_R, false); - - // When there was a serious error abort getting the - // command line. - if (aborting()) { - s->gotesc = true; // will free ccline.cmdbuff after - // putting it in history - return 0; // back to cmd mode - } - KeyTyped = false; // Don't do p_wc completion. - if (new_cmdpos >= 0) { - // set_cmdline_pos() was used - if (new_cmdpos > ccline.cmdlen) { - ccline.cmdpos = ccline.cmdlen; - } else { - ccline.cmdpos = new_cmdpos; - } - } + case Ctrl_R: // insert register + switch (command_line_insert_reg(s)) { + case CMDLINE_NOT_CHANGED: + return command_line_not_changed(s); + case GOTO_NORMAL_MODE: + return 0; // back to cmd mode + default: + return command_line_changed(s); } - ccline.special_char = NUL; - redrawcmd(); - return command_line_changed(s); - } case Ctrl_D: if (showmatches(&s->xpc, false) == EXPAND_NOTHING) { @@ -1722,26 +1944,7 @@ static int command_line_handle_key(CommandLineState *s) FALLTHROUGH; case K_LEFTMOUSE: case K_RIGHTMOUSE: - if (s->c == K_LEFTRELEASE || s->c == K_RIGHTRELEASE) { - s->ignore_drag_release = true; - } else { - s->ignore_drag_release = false; - } - - ccline.cmdspos = cmd_startcol(); - for (ccline.cmdpos = 0; ccline.cmdpos < ccline.cmdlen; - ccline.cmdpos++) { - int cells = cmdline_charsize(ccline.cmdpos); - if (mouse_row <= cmdline_row + ccline.cmdspos / Columns - && mouse_col < ccline.cmdspos % Columns + cells) { - break; - } - - // Count ">" for double-wide char that doesn't fit. - correct_screencol(ccline.cmdpos, cells, &ccline.cmdspos); - ccline.cmdpos += utfc_ptr2len(ccline.cmdbuff + ccline.cmdpos) - 1; - ccline.cmdspos += cells; - } + command_line_left_right_mouse(s); return command_line_not_changed(s); // Mouse scroll wheel: ignored here @@ -1808,8 +2011,8 @@ static int command_line_handle_key(CommandLineState *s) case Ctrl_N: // next match case Ctrl_P: // previous match if (s->xpc.xp_numfiles > 0) { - if (nextwild(&s->xpc, (s->c == Ctrl_P) ? WILD_PREV : WILD_NEXT, - 0, s->firstc != '@') == FAIL) { + const int wild_type = (s->c == Ctrl_P) ? WILD_PREV : WILD_NEXT; + if (nextwild(&s->xpc, wild_type, 0, s->firstc != '@') == FAIL) { break; } return command_line_not_changed(s); @@ -1824,88 +2027,27 @@ static int command_line_handle_key(CommandLineState *s) case K_KPAGEUP: case K_PAGEDOWN: case K_KPAGEDOWN: - if (s->histype == HIST_INVALID || get_hislen() == 0 || s->firstc == NUL) { - // no history - return command_line_not_changed(s); - } - - s->save_hiscnt = s->hiscnt; - - // save current command string so it can be restored later - if (s->lookfor == NULL) { - s->lookfor = xstrdup(ccline.cmdbuff); - s->lookfor[ccline.cmdpos] = NUL; - } - - bool next_match = (s->c == K_DOWN || s->c == K_S_DOWN || s->c == Ctrl_N - || s->c == K_PAGEDOWN || s->c == K_KPAGEDOWN); - command_line_next_histidx(s, next_match); - - if (s->hiscnt != s->save_hiscnt) { - // jumped to other entry - char_u *p; - int len = 0; - int old_firstc; - - XFREE_CLEAR(ccline.cmdbuff); - s->xpc.xp_context = EXPAND_NOTHING; - if (s->hiscnt == get_hislen()) { - p = (char_u *)s->lookfor; // back to the old one - } else { - p = (char_u *)get_histentry(s->histype)[s->hiscnt].hisstr; + if (cmdline_pum_active() + && (s->c == K_PAGEUP || s->c == K_PAGEDOWN + || s->c == K_KPAGEUP || s->c == K_KPAGEDOWN)) { + // If the popup menu is displayed, then PageUp and PageDown + // are used to scroll the menu. + const int wild_type = + (s->c == K_PAGEDOWN || s->c == K_KPAGEDOWN) ? WILD_PAGEDOWN : WILD_PAGEUP; + if (nextwild(&s->xpc, wild_type, 0, s->firstc != '@') == FAIL) { + break; } - - if (s->histype == HIST_SEARCH - && p != (char_u *)s->lookfor - && (old_firstc = p[STRLEN(p) + 1]) != s->firstc) { - // Correct for the separator character used when - // adding the history entry vs the one used now. - // First loop: count length. - // Second loop: copy the characters. - for (int i = 0; i <= 1; i++) { - len = 0; - for (int j = 0; p[j] != NUL; j++) { - // Replace old sep with new sep, unless it is - // escaped. - if (p[j] == old_firstc - && (j == 0 || p[j - 1] != '\\')) { - if (i > 0) { - ccline.cmdbuff[len] = (char)s->firstc; - } - } else { - // Escape new sep, unless it is already - // escaped. - if (p[j] == s->firstc - && (j == 0 || p[j - 1] != '\\')) { - if (i > 0) { - ccline.cmdbuff[len] = '\\'; - } - len++; - } - - if (i > 0) { - ccline.cmdbuff[len] = (char)p[j]; - } - } - len++; - } - - if (i == 0) { - alloc_cmdbuff(len); - } - } - ccline.cmdbuff[len] = NUL; - } else { - alloc_cmdbuff((int)STRLEN(p)); - STRCPY(ccline.cmdbuff, p); + return command_line_not_changed(s); + } else { + switch (command_line_browse_history(s)) { + case CMDLINE_CHANGED: + return command_line_changed(s); + case GOTO_NORMAL_MODE: + return 0; + default: + return command_line_not_changed(s); } - - ccline.cmdpos = ccline.cmdlen = (int)strlen(ccline.cmdbuff); - redrawcmd(); - return command_line_changed(s); } - beep_flush(); - return command_line_not_changed(s); case Ctrl_G: // next match case Ctrl_T: // previous match @@ -1981,11 +2123,11 @@ static int command_line_handle_key(CommandLineState *s) // put the character in the command line if (IS_SPECIAL(s->c) || mod_mask != 0) { - put_on_cmdline(get_special_key_name(s->c, mod_mask), -1, true); + put_on_cmdline((char *)get_special_key_name(s->c, mod_mask), -1, true); } else { - int j = utf_char2bytes(s->c, (char *)IObuff); + int j = utf_char2bytes(s->c, IObuff); IObuff[j] = NUL; // exclude composing chars - put_on_cmdline((char_u *)IObuff, j, true); + put_on_cmdline(IObuff, j, true); } return command_line_changed(s); } @@ -2005,16 +2147,35 @@ static int command_line_not_changed(CommandLineState *s) /// Guess that the pattern matches everything. Only finds specific cases, such /// as a trailing \|, which can happen while typing a pattern. -static int empty_pattern(char *p) +static bool empty_pattern(char *p, int delim) { size_t n = strlen(p); + magic_T magic_val = MAGIC_ON; - // remove trailing \v and the like - while (n >= 2 && p[n - 2] == '\\' - && vim_strchr("mMvVcCZ", p[n - 1]) != NULL) { - n -= 2; + if (n > 0) { + (void)skip_regexp_ex(p, delim, magic_isset(), NULL, NULL, &magic_val); + } else { + return true; } - return n == 0 || (n >= 2 && p[n - 2] == '\\' && p[n - 1] == '|'); + + return empty_pattern_magic(p, n, magic_val); +} + +static bool empty_pattern_magic(char *p, size_t len, magic_T magic_val) +{ + // remove trailing \v and the like + while (len >= 2 && p[len - 2] == '\\' + && vim_strchr("mMvVcCZ", (uint8_t)p[len - 1]) != NULL) { + len -= 2; + } + + // true, if the pattern is empty, or the pattern ends with \| and magic is + // set (or it ends with '|' and very magic is set) + return len == 0 || (len > 1 + && ((p[len - 2] == '\\' + && p[len - 1] == '|' && magic_val == MAGIC_ON) + || (p[len - 2] != '\\' + && p[len - 1] == '|' && magic_val == MAGIC_ALL))); } handle_T cmdpreview_get_bufnr(void) @@ -2384,7 +2545,8 @@ static void do_autocmd_cmdlinechanged(int firstc) bool tl_ret = try_leave(&tstate, &err); if (!tl_ret && ERROR_SET(&err)) { msg_putchar('\n'); - msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg); + msg_scroll = true; + msg_puts_attr(err.msg, HL_ATTR(HLF_E)|MSG_HIST); api_clear_error(&err); redrawcmd(); } @@ -2463,9 +2625,9 @@ static void abandon_cmdline(void) /// /// @param count only used for incremental search /// @param indent indent for inside conditionals -char_u *getcmdline(int firstc, long count, int indent, bool do_concat FUNC_ATTR_UNUSED) +char *getcmdline(int firstc, long count, int indent, bool do_concat FUNC_ATTR_UNUSED) { - return command_line_enter(firstc, count, indent, true); + return (char *)command_line_enter(firstc, count, indent, true); } /// Get a command line with a prompt @@ -2498,10 +2660,10 @@ char *getcmdline_prompt(const int firstc, const char *const prompt, const int at CLEAR_FIELD(ccline); } ccline.prompt_id = last_prompt_id++; - ccline.cmdprompt = (char_u *)prompt; + ccline.cmdprompt = (char *)prompt; ccline.cmdattr = attr; ccline.xp_context = xp_context; - ccline.xp_arg = (char_u *)xp_arg; + ccline.xp_arg = (char *)xp_arg; ccline.input_fn = (firstc == '@'); ccline.highlight_callback = highlight_callback; @@ -2525,12 +2687,6 @@ char *getcmdline_prompt(const int firstc, const char *const prompt, const int at return ret; } -// Return current cmdline prompt -char_u *get_cmdprompt(void) -{ - return ccline.cmdprompt; -} - /// Read the 'wildmode' option, fill wim_flags[]. int check_opt_wim(void) { @@ -2547,13 +2703,13 @@ int check_opt_wim(void) if (p[i] != NUL && p[i] != ',' && p[i] != ':') { return FAIL; } - if (i == 7 && STRNCMP(p, "longest", 7) == 0) { + if (i == 7 && strncmp(p, "longest", 7) == 0) { new_wim_flags[idx] |= WIM_LONGEST; - } else if (i == 4 && STRNCMP(p, "full", 4) == 0) { + } else if (i == 4 && strncmp(p, "full", 4) == 0) { new_wim_flags[idx] |= WIM_FULL; - } else if (i == 4 && STRNCMP(p, "list", 4) == 0) { + } else if (i == 4 && strncmp(p, "list", 4) == 0) { new_wim_flags[idx] |= WIM_LIST; - } else if (i == 8 && STRNCMP(p, "lastused", 8) == 0) { + } else if (i == 8 && strncmp(p, "lastused", 8) == 0) { new_wim_flags[idx] |= WIM_BUFLASTUSED; } else { return FAIL; @@ -2710,7 +2866,7 @@ char *getexline(int c, void *cookie, int indent, bool do_concat) (void)vgetc(); } - return (char *)getcmdline(c, 1L, indent, do_concat); + return getcmdline(c, 1L, indent, do_concat); } bool cmdline_overstrike(void) @@ -2748,7 +2904,7 @@ void realloc_cmdbuff(int len) return; // no need to resize } - char_u *p = (char_u *)ccline.cmdbuff; + char *p = ccline.cmdbuff; alloc_cmdbuff(len); // will get some more // There isn't always a NUL after the command, but it may need to be // there, thus copy up to the NUL and add a NUL. @@ -2760,7 +2916,7 @@ void realloc_cmdbuff(int len) && ccline.xpc->xp_pattern != NULL && ccline.xpc->xp_context != EXPAND_NOTHING && ccline.xpc->xp_context != EXPAND_UNSUCCESSFUL) { - int i = (int)((char_u *)ccline.xpc->xp_pattern - p); + int i = (int)(ccline.xpc->xp_pattern - p); // If xp_pattern points inside the old cmdbuff it needs to be adjusted // to point into the newly allocated memory. @@ -3095,10 +3251,10 @@ static void draw_cmdline(int start, int len) bool do_arabicshape = false; int mb_l; for (int i = start; i < start + len; i += mb_l) { - char_u *p = (char_u *)ccline.cmdbuff + i; + char *p = ccline.cmdbuff + i; int u8cc[MAX_MCO]; int u8c = utfc_ptr2char_len(p, u8cc, start + len - i); - mb_l = utfc_ptr2len_len((char *)p, start + len - i); + mb_l = utfc_ptr2len_len(p, start + len - i); if (ARABIC_CHAR(u8c)) { do_arabicshape = true; break; @@ -3131,10 +3287,10 @@ static void draw_cmdline(int start, int len) int prev_c = 0; int prev_c1 = 0; for (int i = start; i < start + len; i += mb_l) { - char_u *p = (char_u *)ccline.cmdbuff + i; + char *p = ccline.cmdbuff + i; int u8cc[MAX_MCO]; int u8c = utfc_ptr2char_len(p, u8cc, start + len - i); - mb_l = utfc_ptr2len_len((char *)p, start + len - i); + mb_l = utfc_ptr2len_len(p, start + len - i); if (ARABIC_CHAR(u8c)) { int pc; int pc1 = 0; @@ -3148,7 +3304,7 @@ static void draw_cmdline(int start, int len) if (i + mb_l >= start + len) { nc = NUL; } else { - nc = utf_ptr2char((char *)p + mb_l); + nc = utf_ptr2char(p + mb_l); } } else { // Displaying from left to right. @@ -3207,7 +3363,7 @@ static void ui_ext_cmdline_show(CmdlineInfo *line) if (cmdline_star) { content = arena_array(&arena, 1); size_t len = 0; - for (char_u *p = (char_u *)ccline.cmdbuff; *p; MB_PTR_ADV(p)) { + for (char *p = ccline.cmdbuff; *p; MB_PTR_ADV(p)) { len++; } char *buf = arena_alloc(&arena, len, false); @@ -3238,7 +3394,7 @@ static void ui_ext_cmdline_show(CmdlineInfo *line) char charbuf[2] = { (char)line->cmdfirstc, 0 }; ui_call_cmdline_show(content, line->cmdpos, cstr_as_string(charbuf), - cstr_as_string((char *)(line->cmdprompt)), + cstr_as_string((line->cmdprompt)), line->cmdindent, line->level); if (line->special_char) { @@ -3374,14 +3530,14 @@ void unputcmdline(void) // part will be redrawn, otherwise it will not. If this function is called // twice in a row, then 'redraw' should be false and redrawcmd() should be // called afterwards. -void put_on_cmdline(char_u *str, int len, int redraw) +void put_on_cmdline(char *str, int len, int redraw) { int i; int m; int c; if (len < 0) { - len = (int)STRLEN(str); + len = (int)strlen(str); } realloc_cmdbuff(ccline.cmdlen + len + 1); @@ -3394,7 +3550,7 @@ void put_on_cmdline(char_u *str, int len, int redraw) } else { // Count nr of characters in the new string. m = 0; - for (i = 0; i < len; i += utfc_ptr2len((char *)str + i)) { + for (i = 0; i < len; i += utfc_ptr2len(str + i)) { m++; } // Count nr of bytes in cmdline that are overwritten by these @@ -3520,7 +3676,7 @@ static void restore_cmdline(CmdlineInfo *ccp) static bool cmdline_paste(int regname, bool literally, bool remcr) { char *arg; - char_u *p; + char *p; bool allocated; // check for valid regname; also accept special characters for CTRL-R in @@ -3552,21 +3708,21 @@ static bool cmdline_paste(int regname, bool literally, bool remcr) // When 'incsearch' is set and CTRL-R CTRL-W used: skip the duplicate // part of the word. - p = (char_u *)arg; + p = arg; if (p_is && regname == Ctrl_W) { - char_u *w; + char *w; int len; // Locate start of last word in the cmd buffer. - for (w = (char_u *)ccline.cmdbuff + ccline.cmdpos; w > (char_u *)ccline.cmdbuff;) { - len = utf_head_off(ccline.cmdbuff, (char *)w - 1) + 1; - if (!vim_iswordc(utf_ptr2char((char *)w - len))) { + for (w = ccline.cmdbuff + ccline.cmdpos; w > ccline.cmdbuff;) { + len = utf_head_off(ccline.cmdbuff, w - 1) + 1; + if (!vim_iswordc(utf_ptr2char(w - len))) { break; } w -= len; } - len = (int)(((char_u *)ccline.cmdbuff + ccline.cmdpos) - w); - if (p_ic ? STRNICMP(w, arg, len) == 0 : STRNCMP(w, arg, len) == 0) { + len = (int)((ccline.cmdbuff + ccline.cmdpos) - w); + if (p_ic ? STRNICMP(w, arg, len) == 0 : strncmp(w, arg, (size_t)len) == 0) { p += len; } } @@ -3585,7 +3741,7 @@ static bool cmdline_paste(int regname, bool literally, bool remcr) // When "literally" is true, insert literally. // When "literally" is false, insert as typed, but don't leave the command // line. -void cmdline_paste_str(char_u *s, int literally) +void cmdline_paste_str(char *s, int literally) { int c, cv; @@ -3593,11 +3749,11 @@ void cmdline_paste_str(char_u *s, int literally) put_on_cmdline(s, -1, true); } else { while (*s != NUL) { - cv = *s; + cv = (uint8_t)(*s); if (cv == Ctrl_V && s[1]) { s++; } - c = mb_cptr2char_adv((const char_u **)&s); + c = mb_cptr2char_adv((const char **)&s); if (cv == Ctrl_V || c == ESC || c == Ctrl_C || c == CAR || c == NL || c == Ctrl_L || (c == Ctrl_BSL && *s == Ctrl_N)) { @@ -3638,7 +3794,7 @@ static void redrawcmdprompt(void) msg_putchar(ccline.cmdfirstc); } if (ccline.cmdprompt != NULL) { - msg_puts_attr((const char *)ccline.cmdprompt, ccline.cmdattr); + msg_puts_attr(ccline.cmdprompt, ccline.cmdattr); ccline.cmdindent = msg_col + (msg_row - cmdline_row) * Columns; // do the reverse of cmd_startcol() if (ccline.cmdfirstc != NUL) { @@ -3708,7 +3864,7 @@ void compute_cmdrow(void) cmdline_row = wp->w_winrow + wp->w_height + wp->w_hsep_height + wp->w_status_height + global_stl_height(); } - if (cmdline_row == Rows) { + if (cmdline_row == Rows && p_ch > 0) { cmdline_row--; } lines_left = cmdline_row; @@ -3796,7 +3952,7 @@ static int ccheck_abbr(int c) spos = 0; } - return check_abbr(c, (char_u *)ccline.cmdbuff, ccline.cmdpos, spos); + return check_abbr(c, ccline.cmdbuff, ccline.cmdpos, spos); } /// Escape special characters in "fname", depending on "what": @@ -3813,34 +3969,31 @@ char *vim_strsave_fnameescape(const char *const fname, const int what) { #ifdef BACKSLASH_IN_FILENAME # define PATH_ESC_CHARS " \t\n*?[{`%#'\"|!<" -# define BUFFER_ESC_CHARS ((char_u *)" \t\n*?[`%#'\"|!<") - char_u buf[sizeof(PATH_ESC_CHARS)]; +# define BUFFER_ESC_CHARS (" \t\n*?[`%#'\"|!<") + char buf[sizeof(PATH_ESC_CHARS)]; int j = 0; // Don't escape '[', '{' and '!' if they are in 'isfname' and for the // ":buffer" command. for (const char *p = what == VSE_BUFFER ? BUFFER_ESC_CHARS : PATH_ESC_CHARS; *p != NUL; p++) { - if ((*p != '[' && *p != '{' && *p != '!') || !vim_isfilec(*p)) { + if ((*p != '[' && *p != '{' && *p != '!') || !vim_isfilec((uint8_t)(*p))) { buf[j++] = *p; } } buf[j] = NUL; - char *p = (char *)vim_strsave_escaped((const char_u *)fname, - (const char_u *)buf); + char *p = vim_strsave_escaped(fname, buf); #else -# define PATH_ESC_CHARS ((char_u *)" \t\n*?[{`$\\%#'\"|!<") -# define SHELL_ESC_CHARS ((char_u *)" \t\n*?[{`$\\%#'\"|!<>();&") -# define BUFFER_ESC_CHARS ((char_u *)" \t\n*?[`$\\%#'\"|!<") - char *p = - (char *)vim_strsave_escaped((const char_u *)fname, - what == VSE_SHELL ? SHELL_ESC_CHARS - : what == VSE_BUFFER ? BUFFER_ESC_CHARS : PATH_ESC_CHARS); +# define PATH_ESC_CHARS " \t\n*?[{`$\\%#'\"|!<" +# define SHELL_ESC_CHARS " \t\n*?[{`$\\%#'\"|!<>();&" +# define BUFFER_ESC_CHARS " \t\n*?[`$\\%#'\"|!<" + char *p = vim_strsave_escaped(fname, + what == VSE_SHELL ? SHELL_ESC_CHARS : what == + VSE_BUFFER ? BUFFER_ESC_CHARS : PATH_ESC_CHARS); if (what == VSE_SHELL && csh_like_shell()) { // For csh and similar shells need to put two backslashes before '!'. // One is taken by Vim, one by the shell. - char *s = (char *)vim_strsave_escaped((const char_u *)p, - (const char_u *)"!"); + char *s = vim_strsave_escaped(p, "!"); xfree(p); p = s; } @@ -3858,16 +4011,16 @@ char *vim_strsave_fnameescape(const char *const fname, const int what) /// Put a backslash before the file name in "pp", which is in allocated memory. void escape_fname(char **pp) { - char_u *p = xmalloc(strlen(*pp) + 2); + char *p = xmalloc(strlen(*pp) + 2); p[0] = '\\'; STRCPY(p + 1, *pp); xfree(*pp); - *pp = (char *)p; + *pp = p; } /// For each file name in files[num_files]: /// If 'orig_pat' starts with "~/", replace the home directory with "~". -void tilde_replace(char_u *orig_pat, int num_files, char **files) +void tilde_replace(char *orig_pat, int num_files, char **files) { char *p; @@ -3948,12 +4101,14 @@ static char *get_cmdline_completion(void) } CmdlineInfo *p = get_ccline_ptr(); - if (p != NULL && p->xpc != NULL) { - set_expand_context(p->xpc); - char *cmd_compl = get_user_cmd_complete(p->xpc, p->xpc->xp_context); - if (cmd_compl != NULL) { - return xstrdup(cmd_compl); - } + if (p == NULL || p->xpc == NULL) { + return NULL; + } + + set_expand_context(p->xpc); + char *cmd_compl = get_user_cmd_complete(p->xpc, p->xpc->xp_context); + if (cmd_compl != NULL) { + return xstrdup(cmd_compl); } return NULL; @@ -4134,7 +4289,7 @@ char *check_cedit(void) if (*p_cedit == NUL) { cedit_key = -1; } else { - n = string_to_key((char_u *)p_cedit); + n = string_to_key(p_cedit); if (vim_isprintc(n)) { return e_invarg; } diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h index 919a6c8f11..61ac4b69c5 100644 --- a/src/nvim/ex_getln.h +++ b/src/nvim/ex_getln.h @@ -1,10 +1,16 @@ #ifndef NVIM_EX_GETLN_H #define NVIM_EX_GETLN_H +#include <stdbool.h> + +#include "klib/kvec.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/types.h" +struct cmdline_info; + /// Command-line colors: one chunk /// /// Defines a region which has the same highlighting. @@ -48,14 +54,14 @@ struct cmdline_info { int cmdspos; ///< cursor column on screen int cmdfirstc; ///< ':', '/', '?', '=', '>' or NUL int cmdindent; ///< number of spaces before cmdline - char_u *cmdprompt; ///< message in front of cmdline + char *cmdprompt; ///< message in front of cmdline int cmdattr; ///< attributes for prompt int overstrike; ///< Typing mode on the command line. Shared by ///< getcmdline() and put_on_cmdline(). expand_T *xpc; ///< struct being used for expansion, xp_pattern ///< may point into cmdbuff int xp_context; ///< type of expansion - char_u *xp_arg; ///< user-defined expansion arg + char *xp_arg; ///< user-defined expansion arg int input_fn; ///< when true Invoked for input() function unsigned prompt_id; ///< Prompt number, used to disable coloring on errors. Callback highlight_callback; ///< Callback used for coloring user input. diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index e907c45e79..3de5e1db52 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -9,31 +9,34 @@ #include <assert.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> -#include <stdlib.h> +#include <stdio.h> #include <string.h> #include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/buffer.h" -#include "nvim/cursor.h" -#include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/ex_session.h" #include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" #include "nvim/globals.h" -#include "nvim/keycodes.h" +#include "nvim/macros.h" #include "nvim/mapping.h" -#include "nvim/move.h" +#include "nvim/mark_defs.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/option.h" -#include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os/time.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/runtime.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -109,40 +112,43 @@ static int ses_win_rec(FILE *fd, frame_T *fr) frame_T *frc; int count = 0; - if (fr->fr_layout != FR_LEAF) { - // Find first frame that's not skipped and then create a window for - // each following one (first frame is already there). - frc = ses_skipframe(fr->fr_child); - if (frc != NULL) { - while ((frc = ses_skipframe(frc->fr_next)) != NULL) { - // Make window as big as possible so that we have lots of room - // to split. - if (fprintf(fd, "%s%s", - "wincmd _ | wincmd |\n", - (fr->fr_layout == FR_COL ? "split\n" : "vsplit\n")) < 0) { - return FAIL; - } - count++; + if (fr->fr_layout == FR_LEAF) { + return OK; + } + + // Find first frame that's not skipped and then create a window for + // each following one (first frame is already there). + frc = ses_skipframe(fr->fr_child); + if (frc != NULL) { + while ((frc = ses_skipframe(frc->fr_next)) != NULL) { + // Make window as big as possible so that we have lots of room + // to split. + if (fprintf(fd, "%s%s", + "wincmd _ | wincmd |\n", + (fr->fr_layout == FR_COL ? "split\n" : "vsplit\n")) < 0) { + return FAIL; } + count++; } + } - // Go back to the first window. - if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL - ? "%dwincmd k\n" : "%dwincmd h\n", count) < 0)) { - return FAIL; - } + // Go back to the first window. + if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL + ? "%dwincmd k\n" : "%dwincmd h\n", count) < 0)) { + return FAIL; + } - // Recursively create frames/windows in each window of this column or row. - frc = ses_skipframe(fr->fr_child); - while (frc != NULL) { - ses_win_rec(fd, frc); - frc = ses_skipframe(frc->fr_next); - // Go to next window. - if (frc != NULL && put_line(fd, "wincmd w") == FAIL) { - return FAIL; - } + // Recursively create frames/windows in each window of this column or row. + frc = ses_skipframe(fr->fr_child); + while (frc != NULL) { + ses_win_rec(fd, frc); + frc = ses_skipframe(frc->fr_next); + // Go to next window. + if (frc != NULL && put_line(fd, "wincmd w") == FAIL) { + return FAIL; } } + return OK; } @@ -211,22 +217,22 @@ static int ses_do_win(win_T *wp) /// @returns FAIL if writing fails. static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, unsigned *flagp) { - char_u *buf = NULL; - char_u *s; + char *buf = NULL; + char *s; if (fprintf(fd, "%s\n%s\n", cmd, "%argdel") < 0) { return FAIL; } for (int i = 0; i < gap->ga_len; i++) { // NULL file names are skipped (only happens when out of memory). - s = (char_u *)alist_name(&((aentry_T *)gap->ga_data)[i]); + s = alist_name(&((aentry_T *)gap->ga_data)[i]); if (s != NULL) { if (fullname) { buf = xmalloc(MAXPATHL); - (void)vim_FullName((char *)s, (char *)buf, MAXPATHL, false); + (void)vim_FullName(s, buf, MAXPATHL, false); s = buf; } - char *fname_esc = ses_escape_fname((char *)s, flagp); + char *fname_esc = ses_escape_fname(s, flagp); if (fprintf(fd, "$argadd %s\n", fname_esc) < 0) { xfree(fname_esc); xfree(buf); @@ -240,7 +246,7 @@ static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, unsigne } /// @return the buffer name for `buf`. -static char *ses_get_fname(buf_T *buf, unsigned *flagp) +static char *ses_get_fname(buf_T *buf, const unsigned *flagp) { // Use the short file name if the current directory is known at the time // the session file will be sourced. @@ -264,7 +270,7 @@ static char *ses_get_fname(buf_T *buf, unsigned *flagp) static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol) { char *name = ses_get_fname(buf, flagp); - if (ses_put_fname(fd, (char_u *)name, flagp) == FAIL + if (ses_put_fname(fd, name, flagp) == FAIL || (add_eol && fprintf(fd, "\n") < 0)) { return FAIL; } @@ -299,9 +305,9 @@ static char *ses_escape_fname(char *name, unsigned *flagp) /// characters. /// /// @return FAIL if writing fails. -static int ses_put_fname(FILE *fd, char_u *name, unsigned *flagp) +static int ses_put_fname(FILE *fd, char *name, unsigned *flagp) { - char *p = ses_escape_fname((char *)name, flagp); + char *p = ses_escape_fname(name, flagp); bool retval = fputs(p, fd) < 0 ? FAIL : OK; xfree(p); return retval; @@ -523,7 +529,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr if (wp->w_localdir != NULL && (flagp != &vop_flags || (*flagp & SSOP_CURDIR))) { if (fputs("lcd ", fd) < 0 - || ses_put_fname(fd, (char_u *)wp->w_localdir, flagp) == FAIL + || ses_put_fname(fd, wp->w_localdir, flagp) == FAIL || fprintf(fd, "\n") < 0) { return FAIL; } @@ -542,7 +548,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr /// @param fd File descriptor to write to /// /// @return FAIL on error, OK otherwise. -static int makeopens(FILE *fd, char_u *dirnow) +static int makeopens(FILE *fd, char *dirnow) { int only_save_windows = true; int nr; @@ -581,7 +587,7 @@ static int makeopens(FILE *fd, char_u *dirnow) if (ssop_flags & SSOP_SESDIR) { PUTLINE_FAIL("exe \"cd \" . escape(expand(\"<sfile>:p:h\"), ' ')"); } else if (ssop_flags & SSOP_CURDIR) { - sname = home_replace_save(NULL, globaldir != NULL ? globaldir : (char *)dirnow); + sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow); char *fname_esc = ses_escape_fname(sname, &ssop_flags); if (fprintf(fd, "cd %s\n", fname_esc) < 0) { xfree(fname_esc); @@ -828,7 +834,7 @@ static int makeopens(FILE *fd, char_u *dirnow) // Take care of tab-local working directories if applicable if (tp->tp_localdir) { if (fputs("if exists(':tcd') == 2 | tcd ", fd) < 0 - || ses_put_fname(fd, (char_u *)tp->tp_localdir, &ssop_flags) == FAIL + || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL || fputs(" | endif\n", fd) < 0) { return FAIL; } @@ -903,12 +909,14 @@ static int makeopens(FILE *fd, char_u *dirnow) void ex_loadview(exarg_T *eap) { char *fname = get_view_file(*eap->arg); - if (fname != NULL) { - if (do_source(fname, false, DOSO_NONE) == FAIL) { - semsg(_(e_notopen), fname); - } - xfree(fname); + if (fname == NULL) { + return; + } + + if (do_source(fname, false, DOSO_NONE) == FAIL) { + semsg(_(e_notopen), fname); } + xfree(fname); } /// ":mkexrc", ":mkvimrc", ":mkview", ":mksession". @@ -956,11 +964,11 @@ void ex_mkrc(exarg_T *eap) } // When using 'viewdir' may have to create the directory. - if (using_vdir && !os_isdir((char *)p_vdir)) { + if (using_vdir && !os_isdir(p_vdir)) { vim_mkdir_emsg((const char *)p_vdir, 0755); } - fd = open_exfile((char_u *)fname, eap->forceit, WRITEBIN); + fd = open_exfile(fname, eap->forceit, WRITEBIN); if (fd != NULL) { if (eap->cmdidx == CMD_mkview) { flagp = &vop_flags; @@ -997,14 +1005,14 @@ void ex_mkrc(exarg_T *eap) failed = true; } if (eap->cmdidx == CMD_mksession) { - char_u *dirnow; // current directory + char *dirnow; // current directory dirnow = xmalloc(MAXPATHL); // // Change to session file's dir. // if (os_dirname(dirnow, MAXPATHL) == FAIL - || os_chdir((char *)dirnow) != 0) { + || os_chdir(dirnow) != 0) { *dirnow = NUL; } if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) { @@ -1024,7 +1032,7 @@ void ex_mkrc(exarg_T *eap) if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR) || ((ssop_flags & SSOP_CURDIR) && globaldir != NULL))) { - if (os_chdir((char *)dirnow) != 0) { + if (os_chdir(dirnow) != 0) { emsg(_(e_prev_dir)); } shorten_fnames(true); @@ -1095,7 +1103,7 @@ static char *get_view_file(int c) len++; } } - char *retval = xmalloc(strlen(sname) + len + STRLEN(p_vdir) + 9); + char *retval = xmalloc(strlen(sname) + len + strlen(p_vdir) + 9); STRCPY(retval, p_vdir); add_pathsep(retval); char *s = retval + strlen(retval); diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index 176ad0d5c8..3e059bcc6c 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -29,20 +29,21 @@ // code for redrawing the line with the deleted decoration. #include <assert.h> +#include <sys/types.h> -#include "klib/kbtree.h" -#include "nvim/api/extmark.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/buffer_updates.h" -#include "nvim/charset.h" #include "nvim/decoration.h" #include "nvim/extmark.h" +#include "nvim/extmark_defs.h" #include "nvim/globals.h" #include "nvim/map.h" +#include "nvim/marktree.h" #include "nvim/memline.h" +#include "nvim/memory.h" #include "nvim/pos.h" #include "nvim/undo.h" -#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "extmark.c.generated.h" @@ -71,7 +72,7 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col || decor->conceal || decor_has_sign(decor) || decor->ui_watched - || decor->spell) { + || decor->spell != kNone) { decor_full = true; decor = xmemdup(decor, sizeof *decor); } @@ -112,6 +113,7 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col marktree_revise(buf->b_marktree, itr, decor_level, old_mark); goto revised; } + decor_remove(buf, old_mark.pos.row, old_mark.pos.row, old_mark.decor_full); marktree_del_itr(buf->b_marktree, itr, false); } } else { diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h index c144504076..657e99a938 100644 --- a/src/nvim/extmark.h +++ b/src/nvim/extmark.h @@ -1,11 +1,18 @@ #ifndef NVIM_EXTMARK_H #define NVIM_EXTMARK_H +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#include "klib/kvec.h" #include "nvim/buffer_defs.h" #include "nvim/decoration.h" #include "nvim/extmark_defs.h" +#include "nvim/macros.h" #include "nvim/marktree.h" #include "nvim/pos.h" +#include "nvim/types.h" EXTERN int extmark_splice_pending INIT(= 0); diff --git a/src/nvim/extmark_defs.h b/src/nvim/extmark_defs.h index 9ef4ec23e5..51b60dcf6d 100644 --- a/src/nvim/extmark_defs.h +++ b/src/nvim/extmark_defs.h @@ -9,6 +9,8 @@ typedef struct { int hl_id; } VirtTextChunk; +typedef kvec_t(VirtTextChunk) VirtText; + 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 d9adf84acc..e236f23895 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -47,25 +47,30 @@ #include <inttypes.h> #include <limits.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include "nvim/ascii.h" #include "nvim/autocmd.h" -#include "nvim/charset.h" +#include "nvim/buffer_defs.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/file_search.h" -#include "nvim/fileio.h" +#include "nvim/gettext.h" #include "nvim/globals.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/option.h" #include "nvim/os/fs_defs.h" #include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os_unix.h" #include "nvim/path.h" #include "nvim/strings.h" -#include "nvim/tag.h" +#include "nvim/types.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -77,8 +82,8 @@ typedef struct ff_stack { // the fix part (no wildcards) and the part containing the wildcards // of the search path - char_u *ffs_fix_path; - char_u *ffs_wc_path; + char *ffs_fix_path; + char *ffs_wc_path; // files/dirs found in the above directory, matched by the first wildcard // of wc_part @@ -138,7 +143,7 @@ typedef struct ff_visited_list_hdr { // '**' can be expanded to several directory levels. // Set the default maximum depth. -#define FF_MAX_STAR_STAR_EXPAND ((char_u)30) +#define FF_MAX_STAR_STAR_EXPAND 30 // The search context: // ffsc_stack_ptr: the stack for the dirs to search @@ -177,7 +182,7 @@ typedef struct ff_search_ctx_T { # include "file_search.c.generated.h" #endif -static char_u e_pathtoolong[] = N_("E854: path too long for completion"); +static char e_pathtoolong[] = N_("E854: path too long for completion"); /// Initialization routine for vim_findfile(). /// @@ -283,9 +288,9 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i && rel_fname != NULL) { size_t len = (size_t)(path_tail(rel_fname) - rel_fname); - if (!vim_isAbsName((char_u *)rel_fname) && len + 1 < MAXPATHL) { + if (!vim_isAbsName(rel_fname) && len + 1 < MAXPATHL) { // Make the start dir an absolute path name. - STRLCPY(ff_expand_buffer, rel_fname, len + 1); + xstrlcpy(ff_expand_buffer, rel_fname, len + 1); search_ctx->ffsc_start_dir = FullName_save(ff_expand_buffer, false); } else { search_ctx->ffsc_start_dir = xstrnsave(rel_fname, len); @@ -293,24 +298,22 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i if (*++path != NUL) { path++; } - } else if (*path == NUL || !vim_isAbsName((char_u *)path)) { + } else if (*path == NUL || !vim_isAbsName(path)) { #ifdef BACKSLASH_IN_FILENAME // "c:dir" needs "c:" to be expanded, otherwise use current dir if (*path != NUL && path[1] == ':') { - char_u drive[3]; + char drive[3]; drive[0] = path[0]; drive[1] = ':'; drive[2] = NUL; - if (vim_FullName((const char *)drive, (char *)ff_expand_buffer, MAXPATHL, - true) - == FAIL) { + if (vim_FullName(drive, ff_expand_buffer, MAXPATHL, true) == FAIL) { goto error_return; } path += 2; - } else + } else // NOLINT(readability/braces) #endif - if (os_dirname((char_u *)ff_expand_buffer, MAXPATHL) == FAIL) { + if (os_dirname(ff_expand_buffer, MAXPATHL) == FAIL) { goto error_return; } @@ -342,7 +345,7 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i } size_t dircount = 1; - search_ctx->ffsc_stopdirs_v = xmalloc(sizeof(char_u *)); + search_ctx->ffsc_stopdirs_v = xmalloc(sizeof(char *)); do { char *helper; @@ -350,7 +353,7 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i helper = walker; ptr = xrealloc(search_ctx->ffsc_stopdirs_v, - (dircount + 1) * sizeof(char_u *)); + (dircount + 1) * sizeof(char *)); search_ctx->ffsc_stopdirs_v = ptr; walker = vim_strchr(walker, ';'); if (walker) { @@ -395,7 +398,7 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i emsg(_(e_pathtoolong)); break; } - if (STRNCMP(wc_part, "**", 2) == 0) { + if (strncmp(wc_part, "**", 2) == 0) { ff_expand_buffer[len++] = *wc_part++; ff_expand_buffer[len++] = *wc_part++; @@ -442,11 +445,11 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i add_pathsep(ff_expand_buffer); { size_t eb_len = strlen(ff_expand_buffer); - char_u *buf = xmalloc(eb_len + strlen(search_ctx->ffsc_fix_path) + 1); + char *buf = xmalloc(eb_len + strlen(search_ctx->ffsc_fix_path) + 1); STRCPY(buf, ff_expand_buffer); STRCPY(buf + eb_len, search_ctx->ffsc_fix_path); - if (os_isdir((char *)buf)) { + if (os_isdir(buf)) { STRCAT(ff_expand_buffer, search_ctx->ffsc_fix_path); add_pathsep(ff_expand_buffer); } else { @@ -458,7 +461,7 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i 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; - if ((len >= 2 && STRNCMP(search_ctx->ffsc_fix_path, "..", 2) == 0) + 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; @@ -499,14 +502,14 @@ error_return: } /// @return the stopdir string. Check that ';' is not escaped. -char_u *vim_findfile_stopdir(char_u *buf) +char *vim_findfile_stopdir(char *buf) { - char_u *r_ptr = buf; + char *r_ptr = buf; while (*r_ptr != NUL && *r_ptr != ';') { if (r_ptr[0] == '\\' && r_ptr[1] == ';') { // Overwrite the escape char, - // use STRLEN(r_ptr) to move the trailing '\0'. + // use strlen(r_ptr) to move the trailing '\0'. STRMOVE(r_ptr, r_ptr + 1); r_ptr++; } @@ -546,14 +549,14 @@ void vim_findfile_cleanup(void *ctx) /// /// @return a pointer to an allocated file name or, /// NULL if nothing found. -char_u *vim_findfile(void *search_ctx_arg) +char *vim_findfile(void *search_ctx_arg) { - char_u *file_path; - char_u *rest_of_wildcards; - char_u *path_end = NULL; + char *file_path; + char *rest_of_wildcards; + char *path_end = NULL; ff_stack_T *stackp = NULL; size_t len; - char_u *p; + char *p; char *suf; ff_search_ctx_T *search_ctx; @@ -569,7 +572,7 @@ char_u *vim_findfile(void *search_ctx_arg) // store the end of the start dir -- needed for upward search if (search_ctx->ffsc_start_dir != NULL) { - path_end = (char_u *)&search_ctx->ffsc_start_dir[strlen(search_ctx->ffsc_start_dir)]; + path_end = &search_ctx->ffsc_start_dir[strlen(search_ctx->ffsc_start_dir)]; } // upward search loop @@ -607,7 +610,7 @@ char_u *vim_findfile(void *search_ctx_arg) // first time (hence stackp->ff_filearray == NULL) if (stackp->ffs_filearray == NULL && ff_check_visited(&search_ctx->ffsc_dir_visited_list->ffvl_visited_list, - (char *)stackp->ffs_fix_path, (char *)stackp->ffs_wc_path) == FAIL) { + stackp->ffs_fix_path, stackp->ffs_wc_path) == FAIL) { #ifdef FF_VERBOSE if (p_verbose >= 5) { verbose_enter_scroll(); @@ -619,16 +622,15 @@ char_u *vim_findfile(void *search_ctx_arg) #endif ff_free_stack_element(stackp); continue; - } #ifdef FF_VERBOSE - else if (p_verbose >= 5) { + } else if (p_verbose >= 5) { verbose_enter_scroll(); smsg("Searching: %s (%s)", stackp->ffs_fix_path, stackp->ffs_wc_path); msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); - } #endif + } // check depth if (stackp->ffs_level <= 0) { @@ -646,7 +648,7 @@ char_u *vim_findfile(void *search_ctx_arg) char *dirptrs[2]; // we use filepath to build the path expand_wildcards() should expand. - dirptrs[0] = (char *)file_path; + dirptrs[0] = file_path; dirptrs[1] = NULL; // if we have a start dir copy it in @@ -657,27 +659,27 @@ char_u *vim_findfile(void *search_ctx_arg) goto fail; } STRCPY(file_path, search_ctx->ffsc_start_dir); - if (!add_pathsep((char *)file_path)) { + if (!add_pathsep(file_path)) { ff_free_stack_element(stackp); goto fail; } } // append the fix part of the search path - if (STRLEN(file_path) + STRLEN(stackp->ffs_fix_path) + 1 >= MAXPATHL) { + if (strlen(file_path) + strlen(stackp->ffs_fix_path) + 1 >= MAXPATHL) { ff_free_stack_element(stackp); goto fail; } STRCAT(file_path, stackp->ffs_fix_path); - if (!add_pathsep((char *)file_path)) { + if (!add_pathsep(file_path)) { ff_free_stack_element(stackp); goto fail; } rest_of_wildcards = stackp->ffs_wc_path; if (*rest_of_wildcards != NUL) { - len = STRLEN(file_path); - if (STRNCMP(rest_of_wildcards, "**", 2) == 0) { + len = strlen(file_path); + if (strncmp(rest_of_wildcards, "**", 2) == 0) { // pointer to the restrict byte // The restrict byte is not a character! p = rest_of_wildcards + 2; @@ -701,7 +703,7 @@ char_u *vim_findfile(void *search_ctx_arg) if (stackp->ffs_star_star_empty == 0) { // if not done before, expand '**' to empty stackp->ffs_star_star_empty = 1; - dirptrs[1] = (char *)stackp->ffs_fix_path; + dirptrs[1] = stackp->ffs_fix_path; } } @@ -744,7 +746,7 @@ char_u *vim_findfile(void *search_ctx_arg) stackp->ffs_filearray_cur = 0; stackp->ffs_stage = 0; } else { - rest_of_wildcards = &stackp->ffs_wc_path[STRLEN(stackp->ffs_wc_path)]; + rest_of_wildcards = &stackp->ffs_wc_path[strlen(stackp->ffs_wc_path)]; } if (stackp->ffs_stage == 0) { @@ -764,7 +766,7 @@ char_u *vim_findfile(void *search_ctx_arg) goto fail; } STRCPY(file_path, stackp->ffs_filearray[i]); - if (!add_pathsep((char *)file_path)) { + if (!add_pathsep(file_path)) { ff_free_stack_element(stackp); goto fail; } @@ -772,7 +774,7 @@ char_u *vim_findfile(void *search_ctx_arg) // Try without extra suffix and then with suffixes // from 'suffixesadd'. - len = STRLEN(file_path); + len = strlen(file_path); if (search_ctx->ffsc_tagfile) { suf = ""; } else { @@ -780,14 +782,14 @@ char_u *vim_findfile(void *search_ctx_arg) } for (;;) { // if file exists and we didn't already find it - if ((path_with_url((char *)file_path) - || (os_path_exists((char *)file_path) + if ((path_with_url(file_path) + || (os_path_exists(file_path) && (search_ctx->ffsc_find_what == FINDFILE_BOTH || ((search_ctx->ffsc_find_what == FINDFILE_DIR) - == os_isdir((char *)file_path))))) + == os_isdir(file_path))))) #ifndef FF_VERBOSE && (ff_check_visited(&search_ctx->ffsc_visited_list->ffvl_visited_list, - (char *)file_path, "") == OK) + file_path, "") == OK) #endif ) { #ifdef FF_VERBOSE @@ -808,12 +810,11 @@ char_u *vim_findfile(void *search_ctx_arg) stackp->ffs_filearray_cur = i + 1; ff_push(search_ctx, stackp); - if (!path_with_url((char *)file_path)) { + if (!path_with_url(file_path)) { simplify_filename(file_path); } - if (os_dirname((char_u *)ff_expand_buffer, MAXPATHL) - == OK) { - p = (char_u *)path_shorten_fname((char *)file_path, ff_expand_buffer); + if (os_dirname(ff_expand_buffer, MAXPATHL) == OK) { + p = path_shorten_fname(file_path, ff_expand_buffer); if (p != NULL) { STRMOVE(file_path, p); } @@ -834,7 +835,7 @@ char_u *vim_findfile(void *search_ctx_arg) break; } assert(MAXPATHL >= len); - copy_option_part(&suf, (char *)file_path + len, MAXPATHL - len, ","); + copy_option_part(&suf, file_path + len, MAXPATHL - len, ","); } } } else { @@ -845,7 +846,7 @@ char_u *vim_findfile(void *search_ctx_arg) } ff_push(search_ctx, ff_create_stack_element(stackp->ffs_filearray[i], - (char *)rest_of_wildcards, + rest_of_wildcards, stackp->ffs_level - 1, 0)); } } @@ -855,11 +856,11 @@ char_u *vim_findfile(void *search_ctx_arg) // if wildcards contains '**' we have to descent till we reach the // leaves of the directory tree. - if (STRNCMP(stackp->ffs_wc_path, "**", 2) == 0) { + if (strncmp(stackp->ffs_wc_path, "**", 2) == 0) { for (int i = stackp->ffs_filearray_cur; i < stackp->ffs_filearray_size; i++) { if (path_fnamecmp(stackp->ffs_filearray[i], - (char *)stackp->ffs_fix_path) == 0) { + stackp->ffs_fix_path) == 0) { continue; // don't repush same directory } if (!os_isdir(stackp->ffs_filearray[i])) { @@ -867,7 +868,7 @@ char_u *vim_findfile(void *search_ctx_arg) } ff_push(search_ctx, ff_create_stack_element(stackp->ffs_filearray[i], - (char *)stackp->ffs_wc_path, stackp->ffs_level - 1, 1)); + stackp->ffs_wc_path, stackp->ffs_level - 1, 1)); } } @@ -883,16 +884,16 @@ char_u *vim_findfile(void *search_ctx_arg) // is the last starting directory in the stop list? if (ff_path_in_stoplist(search_ctx->ffsc_start_dir, - (int)(path_end - (char_u *)search_ctx->ffsc_start_dir), + (int)(path_end - search_ctx->ffsc_start_dir), search_ctx->ffsc_stopdirs_v) == true) { break; } // cut of last dir - while (path_end > (char_u *)search_ctx->ffsc_start_dir && vim_ispathsep(*path_end)) { + while (path_end > search_ctx->ffsc_start_dir && vim_ispathsep(*path_end)) { path_end--; } - while (path_end > (char_u *)search_ctx->ffsc_start_dir && !vim_ispathsep(path_end[-1])) { + while (path_end > search_ctx->ffsc_start_dir && !vim_ispathsep(path_end[-1])) { path_end--; } *path_end = 0; @@ -907,13 +908,13 @@ char_u *vim_findfile(void *search_ctx_arg) goto fail; } STRCPY(file_path, search_ctx->ffsc_start_dir); - if (!add_pathsep((char *)file_path)) { + if (!add_pathsep(file_path)) { goto fail; } STRCAT(file_path, search_ctx->ffsc_fix_path); // create a new stack entry - sptr = ff_create_stack_element((char *)file_path, + sptr = ff_create_stack_element(file_path, search_ctx->ffsc_wc_path, search_ctx->ffsc_level, 0); ff_push(search_ctx, sptr); } else { @@ -1022,7 +1023,7 @@ static ff_visited_list_hdr_T *ff_get_visited_list(char *filename, /// - 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) +static bool ff_wc_equal(char *s1, char *s2) { int i, j; int c1 = NUL; @@ -1039,8 +1040,8 @@ static bool ff_wc_equal(char_u *s1, char_u *s2) } for (i = 0, j = 0; s1[i] != NUL && s2[j] != NUL;) { - c1 = utf_ptr2char((char *)s1 + i); - c2 = utf_ptr2char((char *)s2 + j); + c1 = utf_ptr2char(s1 + i); + c2 = utf_ptr2char(s2 + j); if ((p_fic ? mb_tolower(c1) != mb_tolower(c2) : c1 != c2) && (prev1 != '*' || prev2 != '*')) { @@ -1049,8 +1050,8 @@ static bool ff_wc_equal(char_u *s1, char_u *s2) prev2 = prev1; prev1 = c1; - i += utfc_ptr2len((char *)s1 + i); - j += utfc_ptr2len((char *)s2 + j); + i += utfc_ptr2len(s1 + i); + j += utfc_ptr2len(s2 + j); } return s1[i] == s2[j]; } @@ -1068,7 +1069,7 @@ static int ff_check_visited(ff_visited_T **visited_list, char *fname, char *wc_p // For a URL we only compare the name, otherwise we compare the // device/inode. if (path_with_url(fname)) { - STRLCPY(ff_expand_buffer, fname, MAXPATHL); + xstrlcpy(ff_expand_buffer, fname, MAXPATHL); url = true; } else { ff_expand_buffer[0] = NUL; @@ -1083,7 +1084,7 @@ static int ff_check_visited(ff_visited_T **visited_list, char *fname, char *wc_p || (!url && vp->file_id_valid && os_fileid_equal(&(vp->file_id), &file_id))) { // are the wildcard parts equal - if (ff_wc_equal((char_u *)vp->ffv_wc_path, (char_u *)wc_path)) { + if (ff_wc_equal(vp->ffv_wc_path, wc_path)) { // already visited return FAIL; } @@ -1132,12 +1133,12 @@ static ff_stack_T *ff_create_stack_element(char *fix_part, char *wc_part, int le if (fix_part == NULL) { fix_part = ""; } - new->ffs_fix_path = (char_u *)xstrdup(fix_part); + new->ffs_fix_path = xstrdup(fix_part); if (wc_part == NULL) { wc_part = ""; } - new->ffs_wc_path = (char_u *)xstrdup(wc_part); + new->ffs_wc_path = xstrdup(wc_part); return new; } @@ -1147,10 +1148,12 @@ 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 // to prevent a crash - if (stack_ptr != NULL) { - stack_ptr->ffs_prev = search_ctx->ffsc_stack_ptr; - search_ctx->ffsc_stack_ptr = stack_ptr; + if (stack_ptr == NULL) { + return; } + + stack_ptr->ffs_prev = search_ctx->ffsc_stack_ptr; + search_ctx->ffsc_stack_ptr = stack_ptr; } /// Pop a dir from the directory stack. @@ -1238,7 +1241,7 @@ static int ff_path_in_stoplist(char *path, int path_len, char **stopdirs_v) } for (i = 0; stopdirs_v[i] != NULL; i++) { - if ((int)STRLEN(stopdirs_v[i]) > path_len) { + if ((int)strlen(stopdirs_v[i]) > path_len) { // match for parent directory. So '/home' also matches // '/home/rks'. Check for PATHSEP in stopdirs_v[i], else // '/home/r' would also match '/home/rks' @@ -1282,13 +1285,13 @@ static int ff_path_in_stoplist(char *path, int path_len, char **stopdirs_v) /// @param rel_fname file name searching relative to /// /// @return an allocated string for the file name. NULL for error. -char_u *find_file_in_path(char_u *ptr, size_t len, int options, int first, char_u *rel_fname) +char *find_file_in_path(char *ptr, size_t len, int options, int first, char *rel_fname) { return find_file_in_path_option(ptr, len, options, first, (*curbuf->b_p_path == NUL ? p_path - : (char_u *)curbuf->b_p_path), - FINDFILE_BOTH, rel_fname, (char_u *)curbuf->b_p_sua); + : curbuf->b_p_path), + FINDFILE_BOTH, rel_fname, curbuf->b_p_sua); } static char *ff_file_to_find = NULL; @@ -1317,10 +1320,10 @@ void free_findfile(void) /// @param rel_fname file name searching relative to /// /// @return an allocated string for the file name. NULL for error. -char_u *find_directory_in_path(char_u *ptr, size_t len, int options, char_u *rel_fname) +char *find_directory_in_path(char *ptr, size_t len, int options, char *rel_fname) { return find_file_in_path_option(ptr, len, options, true, p_cdpath, - FINDFILE_DIR, rel_fname, (char_u *)""); + FINDFILE_DIR, rel_fname, ""); } /// @param ptr file name @@ -1330,13 +1333,12 @@ char_u *find_directory_in_path(char_u *ptr, size_t len, int options, char_u *rel /// @param find_what FINDFILE_FILE, _DIR or _BOTH /// @param rel_fname file name we are looking relative to. /// @param suffixes list of suffixes, 'suffixesadd' option -char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first, - char_u *path_option, int find_what, char_u *rel_fname, - char_u *suffixes) +char *find_file_in_path_option(char *ptr, size_t len, int options, int first, char *path_option, + int find_what, char *rel_fname, char *suffixes) { static char *dir; static int did_findfile_init = false; - char_u save_char; + char save_char; char *file_name = NULL; char *buf = NULL; int rel_to_curdir; @@ -1354,16 +1356,16 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first // copy file name into NameBuff, expanding environment variables save_char = ptr[len]; ptr[len] = NUL; - expand_env_esc(ptr, (char_u *)NameBuff, MAXPATHL, false, true, NULL); + expand_env_esc(ptr, NameBuff, MAXPATHL, false, true, NULL); ptr[len] = save_char; xfree(ff_file_to_find); ff_file_to_find = xstrdup(NameBuff); if (options & FNAME_UNESC) { // Change all "\ " to " ". - for (ptr = (char_u *)ff_file_to_find; *ptr != NUL; ptr++) { + for (ptr = ff_file_to_find; *ptr != NUL; ptr++) { if (ptr[0] == '\\' && ptr[1] == ' ') { - memmove(ptr, ptr + 1, STRLEN(ptr)); + memmove(ptr, ptr + 1, strlen(ptr)); } } } @@ -1375,7 +1377,7 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first || (ff_file_to_find[1] == '.' && (ff_file_to_find[2] == NUL || vim_ispathsep(ff_file_to_find[2]))))); - if (vim_isAbsName((char_u *)ff_file_to_find) + if (vim_isAbsName(ff_file_to_find) // "..", "../path", "." and "./path": don't use the path_option || rel_to_curdir #if defined(MSWIN) @@ -1402,9 +1404,9 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first && rel_to_curdir && (options & FNAME_REL) && rel_fname != NULL - && STRLEN(rel_fname) + l < MAXPATHL) { + && strlen(rel_fname) + l < MAXPATHL) { STRCPY(NameBuff, rel_fname); - STRCPY(path_tail((char *)NameBuff), ff_file_to_find); + STRCPY(path_tail(NameBuff), ff_file_to_find); l = strlen(NameBuff); } else { STRCPY(NameBuff, ff_file_to_find); @@ -1412,10 +1414,9 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first } // When the file doesn't exist, try adding parts of 'suffixesadd'. - buf = (char *)suffixes; + buf = suffixes; for (;;) { - if ( - (os_path_exists(NameBuff) + if ((os_path_exists(NameBuff) && (find_what == FINDFILE_BOTH || ((find_what == FINDFILE_DIR) == os_isdir(NameBuff))))) { @@ -1426,7 +1427,7 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first break; } assert(MAXPATHL >= l); - copy_option_part(&buf, (char *)NameBuff + l, MAXPATHL - l, ","); + copy_option_part(&buf, NameBuff + l, MAXPATHL - l, ","); } } } @@ -1437,20 +1438,20 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first if (first == true) { // vim_findfile_free_visited can handle a possible NULL pointer vim_findfile_free_visited(fdip_search_ctx); - dir = (char *)path_option; + dir = path_option; did_findfile_init = false; } for (;;) { if (did_findfile_init) { - file_name = (char *)vim_findfile(fdip_search_ctx); + file_name = vim_findfile(fdip_search_ctx); if (file_name != NULL) { break; } did_findfile_init = false; } else { - char_u *r_ptr; + char *r_ptr; if (dir == NULL || *dir == NUL) { // We searched all paths of the option, now we can free the search context. @@ -1466,10 +1467,10 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first copy_option_part(&dir, buf, MAXPATHL, " ,"); // get the stopdir string - r_ptr = vim_findfile_stopdir((char_u *)buf); + r_ptr = vim_findfile_stopdir(buf); fdip_search_ctx = vim_findfile_init(buf, ff_file_to_find, - (char *)r_ptr, 100, false, find_what, - fdip_search_ctx, false, (char *)rel_fname); + r_ptr, 100, false, find_what, + fdip_search_ctx, false, rel_fname); if (fdip_search_ctx != NULL) { did_findfile_init = true; } @@ -1498,7 +1499,7 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first } theend: - return (char_u *)file_name; + return file_name; } void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause, bool pre) @@ -1577,14 +1578,14 @@ int vim_chdirfile(char *fname, CdCause cause) { char dir[MAXPATHL]; - STRLCPY(dir, fname, MAXPATHL); + xstrlcpy(dir, fname, MAXPATHL); *path_tail_with_sep(dir) = NUL; - if (os_dirname((char_u *)NameBuff, sizeof(NameBuff)) != OK) { + if (os_dirname(NameBuff, sizeof(NameBuff)) != OK) { NameBuff[0] = NUL; } - if (pathcmp(dir, (char *)NameBuff, -1) == 0) { + if (pathcmp(dir, NameBuff, -1) == 0) { // nothing to do return OK; } @@ -1605,10 +1606,10 @@ int vim_chdirfile(char *fname, CdCause cause) } /// Change directory to "new_dir". Search 'cdpath' for relative directory names. -int vim_chdir(char_u *new_dir) +int vim_chdir(char *new_dir) { - char *dir_name = (char *)find_directory_in_path(new_dir, STRLEN(new_dir), - FNAME_MESS, (char_u *)curbuf->b_ffname); + char *dir_name = find_directory_in_path(new_dir, strlen(new_dir), + FNAME_MESS, curbuf->b_ffname); if (dir_name == NULL) { return -1; } diff --git a/src/nvim/file_search.h b/src/nvim/file_search.h index 4d4e723922..b69a6fa154 100644 --- a/src/nvim/file_search.h +++ b/src/nvim/file_search.h @@ -1,10 +1,10 @@ #ifndef NVIM_FILE_SEARCH_H #define NVIM_FILE_SEARCH_H -#include <stdlib.h> // for size_t +#include <stdlib.h> -#include "nvim/globals.h" // for CdScope -#include "nvim/types.h" // for char_u +#include "nvim/globals.h" +#include "nvim/types.h" // Flags for find_file_*() functions. #define FINDFILE_FILE 0 // only files diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 30898981de..9da9d6199e 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -6,62 +6,72 @@ #include <assert.h> #include <errno.h> #include <fcntl.h> +#include <iconv.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stdio.h> #include <string.h> +#include <sys/stat.h> +#include <uv.h> -#include "nvim/api/private/helpers.h" +#include "auto/config.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" -#include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" -#include "nvim/eval/typval.h" -#include "nvim/eval/userfunc.h" #include "nvim/ex_cmds.h" -#include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/fileio.h" #include "nvim/fold.h" -#include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/getchar.h" -#include "nvim/hashtab.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" #include "nvim/iconv.h" #include "nvim/input.h" +#include "nvim/log.h" +#include "nvim/macros.h" #include "nvim/mbyte.h" #include "nvim/memfile.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/move.h" -#include "nvim/normal.h" #include "nvim/option.h" #include "nvim/optionstr.h" +#include "nvim/os/fs_defs.h" #include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os/os_defs.h" #include "nvim/os/time.h" -#include "nvim/os_unix.h" #include "nvim/path.h" -#include "nvim/quickfix.h" +#include "nvim/pos.h" #include "nvim/regexp.h" -#include "nvim/search.h" +#include "nvim/screen.h" #include "nvim/sha256.h" #include "nvim/shada.h" -#include "nvim/state.h" #include "nvim/strings.h" #include "nvim/types.h" #include "nvim/ui.h" -#include "nvim/ui_compositor.h" #include "nvim/undo.h" #include "nvim/vim.h" -#include "nvim/window.h" + +#if defined(HAVE_FLOCK) && defined(HAVE_DIRFD) +# include <dirent.h> +# include <sys/file.h> +#endif + +#ifdef OPEN_CHR_FILES +# include "nvim/charset.h" +#endif #define BUFSIZE 8192 // size of normal write buffer #define SMBUFSIZE 256 // size of emergency write buffer @@ -72,15 +82,17 @@ #endif #define HAS_BW_FLAGS -#define FIO_LATIN1 0x01 // convert Latin1 -#define FIO_UTF8 0x02 // convert UTF-8 -#define FIO_UCS2 0x04 // convert UCS-2 -#define FIO_UCS4 0x08 // convert UCS-4 -#define FIO_UTF16 0x10 // convert UTF-16 -#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 +enum { + FIO_LATIN1 = 0x01, // convert Latin1 + FIO_UTF8 = 0x02, // convert UTF-8 + FIO_UCS2 = 0x04, // convert UCS-2 + FIO_UCS4 = 0x08, // convert UCS-4 + FIO_UTF16 = 0x10, // convert UTF-16 + FIO_ENDIAN_L = 0x80, // little endian + FIO_NOCONVERT = 0x2000, // skip encoding conversion + FIO_UCSBOM = 0x4000, // check for BOM at start of file + 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... @@ -93,7 +105,7 @@ // Structure to pass arguments from buf_write() to buf_write_bytes(). struct bw_info { int bw_fd; // file descriptor - char_u *bw_buf; // buffer with data to be written + char *bw_buf; // buffer with data to be written int bw_len; // length of data #ifdef HAS_BW_FLAGS int bw_flags; // FIO_ flags @@ -101,14 +113,12 @@ 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 + char *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 -#ifdef HAVE_ICONV iconv_t bw_iconv_fd; // descriptor for iconv() or -1 -#endif }; #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -126,7 +136,7 @@ void filemess(buf_T *buf, char *name, char *s, int attr) if (msg_silent != 0) { return; } - add_quoted_fname((char *)IObuff, IOSIZE - 100, buf, (const char *)name); + add_quoted_fname(IObuff, IOSIZE - 100, buf, (const char *)name); // Avoid an over-long translation to cause trouble. xstrlcat(IObuff, s, IOSIZE); // For the first message may have to start a new line. @@ -143,7 +153,7 @@ void filemess(buf_T *buf, char *name, char *s, int attr) msg_scroll = msg_scroll_save; msg_scrolled_ign = true; // may truncate the message to avoid a hit-return prompt - msg_outtrans_attr(msg_may_trunc(false, (char *)IObuff), attr); + msg_outtrans_attr(msg_may_trunc(false, IObuff), attr); msg_clr_eos(); ui_flush(); msg_scrolled_ign = false; @@ -236,11 +246,9 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, char *fenc_next = NULL; // next item in 'fencs' or NULL bool advance_fenc = false; long real_size = 0; -#ifdef HAVE_ICONV iconv_t iconv_fd = (iconv_t)-1; // descriptor for iconv() or -1 bool did_iconv = false; // true when iconv() failed and trying // 'charconvert' next -#endif bool converted = false; // true if conversion done bool notconverted = false; // true if conversion wanted but it wasn't possible char conv_rest[CONV_RESTLEN]; @@ -266,7 +274,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, && fname != NULL && vim_strchr(p_cpo, CPO_FNAMER) != NULL && !(flags & READ_DUMMY)) { - if (set_rw_fname((char_u *)fname, (char_u *)sfname) == FAIL) { + if (set_rw_fname(fname, sfname) == FAIL) { return FAIL; } } @@ -476,7 +484,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, } } if (!silent) { - if (dir_of_file_exists((char_u *)fname)) { + if (dir_of_file_exists(fname)) { filemess(curbuf, sfname, new_file_message(), 0); } else { filemess(curbuf, sfname, _("[New DIRECTORY]"), 0); @@ -498,18 +506,17 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, return FAIL; } return OK; // a new file is not an error - } else { - filemess(curbuf, sfname, ((fd == UV_EFBIG) ? _("[File too big]") : + } + filemess(curbuf, sfname, ((fd == UV_EFBIG) ? _("[File too big]") : #if defined(UNIX) && defined(EOVERFLOW) - // libuv only returns -errno - // in Unix and in Windows - // open() does not set - // EOVERFLOW - (fd == -EOVERFLOW) ? _("[File too big]") : + // libuv only returns -errno + // in Unix and in Windows + // open() does not set + // EOVERFLOW + (fd == -EOVERFLOW) ? _("[File too big]") : #endif - _("[Permission Denied]")), 0); - curbuf->b_p_ro = true; // must use "w!" now - } + _("[Permission Denied]")), 0); + curbuf->b_p_ro = true; // must use "w!" now return FAIL; } @@ -524,6 +531,8 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, // Don't change 'eol' if reading from buffer as it will already be // correctly set when reading stdin. if (!read_buffer) { + curbuf->b_p_eof = false; + curbuf->b_start_eof = false; curbuf->b_p_eol = true; curbuf->b_start_eol = true; } @@ -766,13 +775,11 @@ retry: } } -#ifdef HAVE_ICONV if (iconv_fd != (iconv_t)-1) { // aborted conversion with iconv(), close the descriptor iconv_close(iconv_fd); iconv_fd = (iconv_t)-1; } -#endif if (advance_fenc) { // Try the next entry in 'fileencodings'. @@ -822,32 +829,24 @@ retry: // appears not to handle this correctly. This works just like // conversion to UTF-8 except how the resulting character is put in // the buffer. - fio_flags = get_fio_flags((char_u *)fenc); + fio_flags = get_fio_flags(fenc); } -#ifdef HAVE_ICONV // Try using iconv() if we can't convert internally. if (fio_flags == 0 && !did_iconv) { - iconv_fd = (iconv_t)my_iconv_open((char_u *)"utf-8", (char_u *)fenc); + iconv_fd = (iconv_t)my_iconv_open("utf-8", fenc); } -#endif // Use the 'charconvert' expression when conversion is required // and we can't do it internally or with iconv(). if (fio_flags == 0 && !read_stdin && !read_buffer && *p_ccv != NUL - && !read_fifo -#ifdef HAVE_ICONV - && iconv_fd == (iconv_t)-1 -#endif - ) { -#ifdef HAVE_ICONV + && !read_fifo && iconv_fd == (iconv_t)-1) { did_iconv = false; -#endif // Skip conversion when it's already done (retry for wrong // "fileformat"). if (tmpname == NULL) { - tmpname = (char *)readfile_charconvert((char_u *)fname, (char_u *)fenc, &fd); + tmpname = readfile_charconvert(fname, fenc, &fd); if (tmpname == NULL) { // Conversion failed. Try another one. advance_fenc = true; @@ -861,11 +860,7 @@ retry: } } } else { - if (fio_flags == 0 -#ifdef HAVE_ICONV - && iconv_fd == (iconv_t)-1 -#endif - ) { + if (fio_flags == 0 && iconv_fd == (iconv_t)-1) { // Conversion wanted but we can't. // Try the next conversion in 'fileencodings' advance_fenc = true; @@ -948,12 +943,9 @@ retry: // ucs-4 to utf-8: 4 bytes become up to 6 bytes, size must be // multiple of 4 real_size = (int)size; -#ifdef HAVE_ICONV if (iconv_fd != (iconv_t)-1) { size = size / ICONV_MULT; - } else { -#endif - if (fio_flags & FIO_LATIN1) { + } else if (fio_flags & FIO_LATIN1) { size = size / 2; } else if (fio_flags & (FIO_UCS2 | FIO_UTF16)) { size = (size * 2 / 3) & ~1; @@ -962,9 +954,7 @@ retry: } else if (fio_flags == FIO_UCSBOM) { size = size / ICONV_MULT; // worst case } -#ifdef HAVE_ICONV - } -#endif + if (conv_restlen > 0) { // Insert unconverted bytes from previous line. memmove(ptr, conv_rest, (size_t)conv_restlen); // -V614 @@ -984,7 +974,7 @@ retry: tlen = 0; for (;;) { p = (char_u *)ml_get(read_buf_lnum) + read_buf_col; - n = (int)STRLEN(p); + n = (int)strlen((char *)p); if ((int)tlen + n + 1 > size) { // Filled up to "size", append partial line. // Change NL to NUL to reverse the effect done @@ -1036,11 +1026,7 @@ retry: // not be converted. Truncated file? // When we did a conversion report an error. - if (fio_flags != 0 -#ifdef HAVE_ICONV - || iconv_fd != (iconv_t)-1 -#endif - ) { + if (fio_flags != 0 || iconv_fd != (iconv_t)-1) { if (can_retry) { goto rewind_retry; } @@ -1048,9 +1034,8 @@ retry: conv_error = curbuf->b_ml.ml_line_count - linecnt + 1; } - } - // Remember the first linenr with an illegal byte - else if (illegal_byte == 0) { + } else if (illegal_byte == 0) { + // Remember the first linenr with an illegal byte illegal_byte = curbuf->b_ml.ml_line_count - linecnt + 1; } @@ -1062,23 +1047,17 @@ retry: // character if we were converting; if we weren't, // leave the UTF8 checking code to do it, as it // works slightly differently. - if (bad_char_behavior != BAD_KEEP && (fio_flags != 0 -#ifdef HAVE_ICONV - || iconv_fd != (iconv_t)-1 -#endif - )) { + if (bad_char_behavior != BAD_KEEP && (fio_flags != 0 || iconv_fd != (iconv_t)-1)) { while (conv_restlen > 0) { *(--ptr) = (char)bad_char_behavior; conv_restlen--; } } fio_flags = 0; // don't convert this -#ifdef HAVE_ICONV if (iconv_fd != (iconv_t)-1) { iconv_close(iconv_fd); iconv_fd = (iconv_t)-1; } -#endif } } } @@ -1095,15 +1074,15 @@ retry: || (!curbuf->b_p_bomb && tmpname == NULL && (*fenc == 'u' || *fenc == NUL)))) { - char_u *ccname; + char *ccname; int blen = 0; // no BOM detection in a short file or in binary mode if (size < 2 || curbuf->b_p_bin) { ccname = NULL; } else { - ccname = check_for_bom((char_u *)ptr, size, &blen, - fio_flags == FIO_UCSBOM ? FIO_ALL : get_fio_flags((char_u *)fenc)); + ccname = check_for_bom(ptr, size, &blen, + fio_flags == FIO_UCSBOM ? FIO_ALL : get_fio_flags(fenc)); } if (ccname != NULL) { // Remove BOM from the text @@ -1125,7 +1104,7 @@ retry: if (fenc_alloced) { xfree(fenc); } - fenc = (char *)ccname; + fenc = ccname; fenc_alloced = false; } // retry reading without getting new bytes or rewinding @@ -1143,7 +1122,6 @@ retry: break; } -#ifdef HAVE_ICONV if (iconv_fd != (iconv_t)-1) { // Attempt conversion of the read bytes to 'encoding' using iconv(). const char *fromp = ptr; @@ -1163,7 +1141,7 @@ retry: goto rewind_retry; } if (conv_error == 0) { - conv_error = readfile_linenr(linecnt, (char_u *)ptr, (char_u *)top); + conv_error = readfile_linenr(linecnt, ptr, top); } // Deal with a bad byte and continue with the next. @@ -1181,7 +1159,7 @@ retry: if (from_size > 0) { // Some remaining characters, keep them for the next // round. - memmove(conv_rest, (char_u *)fromp, from_size); + memmove(conv_rest, fromp, from_size); conv_restlen = (int)from_size; } @@ -1190,7 +1168,6 @@ retry: memmove(line_start, buffer, (size_t)linerest); size = (top - ptr); } -#endif if (fio_flags != 0) { unsigned int u8c; @@ -1275,7 +1252,7 @@ retry: goto rewind_retry; } if (conv_error == 0) { - conv_error = readfile_linenr(linecnt, (char_u *)ptr, p); + conv_error = readfile_linenr(linecnt, ptr, (char *)p); } if (bad_char_behavior == BAD_DROP) { continue; @@ -1303,7 +1280,7 @@ retry: goto rewind_retry; } if (conv_error == 0) { - conv_error = readfile_linenr(linecnt, (char_u *)ptr, p); + conv_error = readfile_linenr(linecnt, ptr, (char *)p); } if (bad_char_behavior == BAD_DROP) { continue; @@ -1344,7 +1321,7 @@ retry: goto rewind_retry; } if (conv_error == 0) { - conv_error = readfile_linenr(linecnt, (char_u *)ptr, p); + conv_error = readfile_linenr(linecnt, ptr, (char *)p); } if (bad_char_behavior == BAD_DROP) { continue; @@ -1382,7 +1359,7 @@ retry: // an incomplete character at the end though, the next // read() will get the next bytes, we'll check it // then. - l = utf_ptr2len_len(p, todo); + l = utf_ptr2len_len((char *)p, todo); if (l > todo && !incomplete_tail) { // Avoid retrying with a different encoding when // a truncated file is more likely, or attempting @@ -1408,15 +1385,15 @@ retry: if (can_retry && !incomplete_tail) { break; } -#ifdef HAVE_ICONV + // When we did a conversion report an error. if (iconv_fd != (iconv_t)-1 && conv_error == 0) { - conv_error = readfile_linenr(linecnt, (char_u *)ptr, p); + conv_error = readfile_linenr(linecnt, ptr, (char *)p); } -#endif + // Remember the first linenr with an illegal byte if (conv_error == 0 && illegal_byte == 0) { - illegal_byte = readfile_linenr(linecnt, (char_u *)ptr, p); + illegal_byte = readfile_linenr(linecnt, ptr, (char *)p); } // Drop, keep or replace the bad byte. @@ -1436,17 +1413,13 @@ retry: // Detected a UTF-8 error. rewind_retry: // Retry reading with another conversion. -#ifdef HAVE_ICONV if (*p_ccv != NUL && iconv_fd != (iconv_t)-1) { // iconv() failed, try 'charconvert' did_iconv = true; } else { -#endif - // use next item from 'fileencodings' - advance_fenc = true; -#ifdef HAVE_ICONV - } -#endif + // use next item from 'fileencodings' + advance_fenc = true; + } file_rewind = true; goto retry; } @@ -1542,7 +1515,7 @@ rewind_retry: break; } if (read_undo_file) { - sha256_update(&sha_ctx, (char_u *)line_start, (size_t)len); + sha256_update(&sha_ctx, (uint8_t *)line_start, (size_t)len); } lnum++; if (--read_count == 0) { @@ -1598,7 +1571,7 @@ rewind_retry: break; } if (read_undo_file) { - sha256_update(&sha_ctx, (char_u *)line_start, (size_t)len); + sha256_update(&sha_ctx, (uint8_t *)line_start, (size_t)len); } lnum++; if (--read_count == 0) { @@ -1623,16 +1596,28 @@ failed: error = false; } + // In Dos format ignore a trailing CTRL-Z, unless 'binary' is set. + // In old days the file length was in sector count and the CTRL-Z the + // marker where the file really ended. Assuming we write it to a file + // system that keeps file length properly the CTRL-Z should be dropped. + // Set the 'endoffile' option so the user can decide what to write later. + // In Unix format the CTRL-Z is just another character. + if (linerest != 0 + && !curbuf->b_p_bin + && fileformat == EOL_DOS + && ptr[-1] == Ctrl_Z) { + ptr--; + linerest--; + if (set_options) { + curbuf->b_p_eof = true; + } + } + // If we get EOF in the middle of a line, note the fact and // complete the line ourselves. - // In Dos format ignore a trailing CTRL-Z, unless 'binary' set. if (!error && !got_int - && linerest != 0 - && !(!curbuf->b_p_bin - && fileformat == EOL_DOS - && *line_start == Ctrl_Z - && ptr == line_start + 1)) { + && linerest != 0) { // remember for when writing if (set_options) { curbuf->b_p_eol = false; @@ -1643,7 +1628,7 @@ failed: error = true; } else { if (read_undo_file) { - sha256_update(&sha_ctx, (char_u *)line_start, (size_t)len); + sha256_update(&sha_ctx, (uint8_t *)line_start, (size_t)len); } read_no_eol_lnum = ++lnum; } @@ -1659,11 +1644,9 @@ failed: if (fenc_alloced) { xfree(fenc); } -#ifdef HAVE_ICONV if (iconv_fd != (iconv_t)-1) { iconv_close(iconv_fd); } -#endif if (!read_buffer && !read_stdin) { close(fd); // errors are ignored @@ -1734,7 +1717,7 @@ failed: } if (!filtering && !(flags & READ_DUMMY) && !silent) { - add_quoted_fname((char *)IObuff, IOSIZE, curbuf, (const char *)sfname); + add_quoted_fname(IObuff, IOSIZE, curbuf, (const char *)sfname); c = false; #ifdef UNIX @@ -1799,7 +1782,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(IObuff, false, 0); } if (read_stdin || read_buffer || restart_edit != 0 @@ -1863,7 +1846,7 @@ failed: char_u hash[UNDO_HASH_SIZE]; sha256_finish(&sha_ctx, hash); - u_read_undo(NULL, hash, (char_u *)fname); + u_read_undo(NULL, hash, fname); } if (!read_stdin && !read_fifo && (!read_buffer || sfname != NULL)) { @@ -1919,7 +1902,7 @@ failed: bool is_dev_fd_file(char *fname) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - return STRNCMP(fname, "/dev/fd/", 8) == 0 + return strncmp(fname, "/dev/fd/", 8) == 0 && ascii_isdigit((uint8_t)fname[8]) && *skipdigits(fname + 9) == NUL && (fname[9] != NUL @@ -1934,9 +1917,9 @@ bool is_dev_fd_file(char *fname) /// @param linecnt line count before reading more bytes /// @param p start of more bytes read /// @param endp end of more bytes read -static linenr_T readfile_linenr(linenr_T linecnt, char_u *p, char_u *endp) +static linenr_T readfile_linenr(linenr_T linecnt, char *p, const char *endp) { - char_u *s; + char *s; linenr_T lnum; lnum = curbuf->b_ml.ml_line_count - linecnt + 1; @@ -1990,11 +1973,13 @@ void set_file_options(int set_options, exarg_T *eap) /// Set forced 'fileencoding'. void set_forced_fenc(exarg_T *eap) { - if (eap->force_enc != 0) { - char *fenc = enc_canonize(eap->cmd + eap->force_enc); - set_string_option_direct("fenc", -1, fenc, OPT_FREE|OPT_LOCAL, 0); - xfree(fenc); + if (eap->force_enc == 0) { + return; } + + char *fenc = enc_canonize(eap->cmd + eap->force_enc); + set_string_option_direct("fenc", -1, fenc, OPT_FREE|OPT_LOCAL, 0); + xfree(fenc); } /// Find next fileencoding to use from 'fileencodings'. @@ -2014,7 +1999,7 @@ static char *next_fenc(char **pp, bool *alloced) *pp = NULL; return ""; } - p = vim_strchr((*pp), ','); + p = vim_strchr(*pp, ','); if (p == NULL) { r = enc_canonize(*pp); *pp += strlen(*pp); @@ -2039,22 +2024,22 @@ static char *next_fenc(char **pp, bool *alloced) /// /// @return name of the resulting converted file (the caller should delete it after reading it). /// Returns NULL if the conversion failed ("*fdp" is not set) . -static char_u *readfile_charconvert(char_u *fname, char_u *fenc, int *fdp) +static char *readfile_charconvert(char *fname, char *fenc, int *fdp) { - char_u *tmpname; + char *tmpname; char *errmsg = NULL; - tmpname = (char_u *)vim_tempname(); + tmpname = vim_tempname(); if (tmpname == NULL) { errmsg = _("Can't find temp file for conversion"); } else { close(*fdp); // close the input file, ignore errors *fdp = -1; - if (eval_charconvert((char *)fenc, "utf-8", - (char *)fname, (char *)tmpname) == FAIL) { + if (eval_charconvert(fenc, "utf-8", + fname, tmpname) == FAIL) { errmsg = _("Conversion with 'charconvert' failed"); } - if (errmsg == NULL && (*fdp = os_open((char *)tmpname, O_RDONLY, 0)) < 0) { + if (errmsg == NULL && (*fdp = os_open(tmpname, O_RDONLY, 0)) < 0) { errmsg = _("can't read output of 'charconvert'"); } } @@ -2064,14 +2049,14 @@ static char_u *readfile_charconvert(char_u *fname, char_u *fenc, int *fdp) // another type of conversion might still work. msg(errmsg); if (tmpname != NULL) { - os_remove((char *)tmpname); // delete converted file + os_remove(tmpname); // delete converted file XFREE_CLEAR(tmpname); } } // If the input file is closed, open it (caller should check for error). if (*fdp < 0) { - *fdp = os_open((char *)fname, O_RDONLY, 0); + *fdp = os_open(fname, O_RDONLY, 0); } return tmpname; @@ -2191,8 +2176,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en return FAIL; } - // Disallow writing from .exrc and .vimrc in current directory for - // security reasons. + // Disallow writing in secure mode. if (check_secure()) { return FAIL; } @@ -2208,9 +2192,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en write_info.bw_conv_error = false; write_info.bw_conv_error_lnum = 0; write_info.bw_restlen = 0; -#ifdef HAVE_ICONV write_info.bw_iconv_fd = (iconv_t)-1; -#endif // After writing a file changedtick changes but we don't want to display // the line. @@ -2229,7 +2211,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en && !filtering && (!append || vim_strchr(p_cpo, CPO_FNAMEAPP) != NULL) && vim_strchr(p_cpo, CPO_FNAMEW) != NULL) { - if (set_rw_fname((char_u *)fname, (char_u *)sfname) == FAIL) { + if (set_rw_fname(fname, sfname) == FAIL) { return FAIL; } buf = curbuf; // just in case autocmds made "buf" invalid @@ -2448,7 +2430,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en if (!filtering) { filemess(buf, #ifndef UNIX - (char_u *)sfname, + sfname, #else fname, #endif @@ -2492,7 +2474,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en } #else // win32 // Check for a writable device name. - c = fname == NULL ? NODE_OTHER : os_nodetype((char *)fname); + c = fname == NULL ? NODE_OTHER : os_nodetype(fname); if (c == NODE_OTHER) { SET_ERRMSG_NUM("E503", _("is not a file or writable device")); goto fail; @@ -2510,7 +2492,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en goto fail; } if (overwriting) { - os_fileinfo((char *)fname, &file_info_old); + os_fileinfo(fname, &file_info_old); } } #endif // !UNIX @@ -2541,13 +2523,13 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en #ifdef HAVE_ACL // For systems that support ACL: get the ACL from the original file. if (!newfile) { - acl = mch_get_acl((char_u *)fname); + acl = os_get_acl(fname); } #endif // If 'backupskip' is not empty, don't make a backup for some files. dobackup = (p_wb || p_bk || *p_pm != NUL); - if (dobackup && *p_bsk != NUL && match_file_list(p_bsk, (char_u *)sfname, (char_u *)ffname)) { + if (dobackup && *p_bsk != NUL && match_file_list(p_bsk, sfname, ffname)) { dobackup = false; } @@ -2590,21 +2572,21 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en // arbitrary numbers). STRCPY(IObuff, fname); for (i = 4913;; i += 123) { - char *tail = path_tail((char *)IObuff); + char *tail = path_tail(IObuff); size_t size = (size_t)(tail - IObuff); snprintf(tail, IOSIZE - size, "%d", i); - if (!os_fileinfo_link((char *)IObuff, &file_info)) { + if (!os_fileinfo_link(IObuff, &file_info)) { break; } } - fd = os_open((char *)IObuff, + fd = os_open(IObuff, O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, (int)perm); if (fd < 0) { // can't write in directory backup_copy = true; } else { #ifdef UNIX os_fchown(fd, (uv_uid_t)file_info_old.stat.st_uid, (uv_gid_t)file_info_old.stat.st_gid); - if (!os_fileinfo((char *)IObuff, &file_info) + if (!os_fileinfo(IObuff, &file_info) || file_info.stat.st_uid != file_info_old.stat.st_uid || file_info.stat.st_gid != file_info_old.stat.st_gid || (long)file_info.stat.st_mode != perm) { @@ -2614,7 +2596,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en // Close the file before removing it, on MS-Windows we // can't delete an open file. close(fd); - os_remove((char *)IObuff); + os_remove(IObuff); } } } @@ -2668,16 +2650,16 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en dirp = p_bdir; while (*dirp) { // Isolate one directory name, using an entry in 'bdir'. - size_t dir_len = copy_option_part(&dirp, (char *)IObuff, IOSIZE, ","); - p = (char *)IObuff + dir_len; - bool trailing_pathseps = after_pathsep((char *)IObuff, p) && p[-1] == p[-2]; + size_t dir_len = copy_option_part(&dirp, IObuff, IOSIZE, ","); + p = IObuff + dir_len; + bool trailing_pathseps = after_pathsep(IObuff, p) && p[-1] == p[-2]; if (trailing_pathseps) { IObuff[dir_len - 2] = NUL; } - if (*dirp == NUL && !os_isdir((char *)IObuff)) { + if (*dirp == NUL && !os_isdir(IObuff)) { int ret; char *failed_dir; - if ((ret = os_mkdir_recurse((char *)IObuff, 0755, &failed_dir)) != 0) { + if ((ret = os_mkdir_recurse(IObuff, 0755, &failed_dir)) != 0) { semsg(_("E303: Unable to create directory \"%s\" for backup file: %s"), failed_dir, os_strerror(ret)); xfree(failed_dir); @@ -2685,14 +2667,14 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en } if (trailing_pathseps) { // Ends with '//', Use Full path - if ((p = make_percent_swname((char *)IObuff, fname)) + if ((p = make_percent_swname(IObuff, fname)) != NULL) { backup = modname(p, backup_ext, no_prepend_dot); xfree(p); } } - rootname = get_file_in_dir(fname, (char *)IObuff); + rootname = get_file_in_dir(fname, IObuff); if (rootname == NULL) { some_error = true; // out of memory goto nobackup; @@ -2768,10 +2750,11 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en #endif // copy the file - if (os_copy(fname, backup, UV_FS_COPYFILE_FICLONE) - != 0) { - SET_ERRMSG(_("E506: Can't write to backup file " - "(add ! to override)")); + if (os_copy(fname, backup, UV_FS_COPYFILE_FICLONE) != 0) { + SET_ERRMSG(_("E509: Cannot create backup file (add ! to override)")); + XFREE_CLEAR(backup); + backup = NULL; + continue; } #ifdef UNIX @@ -2780,8 +2763,9 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en (double)file_info_old.stat.st_mtim.tv_sec); #endif #ifdef HAVE_ACL - mch_set_acl((char_u *)backup, acl); + os_set_acl(backup, acl); #endif + SET_ERRMSG(NULL); break; } } @@ -2817,16 +2801,16 @@ nobackup: dirp = p_bdir; while (*dirp) { // Isolate one directory name and make the backup file name. - size_t dir_len = copy_option_part(&dirp, (char *)IObuff, IOSIZE, ","); - p = (char *)IObuff + dir_len; - bool trailing_pathseps = after_pathsep((char *)IObuff, p) && p[-1] == p[-2]; + size_t dir_len = copy_option_part(&dirp, IObuff, IOSIZE, ","); + p = IObuff + dir_len; + bool trailing_pathseps = after_pathsep(IObuff, p) && p[-1] == p[-2]; if (trailing_pathseps) { IObuff[dir_len - 2] = NUL; } - if (*dirp == NUL && !os_isdir((char *)IObuff)) { + if (*dirp == NUL && !os_isdir(IObuff)) { int ret; char *failed_dir; - if ((ret = os_mkdir_recurse((char *)IObuff, 0755, &failed_dir)) != 0) { + if ((ret = os_mkdir_recurse(IObuff, 0755, &failed_dir)) != 0) { semsg(_("E303: Unable to create directory \"%s\" for backup file: %s"), failed_dir, os_strerror(ret)); xfree(failed_dir); @@ -2834,7 +2818,7 @@ nobackup: } if (trailing_pathseps) { // path ends with '//', use full path - if ((p = make_percent_swname((char *)IObuff, fname)) + if ((p = make_percent_swname(IObuff, fname)) != NULL) { backup = modname(p, backup_ext, no_prepend_dot); xfree(p); @@ -2842,7 +2826,7 @@ nobackup: } if (backup == NULL) { - rootname = get_file_in_dir(fname, (char *)IObuff); + rootname = get_file_in_dir(fname, IObuff); if (rootname == NULL) { backup = NULL; } else { @@ -2953,7 +2937,7 @@ nobackup: // Latin1 to Unicode conversion. This is handled in buf_write_bytes(). // Prepare the flags for it and allocate bw_conv_buf when needed. if (converted) { - wb_flags = get_fio_flags((char_u *)fenc); + wb_flags = get_fio_flags(fenc); if (wb_flags & (FIO_UCS2 | FIO_UCS4 | FIO_UTF16 | FIO_UTF8)) { // Need to allocate a buffer to translate into. if (wb_flags & (FIO_UCS2 | FIO_UTF16 | FIO_UTF8)) { @@ -2969,10 +2953,9 @@ nobackup: } if (converted && wb_flags == 0) { -#ifdef HAVE_ICONV // Use iconv() conversion when conversion is needed and it's not done // internally. - write_info.bw_iconv_fd = (iconv_t)my_iconv_open((char_u *)fenc, (char_u *)"utf-8"); + write_info.bw_iconv_fd = (iconv_t)my_iconv_open(fenc, "utf-8"); if (write_info.bw_iconv_fd != (iconv_t)-1) { // We're going to use iconv(), allocate a buffer to convert in. write_info.bw_conv_buflen = (size_t)bufsize * ICONV_MULT; @@ -2982,28 +2965,21 @@ nobackup: } write_info.bw_first = true; } else { -#endif - - // When the file needs to be converted with 'charconvert' after - // writing, write to a temp file instead and let the conversion - // overwrite the original file. - if (*p_ccv != NUL) { - wfname = vim_tempname(); - if (wfname == NULL) { // Can't write without a tempfile! - SET_ERRMSG(_("E214: Can't find temp file for writing")); - goto restore_backup; + // When the file needs to be converted with 'charconvert' after + // writing, write to a temp file instead and let the conversion + // overwrite the original file. + if (*p_ccv != NUL) { + wfname = vim_tempname(); + if (wfname == NULL) { // Can't write without a tempfile! + SET_ERRMSG(_("E214: Can't find temp file for writing")); + goto restore_backup; + } } } } -#ifdef HAVE_ICONV -} -#endif - if (converted && wb_flags == 0 -#ifdef HAVE_ICONV && write_info.bw_iconv_fd == (iconv_t)-1 -#endif && wfname == fname) { if (!forceit) { SET_ERRMSG(_("E213: Cannot convert (add ! to write without conversion)")); @@ -3039,9 +3015,11 @@ nobackup: // false. while ((fd = os_open(wfname, O_WRONLY | - (append ? - (forceit ? (O_APPEND | O_CREAT) : O_APPEND) - : (O_CREAT | O_TRUNC)), + (append + ? (forceit + ? (O_APPEND | O_CREAT) + : O_APPEND) + : (O_CREAT | O_TRUNC)), perm < 0 ? 0666 : (perm & 0777))) < 0) { // A forced write will try to create a new file if the old one // is still readonly. This may also happen when the directory @@ -3122,7 +3100,7 @@ restore_backup: } SET_ERRMSG(NULL); - write_info.bw_buf = (char_u *)buffer; + write_info.bw_buf = buffer; nchars = 0; // use "++bin", "++nobin" or 'binary' @@ -3135,7 +3113,7 @@ restore_backup: // Skip the BOM when appending and the file already existed, the BOM // only makes sense at the start of the file. if (buf->b_p_bomb && !write_bin && (!append || perm < 0)) { - write_info.bw_len = make_bom((char_u *)buffer, (char_u *)fenc); + write_info.bw_len = make_bom((char_u *)buffer, fenc); if (write_info.bw_len > 0) { // don't convert write_info.bw_flags = FIO_NOCONVERT | wb_flags; @@ -3167,7 +3145,7 @@ restore_backup: // Keep it fast! ptr = ml_get_buf(buf, lnum, false) - 1; if (write_undo_file) { - sha256_update(&sha_ctx, (char_u *)ptr + 1, (uint32_t)(strlen(ptr + 1) + 1)); + sha256_update(&sha_ctx, (uint8_t *)ptr + 1, (uint32_t)(strlen(ptr + 1) + 1)); } while ((c = *++ptr) != NUL) { if (c == NL) { @@ -3241,6 +3219,11 @@ restore_backup: nchars += len; } + if (!buf->b_p_fixeol && buf->b_p_eof) { + // write trailing CTRL-Z + (void)write_eintr(write_info.bw_fd, "\x1a", 1); + } + // Stop when writing done or an error was encountered. if (!checking_conversion || end == 0) { break; @@ -3308,7 +3291,7 @@ restore_backup: // Probably need to set the ACL before changing the user (can't set the // ACL on a file the user doesn't own). if (!backup_copy) { - mch_set_acl((char_u *)wfname, acl); + os_set_acl(wfname, acl); } #endif @@ -3336,7 +3319,7 @@ restore_backup: } else { errmsg_allocated = true; SET_ERRMSG(xmalloc(300)); - vim_snprintf(errmsg, 300, + vim_snprintf(errmsg, 300, // NOLINT(runtime/printf) _("E513: write error, conversion failed in line %" PRIdLINENR " (make 'fenc' empty to override)"), write_info.bw_conv_error_lnum); @@ -3385,13 +3368,13 @@ restore_backup: fname = sfname; // use shortname now, for the messages #endif if (!filtering) { - add_quoted_fname((char *)IObuff, IOSIZE, buf, (const char *)fname); + add_quoted_fname(IObuff, IOSIZE, buf, (const char *)fname); c = false; if (write_info.bw_conv_error) { STRCAT(IObuff, _(" CONVERSION ERROR")); c = true; if (write_info.bw_conv_error_lnum != 0) { - vim_snprintf_add((char *)IObuff, IOSIZE, _(" in line %" PRId64 ";"), + vim_snprintf_add(IObuff, IOSIZE, _(" in line %" PRId64 ";"), (int64_t)write_info.bw_conv_error_lnum); } } else if (notconverted) { @@ -3425,7 +3408,7 @@ restore_backup: } } - set_keep_msg(msg_trunc_attr((char *)IObuff, false, 0), 0); + set_keep_msg(msg_trunc_attr(IObuff, false, 0), 0); } // When written everything correctly: reset 'modified'. Unless not @@ -3517,22 +3500,20 @@ nofail: } xfree(fenc_tofree); xfree(write_info.bw_conv_buf); -#ifdef HAVE_ICONV if (write_info.bw_iconv_fd != (iconv_t)-1) { iconv_close(write_info.bw_iconv_fd); write_info.bw_iconv_fd = (iconv_t)-1; } -#endif #ifdef HAVE_ACL - mch_free_acl(acl); + os_free_acl(acl); #endif if (errmsg != NULL) { // - 100 to save some space for further error message #ifndef UNIX - add_quoted_fname((char *)IObuff, IOSIZE - 100, buf, (const char *)sfname); + add_quoted_fname(IObuff, IOSIZE - 100, buf, (const char *)sfname); #else - add_quoted_fname((char *)IObuff, IOSIZE - 100, buf, (const char *)fname); + add_quoted_fname(IObuff, IOSIZE - 100, buf, (const char *)fname); #endif if (errnum != NULL) { if (errmsgarg != 0) { @@ -3571,10 +3552,10 @@ nofail: // When writing the whole file and 'undofile' is set, also write the undo // file. if (retval == OK && write_undo_file) { - char hash[UNDO_HASH_SIZE]; + uint8_t hash[UNDO_HASH_SIZE]; - sha256_finish(&sha_ctx, (char_u *)hash); - u_write_undo(NULL, false, buf, (char_u *)hash); + sha256_finish(&sha_ctx, hash); + u_write_undo(NULL, false, buf, hash); } if (!should_abort(retval)) { @@ -3618,7 +3599,7 @@ nofail: /// 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) +static int set_rw_fname(char *fname, char *sfname) { buf_T *buf = curbuf; @@ -3636,7 +3617,7 @@ static int set_rw_fname(char_u *fname, char_u *sfname) return FAIL; } - if (setfname(curbuf, (char *)fname, (char *)sfname, false) == OK) { + if (setfname(curbuf, fname, sfname, false) == OK) { curbuf->b_flags |= BF_NOTEDITED; } @@ -3709,22 +3690,22 @@ static bool msg_add_fileformat(int eol_type) /// Append line and character count to IObuff. void msg_add_lines(int insert_space, long lnum, off_T nchars) { - char_u *p; + char *p; - p = (char_u *)IObuff + STRLEN(IObuff); + p = IObuff + strlen(IObuff); if (insert_space) { *p++ = ' '; } if (shortmess(SHM_LINES)) { - vim_snprintf((char *)p, (size_t)(IOSIZE - (p - (char_u *)IObuff)), "%" PRId64 "L, %" PRId64 "B", + vim_snprintf(p, (size_t)(IOSIZE - (p - IObuff)), "%" PRId64 "L, %" PRId64 "B", (int64_t)lnum, (int64_t)nchars); } else { - vim_snprintf((char *)p, (size_t)(IOSIZE - (p - (char_u *)IObuff)), + vim_snprintf(p, (size_t)(IOSIZE - (p - IObuff)), NGETTEXT("%" PRId64 " line, ", "%" PRId64 " lines, ", lnum), (int64_t)lnum); - p += STRLEN(p); - vim_snprintf((char *)p, (size_t)(IOSIZE - (p - (char_u *)IObuff)), + p += strlen(p); + vim_snprintf(p, (size_t)(IOSIZE - (p - IObuff)), NGETTEXT("%" PRId64 " byte", "%" PRId64 " bytes", nchars), (int64_t)nchars); } @@ -3767,7 +3748,7 @@ static bool time_differs(const FileInfo *file_info, long mtime, long mtime_ns) F || file_info->stat.st_mtim.tv_sec - mtime > 1 || mtime - file_info->stat.st_mtim.tv_sec > 1; #else - || (long)file_info->stat.st_mtim.tv_sec != mtime; + || file_info->stat.st_mtim.tv_sec != mtime; #endif } @@ -3778,7 +3759,7 @@ static bool time_differs(const FileInfo *file_info, long mtime, long mtime_ns) F static int buf_write_bytes(struct bw_info *ip) { int wlen; - char_u *buf = ip->bw_buf; // data to write + char *buf = ip->bw_buf; // data to write int len = ip->bw_len; // length of data #ifdef HAS_BW_FLAGS int flags = ip->bw_flags; // extra flags @@ -3786,7 +3767,7 @@ static int buf_write_bytes(struct bw_info *ip) // Skip conversion when writing the BOM. if (!(flags & FIO_NOCONVERT)) { - char_u *p; + char *p; unsigned c; int n; @@ -3794,7 +3775,7 @@ static int buf_write_bytes(struct bw_info *ip) // Convert latin1 in the buffer to UTF-8 in the file. p = ip->bw_conv_buf; // translate to buffer for (wlen = 0; wlen < len; wlen++) { - p += utf_char2bytes(buf[wlen], (char *)p); + p += utf_char2bytes((uint8_t)buf[wlen], p); } buf = ip->bw_conv_buf; len = (int)(p - ip->bw_conv_buf); @@ -3818,7 +3799,7 @@ static int buf_write_bytes(struct bw_info *ip) l = len; } memmove(ip->bw_rest + ip->bw_restlen, buf, (size_t)l); - n = utf_ptr2len_len(ip->bw_rest, ip->bw_restlen + l); + n = utf_ptr2len_len((char *)ip->bw_rest, ip->bw_restlen + l); if (n > ip->bw_restlen + len) { // We have an incomplete byte sequence at the end to // be written. We can't convert it without the @@ -3858,9 +3839,9 @@ static int buf_write_bytes(struct bw_info *ip) break; } if (n > 1) { - c = (unsigned)utf_ptr2char((char *)buf + wlen); + c = (unsigned)utf_ptr2char(buf + wlen); } else { - c = buf[wlen]; + c = (uint8_t)buf[wlen]; } } @@ -3880,7 +3861,6 @@ static int buf_write_bytes(struct bw_info *ip) } } -#ifdef HAVE_ICONV if (ip->bw_iconv_fd != (iconv_t)-1) { const char *from; size_t fromlen; @@ -3895,17 +3875,17 @@ static int buf_write_bytes(struct bw_info *ip) // the bytes of the current call. Use the end of the // conversion buffer for this. fromlen = (size_t)len + (size_t)ip->bw_restlen; - fp = (char *)ip->bw_conv_buf + ip->bw_conv_buflen - fromlen; + fp = ip->bw_conv_buf + ip->bw_conv_buflen - fromlen; memmove(fp, ip->bw_rest, (size_t)ip->bw_restlen); memmove(fp + ip->bw_restlen, buf, (size_t)len); from = fp; tolen = ip->bw_conv_buflen - fromlen; } else { - from = (const char *)buf; + from = buf; fromlen = (size_t)len; tolen = ip->bw_conv_buflen; } - to = (char *)ip->bw_conv_buf; + to = ip->bw_conv_buf; if (ip->bw_first) { size_t save_len = tolen; @@ -3916,7 +3896,7 @@ static int buf_write_bytes(struct bw_info *ip) // There is a bug in iconv() on Linux (which appears to be // wide-spread) which sets "to" to NULL and messes up "tolen". if (to == NULL) { - to = (char *)ip->bw_conv_buf; + to = ip->bw_conv_buf; tolen = save_len; } ip->bw_first = false; @@ -3937,9 +3917,8 @@ static int buf_write_bytes(struct bw_info *ip) ip->bw_restlen = (int)fromlen; buf = ip->bw_conv_buf; - len = (int)((char_u *)to - ip->bw_conv_buf); + len = (int)(to - ip->bw_conv_buf); } -#endif } if (ip->bw_fd < 0) { @@ -3957,9 +3936,9 @@ static int buf_write_bytes(struct bw_info *ip) /// @param flags FIO_ flags that specify which encoding to use /// /// @return true for an error, false when it's OK. -static bool ucs2bytes(unsigned c, char_u **pp, int flags) FUNC_ATTR_NONNULL_ALL +static bool ucs2bytes(unsigned c, char **pp, int flags) FUNC_ATTR_NONNULL_ALL { - char_u *p = *pp; + char_u *p = (char_u *)(*pp); bool error = false; int cc; @@ -4013,7 +3992,7 @@ static bool ucs2bytes(unsigned c, char_u **pp, int flags) FUNC_ATTR_NONNULL_ALL } } - *pp = p; + *pp = (char *)p; return error; } @@ -4036,8 +4015,8 @@ static bool need_conversion(const char *fenc) } else { // Ignore difference between "ansi" and "latin1", "ucs-4" and // "ucs-4be", etc. - enc_flags = get_fio_flags((char_u *)p_enc); - fenc_flags = get_fio_flags((char_u *)fenc); + enc_flags = get_fio_flags(p_enc); + fenc_flags = get_fio_flags(fenc); same_encoding = (enc_flags != 0 && fenc_flags == enc_flags); } if (same_encoding) { @@ -4055,12 +4034,12 @@ static bool need_conversion(const char *fenc) /// use 'encoding'. /// /// @param name string to check for encoding -static int get_fio_flags(const char_u *name) +static int get_fio_flags(const char *name) { int prop; if (*name == NUL) { - name = (char_u *)p_enc; + name = p_enc; } prop = enc_canon_props(name); if (prop & ENC_UNICODE) { @@ -4096,8 +4075,9 @@ static int get_fio_flags(const char_u *name) /// /// @return the name of the encoding and set "*lenp" to the length or, /// NULL when no BOM found. -static char_u *check_for_bom(char_u *p, long size, int *lenp, int flags) +static char *check_for_bom(const char *p_in, long size, int *lenp, int flags) { + const uint8_t *p = (const uint8_t *)p_in; char *name = NULL; int len = 2; @@ -4133,16 +4113,16 @@ static char_u *check_for_bom(char_u *p, long size, int *lenp, int flags) } *lenp = len; - return (char_u *)name; + return name; } /// 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) +static int make_bom(char_u *buf, char *name) { int flags; - char_u *p; + char *p; flags = get_fio_flags(name); @@ -4157,9 +4137,9 @@ static int make_bom(char_u *buf, char_u *name) buf[2] = 0xbf; return 3; } - p = buf; + p = (char *)buf; (void)ucs2bytes(0xfeff, &p, flags); - return (int)(p - buf); + return (int)((char_u *)p - buf); } /// Shorten filename of a buffer. @@ -4171,7 +4151,7 @@ static int make_bom(char_u *buf, char_u *name) /// /// 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) +void shorten_buf_fname(buf_T *buf, char *dirname, int force) { char *p; @@ -4180,11 +4160,11 @@ void shorten_buf_fname(buf_T *buf, char_u *dirname, int force) && !path_with_url(buf->b_fname) && (force || buf->b_sfname == NULL - || path_is_absolute((char_u *)buf->b_sfname))) { + || path_is_absolute(buf->b_sfname))) { if (buf->b_sfname != buf->b_ffname) { XFREE_CLEAR(buf->b_sfname); } - p = path_shorten_fname(buf->b_ffname, (char *)dirname); + p = path_shorten_fname(buf->b_ffname, dirname); if (p != NULL) { buf->b_sfname = xstrdup(p); buf->b_fname = buf->b_sfname; @@ -4198,11 +4178,11 @@ void shorten_buf_fname(buf_T *buf, char_u *dirname, int force) /// Shorten filenames for all buffers. void shorten_fnames(int force) { - char_u dirname[MAXPATHL]; + char dirname[MAXPATHL]; os_dirname(dirname, MAXPATHL); FOR_ALL_BUFFERS(buf) { - shorten_buf_fname(buf, dirname, force); + shorten_buf_fname(buf, (char *)dirname, force); // Always make the swap file name a full path, a "nofile" buffer may // also have a swap file. @@ -4247,7 +4227,7 @@ char *modname(const char *fname, const char *ext, bool prepend_dot) // (we need the full path in case :cd is used). if (fname == NULL || *fname == NUL) { retval = xmalloc(MAXPATHL + extlen + 3); // +3 for PATHSEP, "_" (Win), NUL - if (os_dirname((char_u *)retval, MAXPATHL) == FAIL + if (os_dirname(retval, MAXPATHL) == FAIL || strlen(retval) == 0) { xfree(retval); return NULL; @@ -4258,7 +4238,7 @@ char *modname(const char *fname, const char *ext, bool prepend_dot) } else { fnamelen = strlen(fname); retval = xmalloc(fnamelen + extlen + 3); - strcpy(retval, fname); + strcpy(retval, fname); // NOLINT(runtime/printf) } // Search backwards until we hit a '/', '\' or ':'. @@ -4276,12 +4256,11 @@ char *modname(const char *fname, const char *ext, bool prepend_dot) ptr[BASENAMELEN] = '\0'; } - char *s; - s = ptr + strlen(ptr); + char *s = ptr + strlen(ptr); // Append the extension. // ext can start with '.' and cannot exceed 3 more characters. - strcpy(s, ext); + strcpy(s, ext); // NOLINT(runtime/printf) char *e; // Prepend the dot if needed. @@ -4315,7 +4294,8 @@ char *modname(const char *fname, const char *ext, bool prepend_dot) /// @param fp file to read from /// /// @return true for EOF or error -bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL +bool vim_fgets(char *buf, int size, FILE *fp) + FUNC_ATTR_NONNULL_ALL { char *retval; @@ -4324,7 +4304,7 @@ bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL do { errno = 0; - retval = fgets((char *)buf, size, fp); + retval = fgets(buf, size, fp); } while (retval == NULL && errno == EINTR && ferror(fp)); if (buf[size - 2] != NUL && buf[size - 2] != '\n') { @@ -4518,7 +4498,7 @@ int vim_rename(const char *from, const char *to) } if (use_tmp_file) { - char_u tempname[MAXPATHL + 1]; + char tempname[MAXPATHL + 1]; // Find a name that doesn't exist and is in the same directory. // Rename "from" to "tempname" and then rename "tempname" to "to". @@ -4527,17 +4507,17 @@ int vim_rename(const char *from, const char *to) } STRCPY(tempname, from); for (n = 123; n < 99999; n++) { - char *tail = path_tail((char *)tempname); - snprintf(tail, (size_t)((MAXPATHL + 1) - (tail - (char *)tempname - 1)), "%d", n); + char *tail = path_tail(tempname); + snprintf(tail, (size_t)((MAXPATHL + 1) - (tail - tempname - 1)), "%d", n); - if (!os_path_exists((char *)tempname)) { - if (os_rename((char_u *)from, tempname) == OK) { - if (os_rename(tempname, (char_u *)to) == OK) { + if (!os_path_exists(tempname)) { + if (os_rename(from, tempname) == OK) { + if (os_rename(tempname, to) == OK) { return 0; } // Strange, the second step failed. Try moving the // file back and return failure. - (void)os_rename(tempname, (char_u *)from); + (void)os_rename(tempname, from); return -1; } // If it fails for one temp name it will most likely fail @@ -4555,7 +4535,7 @@ int vim_rename(const char *from, const char *to) os_remove((char *)to); // First try a normal rename, return if it works. - if (os_rename((char_u *)from, (char_u *)to) == OK) { + if (os_rename(from, to) == OK) { return 0; } @@ -4563,12 +4543,12 @@ int vim_rename(const char *from, const char *to) perm = os_getperm(from); #ifdef HAVE_ACL // For systems that support ACL: get the ACL from the original file. - acl = mch_get_acl((char_u *)from); + acl = os_get_acl(from); #endif fd_in = os_open((char *)from, O_RDONLY, 0); if (fd_in < 0) { #ifdef HAVE_ACL - mch_free_acl(acl); + os_free_acl(acl); #endif return -1; } @@ -4579,7 +4559,7 @@ int vim_rename(const char *from, const char *to) if (fd_out < 0) { close(fd_in); #ifdef HAVE_ACL - mch_free_acl(acl); + os_free_acl(acl); #endif return -1; } @@ -4591,7 +4571,7 @@ int vim_rename(const char *from, const char *to) close(fd_out); close(fd_in); #ifdef HAVE_ACL - mch_free_acl(acl); + os_free_acl(acl); #endif return -1; } @@ -4613,11 +4593,11 @@ int vim_rename(const char *from, const char *to) to = from; } #ifndef UNIX // For Unix os_open() already set the permission. - os_setperm((const char *)to, perm); + os_setperm(to, perm); #endif #ifdef HAVE_ACL - mch_set_acl((char_u *)to, acl); - mch_free_acl(acl); + os_set_acl(to, acl); + os_free_acl(acl); #endif if (errmsg != NULL) { semsg(errmsg, to); @@ -4924,7 +4904,7 @@ int buf_check_timestamp(buf_T *buf) } msg_clr_eos(); (void)msg_end(); - if (emsg_silent == 0) { + if (emsg_silent == 0 && !in_assert_fails) { ui_flush(); // give the user some time to think about it os_delay(1004L, true); @@ -4975,7 +4955,7 @@ void buf_reload(buf_T *buf, int orig_mode, bool reload_options) aco_save_T aco; int flags = READ_NEW; - // set curwin/curbuf for "buf" and save some things + // Set curwin/curbuf for "buf" and save some things. aucmd_prepbuf(&aco, buf); // Unless reload_options is set, we only want to read the text from the @@ -5122,11 +5102,11 @@ void write_lnum_adjust(linenr_T offset) #if defined(BACKSLASH_IN_FILENAME) /// Convert all backslashes in fname to forward slashes in-place, /// unless when it looks like a URL. -void forward_slash(char_u *fname) +void forward_slash(char *fname) { - char_u *p; + char *p; - if (path_with_url((const char *)fname)) { + if (path_with_url(fname)) { return; } for (p = fname; *p != NUL; p++) { @@ -5139,6 +5119,9 @@ void forward_slash(char_u *fname) /// Path to Nvim's own temp dir. Ends in a slash. static char *vim_tempdir = NULL; +#if defined(HAVE_FLOCK) && defined(HAVE_DIRFD) +DIR *vim_tempdir_dp = NULL; ///< File descriptor of temp dir +#endif /// Creates a directory for private use by this instance of Nvim, trying each of /// `TEMP_DIR_NAMES` until one succeeds. @@ -5211,10 +5194,9 @@ static void vim_mktempdir(void) if (vim_settempdir(path)) { // Successfully created and set temporary directory so stop trying. break; - } else { - // Couldn't set `vim_tempdir` to `path` so remove created directory. - os_rmdir(path); } + // Couldn't set `vim_tempdir` to `path` so remove created directory. + os_rmdir(path); } (void)umask(umask_save); } @@ -5281,7 +5263,7 @@ int delete_recursive(const char *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]); + vim_snprintf(NameBuff, MAXPATHL, "%s/%s", exp, ((char **)ga.ga_data)[i]); if (delete_recursive((const char *)NameBuff) != 0) { // Remember the failure but continue deleting any further // entries. @@ -5304,15 +5286,50 @@ int delete_recursive(const char *name) return result; } +#if defined(HAVE_FLOCK) && defined(HAVE_DIRFD) +/// Open temporary directory and take file lock to prevent +/// to be auto-cleaned. +static void vim_opentempdir(void) +{ + if (vim_tempdir_dp != NULL) { + return; + } + + DIR *dp = opendir(vim_tempdir); + if (dp == NULL) { + return; + } + + vim_tempdir_dp = dp; + flock(dirfd(vim_tempdir_dp), LOCK_SH); +} + +/// Close temporary directory - it automatically release file lock. +static void vim_closetempdir(void) +{ + if (vim_tempdir_dp == NULL) { + return; + } + + closedir(vim_tempdir_dp); + vim_tempdir_dp = NULL; +} +#endif + /// Delete the temp directory and all files it contains. void vim_deltempdir(void) { - if (vim_tempdir != NULL) { - // remove the trailing path separator - path_tail(vim_tempdir)[-1] = NUL; - delete_recursive(vim_tempdir); - XFREE_CLEAR(vim_tempdir); + if (vim_tempdir == NULL) { + return; } + +#if defined(HAVE_FLOCK) && defined(HAVE_DIRFD) + vim_closetempdir(); +#endif + // remove the trailing path separator + path_tail(vim_tempdir)[-1] = NUL; + delete_recursive(vim_tempdir); + XFREE_CLEAR(vim_tempdir); } /// Gets path to Nvim's own temp dir (ending with slash). @@ -5337,12 +5354,16 @@ char *vim_gettempdir(void) static bool vim_settempdir(char *tempdir) { char *buf = verbose_try_malloc(MAXPATHL + 2); - if (!buf) { + if (buf == NULL) { return false; } + vim_FullName(tempdir, buf, MAXPATHL, false); add_pathsep(buf); vim_tempdir = xstrdup(buf); +#if defined(HAVE_FLOCK) && defined(HAVE_DIRFD) + vim_opentempdir(); +#endif xfree(buf); return true; } @@ -5430,28 +5451,27 @@ bool match_file_pat(char *pattern, regprog_T **prog, char *fname, char *sfname, /// @param ffname full file name /// /// @return true if there was a match -bool match_file_list(char_u *list, char_u *sfname, char_u *ffname) +bool match_file_list(char *list, char *sfname, char *ffname) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1, 3) { - char_u buf[100]; - char_u *tail; - char_u *regpat; + char buf[100]; + char *tail; + char *regpat; char allow_dirs; bool match; char *p; - tail = (char_u *)path_tail((char *)sfname); + tail = path_tail(sfname); // try all patterns in 'wildignore' - p = (char *)list; + p = list; while (*p) { - copy_option_part(&p, (char *)buf, ARRAY_SIZE(buf), ","); - regpat = (char_u *)file_pat_to_reg_pat((char *)buf, NULL, &allow_dirs, false); + copy_option_part(&p, buf, ARRAY_SIZE(buf), ","); + regpat = file_pat_to_reg_pat(buf, NULL, &allow_dirs, false); if (regpat == NULL) { break; } - match = match_file_pat((char *)regpat, NULL, (char *)ffname, (char *)sfname, (char *)tail, - (int)allow_dirs); + match = match_file_pat(regpat, NULL, ffname, sfname, tail, (int)allow_dirs); xfree(regpat); if (match) { return true; @@ -5560,7 +5580,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs // "\*" to "\\.*" e.g., "dir\*.c" // "\?" to "\\." e.g., "dir\??.c" // "\+" to "\+" e.g., "fileX\+.c" - if ((vim_isfilec(p[1]) || p[1] == '*' || p[1] == '?') + if ((vim_isfilec((uint8_t)p[1]) || p[1] == '*' || p[1] == '?') && p[1] != '+') { reg_pat[i++] = '['; reg_pat[i++] = '\\'; diff --git a/src/nvim/fileio.h b/src/nvim/fileio.h index ae3c51f1bc..dabcda5bf2 100644 --- a/src/nvim/fileio.h +++ b/src/nvim/fileio.h @@ -3,6 +3,7 @@ #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/garray.h" #include "nvim/os/os.h" diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 1872558669..7306131574 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -5,10 +5,15 @@ // fold.c: code for folding +#include <assert.h> #include <inttypes.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" #include "nvim/charset.h" @@ -16,14 +21,17 @@ #include "nvim/diff.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" -#include "nvim/ex_docmd.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_session.h" #include "nvim/extmark.h" #include "nvim/fold.h" -#include "nvim/func_attr.h" #include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/indent.h" #include "nvim/mark.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" @@ -35,6 +43,7 @@ #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/syntax.h" +#include "nvim/types.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -57,9 +66,11 @@ typedef struct { // folds too } fold_T; -#define FD_OPEN 0 // fold is open (nested ones can be closed) -#define FD_CLOSED 1 // fold is closed -#define FD_LEVEL 2 // depends on 'foldlevel' (nested folds too) +enum { + FD_OPEN = 0, // fold is open (nested ones can be closed) + FD_CLOSED = 1, // fold is closed + FD_LEVEL = 2, // depends on 'foldlevel' (nested folds too) +}; #define MAX_LEVEL 20 // maximum fold depth @@ -466,12 +477,15 @@ static void newFoldLevelWin(win_T *wp) /// Apply 'foldlevel' to all folds that don't contain the cursor. void foldCheckClose(void) { - if (*p_fcl != NUL) { // can only be "all" right now - checkupdate(curwin); - if (checkCloseRec(&curwin->w_folds, curwin->w_cursor.lnum, - (int)curwin->w_p_fdl)) { - changed_window_setting(); - } + if (*p_fcl == NUL) { + return; + } + + // 'foldclose' can only be "all" right now + checkupdate(curwin); + if (checkCloseRec(&curwin->w_folds, curwin->w_cursor.lnum, + (int)curwin->w_p_fdl)) { + changed_window_setting(); } } @@ -994,15 +1008,18 @@ void foldAdjustVisual(void) if (hasFolding(start->lnum, &start->lnum, NULL)) { start->col = 0; } - if (hasFolding(end->lnum, NULL, &end->lnum)) { - ptr = ml_get(end->lnum); - end->col = (colnr_T)strlen(ptr); - if (end->col > 0 && *p_sel == 'o') { - end->col--; - } - // prevent cursor from moving on the trail byte - mb_adjust_cursor(); + + if (!hasFolding(end->lnum, NULL, &end->lnum)) { + return; + } + + ptr = ml_get(end->lnum); + end->col = (colnr_T)strlen(ptr); + if (end->col > 0 && *p_sel == 'o') { + end->col--; } + // prevent cursor from moving on the trail byte + mb_adjust_cursor(); } // cursor_foldstart() {{{2 @@ -1083,7 +1100,7 @@ static int foldLevelWin(win_T *wp, linenr_T lnum) { fold_T *fp; linenr_T lnum_rel = lnum; - int level = 0; + int level = 0; // Recursively search for a fold that contains "lnum". garray_T *gap = &wp->w_folds; @@ -1104,10 +1121,12 @@ static int foldLevelWin(win_T *wp, linenr_T lnum) /// Check if the folds in window "wp" are invalid and update them if needed. static void checkupdate(win_T *wp) { - if (wp->w_foldinvalid) { - foldUpdate(wp, (linenr_T)1, (linenr_T)MAXLNUM); // will update all - wp->w_foldinvalid = false; + if (!wp->w_foldinvalid) { + return; } + + foldUpdate(wp, (linenr_T)1, (linenr_T)MAXLNUM); // will update all + wp->w_foldinvalid = false; } // setFoldRepeat() {{{2 @@ -1425,15 +1444,13 @@ static void foldMarkAdjustRecurse(win_T *wp, garray_T *gap, linenr_T line1, line // 5. fold is below line1 and contains line2; need to // correct nested folds too if (amount == MAXLNUM) { - foldMarkAdjustRecurse(wp, &fp->fd_nested, line1 - fp->fd_top, - line2 - fp->fd_top, amount, - amount_after + (fp->fd_top - top)); + foldMarkAdjustRecurse(wp, &fp->fd_nested, 0, line2 - fp->fd_top, + amount, amount_after + (fp->fd_top - top)); fp->fd_len -= line2 - fp->fd_top + 1; fp->fd_top = line1; } else { - foldMarkAdjustRecurse(wp, &fp->fd_nested, line1 - fp->fd_top, - line2 - fp->fd_top, amount, - amount_after - amount); + foldMarkAdjustRecurse(wp, &fp->fd_nested, 0, line2 - fp->fd_top, + amount, amount_after - amount); fp->fd_len += amount_after - amount; fp->fd_top += amount; } @@ -1514,23 +1531,25 @@ static bool check_closed(win_T *const wp, fold_T *const fp, bool *const use_leve /// @param lnum_off offset for fp->fd_top static void checkSmall(win_T *const wp, fold_T *const fp, const linenr_T lnum_off) { - if (fp->fd_small == kNone) { - // Mark any nested folds to maybe-small - setSmallMaybe(&fp->fd_nested); + if (fp->fd_small != kNone) { + return; + } - if (fp->fd_len > wp->w_p_fml) { - fp->fd_small = kFalse; - } else { - int count = 0; - for (int n = 0; n < fp->fd_len; n++) { - count += plines_win_nofold(wp, fp->fd_top + lnum_off + n); - if (count > wp->w_p_fml) { - fp->fd_small = kFalse; - return; - } + // Mark any nested folds to maybe-small + setSmallMaybe(&fp->fd_nested); + + if (fp->fd_len > wp->w_p_fml) { + fp->fd_small = kFalse; + } else { + int count = 0; + for (int n = 0; n < fp->fd_len; n++) { + count += plines_win_nofold(wp, fp->fd_top + lnum_off + n); + if (count > wp->w_p_fml) { + fp->fd_small = kFalse; + return; } - fp->fd_small = kTrue; } + fp->fd_small = kTrue; } } @@ -1583,29 +1602,31 @@ static void foldAddMarker(buf_T *buf, pos_T pos, const char *marker, size_t mark // Allocate a new line: old-line + 'cms'-start + marker + 'cms'-end char *line = ml_get_buf(buf, lnum, false); - size_t line_len = STRLEN(line); + size_t line_len = strlen(line); size_t added = 0; - if (u_save(lnum - 1, lnum + 1) == OK) { - // Check if the line ends with an unclosed comment - skip_comment(line, false, false, &line_is_comment); - newline = xmalloc(line_len + markerlen + strlen(cms) + 1); - STRCPY(newline, line); - // Append the marker to the end of the line - if (p == NULL || line_is_comment) { - STRLCPY(newline + line_len, marker, markerlen + 1); - added = markerlen; - } else { - STRCPY(newline + line_len, cms); - memcpy(newline + line_len + (p - cms), marker, markerlen); - STRCPY(newline + line_len + (p - cms) + markerlen, p + 2); - added = markerlen + strlen(cms) - 2; - } - ml_replace_buf(buf, lnum, newline, false); - if (added) { - extmark_splice_cols(buf, (int)lnum - 1, (int)line_len, - 0, (int)added, kExtmarkUndo); - } + if (u_save(lnum - 1, lnum + 1) != OK) { + return; + } + + // Check if the line ends with an unclosed comment + skip_comment(line, false, false, &line_is_comment); + newline = xmalloc(line_len + markerlen + strlen(cms) + 1); + STRCPY(newline, line); + // Append the marker to the end of the line + if (p == NULL || line_is_comment) { + xstrlcpy(newline + line_len, marker, markerlen + 1); + added = markerlen; + } else { + STRCPY(newline + line_len, cms); + memcpy(newline + line_len + (p - cms), marker, markerlen); + STRCPY(newline + line_len + (p - cms) + markerlen, p + 2); + added = markerlen + strlen(cms) - 2; + } + ml_replace_buf(buf, lnum, newline, false); + if (added) { + extmark_splice_cols(buf, (int)lnum - 1, (int)line_len, + 0, (int)added, kExtmarkUndo); } } @@ -1642,7 +1663,7 @@ static void foldDelMarker(buf_T *buf, linenr_T lnum, char *marker, size_t marker char *cms = buf->b_p_cms; char *line = ml_get_buf(buf, lnum, false); for (char *p = line; *p != NUL; p++) { - if (STRNCMP(p, marker, markerlen) != 0) { + if (strncmp(p, marker, markerlen) != 0) { continue; } // Found the marker, include a digit if it's there. @@ -1654,8 +1675,8 @@ static void foldDelMarker(buf_T *buf, linenr_T lnum, char *marker, size_t marker // Also delete 'commentstring' if it matches. char *cms2 = strstr(cms, "%s"); if (p - line >= cms2 - cms - && STRNCMP(p - (cms2 - cms), cms, cms2 - cms) == 0 - && STRNCMP(p + len, cms2 + 2, strlen(cms2 + 2)) == 0) { + && strncmp(p - (cms2 - cms), cms, (size_t)(cms2 - cms)) == 0 + && strncmp(p + len, cms2 + 2, strlen(cms2 + 2)) == 0) { p -= cms2 - cms; len += strlen(cms) - 2; } @@ -1772,7 +1793,7 @@ char *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldinfo } } if (text == NULL) { - unsigned long count = (unsigned long)(lnume - lnum + 1); + long count = lnume - lnum + 1; vim_snprintf(buf, FOLD_TEXT_LEN, NGETTEXT("+--%3ld line folded", @@ -1818,9 +1839,9 @@ static void foldtext_cleanup(char *str) for (char *s = str; *s != NUL;) { size_t len = 0; - if (STRNCMP(s, curwin->w_p_fmr, foldstartmarkerlen) == 0) { + if (strncmp(s, curwin->w_p_fmr, foldstartmarkerlen) == 0) { len = foldstartmarkerlen; - } else if (STRNCMP(s, foldendmarker, foldendmarkerlen) == 0) { + } else if (strncmp(s, foldendmarker, foldendmarkerlen) == 0) { len = foldendmarkerlen; } if (len > 0) { @@ -1833,16 +1854,16 @@ static void foldtext_cleanup(char *str) char *p; for (p = s; p > str && ascii_iswhite(p[-1]); p--) {} if (p >= str + cms_slen - && STRNCMP(p - cms_slen, cms_start, cms_slen) == 0) { + && strncmp(p - cms_slen, cms_start, cms_slen) == 0) { len += (size_t)(s - p) + cms_slen; s = p - cms_slen; } } else if (cms_end != NULL) { - if (!did1 && cms_slen > 0 && STRNCMP(s, cms_start, cms_slen) == 0) { + if (!did1 && cms_slen > 0 && strncmp(s, cms_start, cms_slen) == 0) { len = cms_slen; did1 = true; } else if (!did2 && cms_elen > 0 - && STRNCMP(s, cms_end, cms_elen) == 0) { + && strncmp(s, cms_end, cms_elen) == 0) { len = cms_elen; did2 = true; } @@ -2852,7 +2873,7 @@ static void foldlevelIndent(fline_T *flp) // empty line or lines starting with a character in 'foldignore': level // depends on surrounding lines - if (*s == NUL || vim_strchr(flp->wp->w_p_fdi, *s) != NULL) { + if (*s == NUL || vim_strchr(flp->wp->w_p_fdi, (uint8_t)(*s)) != NULL) { // first and last line can't be undefined, use level 0 if (lnum == 1 || lnum == buf->b_ml.ml_line_count) { flp->lvl = 0; @@ -3013,7 +3034,7 @@ static void foldlevelMarker(fline_T *flp) char *s = ml_get_buf(flp->wp->w_buffer, flp->lnum + flp->off, false); while (*s) { if (*s == cstart - && STRNCMP(s + 1, startmarker, foldstartmarkerlen - 1) == 0) { + && strncmp(s + 1, startmarker, foldstartmarkerlen - 1) == 0) { // found startmarker: set flp->lvl s += foldstartmarkerlen; if (ascii_isdigit(*s)) { @@ -3033,7 +3054,7 @@ static void foldlevelMarker(fline_T *flp) flp->start++; } } else if (*s == cend - && STRNCMP(s + 1, foldendmarker + 1, foldendmarkerlen - 1) == 0) { + && strncmp(s + 1, foldendmarker + 1, foldendmarkerlen - 1) == 0) { // found endmarker: set flp->lvl_next s += foldendmarkerlen; if (ascii_isdigit(*s)) { @@ -3115,7 +3136,7 @@ static int put_folds_recurse(FILE *fd, garray_T *gap, linenr_T off) return FAIL; } if (fprintf(fd, "%" PRId64 ",%" PRId64 "fold", - (int64_t)(fp->fd_top + off), + (int64_t)fp->fd_top + off, (int64_t)(fp->fd_top + off + fp->fd_len - 1)) < 0 || put_eol(fd) == FAIL) { return FAIL; @@ -3136,7 +3157,7 @@ static int put_foldopen_recurse(FILE *fd, win_T *wp, garray_T *gap, linenr_T off if (fp->fd_flags != FD_LEVEL) { if (!GA_EMPTY(&fp->fd_nested)) { // open nested folds while this fold is open - if (fprintf(fd, "%" PRId64, (int64_t)(fp->fd_top + off)) < 0 + if (fprintf(fd, "%" PRId64, (int64_t)fp->fd_top + off) < 0 || put_eol(fd) == FAIL || put_line(fd, "normal! zo") == FAIL) { return FAIL; @@ -3249,23 +3270,23 @@ void f_foldtext(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } // Find interesting text in this line. - char_u *s = (char_u *)skipwhite(ml_get(lnum)); + char *s = skipwhite(ml_get(lnum)); // skip C comment-start if (s[0] == '/' && (s[1] == '*' || s[1] == '/')) { - s = (char_u *)skipwhite((char *)s + 2); - if (*skipwhite((char *)s) == NUL && lnum + 1 < foldend) { - s = (char_u *)skipwhite(ml_get(lnum + 1)); + s = skipwhite(s + 2); + if (*skipwhite(s) == NUL && lnum + 1 < foldend) { + s = skipwhite(ml_get(lnum + 1)); if (*s == '*') { - s = (char_u *)skipwhite((char *)s + 1); + s = skipwhite(s + 1); } } } - int count = foldend - foldstart + 1; + long count = foldend - foldstart + 1; char *txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count); size_t len = strlen(txt) + strlen(dashes) // for %s + 20 // for %3ld - + STRLEN(s); // concatenated + + strlen(s); // concatenated char *r = xmalloc(len); snprintf(r, len, txt, dashes, count); len = strlen(r); diff --git a/src/nvim/fold.h b/src/nvim/fold.h index 395cd8e30a..ac1e8c9419 100644 --- a/src/nvim/fold.h +++ b/src/nvim/fold.h @@ -5,6 +5,7 @@ #include "nvim/buffer_defs.h" #include "nvim/garray.h" +#include "nvim/macros.h" #include "nvim/pos.h" #include "nvim/types.h" @@ -19,8 +20,6 @@ typedef struct foldinfo { linenr_T fi_lines; } foldinfo_T; -#define FOLDINFO_INIT { 0, 0, 0, 0 } - EXTERN int disable_fold_update INIT(= 0); #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/garray.c b/src/nvim/garray.c index 6c63ce5a7c..aa9a44d410 100644 --- a/src/nvim/garray.c +++ b/src/nvim/garray.c @@ -5,22 +5,17 @@ /// /// Functions for handling growing arrays. -#include <inttypes.h> #include <string.h> -#include "nvim/ascii.h" #include "nvim/garray.h" #include "nvim/log.h" #include "nvim/memory.h" #include "nvim/path.h" #include "nvim/strings.h" -#include "nvim/vim.h" - -// #include "nvim/globals.h" -#include "nvim/memline.h" +#include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "garray.c.generated.h" +# include "garray.c.generated.h" // IWYU pragma: export #endif /// Clear an allocated growing array. @@ -167,7 +162,7 @@ char *ga_concat_strings_sep(const garray_T *gap, const char *sep) s = xstpcpy(s, strings[i]); s = xstpcpy(s, sep); } - strcpy(s, strings[nelem - 1]); + strcpy(s, strings[nelem - 1]); // NOLINT(runtime/printf) return ret; } @@ -178,9 +173,9 @@ char *ga_concat_strings_sep(const garray_T *gap, const char *sep) /// @param gap /// /// @returns the concatenated strings -char_u *ga_concat_strings(const garray_T *gap) FUNC_ATTR_NONNULL_RET +char *ga_concat_strings(const garray_T *gap) FUNC_ATTR_NONNULL_RET { - return (char_u *)ga_concat_strings_sep(gap, ","); + return ga_concat_strings_sep(gap, ","); } /// Concatenate a string to a growarray which contains characters. @@ -221,7 +216,7 @@ void ga_concat_len(garray_T *const gap, const char *restrict s, const size_t len /// /// @param gap /// @param c -void ga_append(garray_T *gap, char c) +void ga_append(garray_T *gap, uint8_t c) { - GA_APPEND(char, gap, c); + GA_APPEND(uint8_t, gap, c); } diff --git a/src/nvim/garray.h b/src/nvim/garray.h index 0281678925..1623c4db7b 100644 --- a/src/nvim/garray.h +++ b/src/nvim/garray.h @@ -1,9 +1,11 @@ #ifndef NVIM_GARRAY_H #define NVIM_GARRAY_H -#include <stddef.h> // for size_t +#include <stdbool.h> +#include <stddef.h> #include "nvim/log.h" +#include "nvim/memory.h" #include "nvim/types.h" /// Structure used for growing arrays. @@ -25,7 +27,8 @@ typedef struct growarray { #define GA_APPEND(item_type, gap, item) \ do { \ ga_grow(gap, 1); \ - ((item_type *)(gap)->ga_data)[(gap)->ga_len++] = (item); \ + ((item_type *)(gap)->ga_data)[(gap)->ga_len] = (item); \ + (gap)->ga_len++; \ } while (0) #define GA_APPEND_VIA_PTR(item_type, gap) \ diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua index d872ffd6a9..3e89b60b4a 100644 --- a/src/nvim/generators/c_grammar.lua +++ b/src/nvim/generators/c_grammar.lua @@ -27,6 +27,7 @@ local c_void = P('void') local c_param_type = ( ((P('Error') * fill * P('*') * fill) * Cc('error')) + ((P('Arena') * fill * P('*') * fill) * Cc('arena')) + + ((P('lua_State') * fill * P('*') * fill) * Cc('lstate')) + C((P('const ') ^ -1) * (c_id) * (ws ^ 1) * P('*')) + (C(c_id) * (ws ^ 1)) ) @@ -48,9 +49,9 @@ local c_proto = Ct( (fill * Cg((P('FUNC_API_LUA_ONLY') * Cc(true)), 'lua_only') ^ -1) * (fill * Cg((P('FUNC_API_CHECK_TEXTLOCK') * Cc(true)), 'check_textlock') ^ -1) * (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 * Cg((P('FUNC_API_CLIENT_IGNORE') * Cc(true)), 'client_ignore') ^ -1) * fill * P(';') ) diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 67b8f5f0f5..35f6bf8455 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -60,6 +60,12 @@ for i = 6, #arg do if public and not fn.noexport then functions[#functions + 1] = tmp[j] function_names[fn.name] = true + if #fn.parameters >= 2 and fn.parameters[2][1] == 'Array' and fn.parameters[2][2] == 'uidata' then + -- function receives the "args" as a parameter + fn.receives_array_args = true + -- remove the args parameter + table.remove(fn.parameters, 2) + end if #fn.parameters ~= 0 and fn.parameters[1][2] == 'channel_id' then -- this function should receive the channel id fn.receives_channel_id = true @@ -78,6 +84,10 @@ for i = 6, #arg do fn.arena_return = true fn.parameters[#fn.parameters] = nil end + if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'lstate' then + fn.has_lua_imp = true + fn.parameters[#fn.parameters] = nil + end end end input:close() @@ -155,7 +165,7 @@ local exported_attributes = {'name', 'return_type', 'method', 'since', 'deprecated_since'} local exported_functions = {} for _,f in ipairs(functions) do - if not (startswith(f.name, "nvim__") or f.name == "nvim_error_event") then + if not (startswith(f.name, "nvim__") or f.name == "nvim_error_event" or f.name == "redraw") then local f_exported = {} for _,attr in ipairs(exported_attributes) do f_exported[attr] = f[attr] @@ -185,6 +195,35 @@ funcs_metadata_output:close() -- start building the dispatch wrapper output local output = io.open(dispatch_outputf, 'wb') + +-- =========================================================================== +-- 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! +-- =========================================================================== +output:write([[ +#include "nvim/log.h" +#include "nvim/map.h" +#include "nvim/msgpack_rpc/helpers.h" +#include "nvim/vim.h" + +#include "nvim/api/autocmd.h" +#include "nvim/api/buffer.h" +#include "nvim/api/command.h" +#include "nvim/api/deprecated.h" +#include "nvim/api/extmark.h" +#include "nvim/api/options.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/ui_client.h" + +]]) + local function real_type(type) local rv = type local rmatch = string.match(type, "Dict%(([_%w]+)%)") @@ -231,11 +270,13 @@ for i = 1, #functions do output:write('\n '..rt..' '..converted..';') end output:write('\n') - output:write('\n if (args.size != '..#fn.parameters..') {') - output:write('\n api_set_error(error, kErrorTypeException, \ - "Wrong number of arguments: expecting '..#fn.parameters..' but got %zu", args.size);') - output:write('\n goto cleanup;') - output:write('\n }\n') + if not fn.receives_array_args then + output:write('\n if (args.size != '..#fn.parameters..') {') + output:write('\n api_set_error(error, kErrorTypeException, \ + "Wrong number of arguments: expecting '..#fn.parameters..' but got %zu", args.size);') + output:write('\n goto cleanup;') + output:write('\n }\n') + end -- Validation/conversion for each argument for j = 1, #fn.parameters do @@ -317,18 +358,42 @@ for i = 1, #functions do if fn.receives_channel_id then -- if the function receives the channel id, pass it as first argument if #args > 0 or fn.can_fail then - output:write('channel_id, '..call_args) + output:write('channel_id, ') + if fn.receives_array_args then + -- if the function receives the array args, pass it the second argument + output:write('args, ') + end + output:write(call_args) else output:write('channel_id') + if fn.receives_array_args then + output:write(', args') + end end else - output:write(call_args) + if fn.receives_array_args then + if #args > 0 or fn.call_fail then + output:write('args, '..call_args) + else + output:write('args') + end + else + output:write(call_args) + end end if fn.arena_return then output:write(', arena') end + if fn.has_lua_imp then + if #args > 0 then + output:write(', NULL') + else + output:write('NULL') + end + end + if fn.can_fail then -- if the function can fail, also pass a pointer to the local error object if #args > 0 then @@ -361,7 +426,9 @@ for _,fn in ipairs(functions) do end remote_fns.redraw = {impl_name="ui_client_redraw", fast=true} -local hashorder, hashfun = hashy.hashy_hash("msgpack_rpc_get_handler_for", vim.tbl_keys(remote_fns), function (idx) +local names = vim.tbl_keys(remote_fns) +table.sort(names) +local hashorder, hashfun = hashy.hashy_hash("msgpack_rpc_get_handler_for", names, function (idx) return "method_handlers["..idx.."].name" end) @@ -497,6 +564,10 @@ local function process_function(fn) ]]) end + if fn.has_lua_imp then + cparams = cparams .. 'lstate, ' + end + if fn.can_fail then cparams = cparams .. '&err' else @@ -539,13 +610,27 @@ local function process_function(fn) end write_shifted_output(output, string.format([[ const %s ret = %s(%s); + ]], fn.return_type, fn.name, cparams)) + + if fn.has_lua_imp then + -- only push onto the Lua stack if we haven't already + write_shifted_output(output, string.format([[ + if (lua_gettop(lstate) == 0) { + nlua_push_%s(lstate, ret, true); + } + ]], return_type)) + else + write_shifted_output(output, string.format([[ nlua_push_%s(lstate, ret, true); + ]], return_type)) + end + + write_shifted_output(output, string.format([[ %s %s %s return 1; - ]], fn.return_type, fn.name, cparams, return_type, - free_retval, free_at_exit_code, err_throw_code)) + ]], free_retval, free_at_exit_code, err_throw_code)) else write_shifted_output(output, string.format([[ %s(%s); diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index ea66be7ee8..827097f69d 100755 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -3,14 +3,12 @@ local mpack = require('mpack') local nvimdir = arg[1] package.path = nvimdir .. '/?.lua;' .. package.path -assert(#arg == 8) +assert(#arg == 6) 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 call_output = io.open(arg[3], 'wb') +local remote_output = io.open(arg[4], 'wb') +local metadata_output = io.open(arg[5], 'wb') +local client_output = io.open(arg[6], 'wb') local c_grammar = require('generators.c_grammar') local events = c_grammar.grammar:match(input:read('*all')) @@ -82,12 +80,9 @@ local function call_ui_event_method(output, ev) end end - output:write(' ui_call_'..ev.name..'(') + output:write(' tui_'..ev.name..'(tui') for j = 1, #ev.parameters do - output:write('arg_'..j) - if j ~= #ev.parameters then - output:write(', ') - end + output:write(', arg_'..j) end output:write(');\n') @@ -105,12 +100,9 @@ for i = 1, #events do ev.since = tonumber(ev.since) if not ev.remote_only then - proto_output:write(' void (*'..ev.name..')') - write_signature(proto_output, ev, 'UI *ui') - proto_output:write(';\n') if not ev.remote_impl and not ev.noexport then - remote_output:write('static void remote_ui_'..ev.name) + remote_output:write('void remote_ui_'..ev.name) write_signature(remote_output, ev, 'UI *ui') remote_output:write('\n{\n') remote_output:write(' UIData *data = ui->data;\n') @@ -119,62 +111,6 @@ for i = 1, #events do remote_output:write(' push_call(ui, "'..ev.name..'", args);\n') remote_output:write('}\n\n') end - - if not ev.bridge_impl and not ev.noexport then - local send, argv, recv, recv_argv, recv_cleanup = '', '', '', '', '' - local argc = 1 - for j = 1, #ev.parameters do - local param = ev.parameters[j] - local copy = 'copy_'..param[2] - if param[1] == 'String' then - send = send..' String copy_'..param[2]..' = copy_string('..param[2]..', NULL);\n' - argv = argv..', '..copy..'.data, INT2PTR('..copy..'.size)' - recv = (recv..' String '..param[2].. - ' = (String){.data = argv['..argc..'],'.. - '.size = (size_t)argv['..(argc+1)..']};\n') - recv_argv = recv_argv..', '..param[2] - recv_cleanup = recv_cleanup..' api_free_string('..param[2]..');\n' - argc = argc+2 - elseif param[1] == 'Array' then - send = send..' Array '..copy..' = copy_array('..param[2]..', NULL);\n' - argv = argv..', '..copy..'.items, INT2PTR('..copy..'.size)' - recv = (recv..' Array '..param[2].. - ' = (Array){.items = argv['..argc..'],'.. - '.size = (size_t)argv['..(argc+1)..']};\n') - recv_argv = recv_argv..', '..param[2] - recv_cleanup = recv_cleanup..' api_free_array('..param[2]..');\n' - argc = argc+2 - elseif param[1] == 'Object' then - send = send..' Object *'..copy..' = xmalloc(sizeof(Object));\n' - send = send..' *'..copy..' = copy_object('..param[2]..', NULL);\n' - argv = argv..', '..copy - recv = recv..' Object '..param[2]..' = *(Object *)argv['..argc..'];\n' - recv_argv = recv_argv..', '..param[2] - recv_cleanup = (recv_cleanup..' api_free_object('..param[2]..');\n'.. - ' xfree(argv['..argc..']);\n') - argc = argc+1 - elseif param[1] == 'Integer' or param[1] == 'Boolean' then - argv = argv..', INT2PTR('..param[2]..')' - recv_argv = recv_argv..', PTR2INT(argv['..argc..'])' - argc = argc+1 - else - assert(false) - end - end - bridge_output:write('static void ui_bridge_'..ev.name.. - '_event(void **argv)\n{\n') - bridge_output:write(' UI *ui = UI(argv[0]);\n') - bridge_output:write(recv) - bridge_output:write(' ui->'..ev.name..'(ui'..recv_argv..');\n') - bridge_output:write(recv_cleanup) - bridge_output:write('}\n\n') - - bridge_output:write('static void ui_bridge_'..ev.name) - write_signature(bridge_output, ev, 'UI *ui') - bridge_output:write('\n{\n') - bridge_output:write(send) - bridge_output:write(' UI_BRIDGE_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n\n') - end end if not (ev.remote_only and ev.remote_impl) then @@ -187,6 +123,9 @@ for i = 1, #events do call_output:write(' UI_LOG('..ev.name..');\n') call_output:write(' ui_call_event("'..ev.name..'", args);\n') elseif ev.compositor_impl then + call_output:write(' ui_comp_'..ev.name) + write_signature(call_output, ev, '', true) + call_output:write(";\n") call_output:write(' UI_CALL') write_signature(call_output, ev, '!ui->composed, '..ev.name..', ui', true) call_output:write(";\n") @@ -208,14 +147,14 @@ for i = 1, #events do call_output:write("}\n\n") end - if (not ev.remote_only) and (not ev.noexport) and (not ev.client_impl) then + if (not ev.remote_only) and (not ev.noexport) and (not ev.client_impl) and (not ev.client_ignore) then call_ui_event_method(client_output, ev) end end local client_events = {} for _,ev in ipairs(events) do - if (not ev.noexport) and ((not ev.remote_only) or ev.client_impl) then + if (not ev.noexport) and ((not ev.remote_only) or ev.client_impl) and (not ev.client_ignore) then client_events[ev.name] = ev end end @@ -233,7 +172,6 @@ end client_output:write('\n};\n\n') client_output:write(hashfun) -proto_output:close() call_output:close() remote_output:close() client_output:close() diff --git a/src/nvim/generators/gen_declarations.lua b/src/nvim/generators/gen_declarations.lua index bf1adaeee3..4097ff7dc5 100755 --- a/src/nvim/generators/gen_declarations.lua +++ b/src/nvim/generators/gen_declarations.lua @@ -182,8 +182,7 @@ Additionally uses the following environment variables: If set to 1 then all generated declarations receive a comment with file name and line number after the declaration. This may be useful for debugging gen_declarations script, but not much beyond that with - configured development environment (i.e. with ctags/cscope/finding - definitions with clang/etc). + configured development environment (i.e. with with clang/etc). WARNING: setting this to 1 will cause extensive rebuilds: declarations generator script will not regenerate non-static.h file if its diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua index 8e6d1f2634..baed6a74c2 100644 --- a/src/nvim/generators/gen_eval.lua +++ b/src/nvim/generators/gen_eval.lua @@ -27,6 +27,33 @@ local hashy = require'generators.hashy' local hashpipe = io.open(funcsfname, 'wb') +hashpipe:write([[ +#include "nvim/arglist.h" +#include "nvim/cmdexpand.h" +#include "nvim/cmdhist.h" +#include "nvim/digraph.h" +#include "nvim/eval/buffer.h" +#include "nvim/eval/funcs.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/vars.h" +#include "nvim/eval/window.h" +#include "nvim/ex_docmd.h" +#include "nvim/ex_getln.h" +#include "nvim/fold.h" +#include "nvim/getchar.h" +#include "nvim/insexpand.h" +#include "nvim/mapping.h" +#include "nvim/match.h" +#include "nvim/mbyte.h" +#include "nvim/menu.h" +#include "nvim/move.h" +#include "nvim/quickfix.h" +#include "nvim/search.h" +#include "nvim/sign.h" +#include "nvim/testing.h" + +]]) + local funcs = require('eval').funcs for _, func in pairs(funcs) do if func.float_func then @@ -46,14 +73,13 @@ for _,fun in ipairs(metadata) do end end +local func_names = vim.tbl_keys(funcs) +table.sort(func_names) local funcsdata = io.open(funcs_file, 'w') -funcsdata:write(mpack.pack(funcs)) +funcsdata:write(mpack.pack(func_names)) funcsdata:close() - -local names = vim.tbl_keys(funcs) - -local neworder, hashfun = hashy.hashy_hash("find_internal_func", names, function (idx) +local neworder, hashfun = hashy.hashy_hash("find_internal_func", func_names, function (idx) return "functions["..idx.."].name" end) hashpipe:write("static const EvalFuncDef functions[] = {\n") diff --git a/src/nvim/generators/gen_events.lua b/src/nvim/generators/gen_events.lua index 6ee45a14af..8db7f22452 100644 --- a/src/nvim/generators/gen_events.lua +++ b/src/nvim/generators/gen_events.lua @@ -32,7 +32,9 @@ for i, event in ipairs(events) do end end -for alias, event in pairs(aliases) do +for _, v in ipairs(aliases) do + local alias = v[1] + local event = v[2] names_tgt:write(('\n {%u, "%s", EVENT_%s},'):format(#alias, alias, event:upper())) end diff --git a/src/nvim/generators/gen_ex_cmds.lua b/src/nvim/generators/gen_ex_cmds.lua index 255c415a4d..0c1051b04e 100644 --- a/src/nvim/generators/gen_ex_cmds.lua +++ b/src/nvim/generators/gen_ex_cmds.lua @@ -49,6 +49,43 @@ enumfile:write([[ typedef enum CMD_index { ]]) defsfile:write(string.format([[ +#include "nvim/arglist.h" +#include "nvim/autocmd.h" +#include "nvim/buffer.h" +#include "nvim/cmdhist.h" +#include "nvim/debugger.h" +#include "nvim/diff.h" +#include "nvim/digraph.h" +#include "nvim/eval.h" +#include "nvim/eval/userfunc.h" +#include "nvim/eval/vars.h" +#include "nvim/ex_cmds.h" +#include "nvim/ex_cmds2.h" +#include "nvim/ex_docmd.h" +#include "nvim/ex_eval.h" +#include "nvim/ex_session.h" +#include "nvim/help.h" +#include "nvim/indent.h" +#include "nvim/locale.h" +#include "nvim/lua/executor.h" +#include "nvim/mapping.h" +#include "nvim/mark.h" +#include "nvim/match.h" +#include "nvim/menu.h" +#include "nvim/message.h" +#include "nvim/ops.h" +#include "nvim/option.h" +#include "nvim/profile.h" +#include "nvim/quickfix.h" +#include "nvim/runtime.h" +#include "nvim/sign.h" +#include "nvim/spell.h" +#include "nvim/spellfile.h" +#include "nvim/syntax.h" +#include "nvim/undo.h" +#include "nvim/usercmd.h" +#include "nvim/version.h" + static const int command_count = %u; static CommandDefinition cmdnames[%u] = { ]], #defs, #defs)) diff --git a/src/nvim/generators/gen_keysets.lua b/src/nvim/generators/gen_keysets.lua index 633c5da184..b1c1f3e2d8 100644 --- a/src/nvim/generators/gen_keysets.lua +++ b/src/nvim/generators/gen_keysets.lua @@ -1,4 +1,3 @@ - local nvimsrcdir = arg[1] local shared_file = arg[2] local funcs_file = arg[3] @@ -38,7 +37,9 @@ local function sanitize(key) return key end -for name, keys in pairs(keysets) do +for _, v in ipairs(keysets) do + local name = v[1] + local keys = v[2] local neworder, hashfun = hashy.hashy_hash(name, keys, function (idx) return name.."_table["..idx.."].str" end) diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index 6dba6552b3..edb7dae159 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -29,13 +29,14 @@ local type_flags={ } local redraw_flags={ + ui_option='P_UI_OPTION', + tabline='P_RTABL', statuslines='P_RSTAT', current_window='P_RWIN', current_window_only='P_RWINONLY', current_buffer='P_RBUF', all_windows='P_RALL', curswant='P_CURSWANT', - ui_option='P_UI_OPTION', } local list_flags={ @@ -77,6 +78,7 @@ local get_flags = function(o) {'deny_in_modelines', 'P_NO_ML'}, {'deny_duplicates', 'P_NODUP'}, {'modelineexpr', 'P_MLE'}, + {'func'} }) do local key_name = flag_desc[1] local def_name = flag_desc[2] or ('P_' .. key_name:upper()) @@ -157,7 +159,7 @@ local dump_option = function(i, o) if #o.scope == 2 then pv_name = 'OPT_BOTH(' .. pv_name .. ')' end - defines['PV_' .. varname:sub(3):upper()] = pv_name + table.insert(defines, { 'PV_' .. varname:sub(3):upper() , pv_name}) w(' .indir=' .. pv_name) end if o.enable_if then @@ -190,7 +192,7 @@ w(' [' .. ('%u'):format(#options.options) .. ']={.fullname=NULL}') w('};') w('') -for k, v in pairs(defines) do - w('#define ' .. k .. ' ' .. v) +for _, v in ipairs(defines) do + w('#define ' .. v[1] .. ' ' .. v[2]) end opt_fd:close() diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 4135065787..9c5364e1b1 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -7,11 +7,15 @@ #include <assert.h> #include <inttypes.h> #include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> +#include "lauxlib.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" -#include "nvim/assert.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" @@ -19,16 +23,21 @@ #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/input.h" #include "nvim/insexpand.h" #include "nvim/keycodes.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" #include "nvim/main.h" #include "nvim/mapping.h" #include "nvim/mbyte.h" @@ -44,8 +53,11 @@ #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/plines.h" +#include "nvim/pos.h" +#include "nvim/screen.h" #include "nvim/state.h" #include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -139,11 +151,11 @@ void free_buff(buffheader_T *buf) /// 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) +static char *get_buffcont(buffheader_T *buffer, int dozero) { size_t count = 0; - char_u *p = NULL; - char_u *p2; + char *p = NULL; + char *p2; // compute the total length of the string for (const buffblock_T *bp = buffer->bh_first.b_next; @@ -156,7 +168,7 @@ static char_u *get_buffcont(buffheader_T *buffer, int dozero) p2 = p; for (const buffblock_T *bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next) { - for (const char_u *str = (char_u *)bp->b_str; *str;) { + for (const char *str = bp->b_str; *str;) { *p2++ = *str++; } } @@ -170,7 +182,7 @@ static char_u *get_buffcont(buffheader_T *buffer, int dozero) /// K_SPECIAL in the returned string is escaped. char_u *get_recorded(void) { - char_u *p; + char *p; size_t len; p = get_buffcont(&recordbuff, true); @@ -178,7 +190,7 @@ char_u *get_recorded(void) // Remove the characters that were added the last time, these must be the // (possibly mapped) characters that stopped the recording. - len = STRLEN(p); + len = strlen(p); if (len >= last_recorded_len) { len -= last_recorded_len; p[len] = NUL; @@ -190,12 +202,12 @@ char_u *get_recorded(void) p[len - 1] = NUL; } - return p; + return (char_u *)p; } /// Return the contents of the redo buffer as a single string. /// K_SPECIAL in the returned string is escaped. -char_u *get_inserted(void) +char *get_inserted(void) { return get_buffcont(&redobuff, false); } @@ -232,7 +244,7 @@ static void add_buff(buffheader_T *const buf, const char *const s, ptrdiff_t sle size_t len; if (buf->bh_space >= (size_t)slen) { len = strlen(buf->bh_curr->b_str); - STRLCPY(buf->bh_curr->b_str + len, s, slen + 1); + xstrlcpy(buf->bh_curr->b_str + len, s, (size_t)slen + 1); buf->bh_space -= (size_t)slen; } else { if (slen < MINIMAL_SIZE) { @@ -242,7 +254,7 @@ static void add_buff(buffheader_T *const buf, const char *const s, ptrdiff_t sle } buffblock_T *p = xmalloc(sizeof(buffblock_T) + len); buf->bh_space = len - (size_t)slen; - STRLCPY(p->b_str, s, slen + 1); + xstrlcpy(p->b_str, s, (size_t)slen + 1); p->b_next = buf->bh_curr->b_next; buf->bh_curr->b_next = p; @@ -260,10 +272,12 @@ static void delete_buff_tail(buffheader_T *buf, int slen) 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; + if (len < slen) { + return; } + + buf->bh_curr->b_str[len - slen] = NUL; + buf->bh_space += (size_t)slen; } /// Add number "n" to buffer "buf". @@ -427,24 +441,28 @@ void beep_flush(void) // This is used for the CTRL-O <.> command in insert mode. void ResetRedobuff(void) { - if (!block_redo) { - free_buff(&old_redobuff); - old_redobuff = redobuff; - redobuff.bh_first.b_next = NULL; + if (block_redo) { + return; } + + free_buff(&old_redobuff); + old_redobuff = redobuff; + redobuff.bh_first.b_next = NULL; } // Discard the contents of the redo buffer and restore the previous redo // buffer. void CancelRedo(void) { - if (!block_redo) { - free_buff(&redobuff); - redobuff = old_redobuff; - old_redobuff.bh_first.b_next = NULL; - start_stuff(); - while (read_readbuffers(true) != NUL) {} + if (block_redo) { + return; } + + free_buff(&redobuff); + redobuff = old_redobuff; + old_redobuff.bh_first.b_next = NULL; + start_stuff(); + while (read_readbuffers(true) != NUL) {} } /// Save redobuff and old_redobuff to save_redobuff and save_old_redobuff. @@ -457,11 +475,13 @@ void saveRedobuff(save_redo_T *save_redo) old_redobuff.bh_first.b_next = NULL; // Make a copy, so that ":normal ." in a function works. - char *const s = (char *)get_buffcont(&save_redo->sr_redobuff, false); - if (s != NULL) { - add_buff(&redobuff, s, -1L); - xfree(s); + char *const s = get_buffcont(&save_redo->sr_redobuff, false); + if (s == NULL) { + return; } + + add_buff(&redobuff, s, -1L); + xfree(s); } /// Restore redobuff and old_redobuff from save_redobuff and save_old_redobuff. @@ -509,7 +529,7 @@ void AppendToRedobuffLit(const char *str, int len) s--; } if (s > start) { - add_buff(&redobuff, start, (long)(s - start)); + add_buff(&redobuff, start, s - start); } if (*s == NUL || (len >= 0 && s - str >= len)) { @@ -518,7 +538,7 @@ void AppendToRedobuffLit(const char *str, int len) // Handle a special or multibyte character. // Composing chars separately are handled separately. - const int c = mb_cptr2char_adv((const char_u **)&s); + const int c = mb_cptr2char_adv(&s); if (c < ' ' || c == DEL || (*s == NUL && (c == '0' || c == '^'))) { add_char_buff(&redobuff, Ctrl_V); } @@ -579,7 +599,7 @@ void stuffReadbuffSpec(const char *s) stuffReadbuffLen(s, 3); s += 3; } else { - int c = mb_cptr2char_adv((const char_u **)&s); + int c = mb_cptr2char_adv(&s); if (c == CAR || c == NL || c == ESC) { c = ' '; } @@ -620,7 +640,7 @@ void stuffescaped(const char *arg, bool literally) // stuff a single special character if (*arg != NUL) { - const int c = mb_cptr2char_adv((const char_u **)&arg); + const int c = mb_cptr2char_adv(&arg); if (literally && ((c < ' ' && c != TAB) || c == DEL)) { stuffcharReadbuff(Ctrl_V); } @@ -799,14 +819,16 @@ void stop_redo_ins(void) // be impossible to type anything. static void init_typebuf(void) { - if (typebuf.tb_buf == NULL) { - typebuf.tb_buf = typebuf_init; - typebuf.tb_noremap = noremapbuf_init; - typebuf.tb_buflen = TYPELEN_INIT; - typebuf.tb_len = 0; - typebuf.tb_off = MAXMAPLEN + 4; - typebuf.tb_change_cnt = 1; + if (typebuf.tb_buf != NULL) { + return; } + + typebuf.tb_buf = typebuf_init; + typebuf.tb_noremap = noremapbuf_init; + typebuf.tb_buflen = TYPELEN_INIT; + typebuf.tb_len = 0; + typebuf.tb_off = MAXMAPLEN + 4; + typebuf.tb_change_cnt = 1; } /// @return true when keys cannot be remapped. @@ -864,7 +886,7 @@ int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent) // often. int newoff = MAXMAPLEN + 4; int extra = addlen + newoff + 4 * (MAXMAPLEN + 4); - if (typebuf.tb_len > 2147483674 - extra) { + if (typebuf.tb_len > INT_MAX - extra) { // string is getting too long for 32 bit int emsg(_(e_toocompl)); // also calls flush_buffers setcursor(); @@ -1114,10 +1136,12 @@ static void gotchars(const char_u *chars, size_t len) /// Only affects recorded characters. void ungetchars(int len) { - if (reg_recording != 0) { - delete_buff_tail(&recordbuff, len); - last_recorded_len -= (size_t)len; + if (reg_recording == 0) { + return; } + + delete_buff_tail(&recordbuff, len); + last_recorded_len -= (size_t)len; } // Sync undo. Called when typed characters are obtained from the typeahead @@ -1251,7 +1275,7 @@ void openscript(char *name, bool directly) // use NameBuff for expanded name expand_env(name, NameBuff, MAXPATHL); int error; - if ((scriptin[curscript] = file_open_new(&error, (char *)NameBuff, + if ((scriptin[curscript] = file_open_new(&error, NameBuff, kFileReadOnly, 0)) == NULL) { semsg(_(e_notopen_2), name, os_strerror(error)); if (curscript) { @@ -1763,19 +1787,21 @@ void f_getcharstr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { getchar_common(argvars, rettv); - if (rettv->v_type == VAR_NUMBER) { - char temp[7]; // mbyte-char: 6, NUL: 1 - const varnumber_T n = rettv->vval.v_number; - int i = 0; + if (rettv->v_type != VAR_NUMBER) { + return; + } - if (n != 0) { - i += utf_char2bytes((int)n, (char *)temp); - } - assert(i < 7); - temp[i++] = NUL; - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xstrdup(temp); + char temp[7]; // mbyte-char: 6, NUL: 1 + const varnumber_T n = rettv->vval.v_number; + int i = 0; + + if (n != 0) { + i += utf_char2bytes((int)n, (char *)temp); } + assert(i < 7); + temp[i++] = NUL; + rettv->v_type = VAR_STRING; + rettv->vval.v_string = xstrdup(temp); } /// "getcharmod()" function @@ -1886,7 +1912,7 @@ static int check_simplify_modifier(int max_offset) /// - When there is no match yet, return map_result_nomatch, need to get more /// typeahead. /// - On failure (out of memory) return map_result_fail. -static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) +static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth) { mapblock_T *mp = NULL; mapblock_T *mp2; @@ -1956,21 +1982,33 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // 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) + if ((uint8_t)mp->m_keys[0] == tb_c1 && (mp->m_mode & local_State) && ((mp->m_mode & MODE_LANGMAP) == 0 || typebuf.tb_maplen == 0)) { int nomap = nolmaplen; - int c2; + int modifiers = 0; // find the match length of this mapping for (mlen = 1; mlen < typebuf.tb_len; mlen++) { - c2 = typebuf.tb_buf[typebuf.tb_off + mlen]; + int c2 = typebuf.tb_buf[typebuf.tb_off + mlen]; if (nomap > 0) { + if (nomap == 2 && c2 == KS_MODIFIER) { + modifiers = 1; + } else if (nomap == 1 && modifiers == 1) { + modifiers = c2; + } nomap--; - } else if (c2 == K_SPECIAL) { - nomap = 2; } else { - LANGMAP_ADJUST(c2, true); + if (c2 == K_SPECIAL) { + nomap = 2; + } else if (merge_modifiers(c2, &modifiers) == c2) { + // Only apply 'langmap' if merging modifiers into + // the key will not result in another character, + // so that 'langmap' behaves consistently in + // different terminals and GUIs. + LANGMAP_ADJUST(c2, true); + } + modifiers = 0; } - if (mp->m_keys[mlen] != c2) { + if ((uint8_t)mp->m_keys[mlen] != c2) { break; } } @@ -1978,7 +2016,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *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 *p1 = (char_u *)mp->m_keys; char_u *p2 = (char_u *)mb_unescape((const char **)&p1); if (p2 != NULL && MB_BYTE2LEN(tb_c1) > utfc_ptr2len((char *)p2)) { @@ -1996,8 +2034,8 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // mapping starts with K_SNR. uint8_t *s = typebuf.tb_noremap + typebuf.tb_off; if (*s == RM_SCRIPT - && (mp->m_keys[0] != K_SPECIAL - || mp->m_keys[1] != KS_EXTRA + && ((uint8_t)mp->m_keys[0] != K_SPECIAL + || (uint8_t)mp->m_keys[1] != KS_EXTRA || mp->m_keys[2] != KE_SNR)) { continue; } @@ -2167,12 +2205,12 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // 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; + const bool save_m_expr = mp->m_expr; + const int save_m_noremap = mp->m_noremap; + const bool save_m_silent = mp->m_silent; char *save_m_keys = NULL; // only saved when needed char *save_m_str = NULL; // only saved when needed - LuaRef save_m_luaref = mp->m_luaref; + const LuaRef save_m_luaref = mp->m_luaref; // Handle ":map <expr>": evaluate the {rhs} as an // expression. Also save and restore the command line @@ -2188,7 +2226,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) vgetc_busy = 0; may_garbage_collect = false; - save_m_keys = xstrdup((char *)mp->m_keys); + save_m_keys = xstrdup(mp->m_keys); if (save_m_luaref == LUA_NOREF) { save_m_str = xstrdup(mp->m_str); } @@ -2237,12 +2275,12 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // 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 & MODE_LANGMAP) != 0) { - gotchars((char_u *)map_str, STRLEN(map_str)); + gotchars((char_u *)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 : (char *)mp->m_keys, + } else if (strncmp(map_str, save_m_keys != NULL ? save_m_keys : mp->m_keys, (size_t)keylen) != 0) { noremap = REMAP_YES; } else { @@ -2318,7 +2356,9 @@ void check_end_reg_executing(bool advance) static int vgetorpeek(bool advance) { int c, c1; - bool timedout = false; // waited for more than 1 second for mapping to complete + bool timedout = false; // waited for more than 'timeoutlen' + // for mapping to complete or + // 'ttimeoutlen' for complete key code int mapdepth = 0; // check for recursive mapping bool mode_deleted = false; // set when mode has been deleted int new_wcol, new_wrow; @@ -2953,16 +2993,16 @@ char *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat) ga_concat(&line_ga, "<SNR>"); } else { if (cmod != 0) { - ga_append(&line_ga, (char)K_SPECIAL); - ga_append(&line_ga, (char)KS_MODIFIER); - ga_append(&line_ga, (char)cmod); + ga_append(&line_ga, K_SPECIAL); + ga_append(&line_ga, KS_MODIFIER); + ga_append(&line_ga, (uint8_t)cmod); } if (IS_SPECIAL(c1)) { - ga_append(&line_ga, (char)K_SPECIAL); - ga_append(&line_ga, (char)K_SECOND(c1)); - ga_append(&line_ga, (char)K_THIRD(c1)); + ga_append(&line_ga, K_SPECIAL); + ga_append(&line_ga, (uint8_t)K_SECOND(c1)); + ga_append(&line_ga, (uint8_t)K_THIRD(c1)); } else { - ga_append(&line_ga, (char)c1); + ga_append(&line_ga, (uint8_t)c1); } } @@ -2998,7 +3038,7 @@ bool map_execute_lua(void) } else if (c1 == '\r' || c1 == '\n') { c1 = NUL; // end the line } else { - ga_append(&line_ga, (char)c1); + ga_append(&line_ga, (uint8_t)c1); } } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 209caca880..df4ae05522 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -75,6 +75,10 @@ # define VIMRC_FILE ".nvimrc" #endif +#ifndef VIMRC_LUA_FILE +# define VIMRC_LUA_FILE ".nvim.lua" +#endif + EXTERN struct nvim_stats_s { int64_t fsync; int64_t redraw; @@ -194,13 +198,18 @@ EXTERN int emsg_skip INIT(= 0); // don't display errors for // expression that is skipped EXTERN bool emsg_severe INIT(= false); // use message of next of several // emsg() calls for throw +// used by assert_fails() +EXTERN char *emsg_assert_fails_msg INIT(= NULL); +EXTERN long emsg_assert_fails_lnum INIT(= 0); +EXTERN char *emsg_assert_fails_context INIT(= NULL); + EXTERN bool did_endif INIT(= false); // just had ":endif" EXTERN dict_T vimvardict; // Dictionary with v: variables EXTERN dict_T globvardict; // Dictionary with g: variables /// g: value #define globvarht globvardict.dv_hashtab -EXTERN int did_emsg; // set by emsg() when the message - // is displayed or thrown +EXTERN int did_emsg; // incremented by emsg() when a + // message is displayed or thrown EXTERN bool called_vim_beep; // set if vim_beep() is called EXTERN bool did_emsg_syntax; // did_emsg set because of a // syntax error @@ -315,13 +324,10 @@ EXTERN int garbage_collect_at_exit INIT(= false); #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 }); +EXTERN sctx_T current_sctx INIT(= { 0, 0, 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 @@ -413,12 +419,22 @@ EXTERN win_T *prevwin INIT(= NULL); // previous window // -V:FOR_ALL_WINDOWS_IN_TAB:501 #define FOR_ALL_WINDOWS_IN_TAB(wp, tp) \ for (win_T *wp = ((tp) == curtab) \ - ? firstwin : (tp)->tp_firstwin; wp != NULL; wp = wp->w_next) + ? firstwin : (tp)->tp_firstwin; wp != NULL; wp = wp->w_next) EXTERN win_T *curwin; // currently active window -EXTERN win_T *aucmd_win; // window used in aucmd_prepbuf() -EXTERN int aucmd_win_used INIT(= false); // aucmd_win is being used +typedef struct { + win_T *auc_win; ///< Window used in aucmd_prepbuf(). When not NULL the + ///< window has been allocated. + bool auc_win_used; ///< This auc_win is being used. +} aucmdwin_T; + +/// When executing autocommands for a buffer that is not in any window, a +/// special window is created to handle the side effects. When autocommands +/// nest we may need more than one. +EXTERN kvec_t(aucmdwin_T) aucmd_win_vec INIT(= KV_INITIAL_VALUE); +#define aucmd_win (aucmd_win_vec.items) +#define AUCMD_WIN_COUNT ((int)aucmd_win_vec.size) // The window layout is kept in a tree of frames. topframe points to the top // of the tree. @@ -475,17 +491,19 @@ EXTERN bool exiting INIT(= false); // internal value of v:dying EXTERN int v_dying INIT(= 0); // is stdin a terminal? -EXTERN int stdin_isatty INIT(= true); +EXTERN bool stdin_isatty INIT(= true); // is stdout a terminal? -EXTERN int stdout_isatty INIT(= true); +EXTERN bool stdout_isatty INIT(= true); +// is stderr a terminal? +EXTERN bool stderr_isatty INIT(= true); + /// filedesc set by embedder for reading first buffer like `cmd | nvim -` EXTERN int stdin_fd INIT(= -1); // true when doing full-screen output, otherwise only writing some messages. EXTERN int full_screen INIT(= false); -/// Non-zero when only "safe" commands are allowed, e.g. when sourcing .exrc or -/// .vimrc in current directory. +/// Non-zero when only "safe" commands are allowed EXTERN int secure INIT(= 0); /// Non-zero when changing text and jumping to another window or editing another buffer is not @@ -500,7 +518,7 @@ EXTERN int allbuf_lock INIT(= 0); /// not allowed then. EXTERN int sandbox INIT(= 0); -/// Batch-mode: "-es" or "-Es" commandline argument was given. +/// Batch-mode: "-es", "-Es", "-l" commandline argument was given. EXTERN int silent_mode INIT(= false); /// Start position of active Visual selection. @@ -613,7 +631,7 @@ EXTERN int State INIT(= MODE_NORMAL); EXTERN bool debug_mode INIT(= false); EXTERN bool finish_op INIT(= false); // true while an operator is pending EXTERN long opcount INIT(= 0); // count for pending operator -EXTERN int motion_force INIT(=0); // motion force for pending operator +EXTERN int motion_force INIT(= 0); // motion force for pending operator // Ex Mode (Q) state EXTERN bool exmode_active INIT(= false); // true if Ex mode is active @@ -621,7 +639,7 @@ EXTERN bool exmode_active INIT(= false); // true if Ex mode is active /// Flag set when normal_check() should return 0 when entering Ex mode. EXTERN bool pending_exmode_active INIT(= false); -EXTERN bool ex_no_reprint INIT(=false); // No need to print after z or p. +EXTERN bool ex_no_reprint INIT(= false); // No need to print after z or p. // 'inccommand' command preview state EXTERN bool cmdpreview INIT(= false); @@ -661,6 +679,8 @@ EXTERN int emsg_silent INIT(= 0); // don't print error messages EXTERN bool emsg_noredir INIT(= false); // don't redirect error messages EXTERN bool cmd_silent INIT(= false); // don't echo the command line +EXTERN bool in_assert_fails INIT(= false); // assert_fails() active + // Values for swap_exists_action: what to do when swap file already exists #define SEA_NONE 0 // don't use dialog #define SEA_DIALOG 1 // use dialog when possible @@ -754,7 +774,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 *escape_chars INIT(= " \t\\\"|"); // need backslash in cmd line EXTERN int keep_help_flag INIT(= false); // doing :ta from help file @@ -786,8 +806,8 @@ enum { extern char *default_vim_dir; extern char *default_vimruntime_dir; extern char *default_lib_dir; -extern char_u *compiled_user; -extern char_u *compiled_sys; +extern char *compiled_user; +extern char *compiled_sys; #endif // When a window has a local directory, the absolute path of the global @@ -805,7 +825,7 @@ EXTERN int cmdwin_type INIT(= 0); ///< type of cmdline window or 0 EXTERN int cmdwin_result INIT(= 0); ///< result of cmdline window or 0 EXTERN int cmdwin_level INIT(= 0); ///< cmdline recursion level -EXTERN char_u no_lines_msg[] INIT(= N_("--No lines in buffer--")); +EXTERN char no_lines_msg[] INIT(= N_("--No lines in buffer--")); // When ":global" is used to number of substitutions and changed lines is // accumulated until it's finished. @@ -824,9 +844,6 @@ EXTERN int stl_syntax INIT(= 0); // don't use 'hlsearch' temporarily EXTERN bool no_hlsearch INIT(= false); -// Page number used for %N in 'pageheader' and 'guitablabel'. -EXTERN linenr_T printer_page_num; - EXTERN bool typebuf_was_filled INIT(= false); // received text from client // or from feedkeys() @@ -857,7 +874,7 @@ EXTERN char e_api_spawn_failed[] INIT(= N_("E903: Could not spawn API job")); EXTERN char e_argreq[] INIT(= N_("E471: Argument required")); EXTERN char e_backslash[] INIT(= N_("E10: \\ should be followed by /, ? or &")); EXTERN char e_cmdwin[] INIT(= N_("E11: Invalid in command-line window; <CR> executes, CTRL-C quits")); -EXTERN char e_curdir[] INIT(= N_("E12: Command not allowed from exrc/vimrc in current dir or tag search")); +EXTERN char e_curdir[] INIT(= N_("E12: Command not allowed in secure mode in current dir or tag search")); EXTERN char e_command_too_recursive[] INIT(= N_("E169: Command too recursive")); EXTERN char e_endif[] INIT(= N_("E171: Missing :endif")); EXTERN char e_endtry[] INIT(= N_("E600: Missing :endtry")); @@ -985,7 +1002,6 @@ EXTERN char e_fnametoolong[] INIT(= N_("E856: Filename too long")); EXTERN char e_float_as_string[] INIT(= N_("E806: using Float as a String")); EXTERN char e_cannot_edit_other_buf[] INIT(= N_("E788: Not allowed to edit another buffer now")); -EXTERN char e_autocmd_err[] INIT(= N_("E5500: autocmd has thrown an exception: %s")); EXTERN char e_cmdmap_err[] INIT(= N_("E5520: <Cmd> mapping must end with <CR>")); EXTERN char e_cmdmap_repeated[] INIT(= N_("E5521: <Cmd> mapping must end with <CR> before second <Cmd>")); @@ -997,8 +1013,6 @@ EXTERN char e_luv_api_disabled[] INIT(= N_("E5560: %s must not be called in a lu EXTERN char e_floatonly[] INIT(= N_("E5601: Cannot close window, only floating window would remain")); EXTERN char e_floatexchange[] INIT(= N_("E5602: Cannot exchange or rotate float")); -EXTERN char e_non_empty_string_required[] INIT(= N_("E1142: Non-empty string required")); - EXTERN char e_cannot_define_autocommands_for_all_events[] INIT(= N_("E1155: Cannot define autocommands for ALL events")); EXTERN char e_resulting_text_too_long[] INIT(= N_("E1240: Resulting text too long")); @@ -1009,17 +1023,18 @@ EXTERN char e_highlight_group_name_invalid_char[] INIT(= N_("E5248: Invalid char EXTERN char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight group name too long")); +EXTERN char e_invalid_line_number_nr[] INIT(= N_("E966: Invalid line number: %ld")); + EXTERN char e_undobang_cannot_redo_or_move_branch[] INIT(= N_("E5767: Cannot use :undo! to redo or move to a different undo branch")); +EXTERN char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s")); + 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")); EXTERN char line_msg[] INIT(= N_(" line ")); -// For undo we need to know the lowest time possible. -EXTERN time_t starttime; - EXTERN FILE *time_fd INIT(= NULL); // where to write startup timing // Some compilers warn for not using a return value, but in some situations we @@ -1029,7 +1044,7 @@ EXTERN int vim_ignored; // stdio is an RPC channel (--embed). EXTERN bool embedded_mode INIT(= false); -// Do not start a UI nor read/write to stdio (unless embedding). +// Do not start UI (--headless, -l) nor read/write to stdio (unless embedding). EXTERN bool headless_mode INIT(= false); // uncrustify:on @@ -1069,13 +1084,15 @@ typedef enum { // Only filled for Win32. EXTERN char windowsVersion[20] INIT(= { 0 }); -EXTERN int exit_need_delay INIT(= 0); +/// While executing a regexp and set to OPTION_MAGIC_ON or OPTION_MAGIC_OFF this +/// overrules p_magic. Otherwise set to OPTION_MAGIC_NOT_SET. +EXTERN optmagic_T magic_overruled INIT(= OPTION_MAGIC_NOT_SET); -///< Skip win_fix_cursor() call for 'splitkeep' when cmdwin is closed. +/// Skip win_fix_cursor() call for 'splitkeep' when cmdwin is closed. EXTERN bool skip_win_fix_cursor INIT(= false); -///< Skip win_fix_scroll() call for 'splitkeep' when closing tab page. +/// Skip win_fix_scroll() call for 'splitkeep' when closing tab page. EXTERN bool skip_win_fix_scroll INIT(= false); -///< Skip update_topline() call while executing win_fix_scroll(). +/// Skip update_topline() call while executing win_fix_scroll(). EXTERN bool skip_update_topline INIT(= false); #endif // NVIM_GLOBALS_H diff --git a/src/nvim/grid.c b/src/nvim/grid.c index 2b73ff895d..46f8a59710 100644 --- a/src/nvim/grid.c +++ b/src/nvim/grid.c @@ -11,9 +11,19 @@ // // The grid_*() functions write to the screen and handle updating grid->lines[]. +#include <assert.h> +#include <limits.h> +#include <stdlib.h> + #include "nvim/arabic.h" +#include "nvim/buffer_defs.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" +#include "nvim/log.h" +#include "nvim/message.h" +#include "nvim/option_defs.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -130,18 +140,18 @@ void grid_putchar(ScreenGrid *grid, int c, int row, int col, int attr) /// get a single character directly from grid.chars into "bytes[]". /// Also return its attribute in *attrp; -void grid_getbytes(ScreenGrid *grid, int row, int col, char_u *bytes, int *attrp) +void grid_getbytes(ScreenGrid *grid, int row, int col, char *bytes, int *attrp) { - size_t off; - grid_adjust(&grid, &row, &col); // safety check - if (grid->chars != NULL && row < grid->rows && col < grid->cols) { - off = grid->line_offset[row] + (size_t)col; - *attrp = grid->attrs[off]; - schar_copy((char *)bytes, grid->chars[off]); + if (grid->chars == NULL || row >= grid->rows || col >= grid->cols) { + return; } + + size_t off = grid->line_offset[row] + (size_t)col; + *attrp = grid->attrs[off]; + schar_copy(bytes, grid->chars[off]); } /// put string '*text' on the window grid at position 'row' and 'col', with @@ -243,7 +253,7 @@ void grid_puts_len(ScreenGrid *grid, char *text, int textlen, int row, int col, ? utfc_ptr2len_len(ptr, (int)((text + len) - ptr)) : utfc_ptr2len(ptr); u8c = len >= 0 - ? utfc_ptr2char_len((char_u *)ptr, u8cc, (int)((text + len) - ptr)) + ? utfc_ptr2char_len(ptr, u8cc, (int)((text + len) - ptr)) : utfc_ptr2char(ptr, u8cc); mbyte_cells = utf_char2cells(u8c); if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) { @@ -254,7 +264,7 @@ void grid_puts_len(ScreenGrid *grid, char *text, int textlen, int row, int col, nc1 = NUL; } else { nc = len >= 0 - ? utfc_ptr2char_len((char_u *)ptr + mbyte_blen, pcc, + ? utfc_ptr2char_len(ptr + mbyte_blen, pcc, (int)((text + len) - ptr - mbyte_blen)) : utfc_ptr2char(ptr + mbyte_blen, pcc); nc1 = pcc[0]; diff --git a/src/nvim/grid.h b/src/nvim/grid.h index 0e79183c14..deb3d3785d 100644 --- a/src/nvim/grid.h +++ b/src/nvim/grid.h @@ -2,11 +2,14 @@ #define NVIM_GRID_H #include <stdbool.h> +#include <string.h> #include "nvim/ascii.h" #include "nvim/buffer_defs.h" #include "nvim/grid_defs.h" +#include "nvim/macros.h" #include "nvim/mbyte.h" +#include "nvim/memory.h" /// By default, all windows are drawn on a single rectangular grid, represented by /// this ScreenGrid instance. In multigrid mode each window will have its own diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h index 57b3817bc6..db31f7b984 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -110,24 +110,6 @@ struct ScreenGrid { false, 0, 0, NULL, false, true, 0, \ 0, 0, 0, 0, 0, false } -/// Status line click definition -typedef struct { - enum { - kStlClickDisabled = 0, ///< Clicks to this area are ignored. - kStlClickTabSwitch, ///< Switch to the given tab. - kStlClickTabClose, ///< Close given tab. - kStlClickFuncRun, ///< Run user function. - } type; ///< Type of the click. - int tabnr; ///< Tab page number. - char *func; ///< Function to run. -} StlClickDefinition; - -/// Used for tabline clicks -typedef struct { - StlClickDefinition def; ///< Click definition. - const char *start; ///< Location where region starts. -} StlClickRecord; - typedef struct { int args[3]; int icell; diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c deleted file mode 100644 index c46f95b64f..0000000000 --- a/src/nvim/hardcopy.c +++ /dev/null @@ -1,3074 +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 - -// hardcopy.c: printing to paper - -#include <assert.h> -#include <inttypes.h> -#include <string.h> - -#include "nvim/ascii.h" -#include "nvim/buffer.h" -#include "nvim/charset.h" -#include "nvim/eval.h" -#include "nvim/ex_docmd.h" -#include "nvim/fileio.h" -#include "nvim/garray.h" -#include "nvim/grid.h" -#include "nvim/hardcopy.h" -#include "nvim/highlight_group.h" -#include "nvim/indent.h" -#include "nvim/mbyte.h" -#include "nvim/memline.h" -#include "nvim/memory.h" -#include "nvim/message.h" -#include "nvim/option.h" -#include "nvim/os/input.h" -#include "nvim/os/os.h" -#include "nvim/path.h" -#include "nvim/runtime.h" -#include "nvim/statusline.h" -#include "nvim/strings.h" -#include "nvim/syntax.h" -#include "nvim/ui.h" -#include "nvim/version.h" -#include "nvim/vim.h" - -// To implement printing on a platform, the following functions must be -// defined: -// -// int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) -// Called once. Code should display printer dialogue (if appropriate) and -// determine printer font and margin settings. Reset has_color if the printer -// doesn't support colors at all. -// Returns FAIL to abort. -// -// int mch_print_begin(prt_settings_T *settings) -// Called to start the print job. -// Return false to abort. -// -// int mch_print_begin_page(char_u *msg) -// Called at the start of each page. -// "msg" indicates the progress of the print job, can be NULL. -// Return false to abort. -// -// int mch_print_end_page() -// Called at the end of each page. -// Return false to abort. -// -// int mch_print_blank_page() -// Called to generate a blank page for collated, duplex, multiple copy -// document. Return false to abort. -// -// void mch_print_end(prt_settings_T *psettings) -// Called at normal end of print job. -// -// void mch_print_cleanup() -// Called if print job ends normally or is abandoned. Free any memory, close -// devices and handles. Also called when mch_print_begin() fails, but not -// when mch_print_init() fails. -// -// void mch_print_set_font(int Bold, int Italic, int Underline); -// Called whenever the font style changes. -// -// void mch_print_set_bg(uint32_t bgcol); -// Called to set the background color for the following text. Parameter is an -// RGB value. -// -// void mch_print_set_fg(uint32_t fgcol); -// Called to set the foreground color for the following text. Parameter is an -// RGB value. -// -// mch_print_start_line(int margin, int page_line) -// Sets the current position at the start of line "page_line". -// If margin is true start in the left margin (for header and line number). -// -// int mch_print_text_out(char_u *p, size_t len); -// Output one character of text p[len] at the current position. -// Return true if there is no room for another character in the same line. -// -// Note that the generic code has no idea of margins. The machine code should -// simply make the page look smaller! The header and the line numbers are -// printed in the margin. - -static option_table_T printer_opts[OPT_PRINT_NUM_OPTIONS] = { - { "top", true, 0, NULL, 0, false }, - { "bottom", true, 0, NULL, 0, false }, - { "left", true, 0, NULL, 0, false }, - { "right", true, 0, NULL, 0, false }, - { "header", true, 0, NULL, 0, false }, - { "syntax", false, 0, NULL, 0, false }, - { "number", false, 0, NULL, 0, false }, - { "wrap", false, 0, NULL, 0, false }, - { "duplex", false, 0, NULL, 0, false }, - { "portrait", false, 0, NULL, 0, false }, - { "paper", false, 0, NULL, 0, false }, - { "collate", false, 0, NULL, 0, false }, - { "jobsplit", false, 0, NULL, 0, false }, - { "formfeed", false, 0, NULL, 0, false }, -}; - -static const uint32_t cterm_color_8[8] = { - 0x000000, 0xff0000, 0x00ff00, 0xffff00, - 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff -}; - -static const uint32_t cterm_color_16[16] = { - 0x000000, 0x0000c0, 0x008000, 0x004080, - 0xc00000, 0xc000c0, 0x808000, 0xc0c0c0, - 0x808080, 0x6060ff, 0x00ff00, 0x00ffff, - 0xff8080, 0xff40ff, 0xffff00, 0xffffff -}; - -static int current_syn_id; - -#define PRCOLOR_BLACK 0 -#define PRCOLOR_WHITE 0xffffff - -static TriState curr_italic; -static TriState curr_bold; -static TriState curr_underline; -static uint32_t curr_bg; -static uint32_t curr_fg; -static int page_count; - -#define OPT_MBFONT_USECOURIER 0 -#define OPT_MBFONT_ASCII 1 -#define OPT_MBFONT_REGULAR 2 -#define OPT_MBFONT_BOLD 3 -#define OPT_MBFONT_OBLIQUE 4 -#define OPT_MBFONT_BOLDOBLIQUE 5 -#define OPT_MBFONT_NUM_OPTIONS 6 - -static option_table_T mbfont_opts[OPT_MBFONT_NUM_OPTIONS] = -{ - { "c", false, 0, NULL, 0, false }, - { "a", false, 0, NULL, 0, false }, - { "r", false, 0, NULL, 0, false }, - { "b", false, 0, NULL, 0, false }, - { "i", false, 0, NULL, 0, false }, - { "o", false, 0, NULL, 0, false }, -}; - -// These values determine the print position on a page. -typedef struct { - int lead_spaces; // remaining spaces for a TAB - int print_pos; // virtual column for computing TABs - colnr_T column; // byte column - linenr_T file_line; // line nr in the buffer - size_t bytes_printed; // bytes printed so far - int ff; // seen form feed character -} prt_pos_T; - -struct prt_mediasize_S { - char *name; - double width; // width and height in points for portrait - double height; -}; - -// PS font names, must be in Roman, Bold, Italic, Bold-Italic order -struct prt_ps_font_S { - int wx; - int uline_offset; - int uline_width; - int bbox_min_y; - int bbox_max_y; - char *(ps_fontname[4]); -}; - -// Structures to map user named encoding and mapping to PS equivalents for -// building CID font name -struct prt_ps_encoding_S { - char *encoding; - char *cmap_encoding; - int needs_charset; -}; - -struct prt_ps_charset_S { - char *charset; - char *cmap_charset; - int has_charset; -}; - -// Collections of encodings and charsets for multi-byte printing -struct prt_ps_mbfont_S { - int num_encodings; - struct prt_ps_encoding_S *encodings; - int num_charsets; - struct prt_ps_charset_S *charsets; - char *ascii_enc; - char *defcs; -}; - -// Types of PS resource file currently used -typedef enum { - PRT_RESOURCE_TYPE_PROCSET = 0, - PRT_RESOURCE_TYPE_ENCODING = 1, - PRT_RESOURCE_TYPE_CMAP = 2, -} PrtResourceType; - -// String versions of PS resource types -static const char *const prt_resource_types[] = -{ - [PRT_RESOURCE_TYPE_PROCSET] = "procset", - [PRT_RESOURCE_TYPE_ENCODING] = "encoding", - [PRT_RESOURCE_TYPE_CMAP] = "cmap", -}; - -struct prt_ps_resource_S { - char_u name[64]; - char_u filename[MAXPATHL + 1]; - PrtResourceType type; - char_u title[256]; - char_u version[256]; -}; - -struct prt_dsc_comment_S { - char *string; - int len; - int type; -}; - -struct prt_dsc_line_S { - int type; - char_u *string; - int len; -}; - -// Static buffer to read initial comments in a resource file, some can have a -// couple of KB of comments! -#define PRT_FILE_BUFFER_LEN (2048) -struct prt_resfile_buffer_S { - char_u buffer[PRT_FILE_BUFFER_LEN]; - int len; - int line_start; - int line_end; -}; - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "hardcopy.c.generated.h" -#endif - -// Parse 'printoptions' and set the flags in "printer_opts". -// Returns an error message or NULL; -char *parse_printoptions(void) -{ - return parse_list_options((char_u *)p_popt, printer_opts, OPT_PRINT_NUM_OPTIONS); -} - -// Parse 'printoptions' and set the flags in "printer_opts". -// Returns an error message or NULL; -char *parse_printmbfont(void) -{ - return parse_list_options((char_u *)p_pmfn, mbfont_opts, OPT_MBFONT_NUM_OPTIONS); -} - -// Parse a list of options in the form -// option:value,option:value,option:value -// -// "value" can start with a number which is parsed out, e.g. margin:12mm -// -// Returns an error message for an illegal option, NULL otherwise. -// Only used for the printer at the moment... -static char *parse_list_options(char_u *option_str, option_table_T *table, size_t table_size) -{ - option_table_T *old_opts; - char *ret = NULL; - char_u *stringp; - char_u *colonp; - char *commap; - char *p; - size_t idx = 0; // init for GCC - int len; - - // Save the old values, so that they can be restored in case of an error. - old_opts = (option_table_T *)xmalloc(sizeof(option_table_T) * table_size); - - for (idx = 0; idx < table_size; idx++) { - old_opts[idx] = table[idx]; - table[idx].present = false; - } - - // Repeat for all comma separated parts. - stringp = option_str; - while (*stringp) { - colonp = (char_u *)vim_strchr((char *)stringp, ':'); - if (colonp == NULL) { - ret = N_("E550: Missing colon"); - break; - } - commap = vim_strchr((char *)stringp, ','); - if (commap == NULL) { - commap = (char *)option_str + STRLEN(option_str); - } - - len = (int)(colonp - stringp); - - for (idx = 0; idx < table_size; idx++) { - if (STRNICMP(stringp, table[idx].name, len) == 0) { - break; - } - } - - if (idx == table_size) { - ret = N_("E551: Illegal component"); - break; - } - - p = (char *)colonp + 1; - table[idx].present = true; - - if (table[idx].hasnum) { - if (!ascii_isdigit(*p)) { - ret = N_("E552: digit expected"); - break; - } - - table[idx].number = getdigits_int(&p, false, 0); - } - - table[idx].string = (char_u *)p; - table[idx].strlen = (int)(commap - p); - - stringp = (char_u *)commap; - if (*stringp == ',') { - stringp++; - } - } - - if (ret != NULL) { - // Restore old options in case of error - for (idx = 0; idx < table_size; idx++) { - table[idx] = old_opts[idx]; - } - } - - xfree(old_opts); - return ret; -} - -// If using a dark background, the colors will probably be too bright to show -// up well on white paper, so reduce their brightness. -static uint32_t darken_rgb(uint32_t rgb) -{ - return ((rgb >> 17) << 16) - + (((rgb & 0xff00) >> 9) << 8) - + ((rgb & 0xff) >> 1); -} - -static uint32_t prt_get_term_color(int colorindex) -{ - // TODO(vim): Should check for xterm with 88 or 256 colors. - if (t_colors > 8) { - return cterm_color_16[colorindex % 16]; - } - return cterm_color_8[colorindex % 8]; -} - -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, &colorindex); - if (rgb != -1) { - return (uint32_t)rgb; - } - } - - color = highlight_color(hl_id, "fg", modec); - if (color == NULL) { - colorindex = 0; - } else { - colorindex = atoi(color); - } - - if (colorindex >= 0 && colorindex < t_colors) { - fg_color = prt_get_term_color(colorindex); - } else { - fg_color = PRCOLOR_BLACK; - } - - return fg_color; -} - -static void prt_get_attr(int hl_id, prt_text_attr_T *pattr, int modec) -{ - pattr->bold = (highlight_has_attr(hl_id, HL_BOLD, modec) != NULL); - pattr->italic = (highlight_has_attr(hl_id, HL_ITALIC, modec) != NULL); - pattr->underline = (highlight_has_attr(hl_id, HL_UNDERLINE, modec) != NULL); - pattr->undercurl = (highlight_has_attr(hl_id, HL_UNDERCURL, modec) != NULL); - pattr->underdouble = (highlight_has_attr(hl_id, HL_UNDERDOUBLE, modec) != NULL); - pattr->underdotted = (highlight_has_attr(hl_id, HL_UNDERDOTTED, modec) != NULL); - pattr->underdashed = (highlight_has_attr(hl_id, HL_UNDERDASHED, modec) != NULL); - - uint32_t fg_color = prt_get_color(hl_id, modec); - - if (fg_color == PRCOLOR_WHITE) { - fg_color = PRCOLOR_BLACK; - } else if (*p_bg == 'd') { - fg_color = darken_rgb(fg_color); - } - - pattr->fg_color = fg_color; - pattr->bg_color = PRCOLOR_WHITE; -} - -static void prt_set_fg(uint32_t fg) -{ - if (fg != curr_fg) { - curr_fg = fg; - mch_print_set_fg(fg); - } -} - -static void prt_set_bg(uint32_t bg) -{ - if (bg != curr_bg) { - curr_bg = bg; - mch_print_set_bg(bg); - } -} - -static void prt_set_font(const TriState bold, const TriState italic, const TriState underline) -{ - if (curr_bold != bold - || curr_italic != italic - || curr_underline != underline) { - curr_underline = underline; - curr_italic = italic; - curr_bold = bold; - mch_print_set_font(bold, italic, underline); - } -} - -// Print the line number in the left margin. -static void prt_line_number(prt_settings_T *const psettings, const int page_line, - const linenr_T lnum) -{ - prt_set_fg(psettings->number.fg_color); - prt_set_bg(psettings->number.bg_color); - prt_set_font(psettings->number.bold, psettings->number.italic, - psettings->number.underline); - mch_print_start_line(true, page_line); - - // Leave two spaces between the number and the text; depends on - // PRINT_NUMBER_WIDTH. - char tbuf[20]; - snprintf(tbuf, sizeof(tbuf), "%6ld", (long)lnum); - for (int i = 0; i < 6; i++) { - (void)mch_print_text_out((char_u *)(&tbuf[i]), 1); - } - - if (psettings->do_syntax) { - // Set colors for next character. - current_syn_id = -1; - } else { - // Set colors and font back to normal. - prt_set_fg(PRCOLOR_BLACK); - prt_set_bg(PRCOLOR_WHITE); - prt_set_font(kFalse, kFalse, kFalse); - } -} - -// Get the currently effective header height. -int prt_header_height(void) -{ - if (printer_opts[OPT_PRINT_HEADERHEIGHT].present) { - return printer_opts[OPT_PRINT_HEADERHEIGHT].number; - } - return 2; -} - -// Return true if using a line number for printing. -int prt_use_number(void) -{ - return printer_opts[OPT_PRINT_NUMBER].present - && TOLOWER_ASC(printer_opts[OPT_PRINT_NUMBER].string[0]) == 'y'; -} - -// Return the unit used in a margin item in 'printoptions'. -// Returns PRT_UNIT_NONE if not recognized. -int prt_get_unit(int idx) -{ - int u = PRT_UNIT_NONE; - int i; - static char *(units[4]) = PRT_UNIT_NAMES; - - if (printer_opts[idx].present) { - for (i = 0; i < 4; i++) { - if (STRNICMP(printer_opts[idx].string, units[i], 2) == 0) { - u = i; - break; - } - } - } - return u; -} - -// Print the page header. -static void prt_header(prt_settings_T *const psettings, const int pagenum, const linenr_T lnum) -{ - int width = psettings->chars_per_line; - - // Also use the space for the line number. - if (prt_use_number()) { - width += PRINT_NUMBER_WIDTH; - } - - assert(width >= 0); - const size_t tbuf_size = (size_t)width + IOSIZE; - char_u *tbuf = xmalloc(tbuf_size); - - if (*p_header != NUL) { - linenr_T tmp_lnum, tmp_topline, tmp_botline; - int use_sandbox = false; - - // Need to (temporarily) set current line number and first/last line - // number on the 'window'. Since we don't know how long the page is, - // set the first and current line number to the top line, and guess - // that the page length is 64. - tmp_lnum = curwin->w_cursor.lnum; - tmp_topline = curwin->w_topline; - tmp_botline = curwin->w_botline; - curwin->w_cursor.lnum = lnum; - curwin->w_topline = lnum; - curwin->w_botline = lnum + 63; - printer_page_num = pagenum; - - use_sandbox = was_set_insecurely(curwin, "printheader", 0); - build_stl_str_hl(curwin, (char *)tbuf, (size_t)width + IOSIZE, - (char *)p_header, use_sandbox, - ' ', width, NULL, NULL); - - // Reset line numbers - curwin->w_cursor.lnum = tmp_lnum; - curwin->w_topline = tmp_topline; - curwin->w_botline = tmp_botline; - } else { - snprintf((char *)tbuf, tbuf_size, _("Page %d"), pagenum); - } - - prt_set_fg(PRCOLOR_BLACK); - prt_set_bg(PRCOLOR_WHITE); - prt_set_font(kTrue, kFalse, kFalse); - - // Use a negative line number to indicate printing in the top margin. - int page_line = 0 - prt_header_height(); - mch_print_start_line(true, page_line); - for (char_u *p = tbuf; *p != NUL;) { - const int l = utfc_ptr2len((char *)p); - assert(l >= 0); - if (mch_print_text_out(p, (size_t)l)) { - page_line++; - if (page_line >= 0) { // out of room in header - break; - } - mch_print_start_line(true, page_line); - } - p += l; - } - - xfree(tbuf); - - if (psettings->do_syntax) { - // Set colors for next character. - current_syn_id = -1; - } else { - // Set colors and font back to normal. - prt_set_fg(PRCOLOR_BLACK); - prt_set_bg(PRCOLOR_WHITE); - prt_set_font(kFalse, kFalse, kFalse); - } -} - -// Display a print status message. -static void prt_message(char_u *s) -{ - // TODO(bfredl): delete this - grid_fill(&default_grid, Rows - 1, Rows, 0, Columns, ' ', ' ', 0); - grid_puts(&default_grid, (char *)s, Rows - 1, 0, HL_ATTR(HLF_R)); - ui_flush(); -} - -void ex_hardcopy(exarg_T *eap) -{ - linenr_T lnum; - int collated_copies, uncollated_copies; - prt_settings_T settings; - size_t bytes_to_print = 0; - int page_line; - int jobsplit; - - CLEAR_FIELD(settings); - settings.has_color = true; - - if (*eap->arg == '>') { - char *errormsg = NULL; - - // Expand things like "%.ps". - if (expand_filename(eap, eap->cmdlinep, &errormsg) == FAIL) { - if (errormsg != NULL) { - emsg(errormsg); - } - return; - } - settings.outfile = (char_u *)skipwhite(eap->arg + 1); - } else if (*eap->arg != NUL) { - settings.arguments = (char_u *)eap->arg; - } - - // Initialise for printing. Ask the user for settings, unless forceit is - // set. - // The mch_print_init() code should set up margins if applicable. (It may - // not be a real printer - for example the engine might generate HTML or - // PS.) - if (mch_print_init(&settings, - curbuf->b_fname == NULL ? (char_u *)buf_spname(curbuf) : curbuf->b_sfname == - NULL ? (char_u *)curbuf->b_fname : (char_u *)curbuf->b_sfname, - eap->forceit) == FAIL) { - return; - } - - settings.modec = 'c'; - - if (!syntax_present(curwin)) { - settings.do_syntax = false; - } else if (printer_opts[OPT_PRINT_SYNTAX].present - && TOLOWER_ASC(printer_opts[OPT_PRINT_SYNTAX].string[0]) != 'a') { - settings.do_syntax = - (TOLOWER_ASC(printer_opts[OPT_PRINT_SYNTAX].string[0]) == 'y'); - } else { - settings.do_syntax = settings.has_color; - } - - // Set up printing attributes for line numbers - settings.number.fg_color = PRCOLOR_BLACK; - settings.number.bg_color = PRCOLOR_WHITE; - settings.number.bold = kFalse; - settings.number.italic = kTrue; - settings.number.underline = kFalse; - - // Syntax highlighting of line numbers. - if (prt_use_number() && settings.do_syntax) { - int id = syn_name2id("LineNr"); - if (id > 0) { - id = syn_get_final_id(id); - } - - prt_get_attr(id, &settings.number, settings.modec); - } - - // Estimate the total lines to be printed - for (lnum = eap->line1; lnum <= eap->line2; lnum++) { - bytes_to_print += strlen(skipwhite(ml_get(lnum))); - } - if (bytes_to_print == 0) { - msg(_("No text to be printed")); - goto print_fail_no_begin; - } - - // Set colors and font to normal. - curr_bg = 0xffffffff; - curr_fg = 0xffffffff; - curr_italic = kNone; - curr_bold = kNone; - curr_underline = kNone; - - prt_set_fg(PRCOLOR_BLACK); - prt_set_bg(PRCOLOR_WHITE); - prt_set_font(kFalse, kFalse, kFalse); - current_syn_id = -1; - - jobsplit = (printer_opts[OPT_PRINT_JOBSPLIT].present - && TOLOWER_ASC(printer_opts[OPT_PRINT_JOBSPLIT].string[0]) == 'y'); - - if (!mch_print_begin(&settings)) { - goto print_fail_no_begin; - } - - // Loop over collated copies: 1 2 3, 1 2 3, ... - page_count = 0; - for (collated_copies = 0; - collated_copies < settings.n_collated_copies; - collated_copies++) { - prt_pos_T prtpos; // current print position - prt_pos_T page_prtpos; // print position at page start - int side; - - CLEAR_FIELD(page_prtpos); - page_prtpos.file_line = eap->line1; - prtpos = page_prtpos; - - if (jobsplit && collated_copies > 0) { - // Splitting jobs: Stop a previous job and start a new one. - mch_print_end(&settings); - if (!mch_print_begin(&settings)) { - goto print_fail_no_begin; - } - } - - // Loop over all pages in the print job: 1 2 3 ... - for (page_count = 0; prtpos.file_line <= eap->line2; page_count++) { - // Loop over uncollated copies: 1 1 1, 2 2 2, 3 3 3, ... - // For duplex: 12 12 12 34 34 34, ... - for (uncollated_copies = 0; - uncollated_copies < settings.n_uncollated_copies; - uncollated_copies++) { - // Set the print position to the start of this page. - prtpos = page_prtpos; - - // Do front and rear side of a page. - for (side = 0; side <= settings.duplex; side++) { - // Print one page. - - // Check for interrupt character every page. - os_breakcheck(); - if (got_int || settings.user_abort) { - goto print_fail; - } - - assert(prtpos.bytes_printed <= SIZE_MAX / 100); - sprintf((char *)IObuff, _("Printing page %d (%zu%%)"), - page_count + 1 + side, - prtpos.bytes_printed * 100 / bytes_to_print); - if (!mch_print_begin_page((char_u *)IObuff)) { - goto print_fail; - } - - if (settings.n_collated_copies > 1) { - snprintf(IObuff + strlen(IObuff), IOSIZE - strlen(IObuff), - _(" Copy %d of %d"), - collated_copies + 1, - settings.n_collated_copies); - } - prt_message((char_u *)IObuff); - - // Output header if required - if (prt_header_height() > 0) { - prt_header(&settings, page_count + 1 + side, - prtpos.file_line); - } - - for (page_line = 0; page_line < settings.lines_per_page; - ++page_line) { - prtpos.column = hardcopy_line(&settings, - page_line, &prtpos); - if (prtpos.column == 0) { - // finished a file line - prtpos.bytes_printed += - strlen(skipwhite(ml_get(prtpos.file_line))); - if (++prtpos.file_line > eap->line2) { - break; // reached the end - } - } else if (prtpos.ff) { - // Line had a formfeed in it - start new page but - // stay on the current line - break; - } - } - - if (!mch_print_end_page()) { - goto print_fail; - } - if (prtpos.file_line > eap->line2) { - break; // reached the end - } - } - - // Extra blank page for duplexing with odd number of pages and - // more copies to come. - if (prtpos.file_line > eap->line2 && settings.duplex - && side == 0 - && uncollated_copies + 1 < settings.n_uncollated_copies) { - if (!mch_print_blank_page()) { - goto print_fail; - } - } - } - if (settings.duplex && prtpos.file_line <= eap->line2) { - page_count++; - } - - // Remember the position where the next page starts. - page_prtpos = prtpos; - } - - vim_snprintf((char *)IObuff, IOSIZE, _("Printed: %s"), - settings.jobname); - prt_message((char_u *)IObuff); - } - -print_fail: - if (got_int || settings.user_abort) { - snprintf(IObuff, IOSIZE, "%s", _("Printing aborted")); - prt_message((char_u *)IObuff); - } - mch_print_end(&settings); - -print_fail_no_begin: - mch_print_cleanup(); -} - -// Print one page line. -// Return the next column to print, or zero if the line is finished. -static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T *ppos) -{ - colnr_T col; - char_u *line; - int need_break = false; - int outputlen; - int tab_spaces; - int print_pos; - prt_text_attr_T attr; - int id; - - if (ppos->column == 0 || ppos->ff) { - print_pos = 0; - tab_spaces = 0; - if (!ppos->ff && prt_use_number()) { - prt_line_number(psettings, page_line, ppos->file_line); - } - ppos->ff = false; - } else { - // left over from wrap halfway through a tab - print_pos = ppos->print_pos; - tab_spaces = ppos->lead_spaces; - } - - mch_print_start_line(false, page_line); - line = (char_u *)ml_get(ppos->file_line); - - // Loop over the columns until the end of the file line or right margin. - for (col = ppos->column; line[col] != NUL && !need_break; col += outputlen) { - if ((outputlen = utfc_ptr2len((char *)line + col)) < 1) { - outputlen = 1; - } - // syntax highlighting stuff. - if (psettings->do_syntax) { - id = syn_get_id(curwin, ppos->file_line, col, 1, NULL, false); - if (id > 0) { - id = syn_get_final_id(id); - } else { - id = 0; - } - // Get the line again, a multi-line regexp may invalidate it. - line = (char_u *)ml_get(ppos->file_line); - - if (id != current_syn_id) { - current_syn_id = id; - prt_get_attr(id, &attr, psettings->modec); - prt_set_font(attr.bold, attr.italic, attr.underline); - prt_set_fg(attr.fg_color); - prt_set_bg(attr.bg_color); - } - } - - // Appropriately expand any tabs to spaces. - if (line[col] == TAB || tab_spaces != 0) { - if (tab_spaces == 0) { - tab_spaces = tabstop_padding(print_pos, - curbuf->b_p_ts, - curbuf->b_p_vts_array); - } - - while (tab_spaces > 0) { - need_break = mch_print_text_out((char_u *)" ", 1); - print_pos++; - tab_spaces--; - if (need_break) { - break; - } - } - // Keep the TAB if we didn't finish it. - if (need_break && tab_spaces > 0) { - break; - } - } else if (line[col] == FF - && printer_opts[OPT_PRINT_FORMFEED].present - && TOLOWER_ASC(printer_opts[OPT_PRINT_FORMFEED].string[0]) - == 'y') { - ppos->ff = true; - need_break = 1; - } else { - need_break = mch_print_text_out(line + col, (size_t)outputlen); - print_pos += utf_ptr2cells((char *)line + col); - } - } - - ppos->lead_spaces = tab_spaces; - ppos->print_pos = print_pos; - - // Start next line of file if we clip lines, or have reached end of the - // line, unless we are doing a formfeed. - if (!ppos->ff - && (line[col] == NUL - || (printer_opts[OPT_PRINT_WRAP].present - && TOLOWER_ASC(printer_opts[OPT_PRINT_WRAP].string[0]) - == 'n'))) { - return 0; - } - return col; -} - -// PS printer stuff. -// -// Sources of information to help maintain the PS printing code: -// -// 1. PostScript Language Reference, 3rd Edition, -// Addison-Wesley, 1999, ISBN 0-201-37922-8 -// 2. PostScript Language Program Design, -// Addison-Wesley, 1988, ISBN 0-201-14396-8 -// 3. PostScript Tutorial and Cookbook, -// Addison Wesley, 1985, ISBN 0-201-10179-3 -// 4. PostScript Language Document Structuring Conventions Specification, -// version 3.0, -// Adobe Technote 5001, 25th September 1992 -// 5. PostScript Printer Description File Format Specification, Version 4.3, -// Adobe technote 5003, 9th February 1996 -// 6. Adobe Font Metrics File Format Specification, Version 4.1, -// Adobe Technote 5007, 7th October 1998 -// 7. Adobe CMap and CIDFont Files Specification, Version 1.0, -// Adobe Technote 5014, 8th October 1996 -// 8. Adobe CJKV Character Collections and CMaps for CID-Keyed Fonts, -// Adoboe Technote 5094, 8th September, 2001 -// 9. CJKV Information Processing, 2nd Edition, -// O'Reilly, 2002, ISBN 1-56592-224-7 -// -// Some of these documents can be found in PDF form on Adobe's web site - -// http://www.adobe.com - -#define PRT_PS_DEFAULT_DPI (72) // Default user space resolution -#define PRT_PS_DEFAULT_FONTSIZE (10) - -#define PRT_MEDIASIZE_LEN ARRAY_SIZE(prt_mediasize) - -static struct prt_mediasize_S prt_mediasize[] = -{ - { "A4", 595.0, 842.0 }, - { "letter", 612.0, 792.0 }, - { "10x14", 720.0, 1008.0 }, - { "A3", 842.0, 1191.0 }, - { "A5", 420.0, 595.0 }, - { "B4", 729.0, 1032.0 }, - { "B5", 516.0, 729.0 }, - { "executive", 522.0, 756.0 }, - { "folio", 595.0, 935.0 }, - { "ledger", 1224.0, 792.0 }, // Yes, it is wider than taller! - { "legal", 612.0, 1008.0 }, - { "quarto", 610.0, 780.0 }, - { "statement", 396.0, 612.0 }, - { "tabloid", 792.0, 1224.0 } -}; - -#define PRT_PS_FONT_ROMAN (0) -#define PRT_PS_FONT_BOLD (1) -#define PRT_PS_FONT_OBLIQUE (2) -#define PRT_PS_FONT_BOLDOBLIQUE (3) - -// Standard font metrics for Courier family -static struct prt_ps_font_S prt_ps_courier_font = -{ - 600, - -100, 50, - -250, 805, - { "Courier", "Courier-Bold", "Courier-Oblique", "Courier-BoldOblique" } -}; - -// Generic font metrics for multi-byte fonts -static struct prt_ps_font_S prt_ps_mb_font = -{ - 1000, - -100, 50, - -250, 805, - { NULL, NULL, NULL, NULL } -}; - -// Pointer to current font set being used -static struct prt_ps_font_S *prt_ps_font; - -#define CS_JIS_C_1978 (0x01) -#define CS_JIS_X_1983 (0x02) -#define CS_JIS_X_1990 (0x04) -#define CS_NEC (0x08) -#define CS_MSWINDOWS (0x10) -#define CS_CP932 (0x20) -#define CS_KANJITALK6 (0x40) -#define CS_KANJITALK7 (0x80) - -// Japanese encodings and charsets -static struct prt_ps_encoding_S j_encodings[] = -{ - { "iso-2022-jp", NULL, (CS_JIS_C_1978|CS_JIS_X_1983|CS_JIS_X_1990| - CS_NEC) }, - { "euc-jp", "EUC", (CS_JIS_C_1978|CS_JIS_X_1983|CS_JIS_X_1990) }, - { "sjis", "RKSJ", (CS_JIS_C_1978|CS_JIS_X_1983|CS_MSWINDOWS| - CS_KANJITALK6|CS_KANJITALK7) }, - { "cp932", "RKSJ", CS_JIS_X_1983 }, - { "ucs-2", "UCS2", CS_JIS_X_1990 }, - { "utf-8", "UTF8", CS_JIS_X_1990 } -}; -static struct prt_ps_charset_S j_charsets[] = -{ - { "JIS_C_1978", "78", CS_JIS_C_1978 }, - { "JIS_X_1983", NULL, CS_JIS_X_1983 }, - { "JIS_X_1990", "Hojo", CS_JIS_X_1990 }, - { "NEC", "Ext", CS_NEC }, - { "MSWINDOWS", "90ms", CS_MSWINDOWS }, - { "CP932", "90ms", CS_JIS_X_1983 }, - { "KANJITALK6", "83pv", CS_KANJITALK6 }, - { "KANJITALK7", "90pv", CS_KANJITALK7 } -}; - -#define CS_GB_2312_80 (0x01) -#define CS_GBT_12345_90 (0x02) -#define CS_GBK2K (0x04) -#define CS_SC_MAC (0x08) -#define CS_GBT_90_MAC (0x10) -#define CS_GBK (0x20) -#define CS_SC_ISO10646 (0x40) - -// Simplified Chinese encodings and charsets -static struct prt_ps_encoding_S sc_encodings[] = -{ - { "iso-2022", NULL, (CS_GB_2312_80|CS_GBT_12345_90) }, - { "gb18030", NULL, CS_GBK2K }, - { "euc-cn", "EUC", (CS_GB_2312_80|CS_GBT_12345_90|CS_SC_MAC| - CS_GBT_90_MAC) }, - { "gbk", "EUC", CS_GBK }, - { "ucs-2", "UCS2", CS_SC_ISO10646 }, - { "utf-8", "UTF8", CS_SC_ISO10646 } -}; -static struct prt_ps_charset_S sc_charsets[] = -{ - { "GB_2312-80", "GB", CS_GB_2312_80 }, - { "GBT_12345-90", "GBT", CS_GBT_12345_90 }, - { "MAC", "GBpc", CS_SC_MAC }, - { "GBT-90_MAC", "GBTpc", CS_GBT_90_MAC }, - { "GBK", "GBK", CS_GBK }, - { "GB18030", "GBK2K", CS_GBK2K }, - { "ISO10646", "UniGB", CS_SC_ISO10646 } -}; - -#define CS_CNS_PLANE_1 (0x01) -#define CS_CNS_PLANE_2 (0x02) -#define CS_CNS_PLANE_1_2 (0x04) -#define CS_B5 (0x08) -#define CS_ETEN (0x10) -#define CS_HK_GCCS (0x20) -#define CS_HK_SCS (0x40) -#define CS_HK_SCS_ETEN (0x80) -#define CS_MTHKL (0x100) -#define CS_MTHKS (0x200) -#define CS_DLHKL (0x400) -#define CS_DLHKS (0x800) -#define CS_TC_ISO10646 (0x1000) - -// Traditional Chinese encodings and charsets -static struct prt_ps_encoding_S tc_encodings[] = -{ - { "iso-2022", NULL, (CS_CNS_PLANE_1|CS_CNS_PLANE_2) }, - { "euc-tw", "EUC", CS_CNS_PLANE_1_2 }, - { "big5", "B5", (CS_B5|CS_ETEN|CS_HK_GCCS|CS_HK_SCS| - CS_HK_SCS_ETEN|CS_MTHKL|CS_MTHKS|CS_DLHKL| - CS_DLHKS) }, - { "cp950", "B5", CS_B5 }, - { "ucs-2", "UCS2", CS_TC_ISO10646 }, - { "utf-8", "UTF8", CS_TC_ISO10646 }, - { "utf-16", "UTF16", CS_TC_ISO10646 }, - { "utf-32", "UTF32", CS_TC_ISO10646 } -}; -static struct prt_ps_charset_S tc_charsets[] = -{ - { "CNS_1992_1", "CNS1", CS_CNS_PLANE_1 }, - { "CNS_1992_2", "CNS2", CS_CNS_PLANE_2 }, - { "CNS_1993", "CNS", CS_CNS_PLANE_1_2 }, - { "BIG5", NULL, CS_B5 }, - { "CP950", NULL, CS_B5 }, - { "ETEN", "ETen", CS_ETEN }, - { "HK_GCCS", "HKgccs", CS_HK_GCCS }, - { "SCS", "HKscs", CS_HK_SCS }, - { "SCS_ETEN", "ETHK", CS_HK_SCS_ETEN }, - { "MTHKL", "HKm471", CS_MTHKL }, - { "MTHKS", "HKm314", CS_MTHKS }, - { "DLHKL", "HKdla", CS_DLHKL }, - { "DLHKS", "HKdlb", CS_DLHKS }, - { "ISO10646", "UniCNS", CS_TC_ISO10646 } -}; - -#define CS_KR_X_1992 (0x01) -#define CS_KR_MAC (0x02) -#define CS_KR_X_1992_MS (0x04) -#define CS_KR_ISO10646 (0x08) - -// Korean encodings and charsets -static struct prt_ps_encoding_S k_encodings[] = -{ - { "iso-2022-kr", NULL, CS_KR_X_1992 }, - { "euc-kr", "EUC", (CS_KR_X_1992|CS_KR_MAC) }, - { "johab", "Johab", CS_KR_X_1992 }, - { "cp1361", "Johab", CS_KR_X_1992 }, - { "uhc", "UHC", CS_KR_X_1992_MS }, - { "cp949", "UHC", CS_KR_X_1992_MS }, - { "ucs-2", "UCS2", CS_KR_ISO10646 }, - { "utf-8", "UTF8", CS_KR_ISO10646 } -}; -static struct prt_ps_charset_S k_charsets[] = -{ - { "KS_X_1992", "KSC", CS_KR_X_1992 }, - { "CP1361", "KSC", CS_KR_X_1992 }, - { "MAC", "KSCpc", CS_KR_MAC }, - { "MSWINDOWS", "KSCms", CS_KR_X_1992_MS }, - { "CP949", "KSCms", CS_KR_X_1992_MS }, - { "WANSUNG", "KSCms", CS_KR_X_1992_MS }, - { "ISO10646", "UniKS", CS_KR_ISO10646 } -}; - -static struct prt_ps_mbfont_S prt_ps_mbfonts[] = -{ - { - ARRAY_SIZE(j_encodings), - j_encodings, - ARRAY_SIZE(j_charsets), - j_charsets, - "jis_roman", - "JIS_X_1983" - }, - { - ARRAY_SIZE(sc_encodings), - sc_encodings, - ARRAY_SIZE(sc_charsets), - sc_charsets, - "gb_roman", - "GB_2312-80" - }, - { - ARRAY_SIZE(tc_encodings), - tc_encodings, - ARRAY_SIZE(tc_charsets), - tc_charsets, - "cns_roman", - "BIG5" - }, - { - ARRAY_SIZE(k_encodings), - k_encodings, - ARRAY_SIZE(k_charsets), - k_charsets, - "ks_roman", - "KS_X_1992" - } -}; - -// The PS prolog file version number has to match - if the prolog file is -// updated, increment the number in the file and here. Version checking was -// added as of VIM 6.2. -// The CID prolog file version number behaves as per PS prolog. -// Table of VIM and prolog versions: -// -// VIM Prolog CIDProlog -// 6.2 1.3 -// 7.0 1.4 1.0 -#define PRT_PROLOG_VERSION ((char_u *)"1.4") -#define PRT_CID_PROLOG_VERSION ((char_u *)"1.0") - -// Strings to look for in a PS resource file -#define PRT_RESOURCE_HEADER "%!PS-Adobe-" -#define PRT_RESOURCE_RESOURCE "Resource-" -#define PRT_RESOURCE_PROCSET "ProcSet" -#define PRT_RESOURCE_ENCODING "Encoding" -#define PRT_RESOURCE_CMAP "CMap" - -// Data for table based DSC comment recognition, easy to extend if VIM needs to -// read more comments. -#define PRT_DSC_MISC_TYPE (-1) -#define PRT_DSC_TITLE_TYPE (1) -#define PRT_DSC_VERSION_TYPE (2) -#define PRT_DSC_ENDCOMMENTS_TYPE (3) - -#define PRT_DSC_TITLE "%%Title:" -#define PRT_DSC_VERSION "%%Version:" -#define PRT_DSC_ENDCOMMENTS "%%EndComments:" - -#define SIZEOF_CSTR(s) (sizeof(s) - 1) -static struct prt_dsc_comment_S prt_dsc_table[] = -{ - { PRT_DSC_TITLE, SIZEOF_CSTR(PRT_DSC_TITLE), PRT_DSC_TITLE_TYPE }, - { PRT_DSC_VERSION, SIZEOF_CSTR(PRT_DSC_VERSION), - PRT_DSC_VERSION_TYPE }, - { PRT_DSC_ENDCOMMENTS, SIZEOF_CSTR(PRT_DSC_ENDCOMMENTS), - PRT_DSC_ENDCOMMENTS_TYPE } -}; - -// Variables for the output PostScript file. -static FILE *prt_ps_fd; -static bool prt_file_error; -static char_u *prt_ps_file_name = NULL; - -// Various offsets and dimensions in default PostScript user space (points). -// Used for text positioning calculations -static double prt_page_width; -static double prt_page_height; -static double prt_left_margin; -static double prt_right_margin; -static double prt_top_margin; -static double prt_bottom_margin; -static double prt_line_height; -static double prt_first_line_height; -static double prt_char_width; -static double prt_number_width; -static double prt_bgcol_offset; -static double prt_pos_x_moveto = 0.0; -static double prt_pos_y_moveto = 0.0; - -// Various control variables used to decide when and how to change the -// PostScript graphics state. -static bool prt_need_moveto; -static bool prt_do_moveto; -static bool prt_need_font; -static int prt_font; -static bool prt_need_underline; -static TriState prt_underline; -static TriState prt_do_underline; -static bool prt_need_fgcol; -static uint32_t prt_fgcol; -static bool prt_need_bgcol; -static bool prt_do_bgcol; -static uint32_t prt_bgcol; -static uint32_t prt_new_bgcol; -static bool prt_attribute_change; -static double prt_text_run; -static int prt_page_num; -static int prt_bufsiz; - -// Variables controlling physical printing. -static int prt_media; -static int prt_portrait; -static int prt_num_copies; -static int prt_duplex; -static int prt_tumble; -static int prt_collate; - -// Buffers used when generating PostScript output -static char prt_line_buffer[257]; -static garray_T prt_ps_buffer = GA_EMPTY_INIT_VALUE; - -static int prt_do_conv; -static vimconv_T prt_conv; - -static int prt_out_mbyte; -static int prt_custom_cmap; -static char prt_cmap[80]; -static int prt_use_courier; -static bool prt_in_ascii; -static bool prt_half_width; -static char *prt_ascii_encoding; -static char_u prt_hexchar[] = "0123456789abcdef"; - -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; - } -} - -static void prt_write_file(char *buffer) -{ - prt_write_file_len((char_u *)buffer, strlen(buffer)); -} - -static void prt_write_file_len(char_u *buffer, size_t bytes) -{ - prt_write_file_raw_len(buffer, bytes); -} - -// Write a string. -static void prt_write_string(char *s) -{ - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), "%s", s); - prt_write_file(prt_line_buffer); -} - -// Write an int and a space. -static void prt_write_int(int i) -{ - snprintf(prt_line_buffer, sizeof(prt_line_buffer), "%d ", i); - prt_write_file(prt_line_buffer); -} - -// Write a boolean and a space. -static void prt_write_boolean(int b) -{ - snprintf(prt_line_buffer, sizeof(prt_line_buffer), "%s ", (b ? "T" : "F")); - prt_write_file(prt_line_buffer); -} - -// Write PostScript to re-encode and define the font. -static void prt_def_font(char *new_name, char *encoding, int height, char *font) -{ - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "/_%s /VIM-%s /%s ref\n", new_name, encoding, font); - prt_write_file(prt_line_buffer); - if (prt_out_mbyte) { - snprintf(prt_line_buffer, sizeof(prt_line_buffer), "/%s %d %f /_%s sffs\n", - new_name, height, 500./prt_ps_courier_font.wx, new_name); - } else { - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "/%s %d /_%s ffs\n", new_name, height, new_name); - } - prt_write_file(prt_line_buffer); -} - -// Write a line to define the CID font. -static void prt_def_cidfont(char *new_name, int height, char *cidfont) -{ - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "/_%s /%s[/%s] vim_composefont\n", new_name, prt_cmap, cidfont); - prt_write_file(prt_line_buffer); - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "/%s %d /_%s ffs\n", new_name, height, new_name); - prt_write_file(prt_line_buffer); -} - -// Write a line to define a duplicate of a CID font -static void prt_dup_cidfont(char *original_name, char *new_name) -{ - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "/%s %s d\n", new_name, original_name); - prt_write_file(prt_line_buffer); -} - -// Convert a real value into an integer and fractional part as integers, with -// the fractional part being in the range [0,10^precision). The fractional part -// is also rounded based on the precision + 1'th fractional digit. -static void prt_real_bits(double real, int precision, int *pinteger, int *pfraction) -{ - int integer = (int)real; - double fraction = real - integer; - if (real < integer) { - fraction = -fraction; - } - for (int i = 0; i < precision; i++) { - fraction *= 10.0; - } - - *pinteger = integer; - *pfraction = (int)(fraction + 0.5); -} - -// Write a real and a space. Save bytes if real value has no fractional part! -// We use prt_real_bits() as %f in sprintf uses the locale setting to decide -// what decimal point character to use, but PS always requires a '.'. -static void prt_write_real(double val, int prec) -{ - int integer; - int fraction; - - prt_real_bits(val, prec, &integer, &fraction); - // Emit integer part - snprintf(prt_line_buffer, sizeof(prt_line_buffer), "%d", integer); - prt_write_file(prt_line_buffer); - // Only emit fraction if necessary - if (fraction != 0) { - // Remove any trailing zeros - while ((fraction % 10) == 0) { - prec--; - fraction /= 10; - } - // Emit fraction left padded with zeros - snprintf(prt_line_buffer, sizeof(prt_line_buffer), ".%0*d", - prec, fraction); - prt_write_file(prt_line_buffer); - } - snprintf(prt_line_buffer, sizeof(prt_line_buffer), " "); - prt_write_file(prt_line_buffer); -} - -// Write a line to define a numeric variable. -static void prt_def_var(char *name, double value, int prec) -{ - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "/%s ", name); - prt_write_file(prt_line_buffer); - prt_write_real(value, prec); - snprintf(prt_line_buffer, sizeof(prt_line_buffer), "d\n"); - prt_write_file(prt_line_buffer); -} - -// Convert size from font space to user space at current font scale -#define PRT_PS_FONT_TO_USER(scale, size) ((size) * ((scale)/1000.0)) - -static void prt_flush_buffer(void) -{ - if (!GA_EMPTY(&prt_ps_buffer)) { - // Any background color must be drawn first - if (prt_do_bgcol && (prt_new_bgcol != PRCOLOR_WHITE)) { - unsigned int r, g, b; - - if (prt_do_moveto) { - prt_write_real(prt_pos_x_moveto, 2); - prt_write_real(prt_pos_y_moveto, 2); - prt_write_string("m\n"); - prt_do_moveto = false; - } - - // Size of rect of background color on which text is printed - prt_write_real(prt_text_run, 2); - prt_write_real(prt_line_height, 2); - - // Lastly add the color of the background - r = (prt_new_bgcol & 0xff0000) >> 16; - g = (prt_new_bgcol & 0xff00) >> 8; - b = prt_new_bgcol & 0xff; - prt_write_real(r / 255.0, 3); - prt_write_real(g / 255.0, 3); - prt_write_real(b / 255.0, 3); - prt_write_string("bg\n"); - } - // Draw underlines before the text as it makes it slightly easier to - // find the starting point. - if (prt_do_underline) { - if (prt_do_moveto) { - prt_write_real(prt_pos_x_moveto, 2); - prt_write_real(prt_pos_y_moveto, 2); - prt_write_string("m\n"); - prt_do_moveto = false; - } - - // Underline length of text run - prt_write_real(prt_text_run, 2); - prt_write_string("ul\n"); - } - // Draw the text - if (prt_out_mbyte) { - prt_write_string("<"); - } else { - prt_write_string("("); - } - assert(prt_ps_buffer.ga_len >= 0); - prt_write_file_raw_len(prt_ps_buffer.ga_data, (size_t)prt_ps_buffer.ga_len); - if (prt_out_mbyte) { - prt_write_string(">"); - } else { - prt_write_string(")"); - } - // Add a moveto if need be and use the appropriate show procedure - if (prt_do_moveto) { - prt_write_real(prt_pos_x_moveto, 2); - prt_write_real(prt_pos_y_moveto, 2); - // moveto and a show - prt_write_string("ms\n"); - prt_do_moveto = false; - } else { // Simple show - prt_write_string("s\n"); - } - ga_clear(&prt_ps_buffer); - ga_init(&prt_ps_buffer, (int)sizeof(char), prt_bufsiz); - } -} - -static void prt_resource_name(char *filename, void *cookie) -{ - char_u *resource_filename = cookie; - - if (strlen(filename) >= MAXPATHL) { - *resource_filename = NUL; - } else { - STRCPY(resource_filename, filename); - } -} - -static int prt_find_resource(char *name, struct prt_ps_resource_S *resource) -{ - char *buffer; - int retval; - - buffer = xmallocz(MAXPATHL); - - STRLCPY(resource->name, name, 64); - // Look for named resource file in runtimepath - STRCPY(buffer, "print"); - add_pathsep(buffer); - xstrlcat(buffer, name, MAXPATHL); - xstrlcat(buffer, ".ps", MAXPATHL); - resource->filename[0] = NUL; - retval = (do_in_runtimepath(buffer, 0, prt_resource_name, resource->filename) - && resource->filename[0] != NUL); - xfree(buffer); - return retval; -} - -// PS CR and LF characters have platform independent values -#define PSLF (0x0a) -#define PSCR (0x0d) - -static struct prt_resfile_buffer_S prt_resfile; - -static int prt_resfile_next_line(void) -{ - int idx; - - // Move to start of next line and then find end of line - idx = prt_resfile.line_end + 1; - while (idx < prt_resfile.len) { - if (prt_resfile.buffer[idx] != PSLF && prt_resfile.buffer[idx] != PSCR) { - break; - } - idx++; - } - prt_resfile.line_start = idx; - - while (idx < prt_resfile.len) { - if (prt_resfile.buffer[idx] == PSLF || prt_resfile.buffer[idx] == PSCR) { - break; - } - idx++; - } - prt_resfile.line_end = idx; - - return idx < prt_resfile.len; -} - -static int prt_resfile_strncmp(int offset, const char *string, int len) - FUNC_ATTR_NONNULL_ALL -{ - // Force not equal if string is longer than remainder of line - if (len > (prt_resfile.line_end - (prt_resfile.line_start + offset))) { - return 1; - } - return STRNCMP(&prt_resfile.buffer[prt_resfile.line_start + offset], - string, len); -} - -static int prt_resfile_skip_nonws(int offset) -{ - int idx; - - idx = prt_resfile.line_start + offset; - while (idx < prt_resfile.line_end) { - if (isspace(prt_resfile.buffer[idx])) { - return idx - prt_resfile.line_start; - } - idx++; - } - return -1; -} - -static int prt_resfile_skip_ws(int offset) -{ - int idx; - - idx = prt_resfile.line_start + offset; - while (idx < prt_resfile.line_end) { - if (!isspace(prt_resfile.buffer[idx])) { - return idx - prt_resfile.line_start; - } - idx++; - } - return -1; -} - -/// Returns detail on next DSC comment line found. -/// -/// @return true if a DSC comment is found, else false -static bool prt_next_dsc(struct prt_dsc_line_S *p_dsc_line) - FUNC_ATTR_NONNULL_ALL -{ - int comment; - int offset; - - // Move to start of next line - if (!prt_resfile_next_line()) { - return false; - } - // DSC comments always start %% - if (prt_resfile_strncmp(0, "%%", 2) != 0) { - return false; - } - // Find type of DSC comment - for (comment = 0; comment < (int)ARRAY_SIZE(prt_dsc_table); comment++) { - if (prt_resfile_strncmp(0, prt_dsc_table[comment].string, - prt_dsc_table[comment].len) == 0) { - break; - } - } - if (comment != ARRAY_SIZE(prt_dsc_table)) { - // Return type of comment - p_dsc_line->type = prt_dsc_table[comment].type; - offset = prt_dsc_table[comment].len; - } else { - // Unrecognised DSC comment, skip to ws after comment leader - p_dsc_line->type = PRT_DSC_MISC_TYPE; - offset = prt_resfile_skip_nonws(0); - if (offset == -1) { - return false; - } - } - - // Skip ws to comment value - offset = prt_resfile_skip_ws(offset); - if (offset == -1) { - return false; - } - p_dsc_line->string = &prt_resfile.buffer[prt_resfile.line_start + offset]; - p_dsc_line->len = prt_resfile.line_end - (prt_resfile.line_start + offset); - - return true; -} - -/// Improved hand crafted parser to get the type, title, and version number of a -/// PS resource file so the file details can be added to the DSC header comments. -static bool prt_open_resource(struct prt_ps_resource_S *resource) - FUNC_ATTR_NONNULL_ALL -{ - struct prt_dsc_line_S dsc_line; - - FILE *fd_resource = os_fopen((char *)resource->filename, READBIN); - if (fd_resource == NULL) { - semsg(_("E624: Can't open file \"%s\""), resource->filename); - return false; - } - CLEAR_FIELD(prt_resfile.buffer); - - // Parse first line to ensure valid resource file - prt_resfile.len = (int)fread((char *)prt_resfile.buffer, sizeof(char_u), - PRT_FILE_BUFFER_LEN, fd_resource); - if (ferror(fd_resource)) { - semsg(_("E457: Can't read PostScript resource file \"%s\""), - resource->filename); - fclose(fd_resource); - return false; - } - fclose(fd_resource); - - prt_resfile.line_end = -1; - prt_resfile.line_start = 0; - if (!prt_resfile_next_line()) { - return false; - } - int offset = 0; - - if (prt_resfile_strncmp(offset, PRT_RESOURCE_HEADER, - (int)strlen(PRT_RESOURCE_HEADER)) != 0) { - semsg(_("E618: file \"%s\" is not a PostScript resource file"), - resource->filename); - return false; - } - - // Skip over any version numbers and following ws - offset += (int)strlen(PRT_RESOURCE_HEADER); - offset = prt_resfile_skip_nonws(offset); - if (offset == -1) { - return false; - } - offset = prt_resfile_skip_ws(offset); - if (offset == -1) { - return false; - } - if (prt_resfile_strncmp(offset, PRT_RESOURCE_RESOURCE, - (int)strlen(PRT_RESOURCE_RESOURCE)) != 0) { - semsg(_("E619: file \"%s\" is not a supported PostScript resource file"), - resource->filename); - return false; - } - offset += (int)strlen(PRT_RESOURCE_RESOURCE); - - // Decide type of resource in the file - if (prt_resfile_strncmp(offset, PRT_RESOURCE_PROCSET, - (int)strlen(PRT_RESOURCE_PROCSET)) == 0) { - resource->type = PRT_RESOURCE_TYPE_PROCSET; - } else if (prt_resfile_strncmp(offset, PRT_RESOURCE_ENCODING, - (int)strlen(PRT_RESOURCE_ENCODING)) == 0) { - resource->type = PRT_RESOURCE_TYPE_ENCODING; - } else if (prt_resfile_strncmp(offset, PRT_RESOURCE_CMAP, - (int)strlen(PRT_RESOURCE_CMAP)) == 0) { - resource->type = PRT_RESOURCE_TYPE_CMAP; - } else { - semsg(_("E619: file \"%s\" is not a supported PostScript resource file"), - resource->filename); - return false; - } - - // Look for title and version of resource - resource->title[0] = '\0'; - resource->version[0] = '\0'; - bool seen_title = false; - bool seen_version = false; - bool seen_all = false; - while (!seen_all && prt_next_dsc(&dsc_line)) { - switch (dsc_line.type) { - case PRT_DSC_TITLE_TYPE: - STRLCPY(resource->title, dsc_line.string, dsc_line.len + 1); - seen_title = true; - if (seen_version) { - seen_all = true; - } - break; - - case PRT_DSC_VERSION_TYPE: - STRLCPY(resource->version, dsc_line.string, dsc_line.len + 1); - seen_version = true; - if (seen_title) { - seen_all = true; - } - break; - - case PRT_DSC_ENDCOMMENTS_TYPE: - // Won't find title or resource after this comment, stop searching - seen_all = true; - break; - - case PRT_DSC_MISC_TYPE: - // Not interested in whatever comment this line had - break; - } - } - - if (!seen_title || !seen_version) { - semsg(_("E619: file \"%s\" is not a supported PostScript resource file"), - resource->filename); - return false; - } - - return true; -} - -static bool prt_check_resource(const struct prt_ps_resource_S *resource, const char_u *version) - FUNC_ATTR_NONNULL_ALL -{ - // Version number m.n should match, the revision number does not matter - if (STRNCMP(resource->version, version, STRLEN(version))) { - semsg(_("E621: \"%s\" resource file has wrong version"), - resource->name); - return false; - } - - // Other checks to be added as needed - return true; -} - -static void prt_dsc_start(void) -{ - prt_write_string("%!PS-Adobe-3.0\n"); -} - -static void prt_dsc_noarg(char *comment) -{ - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "%%%%%s\n", comment); - prt_write_file(prt_line_buffer); -} - -static void prt_dsc_textline(char *comment, char *text) -{ - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "%%%%%s: %s\n", comment, text); - prt_write_file(prt_line_buffer); -} - -static void prt_dsc_text(char *comment, char *text) -{ - // TODO(vim): - should scan 'text' for any chars needing escaping! - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "%%%%%s: (%s)\n", comment, text); - prt_write_file(prt_line_buffer); -} - -static void prt_dsc_ints(char *comment, int count, int *ints) -{ - int i; - - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "%%%%%s:", comment); - prt_write_file(prt_line_buffer); - - for (i = 0; i < count; i++) { - snprintf(prt_line_buffer, sizeof(prt_line_buffer), " %d", ints[i]); - prt_write_file(prt_line_buffer); - } - - prt_write_string("\n"); -} - -/// @param comment if NULL add to previous -static void prt_dsc_resources(const char *comment, const char *type, const char *string) - FUNC_ATTR_NONNULL_ARG(2, 3) -{ - if (comment != NULL) { - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "%%%%%s: %s", comment, type); - } else { - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "%%%%+ %s", type); - } - prt_write_file(prt_line_buffer); - - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - " %s\n", string); - prt_write_file(prt_line_buffer); -} - -static void prt_dsc_font_resource(char *resource, struct prt_ps_font_S *ps_font) -{ - int i; - - prt_dsc_resources(resource, "font", - ps_font->ps_fontname[PRT_PS_FONT_ROMAN]); - for (i = PRT_PS_FONT_BOLD; i <= PRT_PS_FONT_BOLDOBLIQUE; i++) { - if (ps_font->ps_fontname[i] != NULL) { - prt_dsc_resources(NULL, "font", ps_font->ps_fontname[i]); - } - } -} - -static void prt_dsc_requirements(int duplex, int tumble, int collate, int color, int num_copies) -{ - // Only output the comment if we need to. - // Note: tumble is ignored if we are not duplexing - if (!(duplex || collate || color || (num_copies > 1))) { - return; - } - - snprintf(prt_line_buffer, sizeof(prt_line_buffer), "%%%%Requirements:"); - prt_write_file(prt_line_buffer); - - if (duplex) { - prt_write_string(" duplex"); - if (tumble) { - prt_write_string("(tumble)"); - } - } - if (collate) { - prt_write_string(" collate"); - } - if (color) { - prt_write_string(" color"); - } - if (num_copies > 1) { - prt_write_string(" numcopies("); - // Note: no space wanted so don't use prt_write_int() - snprintf(prt_line_buffer, sizeof(prt_line_buffer), "%d", - num_copies); - prt_write_file(prt_line_buffer); - prt_write_string(")"); - } - prt_write_string("\n"); -} - -static void prt_dsc_docmedia(char *paper_name, double width, double height, double weight, - char *colour, char *type) -{ - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "%%%%DocumentMedia: %s ", paper_name); - prt_write_file(prt_line_buffer); - prt_write_real(width, 2); - prt_write_real(height, 2); - prt_write_real(weight, 2); - if (colour == NULL) { - prt_write_string("()"); - } else { - prt_write_string(colour); - } - prt_write_string(" "); - if (type == NULL) { - prt_write_string("()"); - } else { - prt_write_string(type); - } - prt_write_string("\n"); -} - -void mch_print_cleanup(void) -{ - if (prt_out_mbyte) { - int i; - - // Free off all CID font names created, but first clear duplicate - // pointers to the same string (when the same font is used for more than - // one style). - for (i = PRT_PS_FONT_ROMAN; i <= PRT_PS_FONT_BOLDOBLIQUE; i++) { - if (prt_ps_mb_font.ps_fontname[i] != NULL) { - xfree(prt_ps_mb_font.ps_fontname[i]); - } - prt_ps_mb_font.ps_fontname[i] = NULL; - } - } - - if (prt_do_conv) { - convert_setup(&prt_conv, NULL, NULL); - prt_do_conv = false; - } - if (prt_ps_fd != NULL) { - fclose(prt_ps_fd); - prt_ps_fd = NULL; - prt_file_error = false; - } - if (prt_ps_file_name != NULL) { - XFREE_CLEAR(prt_ps_file_name); - } -} - -static double to_device_units(int idx, double physsize, int def_number) -{ - double ret; - int nr; - - int u = prt_get_unit(idx); - if (u == PRT_UNIT_NONE) { - u = PRT_UNIT_PERC; - nr = def_number; - } else { - nr = printer_opts[idx].number; - } - - switch (u) { - case PRT_UNIT_INCH: - ret = nr * PRT_PS_DEFAULT_DPI; - break; - case PRT_UNIT_MM: - ret = nr * PRT_PS_DEFAULT_DPI / 25.4; - break; - case PRT_UNIT_POINT: - ret = nr; - break; - case PRT_UNIT_PERC: - default: - ret = physsize * nr / 100; - break; - } - - return ret; -} - -// Calculate margins for given width and height from printoptions settings. -static void prt_page_margins(double width, double height, double *left, double *right, double *top, - double *bottom) -{ - *left = to_device_units(OPT_PRINT_LEFT, width, 10); - *right = width - to_device_units(OPT_PRINT_RIGHT, width, 5); - *top = height - to_device_units(OPT_PRINT_TOP, height, 5); - *bottom = to_device_units(OPT_PRINT_BOT, height, 5); -} - -static void prt_font_metrics(int font_scale) -{ - prt_line_height = (double)font_scale; - prt_char_width = PRT_PS_FONT_TO_USER(font_scale, prt_ps_font->wx); -} - -static int prt_get_cpl(void) -{ - if (prt_use_number()) { - prt_number_width = PRINT_NUMBER_WIDTH * prt_char_width; - // If we are outputting multi-byte characters then line numbers will be - // printed with half width characters - if (prt_out_mbyte) { - prt_number_width /= 2; - } - prt_left_margin += prt_number_width; - } else { - prt_number_width = 0.0; - } - - return (int)((prt_right_margin - prt_left_margin) / prt_char_width); -} - -static void prt_build_cid_fontname(int font, char_u *name, int name_len) -{ - assert(name_len >= 0); - char *fontname = xstrndup((char *)name, (size_t)name_len); - prt_ps_mb_font.ps_fontname[font] = fontname; -} - -// Get number of lines of text that fit on a page (excluding the header). -static int prt_get_lpp(void) -{ - int lpp; - - // Calculate offset to lower left corner of background rect based on actual - // font height (based on its bounding box) and the line height, handling the - // case where the font height can exceed the line height. - prt_bgcol_offset = PRT_PS_FONT_TO_USER(prt_line_height, - prt_ps_font->bbox_min_y); - if ((prt_ps_font->bbox_max_y - prt_ps_font->bbox_min_y) < 1000.0) { - prt_bgcol_offset -= PRT_PS_FONT_TO_USER(prt_line_height, - (1000.0 - (prt_ps_font->bbox_max_y - - prt_ps_font->bbox_min_y)) / 2); - } - - // Get height for topmost line based on background rect offset. - prt_first_line_height = prt_line_height + prt_bgcol_offset; - - // Calculate lpp - lpp = (int)((prt_top_margin - prt_bottom_margin) / prt_line_height); - - // Adjust top margin if there is a header - prt_top_margin -= prt_line_height * prt_header_height(); - - return lpp - prt_header_height(); -} - -static int prt_match_encoding(char *p_encoding, struct prt_ps_mbfont_S *p_cmap, - struct prt_ps_encoding_S **pp_mbenc) -{ - int mbenc; - int enc_len; - struct prt_ps_encoding_S *p_mbenc; - - *pp_mbenc = NULL; - // Look for recognised encoding - enc_len = (int)strlen(p_encoding); - p_mbenc = p_cmap->encodings; - for (mbenc = 0; mbenc < p_cmap->num_encodings; mbenc++) { - if (STRNICMP(p_mbenc->encoding, p_encoding, enc_len) == 0) { - *pp_mbenc = p_mbenc; - return true; - } - p_mbenc++; - } - return false; -} - -static int prt_match_charset(char *p_charset, struct prt_ps_mbfont_S *p_cmap, - struct prt_ps_charset_S **pp_mbchar) -{ - int mbchar; - int char_len; - struct prt_ps_charset_S *p_mbchar; - - // Look for recognised character set, using default if one is not given - if (*p_charset == NUL) { - p_charset = p_cmap->defcs; - } - char_len = (int)strlen(p_charset); - p_mbchar = p_cmap->charsets; - for (mbchar = 0; mbchar < p_cmap->num_charsets; mbchar++) { - if (STRNICMP(p_mbchar->charset, p_charset, char_len) == 0) { - *pp_mbchar = p_mbchar; - return true; - } - p_mbchar++; - } - return false; -} - -int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) -{ - int i; - char *paper_name; - int paper_strlen; - int fontsize; - char_u *p; - int props; - int cmap = 0; - struct prt_ps_encoding_S *p_mbenc; - struct prt_ps_encoding_S *p_mbenc_first; - struct prt_ps_charset_S *p_mbchar = NULL; - - // Set up font and encoding. - char_u *p_encoding = (char_u *)enc_skip(p_penc); - if (*p_encoding == NUL) { - p_encoding = (char_u *)enc_skip(p_enc); - } - - // Look for a multi-byte font that matches the encoding and character set. - // Only look if multi-byte character set is defined, or using multi-byte - // encoding other than Unicode. This is because a Unicode encoding does not - // uniquely identify a CJK character set to use. - p_mbenc = NULL; - props = enc_canon_props(p_encoding); - if (!(props & ENC_8BIT) && ((*p_pmcs != NUL) || !(props & ENC_UNICODE))) { - p_mbenc_first = NULL; - int effective_cmap = 0; - for (cmap = 0; cmap < (int)ARRAY_SIZE(prt_ps_mbfonts); cmap++) { - if (prt_match_encoding((char *)p_encoding, &prt_ps_mbfonts[cmap], - &p_mbenc)) { - if (p_mbenc_first == NULL) { - p_mbenc_first = p_mbenc; - effective_cmap = cmap; - } - if (prt_match_charset(p_pmcs, &prt_ps_mbfonts[cmap], &p_mbchar)) { - break; - } - } - } - - // Use first encoding matched if no charset matched - if (p_mbenc_first != NULL && p_mbchar == NULL) { - p_mbenc = p_mbenc_first; - cmap = effective_cmap; - } - - assert(p_mbenc == NULL || cmap < (int)ARRAY_SIZE(prt_ps_mbfonts)); - } - - prt_out_mbyte = (p_mbenc != NULL); - if (prt_out_mbyte) { - // Build CMap name - will be same for all multi-byte fonts used - prt_cmap[0] = NUL; - - prt_custom_cmap = (p_mbchar == NULL); - if (!prt_custom_cmap) { - // 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; - } - - // Add charset name if not empty - if (p_mbchar->cmap_charset != NULL) { - STRLCPY(prt_cmap, p_mbchar->cmap_charset, sizeof(prt_cmap) - 2); - STRCAT(prt_cmap, "-"); - } - } else { - // Add custom CMap character set name - if (*p_pmcs == NUL) { - emsg(_("E674: printmbcharset cannot be empty with multi-byte encoding.")); - return false; - } - STRLCPY(prt_cmap, p_pmcs, sizeof(prt_cmap) - 2); - STRCAT(prt_cmap, "-"); - } - - // CMap name ends with (optional) encoding name and -H for horizontal - if (p_mbenc->cmap_encoding != NULL && strlen(prt_cmap) - + strlen(p_mbenc->cmap_encoding) + 3 < sizeof(prt_cmap)) { - STRCAT(prt_cmap, p_mbenc->cmap_encoding); - STRCAT(prt_cmap, "-"); - } - STRCAT(prt_cmap, "H"); - - if (!mbfont_opts[OPT_MBFONT_REGULAR].present) { - emsg(_("E675: No default font specified for multi-byte printing.")); - return false; - } - - // Derive CID font names with fallbacks if not defined - prt_build_cid_fontname(PRT_PS_FONT_ROMAN, - mbfont_opts[OPT_MBFONT_REGULAR].string, - mbfont_opts[OPT_MBFONT_REGULAR].strlen); - if (mbfont_opts[OPT_MBFONT_BOLD].present) { - prt_build_cid_fontname(PRT_PS_FONT_BOLD, - mbfont_opts[OPT_MBFONT_BOLD].string, - mbfont_opts[OPT_MBFONT_BOLD].strlen); - } - if (mbfont_opts[OPT_MBFONT_OBLIQUE].present) { - prt_build_cid_fontname(PRT_PS_FONT_OBLIQUE, - mbfont_opts[OPT_MBFONT_OBLIQUE].string, - mbfont_opts[OPT_MBFONT_OBLIQUE].strlen); - } - if (mbfont_opts[OPT_MBFONT_BOLDOBLIQUE].present) { - prt_build_cid_fontname(PRT_PS_FONT_BOLDOBLIQUE, - mbfont_opts[OPT_MBFONT_BOLDOBLIQUE].string, - mbfont_opts[OPT_MBFONT_BOLDOBLIQUE].strlen); - } - - // Check if need to use Courier for ASCII code range, and if so pick up - // the encoding to use - prt_use_courier = ( - mbfont_opts[OPT_MBFONT_USECOURIER].present - && (TOLOWER_ASC(mbfont_opts[OPT_MBFONT_USECOURIER].string[0]) == 'y')); - if (prt_use_courier) { - // Use national ASCII variant unless ASCII wanted - if (mbfont_opts[OPT_MBFONT_ASCII].present - && (TOLOWER_ASC(mbfont_opts[OPT_MBFONT_ASCII].string[0]) == 'y')) { - prt_ascii_encoding = "ascii"; - } else { - prt_ascii_encoding = prt_ps_mbfonts[cmap].ascii_enc; - } - } - - prt_ps_font = &prt_ps_mb_font; - } else { - prt_use_courier = false; - prt_ps_font = &prt_ps_courier_font; - } - - // Find the size of the paper and set the margins. - prt_portrait = (!printer_opts[OPT_PRINT_PORTRAIT].present - || TOLOWER_ASC(printer_opts[OPT_PRINT_PORTRAIT].string[0]) == - 'y'); - if (printer_opts[OPT_PRINT_PAPER].present) { - paper_name = (char *)printer_opts[OPT_PRINT_PAPER].string; - paper_strlen = printer_opts[OPT_PRINT_PAPER].strlen; - } else { - paper_name = "A4"; - paper_strlen = 2; - } - for (i = 0; i < (int)PRT_MEDIASIZE_LEN; i++) { - if (strlen(prt_mediasize[i].name) == (unsigned)paper_strlen - && STRNICMP(prt_mediasize[i].name, paper_name, - paper_strlen) == 0) { - break; - } - } - if (i == PRT_MEDIASIZE_LEN) { - i = 0; - } - prt_media = i; - - // Set PS pagesize based on media dimensions and print orientation. - // Note: Media and page sizes have defined meanings in PostScript and should - // be kept distinct. Media is the paper (or transparency, or ...) that is - // printed on, whereas the page size is the area that the PostScript - // interpreter renders into. - if (prt_portrait) { - prt_page_width = prt_mediasize[i].width; - prt_page_height = prt_mediasize[i].height; - } else { - prt_page_width = prt_mediasize[i].height; - prt_page_height = prt_mediasize[i].width; - } - - // Set PS page margins based on the PS pagesize, not the mediasize - this - // needs to be done before the cpl and lpp are calculated. - double left, right, top, bottom; - prt_page_margins(prt_page_width, prt_page_height, &left, &right, &top, - &bottom); - prt_left_margin = left; - prt_right_margin = right; - prt_top_margin = top; - prt_bottom_margin = bottom; - - // Set up the font size. - fontsize = PRT_PS_DEFAULT_FONTSIZE; - for (p = (char_u *)p_pfn; (p = (char_u *)vim_strchr((char *)p, ':')) != NULL; p++) { - if (p[1] == 'h' && ascii_isdigit(p[2])) { - fontsize = atoi((char *)p + 2); - } - } - prt_font_metrics(fontsize); - - // Return the number of characters per line, and lines per page for the - // generic print code. - psettings->chars_per_line = prt_get_cpl(); - psettings->lines_per_page = prt_get_lpp(); - - // Catch margin settings that leave no space for output! - if (psettings->chars_per_line <= 0 || psettings->lines_per_page <= 0) { - return FAIL; - } - - // Sort out the number of copies to be printed. PS by default will do - // uncollated copies for you, so once we know how many uncollated copies are - // wanted cache it away and lie to the generic code that we only want one - // uncollated copy. - psettings->n_collated_copies = 1; - psettings->n_uncollated_copies = 1; - prt_num_copies = 1; - prt_collate = (!printer_opts[OPT_PRINT_COLLATE].present - || TOLOWER_ASC(printer_opts[OPT_PRINT_COLLATE].string[0]) == - 'y'); - if (prt_collate) { - // TODO(vim): Get number of collated copies wanted. - } else { - // TODO(vim): Get number of uncollated copies wanted and update the cached - // count. - } - - psettings->jobname = jobname; - - // Set up printer duplex and tumble based on Duplex option setting - default - // is long sided duplex printing (i.e. no tumble). - prt_duplex = true; - prt_tumble = false; - psettings->duplex = 1; - if (printer_opts[OPT_PRINT_DUPLEX].present) { - if (STRNICMP(printer_opts[OPT_PRINT_DUPLEX].string, "off", 3) == 0) { - prt_duplex = false; - psettings->duplex = 0; - } else if (STRNICMP(printer_opts[OPT_PRINT_DUPLEX].string, "short", 5) - == 0) { - prt_tumble = true; - } - } - - // For now user abort not supported - psettings->user_abort = 0; - - // If the user didn't specify a file name, use a temp file. - if (psettings->outfile == NULL) { - prt_ps_file_name = (char_u *)vim_tempname(); - if (prt_ps_file_name == NULL) { - emsg(_(e_notmp)); - return FAIL; - } - prt_ps_fd = os_fopen((char *)prt_ps_file_name, WRITEBIN); - } else { - p = (char_u *)expand_env_save((char *)psettings->outfile); - if (p != NULL) { - prt_ps_fd = os_fopen((char *)p, WRITEBIN); - xfree(p); - } - } - if (prt_ps_fd == NULL) { - emsg(_("E324: Can't open PostScript output file")); - mch_print_cleanup(); - return FAIL; - } - - prt_bufsiz = psettings->chars_per_line; - if (prt_out_mbyte) { - prt_bufsiz *= 2; - } - ga_init(&prt_ps_buffer, (int)sizeof(char), prt_bufsiz); - - prt_page_num = 0; - - prt_attribute_change = false; - prt_need_moveto = false; - prt_need_font = false; - prt_need_fgcol = false; - prt_need_bgcol = false; - prt_need_underline = false; - - prt_file_error = false; - - return OK; -} - -static bool prt_add_resource(struct prt_ps_resource_S *resource) -{ - FILE *fd_resource; - char_u resource_buffer[512]; - size_t bytes_read; - - fd_resource = os_fopen((char *)resource->filename, READBIN); - if (fd_resource == NULL) { - semsg(_("E456: Can't open file \"%s\""), resource->filename); - return false; - } - switch (resource->type) { - case PRT_RESOURCE_TYPE_PROCSET: - case PRT_RESOURCE_TYPE_ENCODING: - case PRT_RESOURCE_TYPE_CMAP: - prt_dsc_resources("BeginResource", prt_resource_types[resource->type], - (char *)resource->title); - break; - default: - return false; - } - - prt_dsc_textline("BeginDocument", (char *)resource->filename); - - for (;;) { - bytes_read = fread((char *)resource_buffer, sizeof(char_u), - sizeof(resource_buffer), fd_resource); - if (ferror(fd_resource)) { - semsg(_("E457: Can't read PostScript resource file \"%s\""), - resource->filename); - fclose(fd_resource); - return false; - } - if (bytes_read == 0) { - break; - } - prt_write_file_raw_len(resource_buffer, bytes_read); - if (prt_file_error) { - fclose(fd_resource); - return false; - } - } - fclose(fd_resource); - - prt_dsc_noarg("EndDocument"); - - prt_dsc_noarg("EndResource"); - - return true; -} - -bool mch_print_begin(prt_settings_T *psettings) -{ - int bbox[4]; - double left; - double right; - double top; - double bottom; - struct prt_ps_resource_S res_prolog; - struct prt_ps_resource_S res_encoding; - char buffer[256]; - char *p_encoding; - char_u *p; - struct prt_ps_resource_S res_cidfont; - struct prt_ps_resource_S res_cmap; - - // PS DSC Header comments - no PS code! - prt_dsc_start(); - prt_dsc_textline("Title", (char *)psettings->jobname); - if (os_get_username(buffer, 256) == FAIL) { - STRCPY(buffer, "Unknown"); - } - prt_dsc_textline("For", buffer); - prt_dsc_textline("Creator", longVersion); - // Note: to ensure Clean8bit I don't think we can use LC_TIME - char ctime_buf[50]; - char *p_time = os_ctime(ctime_buf, sizeof(ctime_buf)); - // Note: os_ctime() adds a \n so we have to remove it :-( - p = (char_u *)vim_strchr(p_time, '\n'); - if (p != NULL) { - *p = NUL; - } - prt_dsc_textline("CreationDate", p_time); - prt_dsc_textline("DocumentData", "Clean8Bit"); - prt_dsc_textline("Orientation", "Portrait"); - prt_dsc_text(("Pages"), "atend"); - prt_dsc_textline("PageOrder", "Ascend"); - // The bbox does not change with orientation - it is always in the default - // user coordinate system! We have to recalculate right and bottom - // coordinates based on the font metrics for the bbox to be accurate. - prt_page_margins(prt_mediasize[prt_media].width, - prt_mediasize[prt_media].height, - &left, &right, &top, &bottom); - bbox[0] = (int)left; - if (prt_portrait) { - // In portrait printing the fixed point is the top left corner so we - // derive the bbox from that point. We have the expected cpl chars - // across the media and lpp lines down the media. - bbox[1] = (int)(top - (psettings->lines_per_page + prt_header_height()) - * prt_line_height); - bbox[2] = (int)(left + psettings->chars_per_line * prt_char_width - + 0.5); - bbox[3] = (int)(top + 0.5); - } else { - // In landscape printing the fixed point is the bottom left corner so we - // derive the bbox from that point. We have lpp chars across the media - // and cpl lines up the media. - bbox[1] = (int)bottom; - bbox[2] = (int)(left + ((psettings->lines_per_page - + prt_header_height()) * prt_line_height) + 0.5); - bbox[3] = (int)(bottom + psettings->chars_per_line * prt_char_width - + 0.5); - } - prt_dsc_ints("BoundingBox", 4, bbox); - // The media width and height does not change with landscape printing! - prt_dsc_docmedia(prt_mediasize[prt_media].name, - prt_mediasize[prt_media].width, - prt_mediasize[prt_media].height, - (double)0, NULL, NULL); - // Define fonts needed - if (!prt_out_mbyte || prt_use_courier) { - prt_dsc_font_resource("DocumentNeededResources", &prt_ps_courier_font); - } - if (prt_out_mbyte) { - prt_dsc_font_resource((prt_use_courier ? NULL - : "DocumentNeededResources"), &prt_ps_mb_font); - if (!prt_custom_cmap) { - prt_dsc_resources(NULL, "cmap", prt_cmap); - } - } - - // 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; - } - if (!prt_open_resource(&res_prolog)) { - return false; - } - if (!prt_check_resource(&res_prolog, PRT_PROLOG_VERSION)) { - 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; - } - if (!prt_open_resource(&res_cidfont)) { - return false; - } - if (!prt_check_resource(&res_cidfont, PRT_CID_PROLOG_VERSION)) { - return false; - } - } - - // Find an encoding to use for printing. - // Check 'printencoding'. If not set or not found, then use 'encoding'. If - // that cannot be found then default to "latin1". - // Note: VIM specific encoding header is always skipped. - if (!prt_out_mbyte) { - p_encoding = enc_skip(p_penc); - if (*p_encoding == NUL - || !prt_find_resource(p_encoding, &res_encoding)) { - // 'printencoding' not set or not supported - find alternate - int props; - - p_encoding = enc_skip(p_enc); - props = enc_canon_props((char_u *)p_encoding); - if (!(props & ENC_8BIT) - || !prt_find_resource(p_encoding, &res_encoding)) { - // 8-bit 'encoding' is not supported - // Use latin1 as default printing encoding - p_encoding = "latin1"; - if (!prt_find_resource(p_encoding, &res_encoding)) { - semsg(_("E456: Can't find PostScript resource file \"%s.ps\""), - p_encoding); - return false; - } - } - } - if (!prt_open_resource(&res_encoding)) { - return false; - } - // For the moment there are no checks on encoding resource files to - // perform - } else { - p_encoding = enc_skip(p_penc); - if (*p_encoding == NUL) { - p_encoding = enc_skip(p_enc); - } - if (prt_use_courier) { - // Include ASCII range encoding vector - if (!prt_find_resource(prt_ascii_encoding, &res_encoding)) { - semsg(_("E456: Can't find PostScript resource file \"%s.ps\""), - prt_ascii_encoding); - return false; - } - if (!prt_open_resource(&res_encoding)) { - return false; - } - // For the moment there are no checks on encoding resource files to - // perform - } - } - - prt_conv.vc_type = CONV_NONE; - if (!(enc_canon_props((char_u *)p_enc) & enc_canon_props((char_u *)p_encoding) & ENC_8BIT)) { - // Set up encoding conversion if required - if (convert_setup(&prt_conv, p_enc, p_encoding) == FAIL) { - semsg(_("E620: Unable to convert to print encoding \"%s\""), - p_encoding); - return false; - } - } - prt_do_conv = prt_conv.vc_type != CONV_NONE; - - if (prt_out_mbyte && prt_custom_cmap) { - // Find user supplied CMap - if (!prt_find_resource(prt_cmap, &res_cmap)) { - semsg(_("E456: Can't find PostScript resource file \"%s.ps\""), - prt_cmap); - return false; - } - if (!prt_open_resource(&res_cmap)) { - return false; - } - } - - // List resources supplied - STRCPY(buffer, res_prolog.title); - STRCAT(buffer, " "); - STRCAT(buffer, res_prolog.version); - prt_dsc_resources("DocumentSuppliedResources", "procset", buffer); - if (prt_out_mbyte) { - STRCPY(buffer, res_cidfont.title); - STRCAT(buffer, " "); - STRCAT(buffer, res_cidfont.version); - prt_dsc_resources(NULL, "procset", buffer); - - if (prt_custom_cmap) { - STRCPY(buffer, res_cmap.title); - STRCAT(buffer, " "); - STRCAT(buffer, res_cmap.version); - prt_dsc_resources(NULL, "cmap", buffer); - } - } - if (!prt_out_mbyte || prt_use_courier) { - STRCPY(buffer, res_encoding.title); - STRCAT(buffer, " "); - STRCAT(buffer, res_encoding.version); - prt_dsc_resources(NULL, "encoding", buffer); - } - prt_dsc_requirements(prt_duplex, prt_tumble, prt_collate, - psettings->do_syntax, - prt_num_copies); - prt_dsc_noarg("EndComments"); - - // PS Document page defaults - prt_dsc_noarg("BeginDefaults"); - - // List font resources most likely common to all pages - if (!prt_out_mbyte || prt_use_courier) { - prt_dsc_font_resource("PageResources", &prt_ps_courier_font); - } - if (prt_out_mbyte) { - prt_dsc_font_resource((prt_use_courier ? NULL : "PageResources"), - &prt_ps_mb_font); - if (!prt_custom_cmap) { - prt_dsc_resources(NULL, "cmap", prt_cmap); - } - } - - // Paper will be used for all pages - prt_dsc_textline("PageMedia", prt_mediasize[prt_media].name); - - prt_dsc_noarg("EndDefaults"); - - // PS Document prolog inclusion - all required procsets. - prt_dsc_noarg("BeginProlog"); - - // Add required procsets - NOTE: order is important! - if (!prt_add_resource(&res_prolog)) { - return false; - } - if (prt_out_mbyte) { - // Add CID font procset, and any user supplied CMap - if (!prt_add_resource(&res_cidfont)) { - return false; - } - if (prt_custom_cmap && !prt_add_resource(&res_cmap)) { - return false; - } - } - - if (!prt_out_mbyte || prt_use_courier) { - // There will be only one Roman font encoding to be included in the PS - // file. - if (!prt_add_resource(&res_encoding)) { - return false; - } - } - - prt_dsc_noarg("EndProlog"); - - // PS Document setup - must appear after the prolog - prt_dsc_noarg("BeginSetup"); - - // Device setup - page size and number of uncollated copies - prt_write_int((int)prt_mediasize[prt_media].width); - prt_write_int((int)prt_mediasize[prt_media].height); - prt_write_int(0); - prt_write_string("sps\n"); - prt_write_int(prt_num_copies); - prt_write_string("nc\n"); - prt_write_boolean(prt_duplex); - prt_write_boolean(prt_tumble); - prt_write_string("dt\n"); - prt_write_boolean(prt_collate); - prt_write_string("c\n"); - - // Font resource inclusion and definition - if (!prt_out_mbyte || prt_use_courier) { - // When using Courier for ASCII range when printing multi-byte, need to - // pick up ASCII encoding to use with it. - if (prt_use_courier) { - p_encoding = prt_ascii_encoding; - } - prt_dsc_resources("IncludeResource", "font", - prt_ps_courier_font.ps_fontname[PRT_PS_FONT_ROMAN]); - prt_def_font("F0", p_encoding, (int)prt_line_height, - prt_ps_courier_font.ps_fontname[PRT_PS_FONT_ROMAN]); - prt_dsc_resources("IncludeResource", "font", - prt_ps_courier_font.ps_fontname[PRT_PS_FONT_BOLD]); - prt_def_font("F1", p_encoding, (int)prt_line_height, - prt_ps_courier_font.ps_fontname[PRT_PS_FONT_BOLD]); - prt_dsc_resources("IncludeResource", "font", - prt_ps_courier_font.ps_fontname[PRT_PS_FONT_OBLIQUE]); - prt_def_font("F2", p_encoding, (int)prt_line_height, - prt_ps_courier_font.ps_fontname[PRT_PS_FONT_OBLIQUE]); - prt_dsc_resources("IncludeResource", "font", - prt_ps_courier_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE]); - prt_def_font("F3", p_encoding, (int)prt_line_height, - prt_ps_courier_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE]); - } - if (prt_out_mbyte) { - // Define the CID fonts to be used in the job. Typically CJKV fonts do - // not have an italic form being a western style, so where no font is - // defined for these faces VIM falls back to an existing face. - // Note: if using Courier for the ASCII range then the printout will - // have bold/italic/bolditalic regardless of the setting of printmbfont. - prt_dsc_resources("IncludeResource", "font", - prt_ps_mb_font.ps_fontname[PRT_PS_FONT_ROMAN]); - if (!prt_custom_cmap) { - prt_dsc_resources("IncludeResource", "cmap", prt_cmap); - } - prt_def_cidfont("CF0", (int)prt_line_height, - prt_ps_mb_font.ps_fontname[PRT_PS_FONT_ROMAN]); - - if (prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLD] != NULL) { - prt_dsc_resources("IncludeResource", "font", - prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLD]); - if (!prt_custom_cmap) { - prt_dsc_resources("IncludeResource", "cmap", prt_cmap); - } - prt_def_cidfont("CF1", (int)prt_line_height, - prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLD]); - } else { - // Use ROMAN for BOLD - prt_dup_cidfont("CF0", "CF1"); - } - if (prt_ps_mb_font.ps_fontname[PRT_PS_FONT_OBLIQUE] != NULL) { - prt_dsc_resources("IncludeResource", "font", - prt_ps_mb_font.ps_fontname[PRT_PS_FONT_OBLIQUE]); - if (!prt_custom_cmap) { - prt_dsc_resources("IncludeResource", "cmap", prt_cmap); - } - prt_def_cidfont("CF2", (int)prt_line_height, - prt_ps_mb_font.ps_fontname[PRT_PS_FONT_OBLIQUE]); - } else { - // Use ROMAN for OBLIQUE - prt_dup_cidfont("CF0", "CF2"); - } - if (prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE] != NULL) { - prt_dsc_resources("IncludeResource", "font", - prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE]); - if (!prt_custom_cmap) { - prt_dsc_resources("IncludeResource", "cmap", prt_cmap); - } - prt_def_cidfont("CF3", (int)prt_line_height, - prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE]); - } else { - // Use BOLD for BOLDOBLIQUE - prt_dup_cidfont("CF1", "CF3"); - } - } - - // Misc constant vars used for underlining and background rects - prt_def_var("UO", PRT_PS_FONT_TO_USER(prt_line_height, - prt_ps_font->uline_offset), 2); - prt_def_var("UW", PRT_PS_FONT_TO_USER(prt_line_height, - prt_ps_font->uline_width), 2); - prt_def_var("BO", prt_bgcol_offset, 2); - - prt_dsc_noarg("EndSetup"); - - // Fail if any problems writing out to the PS file - return !prt_file_error; -} - -void mch_print_end(prt_settings_T *psettings) -{ - prt_dsc_noarg("Trailer"); - - // Output any info we don't know in toto until we finish - prt_dsc_ints("Pages", 1, &prt_page_num); - - prt_dsc_noarg("EOF"); - - // Write CTRL-D to close serial communication link if used. - // NOTHING MUST BE WRITTEN AFTER THIS! - prt_write_file("\004"); - - if (!prt_file_error && psettings->outfile == NULL - && !got_int && !psettings->user_abort) { - // Close the file first. - if (prt_ps_fd != NULL) { - fclose(prt_ps_fd); - prt_ps_fd = NULL; - } - prt_message((char_u *)_("Sending to printer...")); - - // Not printing to a file: use 'printexpr' to print the file. - if (eval_printexpr((char *)prt_ps_file_name, (char *)psettings->arguments) - == FAIL) { - emsg(_("E365: Failed to print PostScript file")); - } else { - prt_message((char_u *)_("Print job sent.")); - } - } - - mch_print_cleanup(); -} - -int mch_print_end_page(void) -{ - prt_flush_buffer(); - - prt_write_string("re sp\n"); - - prt_dsc_noarg("PageTrailer"); - - return !prt_file_error; -} - -int mch_print_begin_page(char_u *str) -{ - int page_num[2]; - - prt_page_num++; - - page_num[0] = page_num[1] = prt_page_num; - prt_dsc_ints("Page", 2, page_num); - - prt_dsc_noarg("BeginPageSetup"); - - prt_write_string("sv\n0 g\n"); - prt_in_ascii = !prt_out_mbyte; - if (prt_out_mbyte) { - prt_write_string("CF0 sf\n"); - } else { - prt_write_string("F0 sf\n"); - } - prt_fgcol = PRCOLOR_BLACK; - prt_bgcol = PRCOLOR_WHITE; - prt_font = PRT_PS_FONT_ROMAN; - - // Set up page transformation for landscape printing. - if (!prt_portrait) { - prt_write_int(-((int)prt_mediasize[prt_media].width)); - prt_write_string("sl\n"); - } - - prt_dsc_noarg("EndPageSetup"); - - // We have reset the font attributes, force setting them again. - curr_bg = 0xffffffff; - curr_fg = 0xffffffff; - curr_bold = kNone; - - return !prt_file_error; -} - -int mch_print_blank_page(void) -{ - return mch_print_begin_page(NULL) ? (mch_print_end_page()) : false; -} - -static double prt_pos_x = 0; -static double prt_pos_y = 0; - -void mch_print_start_line(const bool margin, const int page_line) -{ - prt_pos_x = prt_left_margin; - if (margin) { - prt_pos_x -= prt_number_width; - } - - prt_pos_y = prt_top_margin - prt_first_line_height - - page_line * prt_line_height; - - prt_attribute_change = true; - prt_need_moveto = true; - prt_half_width = false; -} - -int mch_print_text_out(char_u *const textp, size_t len) -{ - char_u *p = textp; - char_u ch; - char_u ch_buff[8]; - char_u *tofree = NULL; - double char_width = prt_char_width; - - // Ideally VIM would create a rearranged CID font to combine a Roman and - // CJKV font to do what VIM is doing here - use a Roman font for characters - // in the ASCII range, and the original CID font for everything else. - // The problem is that GhostScript still (as of 8.13) does not support - // rearranged fonts even though they have been documented by Adobe for 7 - // years! If they ever do, a lot of this code will disappear. - if (prt_use_courier) { - const bool in_ascii = (len == 1 && *p < 0x80); - if (prt_in_ascii) { - if (!in_ascii) { - // No longer in ASCII range - need to switch font - prt_in_ascii = false; - prt_need_font = true; - prt_attribute_change = true; - } - } else if (in_ascii) { - // Now in ASCII range - need to switch font - prt_in_ascii = true; - prt_need_font = true; - prt_attribute_change = true; - } - } - if (prt_out_mbyte) { - const bool half_width = (utf_ptr2cells((char *)p) == 1); - if (half_width) { - char_width /= 2; - } - if (prt_half_width) { - if (!half_width) { - prt_half_width = false; - prt_pos_x += prt_char_width/4; - prt_need_moveto = true; - prt_attribute_change = true; - } - } else if (half_width) { - prt_half_width = true; - prt_pos_x += prt_char_width/4; - prt_need_moveto = true; - prt_attribute_change = true; - } - } - - // Output any required changes to the graphics state, after flushing any - // text buffered so far. - if (prt_attribute_change) { - prt_flush_buffer(); - // Reset count of number of chars that will be printed - prt_text_run = 0; - - if (prt_need_moveto) { - prt_pos_x_moveto = prt_pos_x; - prt_pos_y_moveto = prt_pos_y; - prt_do_moveto = true; - - prt_need_moveto = false; - } - if (prt_need_font) { - if (!prt_in_ascii) { - prt_write_string("CF"); - } else { - prt_write_string("F"); - } - prt_write_int(prt_font); - prt_write_string("sf\n"); - prt_need_font = false; - } - if (prt_need_fgcol) { - unsigned int r, g, b; - r = (prt_fgcol & 0xff0000) >> 16; - g = (prt_fgcol & 0xff00) >> 8; - b = prt_fgcol & 0xff; - - prt_write_real(r / 255.0, 3); - if (r == g && g == b) { - prt_write_string("g\n"); - } else { - prt_write_real(g / 255.0, 3); - prt_write_real(b / 255.0, 3); - prt_write_string("r\n"); - } - prt_need_fgcol = false; - } - - if (prt_bgcol != PRCOLOR_WHITE) { - prt_new_bgcol = prt_bgcol; - if (prt_need_bgcol) { - prt_do_bgcol = true; - } - } else { - prt_do_bgcol = false; - } - prt_need_bgcol = false; - - if (prt_need_underline) { - prt_do_underline = prt_underline; - } - prt_need_underline = false; - - prt_attribute_change = false; - } - - if (prt_do_conv) { - // Convert from multi-byte to 8-bit encoding - p = (char_u *)string_convert(&prt_conv, (char *)p, &len); - tofree = p; - if (p == NULL) { - p = (char_u *)""; - len = 0; - } - } - - if (prt_out_mbyte) { - // Multi-byte character strings are represented more efficiently as hex - // strings when outputting clean 8 bit PS. - while (len-- > 0) { - ch = prt_hexchar[(unsigned)(*p) >> 4]; - ga_append(&prt_ps_buffer, (char)ch); - ch = prt_hexchar[(*p) & 0xf]; - ga_append(&prt_ps_buffer, (char)ch); - p++; - } - } else { - // Add next character to buffer of characters to output. - // Note: One printed character may require several PS characters to - // represent it, but we only count them as one printed character. - ch = *p; - if (ch < 32 || ch == '(' || ch == ')' || ch == '\\') { - // Convert non-printing characters to either their escape or octal - // sequence, ensures PS sent over a serial line does not interfere - // with the comms protocol. - ga_append(&prt_ps_buffer, '\\'); - switch (ch) { - case BS: - ga_append(&prt_ps_buffer, 'b'); break; - case TAB: - ga_append(&prt_ps_buffer, 't'); break; - case NL: - ga_append(&prt_ps_buffer, 'n'); break; - case FF: - ga_append(&prt_ps_buffer, 'f'); break; - case CAR: - ga_append(&prt_ps_buffer, 'r'); break; - case '(': - ga_append(&prt_ps_buffer, '('); break; - case ')': - ga_append(&prt_ps_buffer, ')'); break; - case '\\': - ga_append(&prt_ps_buffer, '\\'); break; - - default: - sprintf((char *)ch_buff, "%03o", (unsigned int)ch); - ga_append(&prt_ps_buffer, (char)ch_buff[0]); - ga_append(&prt_ps_buffer, (char)ch_buff[1]); - ga_append(&prt_ps_buffer, (char)ch_buff[2]); - break; - } - } else { - ga_append(&prt_ps_buffer, (char)ch); - } - } - - // Need to free any translated characters - xfree(tofree); - - prt_text_run += char_width; - prt_pos_x += char_width; - - // The downside of fp - use relative error on right margin check - const double next_pos = prt_pos_x + prt_char_width; - const bool need_break = (next_pos > prt_right_margin) - && ((next_pos - prt_right_margin) > (prt_right_margin * 1e-5)); - - if (need_break) { - prt_flush_buffer(); - } - - return need_break; -} - -void mch_print_set_font(const TriState iBold, const TriState iItalic, const TriState iUnderline) -{ - int font = 0; - - if (iBold) { - font |= 0x01; - } - if (iItalic) { - font |= 0x02; - } - - if (font != prt_font) { - prt_font = font; - prt_attribute_change = true; - prt_need_font = true; - } - if (prt_underline != iUnderline) { - prt_underline = iUnderline; - prt_attribute_change = true; - prt_need_underline = true; - } -} - -void mch_print_set_bg(uint32_t bgcol) -{ - prt_bgcol = bgcol; - prt_attribute_change = true; - prt_need_bgcol = true; -} - -void mch_print_set_fg(uint32_t fgcol) -{ - if (fgcol != prt_fgcol) { - prt_fgcol = fgcol; - prt_attribute_change = true; - prt_need_fgcol = true; - } -} diff --git a/src/nvim/hardcopy.h b/src/nvim/hardcopy.h deleted file mode 100644 index ce562cd3e6..0000000000 --- a/src/nvim/hardcopy.h +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef NVIM_HARDCOPY_H -#define NVIM_HARDCOPY_H - -#include <stdint.h> -#include <stdlib.h> // for size_t - -#include "nvim/ex_cmds_defs.h" // for exarg_T -#include "nvim/globals.h" // for TriState -#include "nvim/types.h" // for char_u - -// Structure to hold printing color and font attributes. -typedef struct { - uint32_t fg_color; - uint32_t bg_color; - TriState bold; - TriState italic; - TriState underline; - int undercurl; - int underdouble; - int underdotted; - int underdashed; -} prt_text_attr_T; - -// Structure passed back to the generic printer code. -typedef struct { - int n_collated_copies; - int n_uncollated_copies; - int duplex; - int chars_per_line; - int lines_per_page; - int has_color; - prt_text_attr_T number; - int modec; - int do_syntax; - int user_abort; - char_u *jobname; - char_u *outfile; - char_u *arguments; -} prt_settings_T; - -// Generic option table item, only used for printer at the moment. -typedef struct { - const char *name; - int hasnum; - int number; - char_u *string; // points into option string - int strlen; - int present; -} option_table_T; - -#define OPT_PRINT_TOP 0 -#define OPT_PRINT_BOT 1 -#define OPT_PRINT_LEFT 2 -#define OPT_PRINT_RIGHT 3 -#define OPT_PRINT_HEADERHEIGHT 4 -#define OPT_PRINT_SYNTAX 5 -#define OPT_PRINT_NUMBER 6 -#define OPT_PRINT_WRAP 7 -#define OPT_PRINT_DUPLEX 8 -#define OPT_PRINT_PORTRAIT 9 -#define OPT_PRINT_PAPER 10 -#define OPT_PRINT_COLLATE 11 -#define OPT_PRINT_JOBSPLIT 12 -#define OPT_PRINT_FORMFEED 13 -#define OPT_PRINT_NUM_OPTIONS 14 - -// For prt_get_unit(). -#define PRT_UNIT_NONE -1 -#define PRT_UNIT_PERC 0 -#define PRT_UNIT_INCH 1 -#define PRT_UNIT_MM 2 -#define PRT_UNIT_POINT 3 -#define PRT_UNIT_NAMES { "pc", "in", "mm", "pt" } - -#define PRINT_NUMBER_WIDTH 8 - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "hardcopy.h.generated.h" -#endif -#endif // NVIM_HARDCOPY_H diff --git a/src/nvim/hashtab.c b/src/nvim/hashtab.c index 1ebac603c2..851e70caca 100644 --- a/src/nvim/hashtab.c +++ b/src/nvim/hashtab.c @@ -30,6 +30,7 @@ #include "nvim/hashtab.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/types.h" #include "nvim/vim.h" // Magic value for algorithm that walks through the array. @@ -87,7 +88,7 @@ void hash_clear_all(hashtab_T *ht, unsigned int off) /// is changed in any way. hashitem_T *hash_find(const hashtab_T *const ht, const char *const key) { - return hash_lookup(ht, key, strlen(key), hash_hash((char_u *)key)); + return hash_lookup(ht, key, strlen(key), hash_hash(key)); } /// Like hash_find, but key is not NUL-terminated @@ -140,7 +141,7 @@ hashitem_T *hash_lookup(const hashtab_T *const ht, const char *const key, const if (hi->hi_key == HI_KEY_REMOVED) { freeitem = hi; } else if ((hi->hi_hash == hash) - && (STRNCMP(hi->hi_key, key, key_len) == 0) + && (strncmp(hi->hi_key, key, key_len) == 0) && hi->hi_key[key_len] == NUL) { return hi; } @@ -166,7 +167,7 @@ hashitem_T *hash_lookup(const hashtab_T *const ht, const char *const key, const if ((hi->hi_hash == hash) && (hi->hi_key != HI_KEY_REMOVED) - && (STRNCMP(hi->hi_key, key, key_len) == 0) + && (strncmp(hi->hi_key, key, key_len) == 0) && hi->hi_key[key_len] == NUL) { return hi; } @@ -201,10 +202,10 @@ void hash_debug_results(void) /// /// @return OK if success. /// FAIL if key already present -int hash_add(hashtab_T *ht, char_u *key) +int hash_add(hashtab_T *ht, char *key) { hash_T hash = hash_hash(key); - hashitem_T *hi = hash_lookup(ht, (const char *)key, STRLEN(key), hash); + hashitem_T *hi = hash_lookup(ht, key, strlen(key), hash); if (!HASHITEM_EMPTY(hi)) { internal_error("hash_add()"); return FAIL; @@ -220,9 +221,10 @@ int hash_add(hashtab_T *ht, char_u *key) /// @param key Pointer to the key for the new item. The key has to be contained /// in the new item (@see hashitem_T). Must not be NULL. /// @param hash The precomputed hash value for the key. -void hash_add_item(hashtab_T *ht, hashitem_T *hi, char_u *key, hash_T hash) +void hash_add_item(hashtab_T *ht, hashitem_T *hi, char *key, hash_T hash) { ht->ht_used++; + ht->ht_changed++; if (hi->hi_key == NULL) { ht->ht_filled++; } @@ -242,6 +244,7 @@ void hash_add_item(hashtab_T *ht, hashitem_T *hi, char_u *key, hash_T hash) void hash_remove(hashtab_T *ht, hashitem_T *hi) { ht->ht_used--; + ht->ht_changed++; hi->hi_key = HI_KEY_REMOVED; hash_may_resize(ht, 0); } @@ -290,6 +293,7 @@ static void hash_may_resize(hashtab_T *ht, size_t minitems) #endif // ifdef HT_DEBUG size_t minsize; + const size_t oldsize = ht->ht_mask + 1; if (minitems == 0) { // Return quickly for small tables with at least two NULL items. // items are required for the lookup to decide a key isn't there. @@ -302,7 +306,6 @@ static void hash_may_resize(hashtab_T *ht, size_t minitems) // removed items, so that they get cleaned up). // Shrink the array when it's less than 1/5 full. When growing it is // at least 1/4 full (avoids repeated grow-shrink operations) - size_t oldsize = ht->ht_mask + 1; if ((ht->ht_filled * 3 < oldsize * 2) && (ht->ht_used > oldsize / 5)) { return; } @@ -333,6 +336,13 @@ static void hash_may_resize(hashtab_T *ht, size_t minitems) } bool newarray_is_small = newsize == HT_INIT_SIZE; + + if (!newarray_is_small && newsize == oldsize && ht->ht_filled * 3 < oldsize * 2) { + // The hashtab is already at the desired size, and there are not too + // many removed items, bail out. + return; + } + bool keep_smallarray = newarray_is_small && ht->ht_array == ht->ht_smallarray; @@ -384,6 +394,7 @@ static void hash_may_resize(hashtab_T *ht, size_t minitems) ht->ht_array = newarray; ht->ht_mask = newmask; ht->ht_filled = ht->ht_used; + ht->ht_changed++; } #define HASH_CYCLE_BODY(hash, p) \ @@ -395,9 +406,9 @@ static void hash_may_resize(hashtab_T *ht, size_t minitems) /// run a script that uses hashtables a lot. Vim will then print statistics /// when exiting. Try that with the current hash algorithm and yours. The /// lower the percentage the better. -hash_T hash_hash(const char_u *key) +hash_T hash_hash(const char *key) { - hash_T hash = *key; + hash_T hash = (uint8_t)(*key); if (hash == 0) { return (hash_T)0; @@ -405,7 +416,7 @@ hash_T hash_hash(const char_u *key) // A simplistic algorithm that appears to do very well. // Suggested by George Reilly. - const uint8_t *p = key + 1; + const uint8_t *p = (uint8_t *)key + 1; while (*p != NUL) { HASH_CYCLE_BODY(hash, p); } @@ -449,5 +460,5 @@ hash_T hash_hash_len(const char *key, const size_t len) const char_u *_hash_key_removed(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - return HI_KEY_REMOVED; + return (char_u *)HI_KEY_REMOVED; } diff --git a/src/nvim/hashtab.h b/src/nvim/hashtab.h index 7740a3e431..0a50fb2ef8 100644 --- a/src/nvim/hashtab.h +++ b/src/nvim/hashtab.h @@ -15,9 +15,9 @@ typedef size_t hash_T; /// The address of "hash_removed" is used as a magic number /// for hi_key to indicate a removed item. -#define HI_KEY_REMOVED ((char_u *)&hash_removed) +#define HI_KEY_REMOVED (&hash_removed) #define HASHITEM_EMPTY(hi) ((hi)->hi_key == NULL \ - || (hi)->hi_key == (char_u *)&hash_removed) + || (hi)->hi_key == &hash_removed) /// Hashtable item. /// @@ -45,7 +45,7 @@ typedef struct hashitem_S { /// NULL : Item was never used. /// HI_KEY_REMOVED : Item was removed. /// (Any other pointer value) : Item is currently being used. - char_u *hi_key; + char *hi_key; } hashitem_T; /// Initial size for a hashtable. @@ -61,14 +61,15 @@ typedef struct hashitem_S { /// /// The hashtable grows to accommodate more entries when needed. typedef struct hashtable_S { - hash_T ht_mask; /// mask used for hash value - /// (nr of items in array is "ht_mask" + 1) - size_t ht_used; /// number of items used - size_t ht_filled; /// number of items used or removed - int ht_locked; /// counter for hash_lock() - hashitem_T *ht_array; /// points to the array, allocated when it's - /// not "ht_smallarray" - hashitem_T ht_smallarray[HT_INIT_SIZE]; /// initial array + hash_T ht_mask; ///< mask used for hash value + ///< (nr of items in array is "ht_mask" + 1) + size_t ht_used; ///< number of items used + size_t ht_filled; ///< number of items used or removed + int ht_changed; ///< incremented when adding or removing an item + int ht_locked; ///< counter for hash_lock() + hashitem_T *ht_array; ///< points to the array, allocated when it's + ///< not "ht_smallarray" + hashitem_T ht_smallarray[HT_INIT_SIZE]; ///< initial array } hashtab_T; /// Iterate over a hashtab diff --git a/src/nvim/help.c b/src/nvim/help.c index 7c61a56785..bbc552fa4c 100644 --- a/src/nvim/help.c +++ b/src/nvim/help.c @@ -3,26 +3,39 @@ // help.c: functions for Vim help +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> +#include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" #include "nvim/ex_cmds.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/fileio.h" #include "nvim/garray.h" +#include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/help.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/memline.h" #include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/option.h" #include "nvim/optionstr.h" #include "nvim/os/input.h" +#include "nvim/os/os.h" #include "nvim/path.h" +#include "nvim/pos.h" +#include "nvim/runtime.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/tag.h" +#include "nvim/types.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -359,7 +372,7 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep "!=?", "!~?", "<=?", "<?", "==?", "=~?", ">=?", ">?", "is?", "isnot?" }; - char *d = (char *)IObuff; // assume IObuff is long enough! + char *d = IObuff; // assume IObuff is long enough! d[0] = NUL; if (STRNICMP(arg, "expr-", 5) == 0) { @@ -399,7 +412,7 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep // And also "\_$" and "\_^". if (arg[0] == '\\' && ((arg[1] != NUL && arg[2] == NUL) - || (vim_strchr("%_z@", arg[1]) != NULL + || (vim_strchr("%_z@", (uint8_t)arg[1]) != NULL && arg[2] != NUL))) { vim_snprintf(d, IOSIZE, "/\\\\%s", arg + 1); // Check for "/\\_$", should be "/\\_\$" @@ -458,7 +471,7 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep // Insert '-' before and after "CTRL-X" when applicable. if (*s < ' ' || (*s == '^' && s[1] - && (ASCII_ISALPHA(s[1]) || vim_strchr("?@[\\]^", s[1]) != NULL))) { + && (ASCII_ISALPHA(s[1]) || vim_strchr("?@[\\]^", (uint8_t)s[1]) != NULL))) { if (d > IObuff && d[-1] != '_' && d[-1] != '\\') { *d++ = '_'; // prepend a '_' to make x_CTRL-x } @@ -537,12 +550,12 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep if (keep_lang) { flags |= TAG_KEEP_LANG; } - if (find_tags((char *)IObuff, num_matches, matches, flags, MAXCOL, NULL) == OK + if (find_tags(IObuff, num_matches, matches, flags, MAXCOL, NULL) == OK && *num_matches > 0) { // Sort the matches found on the heuristic number that is after the // tag name. qsort((void *)(*matches), (size_t)(*num_matches), - sizeof(char_u *), help_compare); + sizeof(char *), help_compare); // Delete more than TAG_MANY to reduce the size of the listing. while (*num_matches > TAG_MANY) { xfree((*matches)[--*num_matches]); @@ -557,7 +570,7 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep void cleanup_help_tags(int num_file, char **file) { char buf[4]; - char_u *p = (char_u *)buf; + char *p = buf; if (p_hlg[0] != NUL && (p_hlg[0] != 'e' || p_hlg[1] != 'n')) { *p++ = '@'; @@ -578,7 +591,7 @@ void cleanup_help_tags(int num_file, char **file) for (j = 0; j < num_file; j++) { if (j != i && (int)strlen(file[j]) == len + 3 - && STRNCMP(file[i], file[j], len + 1) == 0) { + && strncmp(file[i], file[j], (size_t)len + 1) == 0) { break; } } @@ -659,7 +672,7 @@ void fix_help_buffer(void) if (!syntax_present(curwin)) { for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) { line = ml_get_buf(curbuf, lnum, false); - const size_t len = STRLEN(line); + const size_t len = strlen(line); if (in_example && len > 0 && !ascii_iswhite(line[0])) { // End of example: non-white or '<' in first column. if (line[0] == '<') { @@ -703,10 +716,10 @@ void fix_help_buffer(void) // $VIMRUNTIME. char *p = p_rtp; while (*p != NUL) { - copy_option_part(&p, (char *)NameBuff, MAXPATHL, ","); + copy_option_part(&p, NameBuff, MAXPATHL, ","); char *const rt = vim_getenv("VIMRUNTIME"); if (rt != NULL - && path_full_compare(rt, (char *)NameBuff, false, true) != kEqualFiles) { + && path_full_compare(rt, NameBuff, false, true) != kEqualFiles) { int fcount; char **fnames; char *s; @@ -714,7 +727,7 @@ void fix_help_buffer(void) char *cp; // Find all "doc/ *.txt" files in this directory. - if (!add_pathsep((char *)NameBuff) + if (!add_pathsep(NameBuff) || xstrlcat(NameBuff, "doc/*.??[tx]", // NOLINT sizeof(NameBuff)) >= MAXPATHL) { emsg(_(e_fnametoolong)); @@ -723,35 +736,33 @@ void fix_help_buffer(void) // Note: We cannot just do `&NameBuff` because it is a statically sized array // so `NameBuff == &NameBuff` according to C semantics. - char *buff_list[1] = { (char *)NameBuff }; + char *buff_list[1] = { NameBuff }; if (gen_expand_wildcards(1, buff_list, &fcount, &fnames, EW_FILE|EW_SILENT) == OK && fcount > 0) { // If foo.abx is found use it instead of foo.txt in // the same directory. for (int i1 = 0; i1 < fcount; i1++) { - for (int i2 = 0; i2 < fcount; i2++) { - if (i1 == i2) { - continue; - } - if (fnames[i1] == NULL || fnames[i2] == NULL) { + const char *const f1 = fnames[i1]; + const char *const t1 = path_tail(f1); + const char *const e1 = strrchr(t1, '.'); + if (path_fnamecmp(e1, ".txt") != 0 + && path_fnamecmp(e1, fname + 4) != 0) { + // Not .txt and not .abx, remove it. + XFREE_CLEAR(fnames[i1]); + continue; + } + + for (int i2 = i1 + 1; i2 < fcount; i2++) { + const char *const f2 = fnames[i2]; + if (f2 == NULL) { continue; } - const char *const f1 = fnames[i1]; - const char *const f2 = fnames[i2]; - const char *const t1 = path_tail(f1); const char *const t2 = path_tail(f2); - const char *const e1 = strrchr(t1, '.'); const char *const e2 = strrchr(t2, '.'); if (e1 == NULL || e2 == NULL) { continue; } - if (path_fnamecmp(e1, ".txt") != 0 - && path_fnamecmp(e1, fname + 4) != 0) { - // Not .txt and not .abx, remove it. - XFREE_CLEAR(fnames[i1]); - continue; - } if (e1 - f1 != e2 - f2 || path_fnamencmp(f1, f2, (size_t)(e1 - f1)) != 0) { continue; @@ -772,9 +783,9 @@ void fix_help_buffer(void) if (fd == NULL) { continue; } - vim_fgets((char_u *)IObuff, IOSIZE, fd); + vim_fgets(IObuff, IOSIZE, fd); if (IObuff[0] == '*' - && (s = vim_strchr((char *)IObuff + 1, '*')) + && (s = vim_strchr(IObuff + 1, '*')) != NULL) { TriState this_utf = kNone; // Change tag definition to a @@ -788,7 +799,7 @@ void fix_help_buffer(void) // The text is utf-8 when a byte // above 127 is found and no // illegal byte sequence is found. - if ((char_u)(*s) >= 0x80 && this_utf != kFalse) { + if ((uint8_t)(*s) >= 0x80 && this_utf != kFalse) { this_utf = kTrue; const int l = utf_ptr2len(s); if (l == 1) { @@ -807,13 +818,13 @@ void fix_help_buffer(void) p_enc); if (vc.vc_type == CONV_NONE) { // No conversion needed. - cp = (char *)IObuff; + cp = IObuff; } else { // Do the conversion. If it fails // use the unconverted text. - cp = string_convert(&vc, (char *)IObuff, NULL); + cp = string_convert(&vc, IObuff, NULL); if (cp == NULL) { - cp = (char *)IObuff; + cp = IObuff; } } convert_setup(&vc, NULL, NULL); @@ -869,7 +880,7 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool bool mix = false; // detected mixed encodings // Find all *.txt files. - size_t dirlen = STRLCPY(NameBuff, dir, sizeof(NameBuff)); + size_t dirlen = xstrlcpy(NameBuff, dir, sizeof(NameBuff)); if (dirlen >= MAXPATHL || xstrlcat(NameBuff, "/**/*", sizeof(NameBuff)) >= MAXPATHL // NOLINT || xstrlcat(NameBuff, ext, sizeof(NameBuff)) >= MAXPATHL) { @@ -879,7 +890,7 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool // Note: We cannot just do `&NameBuff` because it is a statically sized array // so `NameBuff == &NameBuff` according to C semantics. - char *buff_list[1] = { (char *)NameBuff }; + char *buff_list[1] = { NameBuff }; const int res = gen_expand_wildcards(1, buff_list, &filecount, &files, EW_FILE|EW_SILENT); if (res == FAIL || filecount == 0) { @@ -895,13 +906,13 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool // Open the tags file for writing. // Do this before scanning through all the files. memcpy(NameBuff, dir, dirlen + 1); - if (!add_pathsep((char *)NameBuff) + if (!add_pathsep(NameBuff) || xstrlcat(NameBuff, tagfname, sizeof(NameBuff)) >= MAXPATHL) { emsg(_(e_fnametoolong)); return; } - FILE *const fd_tags = os_fopen((char *)NameBuff, "w"); + FILE *const fd_tags = os_fopen(NameBuff, "w"); if (fd_tags == NULL) { if (!ignore_writeerr) { semsg(_("E152: Cannot open %s for writing"), NameBuff); @@ -912,7 +923,7 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool // If using the "++t" argument or generating tags for "$VIMRUNTIME/doc" // add the "help-tags" tag. - ga_init(&ga, (int)sizeof(char_u *), 100); + ga_init(&ga, (int)sizeof(char *), 100); if (add_help_tags || path_full_compare("$VIMRUNTIME/doc", dir, false, true) == kEqualFiles) { size_t s_len = 18 + strlen(tagfname); @@ -930,13 +941,14 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool } const char *const fname = files[fi] + dirlen + 1; + bool in_example = false; bool firstline = true; - while (!vim_fgets((char_u *)IObuff, IOSIZE, fd) && !got_int) { + while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) { if (firstline) { // Detect utf-8 file by a non-ASCII char in the first line. TriState this_utf8 = kNone; - for (s = (char *)IObuff; *s != NUL; s++) { - if ((char_u)(*s) >= 0x80) { + for (s = IObuff; *s != NUL; s++) { + if ((uint8_t)(*s) >= 0x80) { this_utf8 = kTrue; const int l = utf_ptr2len(s); if (l == 1) { @@ -960,7 +972,14 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool } firstline = false; } - p1 = vim_strchr((char *)IObuff, '*'); // find first '*' + if (in_example) { + // skip over example; a non-white in the first column ends it + if (vim_strchr(" \t\n\r", (uint8_t)IObuff[0])) { + continue; + } + in_example = false; + } + p1 = vim_strchr(IObuff, '*'); // find first '*' while (p1 != NULL) { p2 = strchr((const char *)p1 + 1, '*'); // Find second '*'. if (p2 != NULL && p2 > p1 + 1) { // Skip "*" and "**". @@ -975,11 +994,11 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool // followed by a white character or end-of-line. if (s == p2 && (p1 == IObuff || p1[-1] == ' ' || p1[-1] == '\t') - && (vim_strchr(" \t\n\r", s[1]) != NULL + && (vim_strchr(" \t\n\r", (uint8_t)s[1]) != NULL || s[1] == '\0')) { *p2 = '\0'; p1++; - size_t s_len= (size_t)(p2 - p1) + strlen(fname) + 2; + size_t s_len = (size_t)(p2 - p1) + strlen(fname) + 2; s = xmalloc(s_len); GA_APPEND(char *, &ga, s); snprintf(s, s_len, "%s\t%s", p1, fname); @@ -990,6 +1009,11 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool } p1 = p2; } + size_t len = strlen(IObuff); + if ((len == 2 && strcmp(&IObuff[len - 2], ">\n") == 0) + || (len >= 3 && strcmp(&IObuff[len - 3], " >\n") == 0)) { + in_example = true; + } line_breakcheck(); } @@ -1009,10 +1033,10 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool while (*p1 == *p2) { if (*p2 == '\t') { *p2 = NUL; - vim_snprintf((char *)NameBuff, MAXPATHL, + vim_snprintf(NameBuff, MAXPATHL, _("E154: Duplicate tag \"%s\" in file %s/%s"), ((char_u **)ga.ga_data)[i], dir, p2 + 1); - emsg((char *)NameBuff); + emsg(NameBuff); *p2 = '\t'; break; } @@ -1028,7 +1052,7 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool // Write the tags into the file. for (int i = 0; i < ga.ga_len; i++) { s = ((char **)ga.ga_data)[i]; - if (STRNCMP(s, "help-tags\t", 10) == 0) { + if (strncmp(s, "help-tags\t", 10) == 0) { // help-tags entry was added in formatted form fputs(s, fd_tags); } else { @@ -1065,7 +1089,7 @@ static void do_helptags(char *dirname, bool add_help_tags, bool ignore_writeerr) char **files; // Get a list of all files in the help directory and in subdirectories. - STRLCPY(NameBuff, dirname, sizeof(NameBuff)); + xstrlcpy(NameBuff, dirname, sizeof(NameBuff)); if (!add_pathsep((char *)NameBuff) || xstrlcat(NameBuff, "**", sizeof(NameBuff)) >= MAXPATHL) { emsg(_(e_fnametoolong)); @@ -1074,7 +1098,7 @@ static void do_helptags(char *dirname, bool add_help_tags, bool ignore_writeerr) // Note: We cannot just do `&NameBuff` because it is a statically sized array // so `NameBuff == &NameBuff` according to C semantics. - char *buff_list[1] = { (char *)NameBuff }; + char *buff_list[1] = { NameBuff }; if (gen_expand_wildcards(1, buff_list, &filecount, &files, EW_FILE|EW_SILENT) == FAIL || filecount == 0) { @@ -1109,7 +1133,7 @@ static void do_helptags(char *dirname, bool add_help_tags, bool ignore_writeerr) // Did we find this language already? for (j = 0; j < ga.ga_len; j += 2) { - if (STRNCMP(lang, ((char_u *)ga.ga_data) + j, 2) == 0) { + if (strncmp(lang, ((char *)ga.ga_data) + j, 2) == 0) { break; } } @@ -1157,7 +1181,7 @@ void ex_helptags(exarg_T *eap) bool add_help_tags = false; // Check for ":helptags ++t {dir}". - if (STRNCMP(eap->arg, "++t", 3) == 0 && ascii_iswhite(eap->arg[3])) { + if (strncmp(eap->arg, "++t", 3) == 0 && ascii_iswhite(eap->arg[3])) { add_help_tags = true; eap->arg = skipwhite(eap->arg + 3); } diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index d507f07bca..9dab91cc2b 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -3,18 +3,32 @@ // highlight.c: low level code for UI and syntax highlighting +#include <assert.h> +#include <inttypes.h> +#include <limits.h> +#include <string.h> + +#include "klib/kvec.h" +#include "lauxlib.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/ui.h" #include "nvim/decoration_provider.h" #include "nvim/drawscreen.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/highlight.h" #include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" +#include "nvim/log.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" #include "nvim/map.h" +#include "nvim/memory.h" #include "nvim/message.h" #include "nvim/option.h" #include "nvim/popupmenu.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -115,19 +129,15 @@ static int get_attr_entry(HlEntry entry) /// When a UI connects, we need to send it the table of highlights used so far. void ui_send_all_hls(UI *ui) { - if (ui->hl_attr_define) { - for (size_t i = 1; i < kv_size(attr_entries); i++) { - Array inspect = hl_inspect((int)i); - ui->hl_attr_define(ui, (Integer)i, kv_A(attr_entries, i).attr, - kv_A(attr_entries, i).attr, inspect); - api_free_array(inspect); - } + for (size_t i = 1; i < kv_size(attr_entries); i++) { + Array inspect = hl_inspect((int)i); + remote_ui_hl_attr_define(ui, (Integer)i, kv_A(attr_entries, i).attr, + kv_A(attr_entries, i).attr, inspect); + api_free_array(inspect); } - if (ui->hl_group_set) { - for (size_t hlf = 0; hlf < HLF_COUNT; hlf++) { - ui->hl_group_set(ui, cstr_as_string((char *)hlf_names[hlf]), - highlight_attr[hlf]); - } + for (size_t hlf = 0; hlf < HLF_COUNT; hlf++) { + remote_ui_hl_group_set(ui, cstr_as_string((char *)hlf_names[hlf]), + highlight_attr[hlf]); } } @@ -141,10 +151,9 @@ int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en) || at_en.rgb_ae_attr != 0 || ns_id != 0) { return get_attr_entry((HlEntry){ .attr = at_en, .kind = kHlSyntax, .id1 = idx, .id2 = ns_id }); - } else { - // If all the fields are cleared, clear the attr field back to default value - return 0; } + // If all the fields are cleared, clear the attr field back to default value + return 0; } void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id, Dict(highlight) *dict) @@ -238,12 +247,11 @@ int ns_get_hl(NS *ns_hl, int hl_id, bool link, bool nodefault) if (link) { if (it.attr_id >= 0) { return 0; - } else { - if (it.link_global) { - *ns_hl = 0; - } - return it.link_id; } + if (it.link_global) { + *ns_hl = 0; + } + return it.link_id; } else { return it.attr_id; } @@ -641,7 +649,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_ANY_UNDERLINE)) { + if (cattrs.rgb_ae_attr & (HL_UNDERLINE_MASK)) { cattrs.rgb_sp_color = rgb_blend(ratio, battrs.rgb_sp_color, fattrs.rgb_bg_color); } else { @@ -659,7 +667,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_ANY_UNDERLINE)) { + if (cattrs.rgb_ae_attr & (HL_UNDERLINE_MASK)) { cattrs.rgb_sp_color = rgb_blend(ratio/2, battrs.rgb_bg_color, fattrs.rgb_sp_color); } else { @@ -817,46 +825,52 @@ void hlattrs2dict(Dictionary *dict, HlAttrs ae, bool use_rgb) Dictionary hl = *dict; int mask = use_rgb ? ae.rgb_ae_attr : ae.cterm_ae_attr; + if (mask & HL_INVERSE) { + PUT_C(hl, "reverse", BOOLEAN_OBJ(true)); + } + if (mask & HL_BOLD) { PUT_C(hl, "bold", BOOLEAN_OBJ(true)); } - if (mask & HL_STANDOUT) { - PUT_C(hl, "standout", BOOLEAN_OBJ(true)); + if (mask & HL_ITALIC) { + PUT_C(hl, "italic", BOOLEAN_OBJ(true)); } - if (mask & HL_UNDERLINE) { + switch (mask & HL_UNDERLINE_MASK) { + case HL_UNDERLINE: PUT_C(hl, "underline", BOOLEAN_OBJ(true)); - } - - if (mask & HL_UNDERCURL) { - PUT_C(hl, "undercurl", BOOLEAN_OBJ(true)); - } + break; - if (mask & HL_UNDERDOUBLE) { + case HL_UNDERDOUBLE: PUT_C(hl, "underdouble", BOOLEAN_OBJ(true)); - } + break; - if (mask & HL_UNDERDOTTED) { + case HL_UNDERCURL: + PUT_C(hl, "undercurl", BOOLEAN_OBJ(true)); + break; + + case HL_UNDERDOTTED: PUT_C(hl, "underdotted", BOOLEAN_OBJ(true)); - } + break; - if (mask & HL_UNDERDASHED) { + case HL_UNDERDASHED: PUT_C(hl, "underdashed", BOOLEAN_OBJ(true)); + break; } - if (mask & HL_ITALIC) { - PUT_C(hl, "italic", BOOLEAN_OBJ(true)); - } - - if (mask & HL_INVERSE) { - PUT_C(hl, "reverse", BOOLEAN_OBJ(true)); + if (mask & HL_STANDOUT) { + PUT_C(hl, "standout", BOOLEAN_OBJ(true)); } if (mask & HL_STRIKETHROUGH) { PUT_C(hl, "strikethrough", BOOLEAN_OBJ(true)); } + if (mask & HL_ALTFONT) { + PUT_C(hl, "altfont", BOOLEAN_OBJ(true)); + } + if (mask & HL_NOCOMBINE) { PUT_C(hl, "nocombine", BOOLEAN_OBJ(true)); } @@ -912,32 +926,37 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e m = m | flag; \ } + CHECK_FLAG(dict, mask, reverse, , HL_INVERSE); CHECK_FLAG(dict, mask, bold, , HL_BOLD); - CHECK_FLAG(dict, mask, standout, , HL_STANDOUT); + CHECK_FLAG(dict, mask, italic, , HL_ITALIC); CHECK_FLAG(dict, mask, underline, , HL_UNDERLINE); - CHECK_FLAG(dict, mask, undercurl, , HL_UNDERCURL); CHECK_FLAG(dict, mask, underdouble, , HL_UNDERDOUBLE); + CHECK_FLAG(dict, mask, undercurl, , HL_UNDERCURL); CHECK_FLAG(dict, mask, underdotted, , HL_UNDERDOTTED); CHECK_FLAG(dict, mask, underdashed, , HL_UNDERDASHED); - CHECK_FLAG(dict, mask, italic, , HL_ITALIC); - CHECK_FLAG(dict, mask, reverse, , HL_INVERSE); + CHECK_FLAG(dict, mask, standout, , HL_STANDOUT); CHECK_FLAG(dict, mask, strikethrough, , HL_STRIKETHROUGH); + CHECK_FLAG(dict, mask, altfont, , HL_ALTFONT); + if (use_rgb) { + CHECK_FLAG(dict, mask, fg_indexed, , HL_FG_INDEXED); + CHECK_FLAG(dict, mask, bg_indexed, , HL_BG_INDEXED); + } CHECK_FLAG(dict, mask, nocombine, , HL_NOCOMBINE); CHECK_FLAG(dict, mask, default, _, HL_DEFAULT); if (HAS_KEY(dict->fg)) { - fg = object_to_color(dict->fg, "fg", true, err); + fg = object_to_color(dict->fg, "fg", use_rgb, err); } else if (HAS_KEY(dict->foreground)) { - fg = object_to_color(dict->foreground, "foreground", true, err); + fg = object_to_color(dict->foreground, "foreground", use_rgb, err); } if (ERROR_SET(err)) { return hlattrs; } if (HAS_KEY(dict->bg)) { - bg = object_to_color(dict->bg, "bg", true, err); + bg = object_to_color(dict->bg, "bg", use_rgb, err); } else if (HAS_KEY(dict->background)) { - bg = object_to_color(dict->background, "background", true, err); + bg = object_to_color(dict->background, "background", use_rgb, err); } if (ERROR_SET(err)) { return hlattrs; @@ -993,13 +1012,14 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e } cterm_mask_provided = true; + CHECK_FLAG(cterm, cterm_mask, reverse, , HL_INVERSE); CHECK_FLAG(cterm, cterm_mask, bold, , HL_BOLD); - CHECK_FLAG(cterm, cterm_mask, standout, , HL_STANDOUT); + CHECK_FLAG(cterm, cterm_mask, italic, , HL_ITALIC); 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, standout, , HL_STANDOUT); CHECK_FLAG(cterm, cterm_mask, strikethrough, , HL_STRIKETHROUGH); + CHECK_FLAG(cterm, cterm_mask, altfont, , HL_ALTFONT); 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 @@ -1024,11 +1044,11 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e } } - // apply gui mask as default for cterm mask - if (!cterm_mask_provided) { - cterm_mask = mask; - } if (use_rgb) { + // apply gui mask as default for cterm mask + if (!cterm_mask_provided) { + cterm_mask = mask; + } hlattrs.rgb_ae_attr = mask; hlattrs.rgb_bg_color = bg; hlattrs.rgb_fg_color = fg; @@ -1038,9 +1058,9 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e hlattrs.cterm_fg_color = ctermfg == -1 ? 0 : ctermfg + 1; hlattrs.cterm_ae_attr = cterm_mask; } else { - hlattrs.cterm_bg_color = ctermbg == -1 ? 0 : ctermbg + 1; - hlattrs.cterm_fg_color = ctermfg == -1 ? 0 : ctermfg + 1; - hlattrs.cterm_ae_attr = cterm_mask; + hlattrs.cterm_bg_color = bg == -1 ? 0 : bg + 1; + hlattrs.cterm_fg_color = fg == -1 ? 0 : fg + 1; + hlattrs.cterm_ae_attr = mask; } return hlattrs; diff --git a/src/nvim/highlight.h b/src/nvim/highlight.h index e85e3859e2..4da7dd65bb 100644 --- a/src/nvim/highlight.h +++ b/src/nvim/highlight.h @@ -6,6 +6,7 @@ #include "nvim/api/private/defs.h" #include "nvim/buffer_defs.h" #include "nvim/highlight_defs.h" +#include "nvim/option_defs.h" #include "nvim/ui.h" #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index ffcb0f3f22..a4dcf6eb60 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -15,19 +15,23 @@ typedef enum { HL_INVERSE = 0x01, HL_BOLD = 0x02, HL_ITALIC = 0x04, + // The next three bits are all underline styles + HL_UNDERLINE_MASK = 0x38, HL_UNDERLINE = 0x08, - HL_UNDERCURL = 0x10, - HL_UNDERDOUBLE = 0x20, - HL_UNDERDOTTED = 0x40, - HL_UNDERDASHED = 0x80, - HL_STANDOUT = 0x0100, - HL_NOCOMBINE = 0x0200, - HL_STRIKETHROUGH = 0x0400, + HL_UNDERDOUBLE = 0x10, + HL_UNDERCURL = 0x18, + HL_UNDERDOTTED = 0x20, + HL_UNDERDASHED = 0x28, + // 0x30 and 0x38 spare for underline styles + HL_STANDOUT = 0x0040, + HL_STRIKETHROUGH = 0x0080, + HL_ALTFONT = 0x0100, + // 0x0200 spare + HL_NOCOMBINE = 0x0400, HL_BG_INDEXED = 0x0800, HL_FG_INDEXED = 0x1000, HL_DEFAULT = 0x2000, HL_GLOBAL = 0x4000, - HL_ANY_UNDERLINE = HL_UNDERLINE | HL_UNDERDOUBLE | HL_UNDERCURL | HL_UNDERDOTTED | HL_UNDERDASHED, } HlAttrFlags; /// Stores a complete highlighting entry, including colors and attributes @@ -114,6 +118,7 @@ typedef enum { HLF_WBR, // Window bars HLF_WBRNC, // Window bars of not-current windows HLF_CU, // Cursor + HLF_BTITLE, // Float Border Title HLF_COUNT, // MUST be the last one } hlf_T; @@ -178,6 +183,7 @@ EXTERN const char *hlf_names[] INIT(= { [HLF_WBR] = "WinBar", [HLF_WBRNC] = "WinBarNC", [HLF_CU] = "Cursor", + [HLF_BTITLE] = "FloatTitle", }); EXTERN int highlight_attr[HLF_COUNT + 1]; // Highl. attr for each context. diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index 3508560e20..5b1ea9967d 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -3,29 +3,53 @@ // highlight_group.c: code for managing highlight groups +#include <ctype.h> #include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/ascii.h" #include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor_shape.h" +#include "nvim/decoration_provider.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/vars.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" -#include "nvim/fold.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/grid_defs.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/lua/executor.h" -#include "nvim/match.h" +#include "nvim/macros.h" +#include "nvim/map.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/option.h" +#include "nvim/os/time.h" #include "nvim/runtime.h" +#include "nvim/strings.h" +#include "nvim/types.h" +#include "nvim/ui.h" +#include "nvim/vim.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 +enum { + SG_CTERM = 2, // cterm has been set + SG_GUI = 4, // gui has been set + SG_LINK = 8, // link has been set +}; /// @} #define MAX_SYN_NAME 200 @@ -43,32 +67,32 @@ Map(cstr_t, int) highlight_unames = MAP_INIT; static char *(hl_name_table[]) = { "bold", "standout", "underline", "undercurl", "underdouble", "underdotted", "underdashed", - "italic", "reverse", "inverse", "strikethrough", "nocombine", "NONE" }; + "italic", "reverse", "inverse", "strikethrough", "altfont", + "nocombine", "NONE" }; static int hl_attr_table[] = { HL_BOLD, HL_STANDOUT, HL_UNDERLINE, HL_UNDERCURL, HL_UNDERDOUBLE, HL_UNDERDOTTED, HL_UNDERDASHED, - HL_ITALIC, HL_INVERSE, HL_INVERSE, HL_STRIKETHROUGH, HL_NOCOMBINE, 0 }; + HL_ITALIC, HL_INVERSE, HL_INVERSE, HL_STRIKETHROUGH, HL_ALTFONT, + 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 + char *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 + 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 + 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 @@ -98,8 +122,6 @@ enum { // 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", @@ -133,6 +155,7 @@ static const char *highlight_init_both[] = { "default link MsgSeparator StatusLine", "default link NormalFloat Pmenu", "default link FloatBorder WinSeparator", + "default link FloatTitle Title", "default FloatShadow blend=80 guibg=Black", "default FloatShadowThrough blend=100 guibg=Black", "RedrawDebugNormal cterm=reverse gui=reverse", @@ -169,26 +192,35 @@ static const char *highlight_init_both[] = { "default DiagnosticWarn ctermfg=3 guifg=Orange", "default DiagnosticInfo ctermfg=4 guifg=LightBlue", "default DiagnosticHint ctermfg=7 guifg=LightGrey", + "default DiagnosticOk ctermfg=10 guifg=LightGreen", "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 DiagnosticUnderlineOk cterm=underline gui=underline guisp=LightGreen", "default link DiagnosticVirtualTextError DiagnosticError", "default link DiagnosticVirtualTextWarn DiagnosticWarn", "default link DiagnosticVirtualTextInfo DiagnosticInfo", "default link DiagnosticVirtualTextHint DiagnosticHint", + "default link DiagnosticVirtualTextOk DiagnosticOk", "default link DiagnosticFloatingError DiagnosticError", "default link DiagnosticFloatingWarn DiagnosticWarn", "default link DiagnosticFloatingInfo DiagnosticInfo", "default link DiagnosticFloatingHint DiagnosticHint", + "default link DiagnosticFloatingOk DiagnosticOk", "default link DiagnosticSignError DiagnosticError", "default link DiagnosticSignWarn DiagnosticWarn", "default link DiagnosticSignInfo DiagnosticInfo", "default link DiagnosticSignHint DiagnosticHint", + "default link DiagnosticSignOk DiagnosticOk", + // Text + "default link @text.literal Comment", + "default link @text.reference Identifier", + "default link @text.title Title", + "default link @text.uri Underlined", "default link @text.underline Underlined", - "default link @todo Todo", - "default link @debug Debug", + "default link @text.todo Todo", // Miscs "default link @comment Comment", @@ -202,6 +234,7 @@ static const char *highlight_init_both[] = { "default link @macro Macro", "default link @string String", "default link @string.escape SpecialChar", + "default link @string.special SpecialChar", "default link @character Character", "default link @character.special SpecialChar", "default link @number Number", @@ -226,12 +259,27 @@ static const char *highlight_init_both[] = { "default link @keyword Keyword", "default link @exception Exception", + "default link @variable Identifier", "default link @type Type", "default link @type.definition Typedef", "default link @storageclass StorageClass", - "default link @structure Structure", + "default link @namespace Identifier", "default link @include Include", "default link @preproc PreProc", + "default link @debug Debug", + "default link @tag Tag", + + // LSP semantic tokens + "default link @class Structure", + "default link @struct Structure", + "default link @enum Type", + "default link @enumMember Constant", + "default link @event Identifier", + "default link @interface Identifier", + "default link @modifier Identifier", + "default link @regexp SpecialChar", + "default link @typeParameter Type", + "default link @decorator Identifier", NULL }; @@ -523,7 +571,7 @@ int highlight_num_groups(void) } /// Returns the name of a highlight group. -char_u *highlight_group_name(int id) +char *highlight_group_name(int id) { return hl_table[id].sg_name; } @@ -553,12 +601,12 @@ void init_highlight(bool both, bool reset) // Try finding the color scheme file. Used when a color file was loaded // and 'background' or 't_Co' is changed. - char *p = (char *)get_var_value("g:colors_name"); + char *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 *copy_p = xstrdup(p); - bool okay = load_colors((char_u *)copy_p); + bool okay = load_colors(copy_p); xfree(copy_p); if (okay) { return; @@ -606,11 +654,10 @@ void init_highlight(bool both, bool reset) } /// Load color file "name". -/// Return OK for success, FAIL for failure. -int load_colors(char_u *name) +/// +/// @return OK for success, FAIL for failure. +int load_colors(char *name) { - char_u *buf; - int retval = FAIL; static bool recursive = false; // When being called recursively, this is probably because setting @@ -621,18 +668,18 @@ int load_colors(char_u *name) } recursive = true; - size_t buflen = STRLEN(name) + 12; - buf = xmalloc(buflen); - apply_autocmds(EVENT_COLORSCHEMEPRE, (char *)name, curbuf->b_fname, false, curbuf); - snprintf((char *)buf, buflen, "colors/%s.vim", name); - retval = source_runtime((char *)buf, DIP_START + DIP_OPT); + size_t buflen = strlen(name) + 12; + char *buf = xmalloc(buflen); + apply_autocmds(EVENT_COLORSCHEMEPRE, name, curbuf->b_fname, false, curbuf); + snprintf(buf, buflen, "colors/%s.vim", name); + int retval = source_runtime(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); + snprintf(buf, buflen, "colors/%s.lua", name); + retval = source_runtime(buf, DIP_START + DIP_OPT); } xfree(buf); if (retval == OK) { - apply_autocmds(EVENT_COLORSCHEME, (char *)name, curbuf->b_fname, false, curbuf); + apply_autocmds(EVENT_COLORSCHEME, name, curbuf->b_fname, false, curbuf); } recursive = false; @@ -824,27 +871,8 @@ update: 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))) { + if (!init && ends_excmd((uint8_t)(*line))) { for (int i = 1; i <= highlight_ga.ga_len && !got_int; i++) { // TODO(brammool): only call when the group has attributes set highlight_list_one(i); @@ -852,9 +880,11 @@ void do_highlight(const char *line, const bool forceit, const bool init) return; } + bool dodefault = false; + // Isolate the name. - name_end = (const char *)skiptowhite(line); - linep = (const char *)skipwhite(name_end); + const char *name_end = (const char *)skiptowhite(line); + const char *linep = (const char *)skipwhite(name_end); // Check for "default" argument. if (strncmp(line, "default", (size_t)(name_end - line)) == 0) { @@ -864,6 +894,9 @@ void do_highlight(const char *line, const bool forceit, const bool init) linep = (const char *)skipwhite(name_end); } + bool doclear = false; + bool dolink = false; + // Check for "clear" or "link" argument. if (strncmp(line, "clear", (size_t)(name_end - line)) == 0) { doclear = true; @@ -873,7 +906,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) // ":highlight {group-name}": list highlighting for one group. if (!doclear && !dolink && ends_excmd((uint8_t)(*linep))) { - id = syn_name2id_len(line, (size_t)(name_end - line)); + int id = syn_name2id_len(line, (size_t)(name_end - line)); if (id == 0) { semsg(_("E411: highlight group not found: %s"), line); } else { @@ -975,11 +1008,11 @@ void do_highlight(const char *line, const bool forceit, const bool init) } // 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)); + int 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. + int 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)) { @@ -987,8 +1020,8 @@ void do_highlight(const char *line, const bool forceit, const bool init) } // 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); + HlGroup item_before = hl_table[idx]; + bool 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)) { @@ -998,11 +1031,16 @@ void do_highlight(const char *line, const bool forceit, const bool init) } } + bool did_change = false; + bool error = false; + char key[64]; char arg[512]; if (!doclear) { + const char *arg_start; + while (!ends_excmd((uint8_t)(*linep))) { - key_start = linep; + const char *key_start = linep; if (*linep == '=') { semsg(_("E415: unexpected equal sign: %s"), key_start); error = true; @@ -1022,7 +1060,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) } memcpy(key, key_start, key_len); key[key_len] = NUL; - vim_strup((char_u *)key); + vim_strup(key); linep = (const char *)skipwhite(linep); if (strcmp(key, "NONE") == 0) { @@ -1079,12 +1117,12 @@ void do_highlight(const char *line, const bool forceit, const bool init) if (strcmp(key, "TERM") == 0 || strcmp(key, "CTERM") == 0 || strcmp(key, "GUI") == 0) { - attr = 0; - off = 0; + int attr = 0; + int off = 0; int i; while (arg[off] != NUL) { for (i = ARRAY_SIZE(hl_attr_table); --i >= 0;) { - len = (int)strlen(hl_name_table[i]); + int len = (int)strlen(hl_name_table[i]); if (STRNICMP(arg + off, hl_name_table[i], len) == 0) { attr |= hl_attr_table[i]; off += len; @@ -1134,6 +1172,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) hl_table[idx].sg_cterm_bold = false; } + int color; if (ascii_isdigit(*arg)) { color = atoi(arg); } else if (STRICMP(arg, "fg") == 0) { @@ -1154,7 +1193,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) } } else { // Reduce calls to STRICMP a bit, it can be slow. - off = TOUPPER_ASC(*arg); + int off = TOUPPER_ASC(*arg); int i; for (i = ARRAY_SIZE(color_names); --i >= 0;) { if (off == color_names[i][0] @@ -1311,6 +1350,8 @@ void do_highlight(const char *line, const bool forceit, const bool init) } } + bool did_highlight_changed = false; + if (!error && is_normal_group) { // Need to update all groups, because they might be using "bg" and/or // "fg", which have been changed now. @@ -1422,7 +1463,7 @@ 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((char *)sgp->sg_name)) { + if (message_filtered(sgp->sg_name)) { return; } @@ -1456,7 +1497,7 @@ static void highlight_list_one(const int id) didh = true; msg_puts_attr("links to", HL_ATTR(HLF_D)); msg_putchar(' '); - msg_outtrans((char *)hl_table[hl_table[id - 1].sg_link - 1].sg_name); + msg_outtrans(hl_table[hl_table[id - 1].sg_link - 1].sg_name); } if (!didh) { @@ -1478,7 +1519,7 @@ Dictionary get_global_hl_defs(Arena *arena) hlattrs2dict(&attrs, syn_attr2entry(h->sg_attr), true); } else if (h->sg_link > 0) { attrs = arena_dict(arena, 1); - char *link = (char *)hl_table[h->sg_link - 1].sg_name; + char *link = hl_table[h->sg_link - 1].sg_name; PUT_C(attrs, "link", STRING_OBJ(cstr_as_string(link))); } PUT_C(rv, (char *)h->sg_name, DICTIONARY_OBJ(attrs)); @@ -1495,39 +1536,41 @@ Dictionary get_global_hl_defs(Arena *arena) static bool highlight_list_arg(const int id, bool didh, const int type, int iarg, const char *sarg, const char *const name) { - char buf[100]; - if (got_int) { return false; } - if (type == LIST_STRING ? (sarg != NULL) : (iarg != 0)) { - const 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" + + if (type == LIST_STRING ? (sarg == NULL) : (iarg == 0)) { + return didh; + } + + char buf[100]; + const 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, vim_strsize((char *)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 *)ts); + (void)syn_list_header(didh, vim_strsize((char *)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 *)ts); } return didh; } @@ -1542,19 +1585,24 @@ static bool highlight_list_arg(const int id, bool didh, const int type, int iarg 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; } + int attr; + if (modec == 'g') { attr = hl_table[id - 1].sg_gui; } else { attr = hl_table[id - 1].sg_cterm; } - return (attr & flag) ? "1" : NULL; + if (flag & HL_UNDERLINE_MASK) { + int ul = attr & HL_UNDERLINE_MASK; + return ul == flag ? "1" : NULL; + } else { + return (attr & flag) ? "1" : NULL; + } } /// Return color name of the given highlight group @@ -1570,7 +1618,6 @@ const char *highlight_color(const int id, const char *const what, const int mode FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { static char name[20]; - int n; bool fg = false; bool sp = false; bool font = false; @@ -1589,6 +1636,9 @@ const char *highlight_color(const int id, const char *const what, const int mode } else if (!(TOLOWER_ASC(what[0]) == 'b' && TOLOWER_ASC(what[1]) == 'g')) { return NULL; } + + int n; + if (modec == 'g') { if (what[2] == '#' && ui_rgb_attached()) { if (fg) { @@ -1650,7 +1700,7 @@ bool syn_list_header(const bool did_header, const int outlen, const int id, bool if (got_int) { return true; } - msg_outtrans((char *)hl_table[id - 1].sg_name); + msg_outtrans(hl_table[id - 1].sg_name); name_col = msg_col; endcol = 15; } else if ((ui_has(kUIMessages) || msg_silent) && !force_newline) { @@ -1722,9 +1772,8 @@ int syn_name2id(const char *name) if (name[0] == '@') { // if we look up @aaa.bbb, we have to consider @aaa as well return syn_check_group(name, strlen(name)); - } else { - return syn_name2id_len(name, strlen(name)); } + return syn_name2id_len(name, strlen(name)); } /// Lookup a highlight group name and return its ID. @@ -1744,7 +1793,7 @@ int syn_name2id_len(const char *name, size_t len) // Avoid alloc()/free(), these are slow too. memcpy(name_u, name, len); name_u[len] = '\0'; - vim_strup((char_u *)name_u); + vim_strup(name_u); // map_get(..., int) returns 0 when no key is present, which is // the expected value for missing highlight group. @@ -1753,10 +1802,10 @@ int syn_name2id_len(const char *name, size_t len) /// Lookup a highlight group name and return its attributes. /// Return zero if not found. -int syn_name2attr(const char_u *name) +int syn_name2attr(const char *name) FUNC_ATTR_NONNULL_ALL { - int id = syn_name2id((char *)name); + int id = syn_name2id(name); if (id != 0) { return syn_id2attr(id); @@ -1772,10 +1821,10 @@ int highlight_exists(const char *name) /// Return the name of highlight group "id". /// When not a valid ID return an empty string. -char_u *syn_id2name(int id) +char *syn_id2name(int id) { if (id <= 0 || id > highlight_ga.ga_len) { - return (char_u *)""; + return ""; } return hl_table[id - 1].sg_name; } @@ -1845,7 +1894,7 @@ static int syn_add_group(const char *name, size_t len) // Append another syntax_highlight entry. HlGroup *hlgp = GA_APPEND_VIA_PTR(HlGroup, &highlight_ga); CLEAR_POINTER(hlgp); - hlgp->sg_name = (char_u *)arena_memdupz(&highlight_arena, name, len); + hlgp->sg_name = arena_memdupz(&highlight_arena, name, len); hlgp->sg_rgb_bg = -1; hlgp->sg_rgb_fg = -1; hlgp->sg_rgb_sp = -1; @@ -1857,7 +1906,7 @@ static int syn_add_group(const char *name, size_t len) hlgp->sg_parent = scoped_parent; // will get set to false by caller if settings are added hlgp->sg_cleared = true; - vim_strup((char_u *)hlgp->sg_name_u); + vim_strup(hlgp->sg_name_u); int id = highlight_ga.ga_len; // ID is index plus one @@ -1997,17 +2046,15 @@ static void combine_stl_hlt(int id, int id_S, int id_alt, int hlcnt, int i, int /// 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])); + int id = syn_check_group(hlf_names[hlf], strlen(hlf_names[hlf])); if (id == 0) { abort(); } @@ -2046,7 +2093,7 @@ void highlight_changed(void) // 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; + int hlcnt = highlight_ga.ga_len; if (id_S == -1) { // Make sure id_S is always valid to simplify code below. Use the last entry CLEAR_POINTER(&hl_table[hlcnt + 9]); @@ -2054,7 +2101,7 @@ void highlight_changed(void) } for (int i = 0; i < 9; i++) { snprintf(userhl, sizeof(userhl), "User%d", i + 1); - id = syn_name2id(userhl); + int id = syn_name2id(userhl); if (id == 0) { highlight_user[i] = 0; highlight_stlnc[i] = 0; @@ -2064,6 +2111,8 @@ void highlight_changed(void) } } highlight_ga.ga_len = hlcnt; + + decor_provider_invalidate_hl(); } /// Handle command line completion for :highlight command. @@ -2075,47 +2124,53 @@ void set_context_in_highlight_cmd(expand_T *xp, const char *arg) include_link = 2; include_default = 1; + if (*arg == NUL) { + return; + } + // (part of) subcommand already typed - if (*arg != NUL) { - const char *p = (const char *)skiptowhite(arg); - if (*p != NUL) { // Past "default" or group name. - include_default = 0; - if (strncmp("default", arg, (unsigned)(p - arg)) == 0) { - arg = (const char *)skipwhite(p); - xp->xp_pattern = (char *)arg; - p = (const char *)skiptowhite(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(p); - p = (const char *)skiptowhite(xp->xp_pattern); - if (*p != NUL) { // Past first group name. - xp->xp_pattern = skipwhite(p); - p = (const char *)skiptowhite(xp->xp_pattern); - } - } - if (*p != NUL) { // Past group name(s). - xp->xp_context = EXPAND_NOTHING; - } - } + const char *p = (const char *)skiptowhite(arg); + if (*p == NUL) { + return; + } + + // past "default" or group name + include_default = 0; + if (strncmp("default", arg, (unsigned)(p - arg)) == 0) { + arg = (const char *)skipwhite(p); + xp->xp_pattern = (char *)arg; + p = (const char *)skiptowhite(arg); + } + if (*p == NUL) { + return; + } + + // 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(p); + p = (const char *)skiptowhite(xp->xp_pattern); + if (*p != NUL) { // past first group name + xp->xp_pattern = skipwhite(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;) { + for (int i = 10; --i >= 0;) { highlight_list_two(i, HL_ATTR(HLF_D)); } - for (i = 40; --i >= 0;) { + for (int i = 40; --i >= 0;) { highlight_list_two(99, 0); } } @@ -2860,9 +2915,9 @@ color_name_table_T color_name_table[] = { /// return the hex value or -1 if could not find a correct value RgbValue name_to_color(const char *name, int *idx) { - if (name[0] == '#' && isxdigit(name[1]) && isxdigit(name[2]) - && isxdigit(name[3]) && isxdigit(name[4]) && isxdigit(name[5]) - && isxdigit(name[6]) && name[7] == NUL) { + if (name[0] == '#' && isxdigit((uint8_t)name[1]) && isxdigit((uint8_t)name[2]) + && isxdigit((uint8_t)name[3]) && isxdigit((uint8_t)name[4]) && isxdigit((uint8_t)name[5]) + && isxdigit((uint8_t)name[6]) && name[7] == NUL) { // rgb hex string *idx = kColorIdxHex; return (RgbValue)strtol((char *)(name + 1), NULL, 16); @@ -2886,7 +2941,6 @@ RgbValue name_to_color(const char *name, int *idx) } else { // found match *idx = m; return color_name_table[m].color; - break; } } diff --git a/src/nvim/iconv.h b/src/nvim/iconv.h index 509f83c415..f5f3f25786 100644 --- a/src/nvim/iconv.h +++ b/src/nvim/iconv.h @@ -1,20 +1,18 @@ #ifndef NVIM_ICONV_H #define NVIM_ICONV_H -#include "auto/config.h" +#include <errno.h> +#include <iconv.h> -#ifdef HAVE_ICONV -# include <errno.h> -# include <iconv.h> +#include "auto/config.h" // define some missing constants if necessary -# ifndef EILSEQ -# define EILSEQ 123 -# endif -# define ICONV_ERRNO errno -# define ICONV_E2BIG E2BIG -# define ICONV_EINVAL EINVAL -# define ICONV_EILSEQ EILSEQ +#ifndef EILSEQ +# define EILSEQ 123 #endif +#define ICONV_ERRNO errno +#define ICONV_E2BIG E2BIG +#define ICONV_EINVAL EINVAL +#define ICONV_EILSEQ EILSEQ #endif // NVIM_ICONV_H diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c deleted file mode 100644 index bc31d702f4..0000000000 --- a/src/nvim/if_cscope.c +++ /dev/null @@ -1,2027 +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 - -// CSCOPE support for Vim added by Andy Kahn <kahn@zk3.dec.com> -// Ported to Win32 by Sergey Khorev <sergey.khorev@gmail.com> -// -// The basic idea/structure of cscope for Vim was borrowed from Nvi. There -// might be a few lines of code that look similar to what Nvi has. - -#include <assert.h> -#include <errno.h> -#include <fcntl.h> -#include <inttypes.h> -#include <stdbool.h> -#include <sys/stat.h> -#include <sys/types.h> - -#include "nvim/ascii.h" -#include "nvim/autocmd.h" -#include "nvim/buffer.h" -#include "nvim/charset.h" -#include "nvim/eval.h" -#include "nvim/event/stream.h" -#include "nvim/ex_eval.h" -#include "nvim/fileio.h" -#include "nvim/if_cscope.h" -#include "nvim/memory.h" -#include "nvim/message.h" -#include "nvim/os/input.h" -#include "nvim/os/os.h" -#include "nvim/os/time.h" -#include "nvim/path.h" -#include "nvim/quickfix.h" -#include "nvim/strings.h" -#include "nvim/tag.h" -#include "nvim/window.h" -#if defined(UNIX) -# include <sys/wait.h> -#endif -#include "nvim/if_cscope_defs.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "if_cscope.c.generated.h" -#endif - -static csinfo_T *csinfo = NULL; -static size_t csinfo_size = 0; // number of items allocated in csinfo[] - -static int eap_arg_len; // length of eap->arg, set in cs_lookup_cmd() -static cscmd_T cs_cmds[] = -{ - { "add", cs_add, - N_("Add a new database"), "add file|dir [pre-path] [flags]", 0 }, - { "find", cs_find, - N_("Query for a pattern"), "find a|c|d|e|f|g|i|s|t name", 1 }, - { "help", cs_help, - N_("Show this message"), "help", 0 }, - { "kill", cs_kill, - N_("Kill a connection"), "kill #", 0 }, - { "reset", cs_reset, - N_("Reinit all connections"), "reset", 0 }, - { "show", cs_show, - N_("Show connections"), "show", 0 }, - { NULL, NULL, NULL, NULL, 0 } -}; - -static void cs_usage_msg(csid_e x) -{ - (void)semsg(_("E560: Usage: cs[cope] %s"), cs_cmds[(int)x].usage); -} - -static enum { - EXP_CSCOPE_SUBCMD, // expand ":cscope" sub-commands - EXP_SCSCOPE_SUBCMD, // expand ":scscope" sub-commands - EXP_CSCOPE_FIND, // expand ":cscope find" arguments - EXP_CSCOPE_KILL, // expand ":cscope kill" arguments -} expand_what; - -// Function given to ExpandGeneric() to obtain the cscope command -// expansion. -char *get_cscope_name(expand_T *xp, int idx) -{ - int current_idx; - - switch (expand_what) { - case EXP_CSCOPE_SUBCMD: - // Complete with sub-commands of ":cscope": - // add, find, help, kill, reset, show - return cs_cmds[idx].name; - case EXP_SCSCOPE_SUBCMD: { - // Complete with sub-commands of ":scscope": same sub-commands as - // ":cscope" but skip commands which don't support split windows - int i; - for (i = 0, current_idx = 0; cs_cmds[i].name != NULL; i++) { - if (cs_cmds[i].cansplit) { - if (current_idx++ == idx) { - break; - } - } - } - return cs_cmds[i].name; - } - case EXP_CSCOPE_FIND: { - const char *query_type[] = - { - "a", "c", "d", "e", "f", "g", "i", "s", "t", NULL - }; - - // Complete with query type of ":cscope find {query_type}". - // {query_type} can be letters (c, d, ... a) or numbers (0, 1, - // ..., 9) but only complete with letters, since numbers are - // redundant. - return (char *)query_type[idx]; - } - case EXP_CSCOPE_KILL: { - static char connection[5]; - - // ":cscope kill" accepts connection numbers or partial names of - // the pathname of the cscope database as argument. Only complete - // with connection numbers. -1 can also be used to kill all - // connections. - size_t i; - for (i = 0, current_idx = 0; i < csinfo_size; i++) { - if (csinfo[i].fname == NULL) { - continue; - } - if (current_idx++ == idx) { - vim_snprintf(connection, sizeof(connection), "%zu", i); - return connection; - } - } - return (current_idx == idx && idx > 0) ? "-1" : NULL; - } - default: - return NULL; - } -} - -// Handle command line completion for :cscope command. -void set_context_in_cscope_cmd(expand_T *xp, const char *arg, cmdidx_T cmdidx) -{ - // Default: expand subcommands. - xp->xp_context = EXPAND_CSCOPE; - xp->xp_pattern = (char *)arg; - expand_what = ((cmdidx == CMD_scscope) - ? EXP_SCSCOPE_SUBCMD : EXP_CSCOPE_SUBCMD); - - // (part of) subcommand already typed - if (*arg != NUL) { - const char *p = (const char *)skiptowhite(arg); - if (*p != NUL) { // Past first word. - xp->xp_pattern = skipwhite(p); - if (*skiptowhite(xp->xp_pattern) != NUL) { - xp->xp_context = EXPAND_NOTHING; - } else if (STRNICMP(arg, "add", p - arg) == 0) { - xp->xp_context = EXPAND_FILES; - } else if (STRNICMP(arg, "kill", p - arg) == 0) { - expand_what = EXP_CSCOPE_KILL; - } else if (STRNICMP(arg, "find", p - arg) == 0) { - expand_what = EXP_CSCOPE_FIND; - } else { - xp->xp_context = EXPAND_NOTHING; - } - } - } -} - -/// Find the command, print help if invalid, and then call the corresponding -/// command function. -/// -/// @param make_split whether to split window -static void do_cscope_general(exarg_T *eap, int make_split) -{ - cscmd_T *cmdp; - - if ((cmdp = cs_lookup_cmd(eap)) == NULL) { - cs_help(eap); - return; - } - - if (make_split) { - if (!cmdp->cansplit) { - (void)msg_puts(_("This cscope command does not support splitting the window.\n")); - return; - } - postponed_split = -1; - postponed_split_flags = cmdmod.cmod_split; - postponed_split_tab = cmdmod.cmod_tab; - } - - cmdp->func(eap); - - postponed_split_flags = 0; - postponed_split_tab = 0; -} - -/// Implementation of ":cscope" and ":lcscope" -void ex_cscope(exarg_T *eap) -{ - do_cscope_general(eap, false); -} - -/// Implementation of ":scscope". Same as ex_cscope(), but splits window, too. -void ex_scscope(exarg_T *eap) -{ - do_cscope_general(eap, true); -} - -/// Implementation of ":cstag" -void ex_cstag(exarg_T *eap) -{ - int ret = false; - - if (*eap->arg == NUL) { - (void)emsg(_("E562: Usage: cstag <ident>")); - return; - } - - switch (p_csto) { - case 0: - if (cs_check_for_connections()) { - ret = cs_find_common("g", eap->arg, eap->forceit, false, - false, (char_u *)(*eap->cmdlinep)); - if (ret == false) { - cs_free_tags(); - if (msg_col) { - msg_putchar('\n'); - } - - if (cs_check_for_tags()) { - ret = do_tag(eap->arg, DT_JUMP, 0, eap->forceit, false); - } - } - } else if (cs_check_for_tags()) { - ret = do_tag(eap->arg, DT_JUMP, 0, eap->forceit, false); - } - break; - case 1: - if (cs_check_for_tags()) { - ret = do_tag(eap->arg, DT_JUMP, 0, eap->forceit, false); - if (ret == false) { - if (msg_col) { - msg_putchar('\n'); - } - - if (cs_check_for_connections()) { - ret = cs_find_common("g", eap->arg, eap->forceit, - false, false, (char_u *)(*eap->cmdlinep)); - if (ret == false) { - cs_free_tags(); - } - } - } - } else if (cs_check_for_connections()) { - ret = cs_find_common("g", eap->arg, eap->forceit, false, - false, (char_u *)(*eap->cmdlinep)); - if (ret == false) { - cs_free_tags(); - } - } - break; - default: - break; - } - - if (!ret) { - (void)emsg(_("E257: cstag: tag not found")); - g_do_tagpreview = 0; - } -} - -/// This simulates a vim_fgets(), but for cscope, returns the next line -/// from the cscope output. should only be called from find_tags() -/// -/// @return true if eof, false otherwise -bool cs_fgets(char_u *buf, int size) - FUNC_ATTR_NONNULL_ALL -{ - char *p; - - if ((p = cs_manage_matches(NULL, NULL, 0, Get)) == NULL) { - return true; - } - STRLCPY(buf, p, size); - - return false; -} - -/// Called only from do_tag(), when popping the tag stack. -void cs_free_tags(void) -{ - cs_manage_matches(NULL, NULL, 0, Free); -} - -/// Called from do_tag(). -void cs_print_tags(void) -{ - cs_manage_matches(NULL, NULL, 0, Print); -} - -// "cscope_connection([{num} , {dbpath} [, {prepend}]])" function -// -// Checks for the existence of a |cscope| connection. If no -// parameters are specified, then the function returns: -// -// 0, if cscope was not available (not compiled in), or if there -// are no cscope connections; or -// 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! -bool cs_connection(int num, char_u *dbpath, char_u *ppath) -{ - if (num < 0 || num > 4 || (num > 0 && !dbpath)) { - return false; - } - - for (size_t i = 0; i < csinfo_size; i++) { - if (!csinfo[i].fname) { - continue; - } - if (num == 0) { - return true; - } - switch (num) { - case 1: - if (strstr(csinfo[i].fname, (char *)dbpath)) { - return true; - } - break; - case 2: - if (strcmp(csinfo[i].fname, (char *)dbpath) == 0) { - return true; - } - break; - case 3: - if (strstr(csinfo[i].fname, (char *)dbpath) - && ((!ppath && !csinfo[i].ppath) - || (ppath - && csinfo[i].ppath - && strstr(csinfo[i].ppath, (char *)ppath)))) { - return true; - } - break; - case 4: - if ((strcmp(csinfo[i].fname, (char *)dbpath) == 0) - && ((!ppath && !csinfo[i].ppath) - || (ppath - && csinfo[i].ppath - && (strcmp(csinfo[i].ppath, (char *)ppath) == 0)))) { - return true; - } - break; - } - } - - return false; -} // cs_connection - -// PRIVATE functions -// ************************************************************************** - -/// Add cscope database or a directory name (to look for cscope.out) -/// to the cscope connection list. -static int cs_add(exarg_T *eap) -{ - char *fname, *ppath, *flags = NULL; - - if ((fname = strtok((char *)NULL, (const char *)" ")) == NULL) { - cs_usage_msg(Add); - return CSCOPE_FAILURE; - } - if ((ppath = strtok((char *)NULL, (const char *)" ")) != NULL) { - flags = strtok((char *)NULL, (const char *)" "); - } - - return cs_add_common(fname, ppath, flags); -} - -static void cs_stat_emsg(char *fname) -{ - int err = errno; - (void)semsg(_("E563: stat(%s) error: %d"), fname, err); -} - -/// The common routine to add a new cscope connection. Called by -/// cs_add() and cs_reset(). I really don't like to do this, but this -/// routine uses a number of goto statements. -/// -/// @param arg1 filename - may contain environment variables -/// @param arg2 prepend path - may contain environment variables -static int cs_add_common(char *arg1, char *arg2, char *flags) -{ - char *fname = NULL; - char *fname2 = NULL; - char *ppath = NULL; - size_t usedlen = 0; - char *fbuf = NULL; - - // get the filename (arg1), expand it, and try to stat it - fname = xmalloc(MAXPATHL + 1); - - expand_env(arg1, fname, MAXPATHL); - size_t len = strlen(fname); - fbuf = fname; - (void)modify_fname(":p", false, &usedlen, &fname, &fbuf, &len); - if (fname == NULL) { - goto add_err; - } - fname = xstrnsave(fname, len); - xfree(fbuf); - FileInfo file_info; - bool file_info_ok = os_fileinfo(fname, &file_info); - if (!file_info_ok) { -staterr: - if (p_csverbose) { - cs_stat_emsg(fname); - } - goto add_err; - } - - // get the prepend path (arg2), expand it, and see if it exists - if (arg2 != NULL) { - ppath = xmalloc(MAXPATHL + 1); - expand_env(arg2, ppath, MAXPATHL); - if (!os_path_exists(ppath)) { - goto staterr; - } - } - - int i; - // if filename is a directory, append the cscope database name to it - if (S_ISDIR(file_info.stat.st_mode)) { - fname2 = (char *)xmalloc(strlen(CSCOPE_DBFILE) + strlen(fname) + 2); - - while (fname[strlen(fname) - 1] == '/' - ) { - fname[strlen(fname) - 1] = '\0'; - if (fname[0] == '\0') { - break; - } - } - if (fname[0] == '\0') { - (void)sprintf(fname2, "/%s", CSCOPE_DBFILE); - } else { - (void)sprintf(fname2, "%s/%s", fname, CSCOPE_DBFILE); - } - - file_info_ok = os_fileinfo(fname2, &file_info); - if (!file_info_ok) { - if (p_csverbose) { - cs_stat_emsg(fname2); - } - goto add_err; - } - - i = cs_insert_filelist(fname2, ppath, flags, &file_info); - } else if (S_ISREG(file_info.stat.st_mode) || S_ISLNK(file_info.stat.st_mode)) { - i = cs_insert_filelist(fname, ppath, flags, &file_info); - } else { - if (p_csverbose) { - (void)semsg(_("E564: %s is not a directory or a valid cscope database"), - fname); - } - goto add_err; - } - - if (i != -1) { - assert(i >= 0); - if (cs_create_connection((size_t)i) == CSCOPE_FAILURE - || cs_read_prompt((size_t)i) == CSCOPE_FAILURE) { - cs_release_csp((size_t)i, true); - goto add_err; - } - - if (p_csverbose) { - msg_clr_eos(); - (void)smsg_attr(HL_ATTR(HLF_R), - _("Added cscope database %s"), - csinfo[i].fname); - } - } - - xfree(fname); - xfree(fname2); - xfree(ppath); - return CSCOPE_SUCCESS; - -add_err: - xfree(fname2); - xfree(fname); - xfree(ppath); - return CSCOPE_FAILURE; -} - -static bool cs_check_for_connections(void) -{ - return cs_cnt_connections() > 0; -} - -static int cs_check_for_tags(void) -{ - return p_tags[0] != NUL && curbuf->b_p_tags != NULL; -} - -/// Count the number of cscope connections. -static size_t cs_cnt_connections(void) -{ - size_t cnt = 0; - - for (size_t i = 0; i < csinfo_size; i++) { - if (csinfo[i].fname != NULL) { - cnt++; - } - } - return cnt; -} - -/// @param idx connection index -static void cs_reading_emsg(size_t idx) -{ - semsg(_("E262: error reading cscope connection %" PRIu64), (uint64_t)idx); -} - -#define CSREAD_BUFSIZE 2048 -/// Count the number of matches for a given cscope connection. -static int cs_cnt_matches(size_t idx) -{ - char *stok; - int nlines = 0; - - char *buf = xmalloc(CSREAD_BUFSIZE); - for (;;) { - errno = 0; - if (!fgets(buf, CSREAD_BUFSIZE, csinfo[idx].fr_fp)) { - if (errno == EINTR) { - continue; - } - - if (feof(csinfo[idx].fr_fp)) { - errno = EIO; - } - - cs_reading_emsg(idx); - - xfree(buf); - return CSCOPE_FAILURE; - } - - // If the database is out of date, or there's some other problem, - // cscope will output error messages before the number-of-lines output. - // Display/discard any output that doesn't match what we want. - // Accept "\S*cscope: X lines", also matches "mlcscope". - // Bail out for the "Unable to search" error. - if (strstr((const char *)buf, "Unable to search database") != NULL) { - break; - } - if ((stok = strtok(buf, (const char *)" ")) == NULL) { - continue; - } - if (strstr((const char *)stok, "cscope:") == NULL) { - continue; - } - - if ((stok = strtok(NULL, (const char *)" ")) == NULL) { - continue; - } - nlines = atoi(stok); - if (nlines < 0) { - nlines = 0; - break; - } - - if ((stok = strtok(NULL, (const char *)" ")) == NULL) { - continue; - } - if (strncmp(stok, "lines", 5)) { - continue; - } - - break; - } - - xfree(buf); - return nlines; -} - -/// Creates the actual cscope command query from what the user entered. -static char *cs_create_cmd(char *csoption, char *pattern) -{ - char *cmd; - short search; - char *pat; - - switch (csoption[0]) { - case '0': - case 's': - search = 0; - break; - case '1': - case 'g': - search = 1; - break; - case '2': - case 'd': - search = 2; - break; - case '3': - case 'c': - search = 3; - break; - case '4': - case 't': - search = 4; - break; - case '6': - case 'e': - search = 6; - break; - case '7': - case 'f': - search = 7; - break; - case '8': - case 'i': - search = 8; - break; - case '9': - case 'a': - search = 9; - break; - default: - (void)emsg(_("E561: unknown cscope search type")); - cs_usage_msg(Find); - return NULL; - } - - // Skip white space before the pattern, except for text and pattern search, - // they may want to use the leading white space. - pat = pattern; - if (search != 4 && search != 6) { - while (ascii_iswhite(*pat)) { - pat++; - } - } - - cmd = xmalloc(strlen(pat) + 2); - - (void)sprintf(cmd, "%d%s", search, pat); - - return cmd; -} - -/// This piece of code was taken/adapted from nvi. do we need to add -/// the BSD license notice? -static int cs_create_connection(size_t i) -{ -#ifdef UNIX - int to_cs[2], from_cs[2]; -#endif - char *prog, *cmd, *ppath = NULL; - -#if defined(UNIX) - // Cscope reads from to_cs[0] and writes to from_cs[1]; vi reads from - // from_cs[0] and writes to to_cs[1]. - to_cs[0] = to_cs[1] = from_cs[0] = from_cs[1] = -1; - if (pipe(to_cs) < 0 || pipe(from_cs) < 0) { - (void)emsg(_("E566: Could not create cscope pipes")); -err_closing: - if (to_cs[0] != -1) { - (void)close(to_cs[0]); - } - if (to_cs[1] != -1) { - (void)close(to_cs[1]); - } - if (from_cs[0] != -1) { - (void)close(from_cs[0]); - } - if (from_cs[1] != -1) { - (void)close(from_cs[1]); - } - return CSCOPE_FAILURE; - } - - switch (csinfo[i].pid = fork()) { - case -1: - (void)emsg(_("E622: Could not fork for cscope")); - goto err_closing; - case 0: // child: run cscope. - if (dup2(to_cs[0], STDIN_FILENO) == -1) { - PERROR("cs_create_connection 1"); - } - if (dup2(from_cs[1], STDOUT_FILENO) == -1) { - PERROR("cs_create_connection 2"); - } - if (dup2(from_cs[1], STDERR_FILENO) == -1) { - PERROR("cs_create_connection 3"); - } - - // close unused - (void)close(to_cs[1]); - (void)close(from_cs[0]); -#else - // Create pipes to communicate with cscope - int fd; - SECURITY_ATTRIBUTES sa; - PROCESS_INFORMATION pi; - BOOL pipe_stdin = FALSE, pipe_stdout = FALSE; // NOLINT(readability/bool) - STARTUPINFO si; - HANDLE stdin_rd, stdout_rd; - HANDLE stdout_wr, stdin_wr; - BOOL created; - - sa.nLength = sizeof(SECURITY_ATTRIBUTES); - sa.bInheritHandle = TRUE; - sa.lpSecurityDescriptor = NULL; - - if (!(pipe_stdin = CreatePipe(&stdin_rd, &stdin_wr, &sa, 0)) - || !(pipe_stdout = CreatePipe(&stdout_rd, &stdout_wr, &sa, 0))) { - (void)emsg(_("E566: Could not create cscope pipes")); -err_closing: - if (pipe_stdin) { - CloseHandle(stdin_rd); - CloseHandle(stdin_wr); - } - if (pipe_stdout) { - CloseHandle(stdout_rd); - CloseHandle(stdout_wr); - } - return CSCOPE_FAILURE; - } -#endif - // expand the cscope exec for env var's - prog = xmalloc(MAXPATHL + 1); - expand_env(p_csprg, prog, MAXPATHL); - - // alloc space to hold the cscope command - size_t len = strlen(prog) + strlen(csinfo[i].fname) + 32; - if (csinfo[i].ppath) { - // expand the prepend path for env var's - ppath = xmalloc(MAXPATHL + 1); - expand_env(csinfo[i].ppath, ppath, MAXPATHL); - - len += strlen(ppath); - } - - if (csinfo[i].flags) { - len += strlen(csinfo[i].flags); - } - - cmd = xmalloc(len); - - // run the cscope command; is there execl for non-unix systems? -#if defined(UNIX) - (void)snprintf(cmd, len, "exec %s -dl -f %s", prog, csinfo[i].fname); -#else - // MS-Windows - (void)snprintf(cmd, len, "%s -dl -f %s", prog, csinfo[i].fname); -#endif - if (csinfo[i].ppath != NULL) { - (void)strcat(cmd, " -P"); - (void)strcat(cmd, csinfo[i].ppath); - } - if (csinfo[i].flags != NULL) { - (void)strcat(cmd, " "); - (void)strcat(cmd, csinfo[i].flags); - } -#ifdef UNIX - // on Win32 we still need prog - xfree(prog); -#endif - xfree(ppath); - -#if defined(UNIX) -# if defined(HAVE_SETSID) || defined(HAVE_SETPGID) - // Change our process group to avoid cscope receiving SIGWINCH. -# if defined(HAVE_SETSID) - (void)setsid(); -# else - if (setpgid(0, 0) == -1) { - PERROR(_("cs_create_connection setpgid failed")); - } -# endif -# endif - if (execl("/bin/sh", "sh", "-c", cmd, (char *)NULL) == -1) { - PERROR(_("cs_create_connection exec failed")); - } - - exit(127); - // NOTREACHED - default: // parent. - // Save the file descriptors for later duplication, and - // reopen as streams. - if ((csinfo[i].to_fp = fdopen(to_cs[1], "w")) == NULL) { - PERROR(_("cs_create_connection: fdopen for to_fp failed")); - } - if ((csinfo[i].fr_fp = fdopen(from_cs[0], "r")) == NULL) { - PERROR(_("cs_create_connection: fdopen for fr_fp failed")); - } - - // close unused - (void)close(to_cs[0]); - (void)close(from_cs[1]); - - break; - } - -#else - // MS-Windows - // Create a new process to run cscope and use pipes to talk with it - GetStartupInfo(&si); - si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; - si.wShowWindow = SW_HIDE; // Hide child application window - si.hStdOutput = stdout_wr; - si.hStdError = stdout_wr; - si.hStdInput = stdin_rd; - created = CreateProcess(NULL, cmd, NULL, NULL, true, CREATE_NEW_CONSOLE, - NULL, NULL, &si, &pi); - xfree(prog); - xfree(cmd); - - if (!created) { - PERROR(_("cs_create_connection exec failed")); - (void)emsg(_("E623: Could not spawn cscope process")); - goto err_closing; - } - // else - csinfo[i].pid = pi.dwProcessId; - csinfo[i].hProc = pi.hProcess; - CloseHandle(pi.hThread); - - // TODO(neovim): tidy up after failure to create files on pipe handles. - if (((fd = _open_osfhandle((intptr_t)stdin_wr, _O_TEXT|_O_APPEND)) < 0) - || ((csinfo[i].to_fp = _fdopen(fd, "w")) == NULL)) { - PERROR(_("cs_create_connection: fdopen for to_fp failed")); - } - if (((fd = _open_osfhandle((intptr_t)stdout_rd, _O_TEXT|_O_RDONLY)) < 0) - || ((csinfo[i].fr_fp = _fdopen(fd, "r")) == NULL)) { - PERROR(_("cs_create_connection: fdopen for fr_fp failed")); - } - // Close handles for file descriptors inherited by the cscope process. - CloseHandle(stdin_rd); - CloseHandle(stdout_wr); - -#endif // !UNIX - - return CSCOPE_SUCCESS; -} - -/// Query cscope using command line interface. Parse the output and use tselect -/// to allow choices. Like Nvi, creates a pipe to send to/from query/cscope. -/// -/// @return true if we jump to a tag or abort, false if not. -static int cs_find(exarg_T *eap) -{ - char *opt, *pat; - - if (cs_check_for_connections() == false) { - (void)emsg(_("E567: no cscope connections")); - return false; - } - - if ((opt = strtok((char *)NULL, (const char *)" ")) == NULL) { - cs_usage_msg(Find); - return false; - } - - pat = opt + strlen(opt) + 1; - if (pat >= eap->arg + eap_arg_len) { - cs_usage_msg(Find); - return false; - } - - // Let's replace the NULs written by strtok() with spaces - we need the - // spaces to correctly display the quickfix/location list window's title. - for (int i = 0; i < eap_arg_len; i++) { - if (NUL == eap->arg[i]) { - eap->arg[i] = ' '; - } - } - - return cs_find_common(opt, pat, eap->forceit, true, - eap->cmdidx == CMD_lcscope, (char_u *)(*eap->cmdlinep)); -} - -/// Common code for cscope find, shared by cs_find() and ex_cstag(). -static bool cs_find_common(char *opt, char *pat, int forceit, int verbose, bool use_ll, - char_u *cmdline) -{ - char *cmd; - int *nummatches; - size_t totmatches; - char cmdletter; - char *qfpos; - - // get cmd letter - switch (opt[0]) { - case '0': - cmdletter = 's'; - break; - case '1': - cmdletter = 'g'; - break; - case '2': - cmdletter = 'd'; - break; - case '3': - cmdletter = 'c'; - break; - case '4': - cmdletter = 't'; - break; - case '6': - cmdletter = 'e'; - break; - case '7': - cmdletter = 'f'; - break; - case '8': - cmdletter = 'i'; - break; - case '9': - cmdletter = 'a'; - break; - default: - cmdletter = opt[0]; - } - - qfpos = vim_strchr(p_csqf, cmdletter); - if (qfpos != NULL) { - qfpos++; - // 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; - } - - if (*qfpos != '0' - && apply_autocmds(EVENT_QUICKFIXCMDPRE, "cscope", curbuf->b_fname, true, curbuf)) { - if (aborting()) { - return false; - } - } - } - - // create the actual command to send to cscope - cmd = cs_create_cmd(opt, pat); - if (cmd == NULL) { - return false; - } - - nummatches = xmalloc(sizeof(int) * csinfo_size); - - // Send query to all open connections, then count the total number - // of matches so we can alloc all in one swell foop. - for (size_t i = 0; i < csinfo_size; i++) { - nummatches[i] = 0; - } - totmatches = 0; - for (size_t i = 0; i < csinfo_size; i++) { - if (csinfo[i].fname == NULL || csinfo[i].to_fp == NULL) { - continue; - } - - // send cmd to cscope - (void)fprintf(csinfo[i].to_fp, "%s\n", cmd); - (void)fflush(csinfo[i].to_fp); - - nummatches[i] = cs_cnt_matches(i); - - if (nummatches[i] > -1) { - totmatches += (size_t)nummatches[i]; - } - - if (nummatches[i] == 0) { - (void)cs_read_prompt(i); - } - } - xfree(cmd); - - if (totmatches == 0) { - if (verbose) { - (void)semsg(_("E259: no matches found for cscope query %s of %s"), opt, pat); - } - xfree(nummatches); - return false; - } - - if (qfpos != NULL && *qfpos != '0') { - // Fill error list. - FILE *f; - char_u *tmp = (char_u *)vim_tempname(); - qf_info_T *qi = NULL; - win_T *wp = NULL; - - f = os_fopen((char *)tmp, "w"); - if (f == NULL) { - semsg(_(e_notopen), tmp); - } else { - cs_file_results(f, nummatches); - fclose(f); - if (use_ll) { // Use location list - wp = curwin; - } - // '-' starts a new error list - if (qf_init(wp, (char *)tmp, "%f%*\\t%l%*\\t%m", - *qfpos == '-', (char *)cmdline, NULL) > 0) { - if (postponed_split != 0) { - (void)win_split(postponed_split > 0 ? postponed_split : 0, - postponed_split_flags); - RESET_BINDING(curwin); - postponed_split = 0; - } - - apply_autocmds(EVENT_QUICKFIXCMDPOST, "cscope", curbuf->b_fname, true, curbuf); - if (use_ll) { - // In the location list window, use the displayed location - // list. Otherwise, use the location list for the window. - qi = (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL) - ? wp->w_llist_ref : wp->w_llist; - } - qf_jump(qi, 0, 0, forceit); - } - } - os_remove((char *)tmp); - xfree(tmp); - xfree(nummatches); - return true; - } else { - char **matches = NULL, **contexts = NULL; - size_t matched = 0; - - // read output - cs_fill_results(pat, totmatches, nummatches, &matches, &contexts, &matched); - xfree(nummatches); - if (matches == NULL) { - return false; - } - - (void)cs_manage_matches(matches, contexts, matched, Store); - - return do_tag(pat, DT_CSCOPE, 0, forceit, verbose); - } -} - -/// Print help. -static int cs_help(exarg_T *eap) -{ - cscmd_T *cmdp = cs_cmds; - - (void)msg_puts(_("cscope commands:\n")); - while (cmdp->name != NULL) { - char *help = _(cmdp->help); - int space_cnt = 30 - vim_strsize(help); - - // Use %*s rather than %30s to ensure proper alignment in utf-8 - if (space_cnt < 0) { - space_cnt = 0; - } - (void)smsg(_("%-5s: %s%*s (Usage: %s)"), - cmdp->name, - help, space_cnt, " ", - cmdp->usage); - if (strcmp(cmdp->name, "find") == 0) { - msg_puts(_("\n" - " a: Find assignments to this symbol\n" - " c: Find functions calling this function\n" - " d: Find functions called by this function\n" - " e: Find this egrep pattern\n" - " f: Find this file\n" - " g: Find this definition\n" - " i: Find files #including this file\n" - " s: Find this C symbol\n" - " t: Find this text string\n")); - } - - cmdp++; - } - - wait_return(true); - return CSCOPE_SUCCESS; -} - -static void clear_csinfo(size_t i) -{ - csinfo[i].fname = NULL; - csinfo[i].ppath = NULL; - csinfo[i].flags = NULL; - csinfo[i].file_id = FILE_ID_EMPTY; - csinfo[i].pid = 0; - csinfo[i].fr_fp = NULL; - csinfo[i].to_fp = NULL; -} - -/// Insert a new cscope database filename into the filelist. -static int cs_insert_filelist(char *fname, char *ppath, char *flags, FileInfo *file_info) -{ - size_t i = 0; - bool empty_found = false; - - for (size_t j = 0; j < csinfo_size; j++) { - if (csinfo[j].fname != NULL - && os_fileid_equal_fileinfo(&(csinfo[j].file_id), file_info)) { - if (p_csverbose) { - (void)emsg(_("E568: duplicate cscope database not added")); - } - return CSCOPE_FAILURE; - } - - if (csinfo[j].fname == NULL && !empty_found) { - i = j; // remember first empty entry - empty_found = true; - } - } - - if (!empty_found) { - i = csinfo_size; - if (csinfo_size == 0) { - // First time allocation: allocate only 1 connection. It should - // be enough for most users. If more is needed, csinfo will be - // reallocated. - csinfo_size = 1; - csinfo = xcalloc(1, sizeof(csinfo_T)); - } else { - // Reallocate space for more connections. - csinfo_size *= 2; - csinfo = xrealloc(csinfo, sizeof(csinfo_T)*csinfo_size); - } - for (size_t j = csinfo_size/2; j < csinfo_size; j++) { - clear_csinfo(j); - } - } - - csinfo[i].fname = xmalloc(strlen(fname) + 1); - - (void)strcpy(csinfo[i].fname, (const char *)fname); - - if (ppath != NULL) { - csinfo[i].ppath = xmalloc(strlen(ppath) + 1); - (void)strcpy(csinfo[i].ppath, (const char *)ppath); - } else { - csinfo[i].ppath = NULL; - } - - if (flags != NULL) { - csinfo[i].flags = xmalloc(strlen(flags) + 1); - (void)strcpy(csinfo[i].flags, (const char *)flags); - } else { - csinfo[i].flags = NULL; - } - - os_fileinfo_id(file_info, &(csinfo[i].file_id)); - assert(i <= INT_MAX); - return (int)i; -} - -/// Find cscope command in command table. -static cscmd_T *cs_lookup_cmd(exarg_T *eap) -{ - cscmd_T *cmdp; - char *stok; - size_t len; - - if (eap->arg == NULL) { - return NULL; - } - - // Store length of eap->arg before it gets modified by strtok(). - eap_arg_len = (int)strlen(eap->arg); - - if ((stok = strtok(eap->arg, (const char *)" ")) == NULL) { // NOLINT(runtime/threadsafe_fn) - return NULL; - } - - len = strlen(stok); - for (cmdp = cs_cmds; cmdp->name != NULL; cmdp++) { - if (strncmp(stok, cmdp->name, len) == 0) { - return cmdp; - } - } - return NULL; -} - -/// Nuke em. -static int cs_kill(exarg_T *eap) -{ - char *stok; - int num; - size_t i = 0; - bool killall = false; - - if ((stok = strtok((char *)NULL, (const char *)" ")) == NULL) { - cs_usage_msg(Kill); - return CSCOPE_FAILURE; - } - - // Check if string is a number, only single digit - // positive and negative integers are allowed - if ((strlen(stok) < 2 && ascii_isdigit((int)(stok[0]))) - || (strlen(stok) < 3 && stok[0] == '-' - && ascii_isdigit((int)(stok[1])))) { - num = atoi(stok); - if (num == -1) { - killall = true; - } else if (num >= 0) { - i = (size_t)num; - } else { // All negative values besides -1 are invalid. - if (p_csverbose) { - (void)semsg(_("E261: cscope connection %s not found"), stok); - } - return CSCOPE_FAILURE; - } - } else { - // Else it must be part of a name. We will try to find a match - // within all the names in the csinfo data structure - for (i = 0; i < csinfo_size; i++) { - if (csinfo[i].fname != NULL && strstr(csinfo[i].fname, stok)) { - break; - } - } - } - - if (!killall && (i >= csinfo_size || csinfo[i].fname == NULL)) { - if (p_csverbose) { - (void)semsg(_("E261: cscope connection %s not found"), stok); - } - return CSCOPE_FAILURE; - } else { - if (killall) { - for (i = 0; i < csinfo_size; i++) { - if (csinfo[i].fname) { - cs_kill_execute(i, csinfo[i].fname); - } - } - } else { - cs_kill_execute(i, stok); - } - } - - return CSCOPE_SUCCESS; -} - -/// Actually kills a specific cscope connection. -/// -/// @param i cscope table index -/// @param cname cscope database name -static void cs_kill_execute(size_t i, char *cname) -{ - if (p_csverbose) { - msg_clr_eos(); - (void)smsg_attr(HL_ATTR(HLF_R) | MSG_HIST, - _("cscope connection %s closed"), cname); - } - cs_release_csp(i, true); -} - -/// Convert the cscope output into a ctags style entry (as might be found -/// in a ctags tags file). there's one catch though: cscope doesn't tell you -/// the type of the tag you are looking for. for example, in Darren Hiebert's -/// ctags (the one that comes with vim), #define's use a line number to find the -/// tag in a file while function definitions use a regexp search pattern. -/// -/// I'm going to always use the line number because cscope does something -/// quirky (and probably other things i don't know about): -/// -/// if you have "# define" in your source file, which is -/// perfectly legal, cscope thinks you have "#define". this -/// will result in a failed regexp search. :( -/// -/// Besides, even if this particular case didn't happen, the search pattern -/// would still have to be modified to escape all the special regular expression -/// characters to comply with ctags formatting. -static char *cs_make_vim_style_matches(char *fname, char *slno, char *search, char *tagstr) -{ - // vim style is ctags: - // - // <tagstr>\t<filename>\t<linenum_or_search>"\t<extra> - // - // but as mentioned above, we'll always use the line number and - // put the search pattern (if one exists) as "extra" - // - // buf is used as part of vim's method of handling tags, and - // (i think) vim frees it when you pop your tags and get replaced - // by new ones on the tag stack. - char *buf; - size_t amt; - - if (search != NULL) { - amt = strlen(fname) + strlen(slno) + strlen(tagstr) + strlen(search) + 6; - buf = xmalloc(amt); - - (void)sprintf(buf, "%s\t%s\t%s;\"\t%s", tagstr, fname, slno, search); - } else { - amt = strlen(fname) + strlen(slno) + strlen(tagstr) + 5; - buf = xmalloc(amt); - - (void)sprintf(buf, "%s\t%s\t%s;\"", tagstr, fname, slno); - } - - return buf; -} - -/// This is kind of hokey, but i don't see an easy way round this. -/// -/// Store: keep a ptr to the (malloc'd) memory of matches originally -/// generated from cs_find(). the matches are originally lines directly -/// from cscope output, but transformed to look like something out of a -/// ctags. see cs_make_vim_style_matches for more details. -/// -/// Get: used only from cs_fgets(), this simulates a vim_fgets() to return -/// the next line from the cscope output. it basically keeps track of which -/// lines have been "used" and returns the next one. -/// -/// Free: frees up everything and resets -/// -/// Print: prints the tags -static char *cs_manage_matches(char **matches, char **contexts, size_t totmatches, mcmd_e cmd) -{ - static char **mp = NULL; - static char **cp = NULL; - static size_t cnt = 0; - static size_t next = 0; - char *p = NULL; - - switch (cmd) { - case Store: - assert(matches != NULL); - assert(totmatches > 0); - if (mp != NULL || cp != NULL) { - (void)cs_manage_matches(NULL, NULL, 0, Free); - } - mp = matches; - cp = contexts; - cnt = totmatches; - next = 0; - break; - case Get: - if (next >= cnt) { - return NULL; - } - - p = mp[next]; - next++; - break; - case Free: - if (mp != NULL) { - while (cnt--) { - xfree(mp[cnt]); - if (cp != NULL) { - xfree(cp[cnt]); - } - } - xfree(mp); - xfree(cp); - } - mp = NULL; - cp = NULL; - cnt = 0; - next = 0; - break; - case Print: - assert(mp != NULL); - assert(cp != NULL); - cs_print_tags_priv(mp, cp, cnt); - break; - default: // should not reach here - iemsg(_("E570: fatal error in cs_manage_matches")); - return NULL; - } - - return p; -} - -/// Parse cscope output. -static char *cs_parse_results(size_t cnumber, char *buf, int bufsize, char **context, - char **linenumber, char **search) -{ - int ch; - char *p; - char *name; - -retry: - errno = 0; - if (fgets(buf, bufsize, csinfo[cnumber].fr_fp) == NULL) { - if (errno == EINTR) { - goto retry; - } - - if (feof(csinfo[cnumber].fr_fp)) { - errno = EIO; - } - - cs_reading_emsg(cnumber); - - return NULL; - } - - // If the line's too long for the buffer, discard it. - if ((p = strchr(buf, '\n')) == NULL) { - while ((ch = getc(csinfo[cnumber].fr_fp)) != EOF && ch != '\n') {} - return NULL; - } - *p = '\0'; - - // cscope output is in the following format: - // - // <filename> <context> <line number> <pattern> - char *saveptr = NULL; - if ((name = os_strtok(buf, (const char *)" ", &saveptr)) == NULL) { - return NULL; - } - if ((*context = os_strtok(NULL, (const char *)" ", &saveptr)) == NULL) { - return NULL; - } - if ((*linenumber = os_strtok(NULL, (const char *)" ", &saveptr)) == NULL) { - return NULL; - } - *search = *linenumber + strlen(*linenumber) + 1; // +1 to skip \0 - - // --- nvi --- - // If the file is older than the cscope database, that is, - // the database was built since the file was last modified, - // or there wasn't a search string, use the line number. - if (strcmp(*search, "<unknown>") == 0) { - *search = NULL; - } - - name = cs_resolve_file(cnumber, name); - return name; -} - -/// Write cscope find results to file. -static void cs_file_results(FILE *f, int *nummatches_a) -{ - char *search, *slno; - char *fullname; - char *cntx; - char *context; - - char *buf = xmalloc(CSREAD_BUFSIZE); - - for (size_t i = 0; i < csinfo_size; i++) { - if (nummatches_a[i] < 1) { - continue; - } - - for (int j = 0; j < nummatches_a[i]; j++) { - if ((fullname = cs_parse_results(i, buf, CSREAD_BUFSIZE, &cntx, - &slno, &search)) == NULL) { - continue; - } - - size_t context_len = strlen(cntx) + 5; - context = xmalloc(context_len); - - if (strcmp(cntx, "<global>") == 0) { - xstrlcpy(context, "<<global>>", context_len); - } else { - snprintf(context, context_len, "<<%s>>", cntx); - } - - if (search == NULL) { - fprintf(f, "%s\t%s\t%s\n", fullname, slno, context); - } else { - fprintf(f, "%s\t%s\t%s %s\n", fullname, slno, context, search); - } - - xfree(context); - xfree(fullname); - } // for all matches - - (void)cs_read_prompt(i); - } // for all cscope connections - xfree(buf); -} - -/// Get parsed cscope output and calls cs_make_vim_style_matches to convert -/// into ctags format. -/// When there are no matches sets "*matches_p" to NULL. -static void cs_fill_results(char *tagstr, size_t totmatches, int *nummatches_a, char ***matches_p, - char ***cntxts_p, size_t *matched) -{ - char *buf; - char *search, *slno; - size_t totsofar = 0; - char **matches = NULL; - char **cntxts = NULL; - char *fullname; - char *cntx; - - assert(totmatches > 0); - - buf = xmalloc(CSREAD_BUFSIZE); - matches = xmalloc(sizeof(char *) * totmatches); - cntxts = xmalloc(sizeof(char *) * totmatches); - - for (size_t i = 0; i < csinfo_size; i++) { - if (nummatches_a[i] < 1) { - continue; - } - - for (int j = 0; j < nummatches_a[i]; j++) { - if ((fullname = cs_parse_results(i, buf, CSREAD_BUFSIZE, &cntx, - &slno, &search)) == NULL) { - continue; - } - - matches[totsofar] = cs_make_vim_style_matches(fullname, slno, search, - tagstr); - - xfree(fullname); - - if (strcmp(cntx, "<global>") == 0) { - cntxts[totsofar] = NULL; - } else { - cntxts[totsofar] = xstrdup(cntx); - } - - totsofar++; - } // for all matches - - (void)cs_read_prompt(i); - } // for all cscope connections - - if (totsofar == 0) { - // No matches, free the arrays and return NULL in "*matches_p". - XFREE_CLEAR(matches); - XFREE_CLEAR(cntxts); - } - *matched = totsofar; - *matches_p = matches; - *cntxts_p = cntxts; - - xfree(buf); -} - -// get the requested path components -static char *cs_pathcomponents(char *path) -{ - if (p_cspc == 0) { - return path; - } - - char *s = path + strlen(path) - 1; - for (int i = 0; i < p_cspc; i++) { - while (s > path && *--s != '/') {} - } - if ((s > path && *s == '/')) { - s++; - } - return s; -} - -/// Print cscope output that was converted into ctags style entries. -/// -/// Only called from cs_manage_matches(). -/// -/// @param matches Array of cscope lines in ctags style. Every entry was -// produced with a format string of the form -// "%s\t%s\t%s;\"\t%s" or -// "%s\t%s\t%s;\"" -// by cs_make_vim_style_matches(). -/// @param cntxts Context for matches. -/// @param num_matches Number of entries in matches/cntxts, always greater 0. -static void cs_print_tags_priv(char **matches, char **cntxts, - size_t num_matches) FUNC_ATTR_NONNULL_ALL -{ - char *globalcntx = "GLOBAL"; - char *cstag_msg = _("Cscope tag: %s"); - - assert(num_matches > 0); - assert(strcnt(matches[0], '\t') >= 2); - - char *ptag = matches[0]; - char *ptag_end = strchr(ptag, '\t'); - assert(ptag_end >= ptag); - // NUL terminate tag string in matches[0]. - *ptag_end = NUL; - - // The "%s" in cstag_msg won't appear in the result string, so we don't need - // extra memory for terminating NUL. - size_t newsize = strlen(cstag_msg) + (size_t)(ptag_end - ptag); - char *buf = xmalloc(newsize); - size_t bufsize = newsize; // Track available bufsize - (void)snprintf(buf, bufsize, cstag_msg, ptag); - msg_puts_attr(buf, HL_ATTR(HLF_T)); - msg_clr_eos(); - - // restore matches[0] - *ptag_end = '\t'; - - // Column headers for match number, line number and filename. - msg_puts_attr(_("\n # line"), HL_ATTR(HLF_T)); - msg_advance(msg_col + 2); - msg_puts_attr(_("filename / context / line\n"), HL_ATTR(HLF_T)); - - for (size_t i = 0; i < num_matches; i++) { - assert(strcnt(matches[i], '\t') >= 2); - - // Parse filename, line number and optional part. - char *fname = strchr(matches[i], '\t') + 1; - char *fname_end = strchr(fname, '\t'); - // Replace second '\t' in matches[i] with NUL to terminate fname. - *fname_end = NUL; - - char *lno = fname_end + 1; - char *extra = xstrchrnul(lno, '\t'); - // Ignore ;" at the end of lno. - char *lno_end = extra - 2; - *lno_end = NUL; - // Do we have an optional part? - extra = *extra ? extra + 1 : NULL; - - const char *csfmt_str = "%4zu %6s "; - // hopefully num_matches will be less than 10^16 - newsize = strlen(csfmt_str) + 16 + (size_t)(lno_end - lno); - if (bufsize < newsize) { - buf = xrealloc(buf, newsize); - bufsize = newsize; - } - (void)snprintf(buf, bufsize, csfmt_str, i + 1, lno); - msg_puts_attr(buf, HL_ATTR(HLF_CM)); - msg_outtrans_long_attr(cs_pathcomponents(fname), HL_ATTR(HLF_CM)); - - // compute the required space for the context - char *context = cntxts[i] ? cntxts[i] : globalcntx; - - const char *cntxformat = " <<%s>>"; - // '%s' won't appear in result string, so: - // newsize = len(cntxformat) - 2 + len(context) + 1 (for NUL). - newsize = strlen(context) + strlen(cntxformat) - 1; - - if (bufsize < newsize) { - buf = xrealloc(buf, newsize); - bufsize = newsize; - } - int buf_len = snprintf(buf, bufsize, cntxformat, context); - assert(buf_len >= 0); - - // Print the context only if it fits on the same line. - if (msg_col + buf_len >= Columns) { - msg_putchar('\n'); - } - msg_advance(12); - msg_outtrans_long_attr(buf, 0); - msg_putchar('\n'); - if (extra != NULL) { - msg_advance(13); - msg_outtrans_long_attr(extra, 0); - } - - // restore matches[i] - *fname_end = '\t'; - *lno_end = ';'; - - if (msg_col) { - msg_putchar('\n'); - } - - os_breakcheck(); - if (got_int) { - got_int = false; // don't print any more matches - break; - } - } - - xfree(buf); -} - -/// Read a cscope prompt (basically, skip over the ">> "). -static int cs_read_prompt(size_t i) -{ - int ch; - char *buf = NULL; // buffer for possible error message from cscope - size_t bufpos = 0; - char *cs_emsg = _("E609: Cscope error: %s"); - size_t cs_emsg_len = strlen(cs_emsg); - static char *eprompt = "Press the RETURN key to continue:"; - size_t epromptlen = strlen(eprompt); - - // compute maximum allowed len for Cscope error message - assert(IOSIZE >= cs_emsg_len); - size_t maxlen = IOSIZE - cs_emsg_len; - - while (1) { - while (1) { - do { - errno = 0; - ch = fgetc(csinfo[i].fr_fp); - } while (ch == EOF && errno == EINTR && ferror(csinfo[i].fr_fp)); - if (ch == EOF || ch == CSCOPE_PROMPT[0]) { - break; - } - // if there is room and char is printable - if (bufpos < maxlen - 1 && vim_isprintc(ch)) { - // lazy buffer allocation - if (buf == NULL) { - buf = xmalloc(maxlen); - } - // append character to the message - buf[bufpos++] = (char)ch; - buf[bufpos] = NUL; - if (bufpos >= epromptlen - && strcmp(&buf[bufpos - epromptlen], eprompt) == 0) { - // remove eprompt from buf - buf[bufpos - epromptlen] = NUL; - - // print message to user - (void)semsg(cs_emsg, buf); - - // send RETURN to cscope - (void)putc('\n', csinfo[i].to_fp); - (void)fflush(csinfo[i].to_fp); - - // clear buf - bufpos = 0; - buf[bufpos] = NUL; - } - } - } - - for (size_t n = 0; n < strlen(CSCOPE_PROMPT); n++) { - if (n > 0) { - do { - errno = 0; - ch = fgetc(csinfo[i].fr_fp); - } while (ch == EOF && errno == EINTR && ferror(csinfo[i].fr_fp)); - } - if (ch == EOF) { - PERROR("cs_read_prompt EOF"); - if (buf != NULL && buf[0] != NUL) { - (void)semsg(cs_emsg, buf); - } else if (p_csverbose) { - cs_reading_emsg(i); // don't have additional information - } - cs_release_csp(i, true); - xfree(buf); - return CSCOPE_FAILURE; - } - - if (ch != CSCOPE_PROMPT[n]) { - ch = EOF; - break; - } - } - - if (ch == EOF) { - continue; // didn't find the prompt - } - break; // did find the prompt - } - - xfree(buf); - return CSCOPE_SUCCESS; -} - -#if defined(UNIX) && defined(SIGALRM) -// Used to catch and ignore SIGALRM below. -static void sig_handler(int s) -{ - // do nothing -} - -#endif - -/// Does the actual free'ing for the cs ptr with an optional flag of whether -/// or not to free the filename. Called by cs_kill and cs_reset. -static void cs_release_csp(size_t i, bool freefnpp) -{ - // Trying to exit normally (not sure whether it is fit to Unix cscope) - if (csinfo[i].to_fp != NULL) { - (void)fputs("q\n", csinfo[i].to_fp); - (void)fflush(csinfo[i].to_fp); - } -#if defined(UNIX) - { - int waitpid_errno; - int pstat; - pid_t pid; - -# if defined(HAVE_SIGACTION) - struct sigaction sa, old; - - // Use sigaction() to limit the waiting time to two seconds. - sigemptyset(&sa.sa_mask); - sa.sa_handler = sig_handler; -# ifdef SA_NODEFER - sa.sa_flags = SA_NODEFER; -# else - sa.sa_flags = 0; -# endif - sigaction(SIGALRM, &sa, &old); - alarm(2); // 2 sec timeout - - // Block until cscope exits or until timer expires - pid = waitpid(csinfo[i].pid, &pstat, 0); - waitpid_errno = errno; - - // cancel pending alarm if still there and restore signal - alarm(0); - sigaction(SIGALRM, &old, NULL); -# else - int waited; - - // Can't use sigaction(), loop for two seconds. First yield the CPU - // to give cscope a chance to exit quickly. - sleep(0); - for (waited = 0; waited < 40; waited++) { - pid = waitpid(csinfo[i].pid, &pstat, WNOHANG); - waitpid_errno = errno; - if (pid != 0) { - break; // break unless the process is still running - } - os_delay(50L, false); // sleep 50 ms - } -# endif - // If the cscope process is still running: kill it. - // Safety check: If the PID would be zero here, the entire X session - // would be killed. -1 and 1 are dangerous as well. - if (pid < 0 && csinfo[i].pid > 1) { -# ifdef ECHILD - bool alive = true; - - if (waitpid_errno == ECHILD) { - // When using 'vim -g', vim is forked and cscope process is - // no longer a child process but a sibling. So waitpid() - // fails with errno being ECHILD (No child processes). - // Don't send SIGKILL to cscope immediately but wait - // (polling) for it to exit normally as result of sending - // the "q" command, hence giving it a chance to clean up - // its temporary files. - int waited; - - sleep(0); - for (waited = 0; waited < 40; waited++) { - // Check whether cscope process is still alive - if (kill(csinfo[i].pid, 0) != 0) { - alive = false; // cscope process no longer exists - break; - } - os_delay(50L, false); // sleep 50 ms - } - } - if (alive) -# endif - { - kill(csinfo[i].pid, SIGKILL); - (void)waitpid(csinfo[i].pid, &pstat, 0); - } - } - } -#else // !UNIX - if (csinfo[i].hProc != NULL) { - // Give cscope a chance to exit normally - if (WaitForSingleObject(csinfo[i].hProc, 1000) == WAIT_TIMEOUT) { - TerminateProcess(csinfo[i].hProc, 0); - } - CloseHandle(csinfo[i].hProc); - } -#endif - - if (csinfo[i].fr_fp != NULL) { - (void)fclose(csinfo[i].fr_fp); - } - if (csinfo[i].to_fp != NULL) { - (void)fclose(csinfo[i].to_fp); - } - - if (freefnpp) { - xfree(csinfo[i].fname); - xfree(csinfo[i].ppath); - xfree(csinfo[i].flags); - } - - clear_csinfo(i); -} - -/// Calls cs_kill on all cscope connections then reinits. -static int cs_reset(exarg_T *eap) -{ - char **dblist = NULL, **pplist = NULL, **fllist = NULL; - char buf[25]; // for snprintf " (#%zu)" - - if (csinfo_size == 0) { - return CSCOPE_SUCCESS; - } - - // malloc our db and ppath list - dblist = xmalloc(csinfo_size * sizeof(char *)); - pplist = xmalloc(csinfo_size * sizeof(char *)); - fllist = xmalloc(csinfo_size * sizeof(char *)); - - for (size_t i = 0; i < csinfo_size; i++) { - dblist[i] = csinfo[i].fname; - pplist[i] = csinfo[i].ppath; - fllist[i] = csinfo[i].flags; - if (csinfo[i].fname != NULL) { - cs_release_csp(i, false); - } - } - - // rebuild the cscope connection list - for (size_t i = 0; i < csinfo_size; i++) { - if (dblist[i] != NULL) { - cs_add_common(dblist[i], pplist[i], fllist[i]); - if (p_csverbose) { - // don't use smsg_attr() because we want to display the - // connection number in the same line as - // "Added cscope database..." - snprintf(buf, ARRAY_SIZE(buf), " (#%zu)", i); - msg_puts_attr(buf, HL_ATTR(HLF_R)); - } - } - xfree(dblist[i]); - xfree(pplist[i]); - xfree(fllist[i]); - } - xfree(dblist); - xfree(pplist); - xfree(fllist); - - if (p_csverbose) { - msg_attr(_("All cscope databases reset"), HL_ATTR(HLF_R) | MSG_HIST); - } - return CSCOPE_SUCCESS; -} - -/// Construct the full pathname to a file found in the cscope database. -/// (Prepends ppath, if there is one and if it's not already prepended, -/// otherwise just uses the name found.) -/// -/// We need to prepend the prefix because on some cscope's (e.g., the one that -/// ships with Solaris 2.6), the output never has the prefix prepended. -/// Contrast this with my development system (Digital Unix), which does. -static char *cs_resolve_file(size_t i, char *name) -{ - char *fullname; - char_u *csdir = NULL; - - // Ppath is freed when we destroy the cscope connection. - // Fullname is freed after cs_make_vim_style_matches, after it's been - // copied into the tag buffer used by Vim. - size_t len = strlen(name) + 2; - if (csinfo[i].ppath != NULL) { - len += strlen(csinfo[i].ppath); - } else if (p_csre && csinfo[i].fname != NULL) { - // If 'cscoperelative' is set and ppath is not set, use cscope.out - // path in path resolution. - csdir = xmalloc(MAXPATHL); - STRLCPY(csdir, csinfo[i].fname, - path_tail(csinfo[i].fname) - - csinfo[i].fname + 1); - len += STRLEN(csdir); - } - - // Note/example: this won't work if the cscope output already starts - // "../.." and the prefix path is also "../..". if something like this - // happens, you are screwed up and need to fix how you're using cscope. - if (csinfo[i].ppath != NULL - && (strncmp(name, csinfo[i].ppath, strlen(csinfo[i].ppath)) != 0) - && (name[0] != '/')) { - fullname = xmalloc(len); - (void)sprintf(fullname, "%s/%s", csinfo[i].ppath, name); - } else if (csdir != NULL && csinfo[i].fname != NULL && *csdir != NUL) { - // Check for csdir to be non empty to avoid empty path concatenated to - // cscope output. - fullname = concat_fnames((char *)csdir, name, true); - } else { - fullname = xstrdup(name); - } - - xfree(csdir); - return fullname; -} - -/// Show all cscope connections. -static int cs_show(exarg_T *eap) -{ - if (cs_cnt_connections() == 0) { - msg_puts(_("no cscope connections\n")); - } else { - msg_puts_attr(_(" # pid database name prepend path\n"), - HL_ATTR(HLF_T)); - for (size_t i = 0; i < csinfo_size; i++) { - if (csinfo[i].fname == NULL) { - continue; - } - - if (csinfo[i].ppath != NULL) { - (void)smsg("%2zu %-5" PRId64 " %-34s %-32s", i, - (int64_t)csinfo[i].pid, csinfo[i].fname, csinfo[i].ppath); - } else { - (void)smsg("%2zu %-5" PRId64 " %-34s <none>", i, - (int64_t)csinfo[i].pid, csinfo[i].fname); - } - } - } - - wait_return(false); - return CSCOPE_SUCCESS; -} - -/// Only called when VIM exits to quit any cscope sessions. -void cs_end(void) -{ - for (size_t i = 0; i < csinfo_size; i++) { - cs_release_csp(i, true); - } - xfree(csinfo); - csinfo_size = 0; -} - -// the end diff --git a/src/nvim/if_cscope.h b/src/nvim/if_cscope.h deleted file mode 100644 index 8dbc78943f..0000000000 --- a/src/nvim/if_cscope.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef NVIM_IF_CSCOPE_H -#define NVIM_IF_CSCOPE_H - -#include "nvim/ex_cmds_defs.h" // for exarg_T -#include "nvim/types.h" // for char_u and expand_T - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "if_cscope.h.generated.h" -#endif -#endif // NVIM_IF_CSCOPE_H diff --git a/src/nvim/if_cscope_defs.h b/src/nvim/if_cscope_defs.h deleted file mode 100644 index 6ded89fa0b..0000000000 --- a/src/nvim/if_cscope_defs.h +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef NVIM_IF_CSCOPE_DEFS_H -#define NVIM_IF_CSCOPE_DEFS_H - -// CSCOPE support for Vim added by Andy Kahn <kahn@zk3.dec.com> -// Ported to Win32 by Sergey Khorev <sergey.khorev@gmail.com> -// -// The basic idea/structure of cscope for Vim was borrowed from Nvi. -// There might be a few lines of code that look similar to what Nvi -// has. If this is a problem and requires inclusion of the annoying -// BSD license, then sue me; I'm not worth much anyway. - -#if defined(UNIX) -# include <sys/types.h> // pid_t -#endif - -#include "nvim/ex_cmds_defs.h" -#include "nvim/os/fs_defs.h" -#include "nvim/os/os_defs.h" - -#define CSCOPE_SUCCESS 0 -#define CSCOPE_FAILURE -1 - -#define CSCOPE_DBFILE "cscope.out" -#define CSCOPE_PROMPT ">> " - -// See ":help cscope-find" for the possible queries. - -typedef struct { - char *name; - int (*func)(exarg_T *eap); - char *help; - char *usage; - int cansplit; // if supports splitting window -} cscmd_T; - -typedef struct csi { - char *fname; // cscope db name - char *ppath; // path to prepend (the -P option) - char *flags; // additional cscope flags/options (e.g, -p2) -#if defined(UNIX) - pid_t pid; // PID of the connected cscope process -#else - DWORD pid; // PID of the connected cscope process - HANDLE hProc; // cscope process handle - DWORD nVolume; // Volume serial number, instead of st_dev - DWORD nIndexHigh; // st_ino has no meaning on Windows - DWORD nIndexLow; -#endif - FileID file_id; - - FILE *fr_fp; // from cscope: FILE. - FILE *to_fp; // to cscope: FILE. -} csinfo_T; - -typedef enum { Add, Find, Help, Kill, Reset, Show, } csid_e; - -typedef enum { - Store, - Get, - Free, - Print, -} mcmd_e; - -#endif // NVIM_IF_CSCOPE_DEFS_H diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 0f7a5a8e44..ec6c72da6d 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -2,8 +2,10 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> -#include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stdlib.h> +#include <string.h> #include "nvim/ascii.h" #include "nvim/assert.h" @@ -11,22 +13,36 @@ #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/ex_docmd.h" #include "nvim/extmark.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/indent.h" +#include "nvim/indent_c.h" #include "nvim/mark.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/move.h" #include "nvim/option.h" +#include "nvim/optionstr.h" +#include "nvim/os/input.h" #include "nvim/plines.h" +#include "nvim/pos.h" #include "nvim/regexp.h" #include "nvim/screen.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/textformat.h" +#include "nvim/types.h" #include "nvim/undo.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "indent.c.generated.h" @@ -100,7 +116,7 @@ bool tabstop_set(char *var, long **array) /// Calculate the number of screen spaces a tab will occupy. /// If "vts" is set then the tab widths are taken from that array, /// otherwise the value of ts is used. -int tabstop_padding(colnr_T col, long ts_arg, long *vts) +int tabstop_padding(colnr_T col, long ts_arg, const long *vts) { long ts = ts_arg == 0 ? 8 : ts_arg; colnr_T tabcol = 0; @@ -128,7 +144,7 @@ int tabstop_padding(colnr_T col, long ts_arg, long *vts) } /// Find the size of the tab that covers a particular column. -int tabstop_at(colnr_T col, long ts, long *vts) +int tabstop_at(colnr_T col, long ts, const long *vts) { colnr_T tabcol = 0; int t; @@ -177,7 +193,7 @@ colnr_T tabstop_start(colnr_T col, long ts, long *vts) /// Find the number of tabs and spaces necessary to get from one column /// to another. -void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, long *vts, int *ntabs, +void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, const long *vts, int *ntabs, int *nspcs) { int spaces = end_col - start_col; @@ -185,6 +201,7 @@ void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, long *vts, long padding = 0; int t; long ts = ts_arg == 0 ? curbuf->b_p_ts : ts_arg; + assert(ts != 0); // suppress clang "Division by zero" if (vts == NULL || vts[0] == 0) { int tabs = 0; @@ -241,7 +258,7 @@ void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, long *vts, } /// See if two tabstop arrays contain the same values. -bool tabstop_eq(long *ts1, long *ts2) +bool tabstop_eq(const long *ts1, const long *ts2) { int t; @@ -265,7 +282,7 @@ bool tabstop_eq(long *ts1, long *ts2) } /// Copy a tabstop array, allocating space for the new array. -int *tabstop_copy(long *oldts) +int *tabstop_copy(const long *oldts) { long *newts; int t; @@ -363,7 +380,7 @@ int get_indent_lnum(linenr_T lnum) int get_indent_buf(buf_T *buf, linenr_T lnum) { return get_indent_str_vtab(ml_get_buf(buf, lnum, false), - curbuf->b_p_ts, + buf->b_p_ts, buf->b_p_vts_array, false); } @@ -371,7 +388,7 @@ int get_indent_buf(buf_T *buf, linenr_T lnum) /// Count the size (in window cells) of the indent in line "ptr", with /// 'tabstop' at "ts". /// If @param list is true, count only screen size for tabs. -int get_indent_str(const char_u *ptr, int ts, bool list) +int get_indent_str(const char *ptr, int ts, bool list) FUNC_ATTR_NONNULL_ALL { int count = 0; @@ -385,7 +402,7 @@ int get_indent_str(const char_u *ptr, int ts, bool list) } else { // In list mode, when tab is not set, count screen char width // for Tab, displays: ^I - count += ptr2cells((char *)ptr); + count += ptr2cells(ptr); } } else if (*ptr == ' ') { // Count a space for one. @@ -434,10 +451,10 @@ int get_indent_str_vtab(const char *ptr, long ts, long *vts, bool list) // Returns true if the line was changed. int set_indent(int size, int flags) { - char_u *p; - char_u *newline; - char_u *oldline; - char_u *s; + char *p; + char *newline; + char *oldline; + char *s; int todo; int ind_len; // Measured in characters. int line_len; @@ -454,7 +471,7 @@ int set_indent(int size, int flags) // characters needed for the indent. todo = size; ind_len = 0; - p = oldline = (char_u *)get_cursor_line_ptr(); + p = oldline = get_cursor_line_ptr(); // Calculate the buffer size for the new indent, and check to see if it // isn't already set. @@ -549,9 +566,9 @@ int set_indent(int size, int flags) if (flags & SIN_INSERT) { p = oldline; } else { - p = (char_u *)skipwhite((char *)p); + p = skipwhite(p); } - line_len = (int)STRLEN(p) + 1; + line_len = (int)strlen(p) + 1; // If 'preserveindent' and 'expandtab' are both set keep the original // characters and allocate accordingly. We will fill the rest with spaces @@ -631,7 +648,7 @@ int set_indent(int size, int flags) todo -= tab_pad; ind_done += tab_pad; } - p = (char_u *)skipwhite((char *)p); + p = skipwhite(p); } for (;;) { @@ -659,7 +676,7 @@ int set_indent(int size, int flags) const colnr_T new_offset = (colnr_T)(s - newline); // this may free "newline" - ml_replace(curwin->w_cursor.lnum, (char *)newline, false); + ml_replace(curwin->w_cursor.lnum, newline, false); if (!(flags & SIN_NOMARK)) { extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum - 1, @@ -742,22 +759,26 @@ bool briopt_check(win_T *wp) int bri_min = 20; bool bri_sbr = false; int bri_list = 0; + int bri_vcol = 0; char *p = wp->w_p_briopt; while (*p != NUL) { - if (STRNCMP(p, "shift:", 6) == 0 + if (strncmp(p, "shift:", 6) == 0 && ((p[6] == '-' && ascii_isdigit(p[7])) || ascii_isdigit(p[6]))) { p += 6; bri_shift = getdigits_int(&p, true, 0); - } else if (STRNCMP(p, "min:", 4) == 0 && ascii_isdigit(p[4])) { + } else if (strncmp(p, "min:", 4) == 0 && ascii_isdigit(p[4])) { p += 4; bri_min = getdigits_int(&p, true, 0); - } else if (STRNCMP(p, "sbr", 3) == 0) { + } else if (strncmp(p, "sbr", 3) == 0) { p += 3; bri_sbr = true; - } else if (STRNCMP(p, "list:", 5) == 0) { + } else if (strncmp(p, "list:", 5) == 0) { p += 5; bri_list = (int)getdigits(&p, false, 0); + } else if (strncmp(p, "column:", 7) == 0) { + p += 7; + bri_vcol = (int)getdigits(&p, false, 0); } if (*p != ',' && *p != NUL) { return false; @@ -770,7 +791,8 @@ bool briopt_check(win_T *wp) wp->w_briopt_shift = bri_shift; wp->w_briopt_min = bri_min; wp->w_briopt_sbr = bri_sbr; - wp->w_briopt_list = bri_list; + wp->w_briopt_list = bri_list; + wp->w_briopt_vcol = bri_vcol; return true; } @@ -778,61 +800,84 @@ bool briopt_check(win_T *wp) // Return appropriate space number for breakindent, taking influencing // parameters into account. Window must be specified, since it is not // necessarily always the current one. -int get_breakindent_win(win_T *wp, char_u *line) +int get_breakindent_win(win_T *wp, char *line) FUNC_ATTR_NONNULL_ALL { static int prev_indent = 0; // Cached indent value. - static long prev_ts = 0; // Cached tabstop value. - static const char_u *prev_line = NULL; // cached pointer to line. + static long prev_ts = 0L; // Cached tabstop value. + static const char *prev_line = NULL; // cached pointer to line. static varnumber_T prev_tick = 0; // Changedtick of cached value. - static long *prev_vts = NULL; // Cached vartabs values. + static long *prev_vts = NULL; // Cached vartabs values. + static int prev_list = 0; // cached list value + static int prev_listopt = 0; // cached w_p_briopt_list value + static char *prev_flp = NULL; // cached formatlistpat value int bri = 0; // window width minus window margin space, i.e. what rests for text const int eff_wwidth = wp->w_width_inner - ((wp->w_p_nu || wp->w_p_rnu) && (vim_strchr(p_cpo, CPO_NUMCOL) == NULL) ? number_width(wp) + 1 : 0); - // used cached indent, unless pointer or 'tabstop' changed + // used cached indent, unless + // - line pointer changed + // - 'tabstop' changed + // - 'briopt_list changed' changed or + // - 'formatlistpattern' changed if (prev_line != line || prev_ts != wp->w_buffer->b_p_ts || prev_tick != buf_get_changedtick(wp->w_buffer) + || prev_listopt != wp->w_briopt_list + || (prev_flp == NULL || (strcmp(prev_flp, get_flp_value(wp->w_buffer)) != 0)) || prev_vts != wp->w_buffer->b_p_vts_array) { prev_line = line; prev_ts = wp->w_buffer->b_p_ts; prev_tick = buf_get_changedtick(wp->w_buffer); prev_vts = wp->w_buffer->b_p_vts_array; - prev_indent = get_indent_str_vtab((char *)line, - wp->w_buffer->b_p_ts, - wp->w_buffer->b_p_vts_array, - wp->w_p_list); + if (wp->w_briopt_vcol == 0) { + prev_indent = get_indent_str_vtab(line, + wp->w_buffer->b_p_ts, + wp->w_buffer->b_p_vts_array, + wp->w_p_list); + } + prev_listopt = wp->w_briopt_list; + prev_list = 0; + xfree(prev_flp); + prev_flp = xstrdup(get_flp_value(wp->w_buffer)); + // add additional indent for numbered lists + if (wp->w_briopt_list != 0 && wp->w_briopt_vcol == 0) { + regmatch_T regmatch = { + .regprog = vim_regcomp(prev_flp, RE_MAGIC + RE_STRING + RE_AUTO + RE_STRICT), + }; + if (regmatch.regprog != NULL) { + regmatch.rm_ic = false; + if (vim_regexec(®match, line, 0)) { + if (wp->w_briopt_list > 0) { + prev_list += wp->w_briopt_list; + } else { + prev_indent = (int)(*regmatch.endp - *regmatch.startp); + } + } + vim_regfree(regmatch.regprog); + } + } + } + if (wp->w_briopt_vcol != 0) { + // column value has priority + bri = wp->w_briopt_vcol; + prev_list = 0; + } else { + bri = prev_indent + wp->w_briopt_shift; } - bri = prev_indent + wp->w_briopt_shift; // Add offset for number column, if 'n' is in 'cpoptions' bri += win_col_off2(wp); // add additional indent for numbered lists - if (wp->w_briopt_list != 0) { - regmatch_T regmatch = { - .regprog = vim_regcomp(curbuf->b_p_flp, - RE_MAGIC + RE_STRING + RE_AUTO + RE_STRICT), - }; - - if (regmatch.regprog != NULL) { - regmatch.rm_ic = false; - if (vim_regexec(®match, (char *)line, 0)) { - if (wp->w_briopt_list > 0) { - bri += wp->w_briopt_list; - } else { - bri = (int)(*regmatch.endp - *regmatch.startp); - } - } - vim_regfree(regmatch.regprog); - } + if (wp->w_briopt_list > 0) { + bri += prev_list; } // indent minus the length of the showbreak string if (wp->w_briopt_sbr) { - bri -= vim_strsize((char *)get_showbreak_value(wp)); + bri -= vim_strsize(get_showbreak_value(wp)); } // never indent past left window margin @@ -854,18 +899,17 @@ int get_breakindent_win(win_T *wp, char_u *line) // the line. int inindent(int extra) { - char_u *ptr; + char *ptr; colnr_T col; - for (col = 0, ptr = (char_u *)get_cursor_line_ptr(); ascii_iswhite(*ptr); col++) { + for (col = 0, ptr = get_cursor_line_ptr(); ascii_iswhite(*ptr); col++) { ptr++; } if (col >= curwin->w_cursor.col + extra) { return true; - } else { - return false; } + return false; } /// @return true if the conditions are OK for smart indenting. @@ -874,6 +918,197 @@ bool may_do_si(void) return curbuf->b_p_si && !curbuf->b_p_cin && *curbuf->b_p_inde == NUL && !p_paste; } +/// Give a "resulting text too long" error and maybe set got_int. +static void emsg_text_too_long(void) +{ + emsg(_(e_resulting_text_too_long)); + // when not inside a try/catch set got_int to break out of any loop + if (trylevel == 0) { + got_int = true; + } +} + +/// ":retab". +void ex_retab(exarg_T *eap) +{ + linenr_T lnum; + bool got_tab = false; + long num_spaces = 0; + long num_tabs; + long len; + long col; + long vcol; + long start_col = 0; // For start of white-space string + long start_vcol = 0; // For start of white-space string + long old_len; + char *ptr; + char *new_line = (char *)1; // init to non-NULL + bool did_undo; // called u_save for current line + long *new_vts_array = NULL; + char *new_ts_str; // string value of tab argument + + int save_list; + linenr_T first_line = 0; // first changed line + linenr_T last_line = 0; // last changed line + + save_list = curwin->w_p_list; + curwin->w_p_list = 0; // don't want list mode here + + new_ts_str = eap->arg; + if (!tabstop_set(eap->arg, &new_vts_array)) { + return; + } + while (ascii_isdigit(*(eap->arg)) || *(eap->arg) == ',') { + (eap->arg)++; + } + + // This ensures that either new_vts_array and new_ts_str are freshly + // allocated, or new_vts_array points to an existing array and new_ts_str + // is null. + if (new_vts_array == NULL) { + new_vts_array = curbuf->b_p_vts_array; + new_ts_str = NULL; + } else { + new_ts_str = xstrnsave(new_ts_str, (size_t)(eap->arg - new_ts_str)); + } + for (lnum = eap->line1; !got_int && lnum <= eap->line2; lnum++) { + ptr = ml_get(lnum); + col = 0; + vcol = 0; + did_undo = false; + for (;;) { + if (ascii_iswhite(ptr[col])) { + if (!got_tab && num_spaces == 0) { + // First consecutive white-space + start_vcol = vcol; + start_col = col; + } + if (ptr[col] == ' ') { + num_spaces++; + } else { + got_tab = true; + } + } else { + if (got_tab || (eap->forceit && num_spaces > 1)) { + // Retabulate this string of white-space + + // len is virtual length of white string + len = num_spaces = vcol - start_vcol; + num_tabs = 0; + if (!curbuf->b_p_et) { + int t, s; + + tabstop_fromto((colnr_T)start_vcol, (colnr_T)vcol, + curbuf->b_p_ts, new_vts_array, &t, &s); + num_tabs = t; + num_spaces = s; + } + if (curbuf->b_p_et || got_tab + || (num_spaces + num_tabs < len)) { + if (did_undo == false) { + did_undo = true; + if (u_save((linenr_T)(lnum - 1), + (linenr_T)(lnum + 1)) == FAIL) { + new_line = NULL; // flag out-of-memory + break; + } + } + + // len is actual number of white characters used + len = num_spaces + num_tabs; + old_len = (long)strlen(ptr); + const long new_len = old_len - col + start_col + len + 1; + if (new_len <= 0 || new_len >= MAXCOL) { + emsg_text_too_long(); + break; + } + new_line = xmalloc((size_t)new_len); + + if (start_col > 0) { + memmove(new_line, ptr, (size_t)start_col); + } + memmove(new_line + start_col + len, + ptr + col, (size_t)(old_len - col + 1)); + ptr = new_line + start_col; + for (col = 0; col < len; col++) { + ptr[col] = (col < num_tabs) ? '\t' : ' '; + } + if (ml_replace(lnum, new_line, false) == OK) { + // "new_line" may have been copied + new_line = curbuf->b_ml.ml_line_ptr; + extmark_splice_cols(curbuf, lnum - 1, 0, (colnr_T)old_len, + (colnr_T)new_len - 1, kExtmarkUndo); + } + if (first_line == 0) { + first_line = lnum; + } + last_line = lnum; + ptr = new_line; + col = start_col + len; + } + } + got_tab = false; + num_spaces = 0; + } + if (ptr[col] == NUL) { + break; + } + vcol += win_chartabsize(curwin, ptr + col, (colnr_T)vcol); + if (vcol >= MAXCOL) { + emsg_text_too_long(); + break; + } + col += utfc_ptr2len(ptr + col); + } + if (new_line == NULL) { // out of memory + break; + } + line_breakcheck(); + } + if (got_int) { + emsg(_(e_interr)); + } + + // If a single value was given then it can be considered equal to + // either the value of 'tabstop' or the value of 'vartabstop'. + if (tabstop_count(curbuf->b_p_vts_array) == 0 + && tabstop_count(new_vts_array) == 1 + && curbuf->b_p_ts == tabstop_first(new_vts_array)) { + // not changed + } else if (tabstop_count(curbuf->b_p_vts_array) > 0 + && tabstop_eq(curbuf->b_p_vts_array, new_vts_array)) { + // not changed + } else { + redraw_curbuf_later(UPD_NOT_VALID); + } + if (first_line != 0) { + changed_lines(first_line, 0, last_line + 1, 0L, true); + } + + curwin->w_p_list = save_list; // restore 'list' + + if (new_ts_str != NULL) { // set the new tabstop + // If 'vartabstop' is in use or if the value given to retab has more + // than one tabstop then update 'vartabstop'. + long *old_vts_ary = curbuf->b_p_vts_array; + + if (tabstop_count(old_vts_ary) > 0 || tabstop_count(new_vts_array) > 1) { + set_string_option_direct("vts", -1, new_ts_str, OPT_FREE | OPT_LOCAL, 0); + curbuf->b_p_vts_array = new_vts_array; + xfree(old_vts_ary); + } else { + // 'vartabstop' wasn't in use and a single value was given to + // retab then update 'tabstop'. + curbuf->b_p_ts = tabstop_first(new_vts_array); + xfree(new_vts_array); + } + xfree(new_ts_str); + } + coladvance(curwin->w_curswant); + + u_clearline(); +} + /// Get indent level from 'indentexpr'. int get_expr_indent(void) { @@ -918,6 +1153,12 @@ int get_expr_indent(void) check_cursor(); State = save_State; + // Reset did_throw, unless 'debug' has "throw" and inside a try/catch. + if (did_throw && (vim_strchr(p_debug, 't') == NULL || trylevel == 0)) { + handle_did_throw(); + did_throw = false; + } + // If there is an error, just keep the current indent. if (indent < 0) { indent = get_indent(); @@ -944,7 +1185,7 @@ int get_lisp_indent(void) { pos_T *pos, realpos, paren; int amount; - char_u *that; + char *that; colnr_T col; colnr_T firsttry; int parencount; @@ -979,7 +1220,7 @@ int get_lisp_indent(void) continue; } - for (that = (char_u *)get_cursor_line_ptr(); *that != NUL; that++) { + for (that = get_cursor_line_ptr(); *that != NUL; that++) { if (*that == ';') { while (*(that + 1) != NUL) { that++; @@ -1029,20 +1270,20 @@ int get_lisp_indent(void) curwin->w_cursor.col = pos->col; col = pos->col; - that = (char_u *)get_cursor_line_ptr(); + that = get_cursor_line_ptr(); if (vi_lisp && (get_indent() == 0)) { amount = 2; } else { - char_u *line = that; + char *line = that; chartabsize_T cts; - init_chartabsize_arg(&cts, curwin, pos->lnum, 0, (char *)line, (char *)line); + init_chartabsize_arg(&cts, curwin, pos->lnum, 0, line, line); while (*cts.cts_ptr != NUL && col > 0) { cts.cts_vcol += lbr_chartabsize_adv(&cts); col--; } amount = cts.cts_vcol; - that = (char_u *)cts.cts_ptr; + that = cts.cts_ptr; clear_chartabsize_arg(&cts); // Some keywords require "body" indenting rules (the @@ -1060,12 +1301,12 @@ int get_lisp_indent(void) firsttry = amount; init_chartabsize_arg(&cts, curwin, (colnr_T)(that - line), - amount, (char *)line, (char *)that); + amount, line, that); while (ascii_iswhite(*cts.cts_ptr)) { cts.cts_vcol += lbr_chartabsize(&cts); cts.cts_ptr++; } - that = (char_u *)cts.cts_ptr; + that = cts.cts_ptr; amount = cts.cts_vcol; clear_chartabsize_arg(&cts); @@ -1081,9 +1322,10 @@ int get_lisp_indent(void) quotecount = 0; init_chartabsize_arg(&cts, curwin, - (colnr_T)(that - line), amount, (char *)line, (char *)that); + (colnr_T)(that - line), amount, line, that); if (vi_lisp || ((*that != '"') && (*that != '\'') - && (*that != '#') && ((*that < '0') || (*that > '9')))) { + && (*that != '#') + && (((uint8_t)(*that) < '0') || ((uint8_t)(*that) > '9')))) { while (*cts.cts_ptr && (!ascii_iswhite(*cts.cts_ptr) || quotecount || parencount) && (!((*cts.cts_ptr == '(' || *cts.cts_ptr == '[') @@ -1109,7 +1351,7 @@ int get_lisp_indent(void) cts.cts_vcol += lbr_chartabsize(&cts); cts.cts_ptr++; } - that = (char_u *)cts.cts_ptr; + that = cts.cts_ptr; amount = cts.cts_vcol; clear_chartabsize_arg(&cts); @@ -1128,19 +1370,63 @@ int get_lisp_indent(void) return amount; } -static int lisp_match(char_u *p) +static int lisp_match(char *p) { - char_u buf[LSIZE]; + char buf[LSIZE]; int len; - char *word = (char *)(*curbuf->b_p_lw != NUL ? (char_u *)curbuf->b_p_lw : p_lispwords); + char *word = *curbuf->b_p_lw != NUL ? curbuf->b_p_lw : p_lispwords; while (*word != NUL) { - (void)copy_option_part(&word, (char *)buf, LSIZE, ","); - len = (int)STRLEN(buf); + (void)copy_option_part(&word, buf, LSIZE, ","); + len = (int)strlen(buf); - if ((STRNCMP(buf, p, len) == 0) && (p[len] == ' ')) { + if ((strncmp(buf, p, (size_t)len) == 0) && ascii_iswhite_or_nul(p[len])) { return true; } } return false; } + +/// Re-indent the current line, based on the current contents of it and the +/// surrounding lines. Fixing the cursor position seems really easy -- I'm very +/// confused what all the part that handles Control-T is doing that I'm not. +/// "get_the_indent" should be get_c_indent, get_expr_indent or get_lisp_indent. +void fixthisline(IndentGetter get_the_indent) +{ + int amount = get_the_indent(); + + if (amount < 0) { + return; + } + + change_indent(INDENT_SET, amount, false, 0, true); + if (linewhite(curwin->w_cursor.lnum)) { + did_ai = true; // delete the indent if the line stays empty + } +} + +/// Return true if 'indentexpr' should be used for Lisp indenting. +/// Caller may want to check 'autoindent'. +bool use_indentexpr_for_lisp(void) +{ + return curbuf->b_p_lisp + && *curbuf->b_p_inde != NUL + && strcmp(curbuf->b_p_lop, "expr:1") == 0; +} + +/// Fix indent for 'lisp' and 'cindent'. +void fix_indent(void) +{ + if (p_paste) { + return; // no auto-indenting when 'paste' is set + } + if (curbuf->b_p_lisp && curbuf->b_p_ai) { + if (use_indentexpr_for_lisp()) { + do_c_expr_indent(); + } else { + fixthisline(get_lisp_indent); + } + } else if (cindent_on()) { + do_c_expr_indent(); + } +} diff --git a/src/nvim/indent.h b/src/nvim/indent.h index f96732bf1c..f807bbb42b 100644 --- a/src/nvim/indent.h +++ b/src/nvim/indent.h @@ -3,6 +3,8 @@ #include "nvim/vim.h" +typedef int (*IndentGetter)(void); + // flags for set_indent() #define SIN_CHANGED 1 // call changed_bytes() when line changed #define SIN_INSERT 2 // insert indent before existing text diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index 5eed2601d5..1c771073b2 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -1,19 +1,26 @@ // 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 <assert.h> #include <inttypes.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/edit.h" +#include "nvim/globals.h" #include "nvim/indent.h" #include "nvim/indent_c.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/option.h" +#include "nvim/pos.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/vim.h" @@ -48,7 +55,7 @@ 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. - if (!is_pos_in_string((char_u *)ml_get(pos->lnum), pos->col)) { + if (!is_pos_in_string(ml_get(pos->lnum), pos->col)) { break; } cur_maxcomment = curwin->w_cursor.lnum - pos->lnum - 1; @@ -109,7 +116,7 @@ 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. - if (!is_pos_in_string((char_u *)ml_get(pos->lnum), pos->col)) { + if (!is_pos_in_string(ml_get(pos->lnum), pos->col)) { break; } cur_maxcomment = curwin->w_cursor.lnum - pos->lnum - 1; @@ -123,7 +130,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 const char_u *skip_string(const char_u *p) +static const char *skip_string(const char *p) { int i; @@ -157,14 +164,14 @@ static const char_u *skip_string(const char_u *p) } } else if (p[0] == 'R' && p[1] == '"') { // Raw string: R"[delim](...)[delim]" - const char *delim = (char *)p + 2; - const char *paren = vim_strchr((char *)delim, '('); + const char *delim = p + 2; + const char *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 + if (p[0] == ')' && strncmp(p + 1, delim, (size_t)delim_len) == 0 && p[delim_len + 1] == '"') { p += delim_len + 1; break; @@ -184,9 +191,9 @@ static const char_u *skip_string(const char_u *p) } /// @returns true if "line[col]" is inside a C string. -int is_pos_in_string(const char_u *line, colnr_T col) +int is_pos_in_string(const char *line, colnr_T col) { - const char_u *p; + const char *p; for (p = line; *p && (colnr_T)(p - line) < col; p++) { p = skip_string(p); @@ -205,13 +212,13 @@ bool cin_is_cinword(const char *line) bool retval = false; size_t cinw_len = strlen(curbuf->b_p_cinw) + 1; - char_u *cinw_buf = xmalloc(cinw_len); - line = skipwhite((char *)line); + char *cinw_buf = xmalloc(cinw_len); + line = skipwhite(line); for (char *cinw = curbuf->b_p_cinw; *cinw;) { - size_t len = copy_option_part(&cinw, (char *)cinw_buf, cinw_len, ","); - if (STRNCMP(line, cinw_buf, len) == 0 - && (!vim_iswordc(line[len]) || !vim_iswordc(line[len - 1]))) { + size_t len = copy_option_part(&cinw, cinw_buf, cinw_len, ","); + if (strncmp(line, cinw_buf, len) == 0 + && (!vim_iswordc((uint8_t)line[len]) || !vim_iswordc((uint8_t)line[len - 1]))) { retval = true; break; } @@ -224,17 +231,17 @@ bool cin_is_cinword(const char *line) // Skip over white space and C comments within the line. // Also skip over Perl/shell comments if desired. -static const char_u *cin_skipcomment(const char_u *s) +static const char *cin_skipcomment(const char *s) { while (*s) { - const char_u *prev_s = s; + const char *prev_s = s; - s = (char_u *)skipwhite((char *)s); + s = skipwhite(s); // Perl/shell # comment comment continues until eol. Require a space // before # to avoid recognizing $#array. if (curbuf->b_ind_hash_comment != 0 && s != prev_s && *s == '#') { - s += STRLEN(s); + s += strlen(s); break; } if (*s != '/') { @@ -242,7 +249,7 @@ static const char_u *cin_skipcomment(const char_u *s) } s++; if (*s == '/') { // slash-slash comment continues till eol - s += STRLEN(s); + s += strlen(s); break; } if (*s != '*') { @@ -260,7 +267,7 @@ static const char_u *cin_skipcomment(const char_u *s) /// Return true if there is no code at *s. White space and comments are /// not considered code. -static int cin_nocode(const char_u *s) +static int cin_nocode(const char *s) { return *cin_skipcomment(s) == NUL; } @@ -269,13 +276,13 @@ static int cin_nocode(const char_u *s) static pos_T *find_line_comment(void) // XXX { static pos_T pos; - char_u *line; - char_u *p; + char *line; + char *p; pos = curwin->w_cursor; while (--pos.lnum > 0) { - line = (char_u *)ml_get(pos.lnum); - p = (char_u *)skipwhite((char *)line); + line = ml_get(pos.lnum); + p = skipwhite(line); if (cin_islinecomment(p)) { pos.col = (int)(p - line); return &pos; @@ -288,21 +295,21 @@ static pos_T *find_line_comment(void) // XXX } /// Checks if `text` starts with "key:". -static bool cin_has_js_key(const char_u *text) +static bool cin_has_js_key(const char *text) { - const char_u *s = (char_u *)skipwhite((char *)text); + const char *s = skipwhite(text); - char_u quote = 0; + char quote = 0; if (*s == '\'' || *s == '"') { // can be 'key': or "key": quote = *s; s++; } - if (!vim_isIDc(*s)) { // need at least one ID character + if (!vim_isIDc((uint8_t)(*s))) { // need at least one ID character return false; } - while (vim_isIDc(*s)) { + while (vim_isIDc((uint8_t)(*s))) { s++; } if (*s && *s == quote) { @@ -317,14 +324,14 @@ static bool cin_has_js_key(const 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(const char_u **s) +static bool cin_islabel_skip(const char **s) FUNC_ATTR_NONNULL_ALL { - if (!vim_isIDc(**s)) { // need at least one ID character + if (!vim_isIDc((uint8_t)(**s))) { // need at least one ID character return false; } - while (vim_isIDc(**s)) { + while (vim_isIDc((uint8_t)(**s))) { (*s)++; } @@ -338,7 +345,7 @@ static bool cin_islabel_skip(const char_u **s) // Note: curwin->w_cursor must be where we are looking for the label. bool cin_islabel(void) // XXX { - const char_u *s = cin_skipcomment((char_u *)get_cursor_line_ptr()); + const char *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. @@ -348,6 +355,7 @@ bool cin_islabel(void) // XXX if (cin_isscopedecl(s)) { return false; } + if (!cin_islabel_skip(&s)) { return false; } @@ -356,7 +364,7 @@ bool cin_islabel(void) // XXX // label. pos_T cursor_save; pos_T *trypos; - const char_u *line; + const char *line; cursor_save = curwin->w_cursor; while (curwin->w_cursor.lnum > 1) { @@ -369,7 +377,7 @@ bool cin_islabel(void) // XXX curwin->w_cursor = *trypos; } - line = (char_u *)get_cursor_line_ptr(); + line = get_cursor_line_ptr(); if (cin_ispreproc(line)) { // ignore #defines, #if, etc. continue; } @@ -395,10 +403,10 @@ bool cin_islabel(void) // XXX // "[typedef] [static|public|protected|private] = {" static int cin_isinit(void) { - const char_u *s; + const char *s; static char *skip[] = { "static", "public", "protected", "private" }; - s = cin_skipcomment((char_u *)get_cursor_line_ptr()); + s = cin_skipcomment(get_cursor_line_ptr()); if (cin_starts_with(s, "typedef")) { s = cin_skipcomment(s + 7); @@ -424,7 +432,7 @@ static int cin_isinit(void) return true; } - if (cin_ends_in(s, (char_u *)"=", (char_u *)"{")) { + if (cin_ends_in(s, "=", "{")) { return true; } @@ -434,7 +442,7 @@ static int cin_isinit(void) /// Recognize a switch label: "case .*:" or "default:". /// /// @param strict Allow relaxed check of case statement for JS -bool cin_iscase(const char_u *s, bool strict) +bool cin_iscase(const char *s, bool strict) { s = cin_skipcomment(s); if (cin_starts_with(s, "case")) { @@ -458,9 +466,8 @@ bool cin_iscase(const char_u *s, bool strict) // JS etc. if (strict) { return false; // stop at string - } else { - return true; } + return true; } } return false; @@ -473,27 +480,27 @@ bool cin_iscase(const char_u *s, bool strict) } // Recognize a "default" switch label. -static int cin_isdefault(const char_u *s) +static int cin_isdefault(const char *s) { - return STRNCMP(s, "default", 7) == 0 + return strncmp(s, "default", 7) == 0 && *(s = cin_skipcomment(s + 7)) == ':' && s[1] != ':'; } /// Recognize a scope declaration label set in 'cinscopedecls'. -bool cin_isscopedecl(const char_u *p) +bool cin_isscopedecl(const char *p) { - const char_u *s = cin_skipcomment(p); + const char *s = cin_skipcomment(p); const size_t cinsd_len = strlen(curbuf->b_p_cinsd) + 1; - char_u *cinsd_buf = xmalloc(cinsd_len); + char *cinsd_buf = xmalloc(cinsd_len); bool found = false; for (char *cinsd = curbuf->b_p_cinsd; *cinsd;) { - const size_t len = copy_option_part(&cinsd, (char *)cinsd_buf, cinsd_len, ","); - if (STRNCMP(s, cinsd_buf, len) == 0) { - const char_u *skip = cin_skipcomment(s + len); + const size_t len = copy_option_part(&cinsd, cinsd_buf, cinsd_len, ","); + if (strncmp(s, cinsd_buf, len) == 0) { + const char *skip = cin_skipcomment(s + len); if (*skip == ':' && skip[1] != ':') { found = true; break; @@ -510,33 +517,33 @@ bool cin_isscopedecl(const char_u *p) #define FIND_NAMESPACE_LIM 20 // Recognize a "namespace" scope declaration. -static bool cin_is_cpp_namespace(const char_u *s) +static bool cin_is_cpp_namespace(const char *s) { - const char_u *p; + const char *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((char_u *)skipwhite((char *)s + 6)); + if (strncmp(s, "inline", 6) == 0 && (s[6] == NUL || !vim_iswordc((uint8_t)s[6]))) { + s = cin_skipcomment(skipwhite(s + 6)); } - if (STRNCMP(s, "namespace", 9) == 0 && (s[9] == NUL || !vim_iswordc(s[9]))) { - p = cin_skipcomment((char_u *)skipwhite((char *)s + 9)); + if (strncmp(s, "namespace", 9) == 0 && (s[9] == NUL || !vim_iswordc((uint8_t)s[9]))) { + p = cin_skipcomment(skipwhite(s + 9)); while (*p != NUL) { if (ascii_iswhite(*p)) { has_name = true; // found end of a name - p = cin_skipcomment((char_u *)skipwhite((char *)p)); + p = cin_skipcomment(skipwhite(p)); } else if (*p == '{') { break; - } else if (vim_iswordc(*p)) { + } else if (vim_iswordc((uint8_t)(*p))) { has_name_start = true; if (has_name) { return false; // word character after skipping past name } p++; - } else if (p[0] == ':' && p[1] == ':' && vim_iswordc(p[2])) { + } else if (p[0] == ':' && p[1] == ':' && vim_iswordc((uint8_t)p[2])) { if (!has_name_start || has_name) { return false; } @@ -555,7 +562,7 @@ static bool cin_is_cpp_namespace(const char_u *s) // Return NULL if not found. // case 234: a = b; // ^ -static const char_u *after_label(const char_u *l) +static const char *after_label(const char *l) { for (; *l; l++) { if (*l == ':') { @@ -582,12 +589,12 @@ static const char_u *after_label(const char_u *l) // Return 0 if there is nothing after the label. static int get_indent_nolabel(linenr_T lnum) // XXX { - const char_u *l; + const char *l; pos_T fp; colnr_T col; - const char_u *p; + const char *p; - l = (char_u *)ml_get(lnum); + l = ml_get(lnum); p = after_label(l); if (p == NULL) { return 0; @@ -603,25 +610,25 @@ static int get_indent_nolabel(linenr_T lnum) // XXX // Also return a pointer to the text (after the label) in "pp". // label: if (asdf && asdfasdf) // ^ -static int skip_label(linenr_T lnum, const char_u **pp) +static int skip_label(linenr_T lnum, const char **pp) { - const char_u *l; + const char *l; int amount; pos_T cursor_save; cursor_save = curwin->w_cursor; curwin->w_cursor.lnum = lnum; - l = (char_u *)get_cursor_line_ptr(); + l = get_cursor_line_ptr(); // XXX if (cin_iscase(l, false) || cin_isscopedecl(l) || cin_islabel()) { amount = get_indent_nolabel(lnum); - l = after_label((char_u *)get_cursor_line_ptr()); + l = after_label(get_cursor_line_ptr()); if (l == NULL) { // just in case - l = (char_u *)get_cursor_line_ptr(); + l = get_cursor_line_ptr(); } } else { amount = get_indent(); - l = (char_u *)get_cursor_line_ptr(); + l = get_cursor_line_ptr(); } *pp = l; @@ -636,38 +643,38 @@ static int skip_label(linenr_T lnum, const char_u **pp) // Returns zero when it doesn't look like a declaration. static int cin_first_id_amount(void) { - char_u *line, *p, *s; + char *line, *p, *s; int len; pos_T fp; colnr_T col; - line = (char_u *)get_cursor_line_ptr(); - p = (char_u *)skipwhite((char *)line); - len = (int)((char_u *)skiptowhite((char *)p) - p); - if (len == 6 && STRNCMP(p, "static", 6) == 0) { - p = (char_u *)skipwhite((char *)p + 6); - len = (int)((char_u *)skiptowhite((char *)p) - p); - } - if (len == 6 && STRNCMP(p, "struct", 6) == 0) { - p = (char_u *)skipwhite((char *)p + 6); - } else if (len == 4 && STRNCMP(p, "enum", 4) == 0) { - p = (char_u *)skipwhite((char *)p + 4); - } else if ((len == 8 && STRNCMP(p, "unsigned", 8) == 0) - || (len == 6 && STRNCMP(p, "signed", 6) == 0)) { - s = (char_u *)skipwhite((char *)p + len); - if ((STRNCMP(s, "int", 3) == 0 && ascii_iswhite(s[3])) - || (STRNCMP(s, "long", 4) == 0 && ascii_iswhite(s[4])) - || (STRNCMP(s, "short", 5) == 0 && ascii_iswhite(s[5])) - || (STRNCMP(s, "char", 4) == 0 && ascii_iswhite(s[4]))) { + line = get_cursor_line_ptr(); + p = skipwhite(line); + len = (int)(skiptowhite(p) - p); + if (len == 6 && strncmp(p, "static", 6) == 0) { + p = skipwhite(p + 6); + len = (int)(skiptowhite(p) - p); + } + if (len == 6 && strncmp(p, "struct", 6) == 0) { + p = skipwhite(p + 6); + } else if (len == 4 && strncmp(p, "enum", 4) == 0) { + p = skipwhite(p + 4); + } else if ((len == 8 && strncmp(p, "unsigned", 8) == 0) + || (len == 6 && strncmp(p, "signed", 6) == 0)) { + s = skipwhite(p + len); + if ((strncmp(s, "int", 3) == 0 && ascii_iswhite(s[3])) + || (strncmp(s, "long", 4) == 0 && ascii_iswhite(s[4])) + || (strncmp(s, "short", 5) == 0 && ascii_iswhite(s[5])) + || (strncmp(s, "char", 4) == 0 && ascii_iswhite(s[4]))) { p = s; } } - for (len = 0; vim_isIDc(p[len]); len++) {} + for (len = 0; vim_isIDc((uint8_t)p[len]); len++) {} if (len == 0 || !ascii_iswhite(p[len]) || cin_nocode(p)) { return 0; } - p = (char_u *)skipwhite((char *)p + len); + p = skipwhite(p + len); fp.lnum = curwin->w_cursor.lnum; fp.col = (colnr_T)(p - line); getvcol(curwin, &fp, &col, NULL, NULL); @@ -683,21 +690,21 @@ static int cin_first_id_amount(void) // here"; static int cin_get_equal_amount(linenr_T lnum) { - const char_u *line; - const char_u *s; + const char *line; + const char *s; colnr_T col; pos_T fp; if (lnum > 1) { - line = (char_u *)ml_get(lnum - 1); - if (*line != NUL && line[STRLEN(line) - 1] == '\\') { + line = ml_get(lnum - 1); + if (*line != NUL && line[strlen(line) - 1] == '\\') { return -1; } } - s = (char_u *)ml_get(lnum); + s = ml_get(lnum); line = s; - while (*s != NUL && vim_strchr("=;{}\"'", *s) == NULL) { + while (*s != NUL && vim_strchr("=;{}\"'", (uint8_t)(*s)) == NULL) { if (cin_iscomment(s)) { // ignore comments s = cin_skipcomment(s); } else { @@ -708,7 +715,7 @@ static int cin_get_equal_amount(linenr_T lnum) return 0; } - s = (char_u *)skipwhite((char *)s + 1); + s = skipwhite(s + 1); if (cin_nocode(s)) { return 0; } @@ -724,9 +731,9 @@ static int cin_get_equal_amount(linenr_T lnum) } // Recognize a preprocessor statement: Any line that starts with '#'. -static int cin_ispreproc(const char_u *s) +static int cin_ispreproc(const char *s) { - if (*skipwhite((char *)s) == '#') { + if (*skipwhite(s) == '#') { return true; } return false; @@ -736,14 +743,14 @@ static int cin_ispreproc(const 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(const char_u **pp, linenr_T *lnump, int *amount) +static int cin_ispreproc_cont(const char **pp, linenr_T *lnump, int *amount) { - const char_u *line = *pp; + const char *line = *pp; linenr_T lnum = *lnump; int retval = false; int candidate_amount = *amount; - if (*line != NUL && line[STRLEN(line) - 1] == '\\') { + if (*line != NUL && line[strlen(line) - 1] == '\\') { candidate_amount = get_indent_lnum(lnum); } @@ -756,14 +763,14 @@ static int cin_ispreproc_cont(const char_u **pp, linenr_T *lnump, int *amount) if (lnum == 1) { break; } - line = (char_u *)ml_get(--lnum); - if (*line == NUL || line[STRLEN(line) - 1] != '\\') { + line = ml_get(--lnum); + if (*line == NUL || line[strlen(line) - 1] != '\\') { break; } } if (lnum != *lnump) { - *pp = (char_u *)ml_get(*lnump); + *pp = ml_get(*lnump); } if (retval) { *amount = candidate_amount; @@ -772,13 +779,13 @@ static int cin_ispreproc_cont(const char_u **pp, linenr_T *lnump, int *amount) } // Recognize the start of a C or C++ comment. -static int cin_iscomment(const char_u *p) +static int cin_iscomment(const char *p) { return p[0] == '/' && (p[1] == '*' || p[1] == '/'); } // Recognize the start of a "//" comment. -static int cin_islinecomment(const char_u *p) +static int cin_islinecomment(const char *p) { return p[0] == '/' && p[1] == '/'; } @@ -794,9 +801,9 @@ static int cin_islinecomment(const char_u *p) /// /// @return the character terminating the line (ending char's have precedence if /// both apply in order to determine initializations). -static char_u cin_isterminated(const char_u *s, int incl_open, int incl_comma) +static char cin_isterminated(const char *s, int incl_open, int incl_comma) { - char_u found_start = 0; + char found_start = 0; unsigned n_open = 0; int is_else = false; @@ -845,9 +852,9 @@ static char_u cin_isterminated(const char_u *s, int incl_open, int incl_comma) /// 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(const char_u **sp, linenr_T first_lnum, linenr_T min_lnum) +static int cin_isfuncdecl(const char **sp, linenr_T first_lnum, linenr_T min_lnum) { - const char_u *s; + const char *s; linenr_T lnum = first_lnum; linenr_T save_lnum = curwin->w_cursor.lnum; int retval = false; @@ -855,7 +862,7 @@ static int cin_isfuncdecl(const char_u **sp, linenr_T first_lnum, linenr_T min_l int just_started = true; if (sp == NULL) { - s = (char_u *)ml_get(lnum); + s = ml_get(lnum); } else { s = *sp; } @@ -868,7 +875,7 @@ static int cin_isfuncdecl(const char_u **sp, linenr_T first_lnum, linenr_T min_l curwin->w_cursor.lnum = save_lnum; return false; } - s = (char_u *)ml_get(lnum); + s = ml_get(lnum); } curwin->w_cursor.lnum = save_lnum; @@ -907,8 +914,8 @@ static int cin_isfuncdecl(const char_u **sp, linenr_T first_lnum, linenr_T min_l // #if defined(x) && {backslash} // defined(y) lnum = first_lnum - 1; - s = (char_u *)ml_get(lnum); - if (*s == NUL || s[STRLEN(s) - 1] != '\\') { + s = ml_get(lnum); + if (*s == NUL || s[strlen(s) - 1] != '\\') { retval = true; } goto done; @@ -924,7 +931,7 @@ static int cin_isfuncdecl(const char_u **sp, linenr_T first_lnum, linenr_T min_l if (lnum >= curbuf->b_ml.ml_line_count) { break; } - s = (char_u *)ml_get(++lnum); + s = ml_get(++lnum); if (!cin_ispreproc(s)) { break; } @@ -934,7 +941,7 @@ static int cin_isfuncdecl(const char_u **sp, linenr_T first_lnum, linenr_T min_l } // Require a comma at end of the line or a comma or ')' at the // start of next line. - s = (char_u *)skipwhite((char *)s); + s = skipwhite(s); if (!just_started && (!comma && *s != ',' && *s != ')')) { break; } @@ -949,34 +956,34 @@ static int cin_isfuncdecl(const char_u **sp, linenr_T first_lnum, linenr_T min_l done: if (lnum != first_lnum && sp != NULL) { - *sp = (char_u *)ml_get(first_lnum); + *sp = ml_get(first_lnum); } return retval; } -static int cin_isif(const char_u *p) +static int cin_isif(const char *p) { - return STRNCMP(p, "if", 2) == 0 && !vim_isIDc(p[2]); + return strncmp(p, "if", 2) == 0 && !vim_isIDc((uint8_t)p[2]); } -static int cin_iselse(const char_u *p) +static int cin_iselse(const char *p) { if (*p == '}') { // accept "} else" p = cin_skipcomment(p + 1); } - return STRNCMP(p, "else", 4) == 0 && !vim_isIDc(p[4]); + return strncmp(p, "else", 4) == 0 && !vim_isIDc((uint8_t)p[4]); } -static int cin_isdo(const char_u *p) +static int cin_isdo(const char *p) { - return STRNCMP(p, "do", 2) == 0 && !vim_isIDc(p[2]); + return strncmp(p, "do", 2) == 0 && !vim_isIDc((uint8_t)p[2]); } // Check if this is a "while" that should have a matching "do". // 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(const char_u *p, linenr_T lnum) // XXX +static int cin_iswhileofdo(const char *p, linenr_T lnum) // XXX { pos_T cursor_save; pos_T *trypos; @@ -990,7 +997,7 @@ static int cin_iswhileofdo(const char_u *p, linenr_T lnum) // XXX cursor_save = curwin->w_cursor; curwin->w_cursor.lnum = lnum; curwin->w_cursor.col = 0; - p = (char_u *)get_cursor_line_ptr(); + p = get_cursor_line_ptr(); while (*p && *p != 'w') { // skip any '}', until the 'w' of the "while" p++; curwin->w_cursor.col++; @@ -1008,7 +1015,7 @@ static int cin_iswhileofdo(const char_u *p, linenr_T lnum) // XXX // Return 0 if there is none. // 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(const char_u *line, int *poffset) +static int cin_is_if_for_while_before_offset(const char *line, int *poffset) { int offset = *poffset; @@ -1020,19 +1027,19 @@ static int cin_is_if_for_while_before_offset(const char_u *line, int *poffset) } offset -= 1; - if (!STRNCMP(line + offset, "if", 2)) { + if (!strncmp(line + offset, "if", 2)) { goto probablyFound; } if (offset >= 1) { offset -= 1; - if (!STRNCMP(line + offset, "for", 3)) { + if (!strncmp(line + offset, "for", 3)) { goto probablyFound; } if (offset >= 2) { offset -= 2; - if (!STRNCMP(line + offset, "while", 5)) { + if (!strncmp(line + offset, "while", 5)) { goto probablyFound; } } @@ -1040,7 +1047,7 @@ static int cin_is_if_for_while_before_offset(const char_u *line, int *poffset) return 0; probablyFound: - if (!offset || !vim_isIDc(line[offset - 1])) { + if (!offset || !vim_isIDc((uint8_t)line[offset - 1])) { *poffset = offset; return 1; } @@ -1055,9 +1062,9 @@ probablyFound: /// Adjust the cursor to the line with "while". static int cin_iswhileofdo_end(int terminated) { - const char_u *line; - const char_u *p; - const char_u *s; + const char *line; + const char *p; + const char *s; pos_T *trypos; int i; @@ -1065,11 +1072,11 @@ static int cin_iswhileofdo_end(int terminated) return false; } - p = line = (char_u *)get_cursor_line_ptr(); + p = line = get_cursor_line_ptr(); while (*p != NUL) { p = cin_skipcomment(p); if (*p == ')') { - s = (char_u *)skipwhite((char *)p + 1); + s = skipwhite(p + 1); if (*s == ';' && cin_nocode(s + 1)) { // Found ");" at end of the line, now check there is "while" // before the matching '('. XXX @@ -1077,7 +1084,7 @@ static int cin_iswhileofdo_end(int terminated) curwin->w_cursor.col = i; trypos = find_match_paren(curbuf->b_ind_maxparen); if (trypos != NULL) { - s = cin_skipcomment((char_u *)ml_get(trypos->lnum)); + s = cin_skipcomment(ml_get(trypos->lnum)); if (*s == '}') { // accept "} while (cond);" s = cin_skipcomment(s + 1); } @@ -1088,7 +1095,7 @@ static int cin_iswhileofdo_end(int terminated) } // Searching may have made "line" invalid, get it again. - line = (char_u *)get_cursor_line_ptr(); + line = get_cursor_line_ptr(); p = line + i; } } @@ -1099,9 +1106,9 @@ static int cin_iswhileofdo_end(int terminated) return false; } -static int cin_isbreak(const char_u *p) +static int cin_isbreak(const char *p) { - return STRNCMP(p, "break", 5) == 0 && !vim_isIDc(p[5]); + return strncmp(p, "break", 5) == 0 && !vim_isIDc((uint8_t)p[5]); } // Find the position of a C++ base-class declaration or @@ -1118,10 +1125,10 @@ static int cin_isbreak(const char_u *p) static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) { lpos_T *pos = &cached->lpos; // find position - const char_u *s; + const char *s; int class_or_struct, lookfor_ctor_init, cpp_base_class; linenr_T lnum = curwin->w_cursor.lnum; - const char_u *line = (char_u *)get_cursor_line_ptr(); + const char *line = get_cursor_line_ptr(); if (pos->lnum <= lnum) { return cached->found; // Use the cached result @@ -1129,7 +1136,7 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) pos->col = 0; - s = (char_u *)skipwhite((char *)line); + s = skipwhite(line); if (*s == '#') { // skip #define FOO x ? (x) : x return false; } @@ -1153,8 +1160,8 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) // somethingelse(3) // {} while (lnum > 1) { - line = (char_u *)ml_get(lnum - 1); - s = (char_u *)skipwhite((char *)line); + line = ml_get(lnum - 1); + s = skipwhite(line); if (*s == '#' || *s == NUL) { break; } @@ -1175,7 +1182,7 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) } pos->lnum = lnum; - line = (char_u *)ml_get(lnum); + line = ml_get(lnum); s = line; for (;;) { if (*s == NUL) { @@ -1183,7 +1190,7 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) break; } // Continue in the cursor line. - line = (char_u *)ml_get(++lnum); + line = ml_get(++lnum); s = line; } if (s == line) { @@ -1215,8 +1222,8 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) } else { s = cin_skipcomment(s + 1); } - } else if ((STRNCMP(s, "class", 5) == 0 && !vim_isIDc(s[5])) - || (STRNCMP(s, "struct", 6) == 0 && !vim_isIDc(s[6]))) { + } else if ((strncmp(s, "class", 5) == 0 && !vim_isIDc((uint8_t)s[5])) + || (strncmp(s, "struct", 6) == 0 && !vim_isIDc((uint8_t)s[6]))) { class_or_struct = true; lookfor_ctor_init = false; @@ -1236,7 +1243,7 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) } else if (s[0] == '?') { // Avoid seeing '() :' after '?' as constructor init. return false; - } else if (!vim_isIDc(s[0])) { + } else if (!vim_isIDc((uint8_t)s[0])) { // if it is not an identifier, we are wrong class_or_struct = false; lookfor_ctor_init = false; @@ -1274,11 +1281,11 @@ static int get_baseclass_amount(int col) if (col == 0) { amount = get_indent(); - if (find_last_paren((char_u *)get_cursor_line_ptr(), '(', ')') + if (find_last_paren(get_cursor_line_ptr(), '(', ')') && (trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL) { amount = get_indent_lnum(trypos->lnum); // XXX } - if (!cin_ends_in((char_u *)get_cursor_line_ptr(), (char_u *)",", NULL)) { + if (!cin_ends_in(get_cursor_line_ptr(), ",", NULL)) { amount += curbuf->b_ind_cpp_baseclass; } } else { @@ -1295,18 +1302,18 @@ static int get_baseclass_amount(int col) /// Return true if string "s" ends with the string "find", possibly followed by /// white space and comments. Skip strings and comments. /// Ignore "ignore" after "find" if it's not NULL. -static int cin_ends_in(const char_u *s, const char_u *find, const char_u *ignore) +static int cin_ends_in(const char *s, const char *find, const char *ignore) { - const char_u *p = s; - const char_u *r; - int len = (int)STRLEN(find); + const char *p = s; + const char *r; + int len = (int)strlen(find); while (*p != NUL) { p = cin_skipcomment(p); - if (STRNCMP(p, find, len) == 0) { - r = (char_u *)skipwhite((char *)p + len); - if (ignore != NULL && STRNCMP(r, ignore, STRLEN(ignore)) == 0) { - r = (char_u *)skipwhite((char *)r + STRLEN(ignore)); + if (strncmp(p, find, (size_t)len) == 0) { + r = skipwhite(p + len); + if (ignore != NULL && strncmp(r, ignore, strlen(ignore)) == 0) { + r = skipwhite(r + strlen(ignore)); } if (cin_nocode(r)) { return true; @@ -1320,25 +1327,25 @@ static int cin_ends_in(const char_u *s, const char_u *find, const char_u *ignore } /// Return true when "s" starts with "word" and then a non-ID character. -static int cin_starts_with(const char_u *s, const char *word) +static int cin_starts_with(const char *s, const char *word) { - int l = (int)strlen(word); + size_t l = strlen(word); - return STRNCMP(s, word, l) == 0 && !vim_isIDc(s[l]); + return strncmp(s, word, l) == 0 && !vim_isIDc((uint8_t)s[l]); } /// Recognize a `extern "C"` or `extern "C++"` linkage specifications. -static int cin_is_cpp_extern_c(const char_u *s) +static int cin_is_cpp_extern_c(const char *s) { - const char_u *p; + const char *p; int has_string_literal = false; s = cin_skipcomment(s); - if (STRNCMP(s, "extern", 6) == 0 && (s[6] == NUL || !vim_iswordc(s[6]))) { - p = cin_skipcomment((char_u *)skipwhite((char *)s + 6)); + if (strncmp(s, "extern", 6) == 0 && (s[6] == NUL || !vim_iswordc((uint8_t)s[6]))) { + p = cin_skipcomment(skipwhite(s + 6)); while (*p != NUL) { if (ascii_iswhite(*p)) { - p = cin_skipcomment((char_u *)skipwhite((char *)p)); + p = cin_skipcomment(skipwhite(p)); } else if (*p == '{') { break; } else if (p[0] == '"' && p[1] == 'C' && p[2] == '"') { @@ -1367,11 +1374,11 @@ static int cin_is_cpp_extern_c(const char_u *s) // Return the column found. static int cin_skip2pos(pos_T *trypos) { - const char_u *line; - const char_u *p; - const char_u *new_p; + const char *line; + const char *p; + const char *new_p; - line = (char_u *)ml_get(trypos->lnum); + line = ml_get(trypos->lnum); p = line; while (*p && (colnr_T)(p - line) < trypos->col) { if (cin_iscomment(p)) { @@ -1429,7 +1436,7 @@ static pos_T *find_match_paren(int ind_maxparen) return find_match_char('(', ind_maxparen); } -static pos_T *find_match_char(char_u c, int ind_maxparen) +static pos_T *find_match_char(char c, int ind_maxparen) { pos_T cursor_save; pos_T *trypos; @@ -1439,7 +1446,7 @@ static pos_T *find_match_char(char_u c, int ind_maxparen) cursor_save = curwin->w_cursor; ind_maxp_wk = ind_maxparen; retry: - if ((trypos = findmatchlimit(NULL, c, 0, ind_maxp_wk)) != NULL) { + if ((trypos = findmatchlimit(NULL, (uint8_t)c, 0, ind_maxp_wk)) != NULL) { // check if the ( is in a // comment if ((colnr_T)cin_skip2pos(trypos) > trypos->col) { ind_maxp_wk = ind_maxparen - (cursor_save.lnum - trypos->lnum); @@ -1507,7 +1514,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(const char_u *l, int start, int end) +static int find_last_paren(const char *l, char start, char end) { int i; int retval = false; @@ -1837,22 +1844,22 @@ int get_c_indent(void) int scope_amount; int cur_amount = MAXCOL; colnr_T col; - char_u *theline; + char *theline; char *linecopy; pos_T *trypos; pos_T *comment_pos; pos_T *tryposBrace = NULL; pos_T tryposCopy; pos_T our_paren_pos; - char_u *start; + char *start; int start_brace; #define BRACE_IN_COL0 1 // '{' is in column 0 #define BRACE_AT_START 2 // '{' is at start of line #define BRACE_AT_END 3 // '{' is at end of line linenr_T ourscope; - const char_u *l; - const char_u *look; - char_u terminated; + const char *l; + const char *look; + char terminated; int lookfor; #define LOOKFOR_INITIAL 0 #define LOOKFOR_IF 1 @@ -1906,7 +1913,7 @@ int get_c_indent(void) linecopy[curwin->w_cursor.col] = NUL; } - theline = (char_u *)skipwhite(linecopy); + theline = skipwhite(linecopy); // move the cursor to the start of the line @@ -1931,8 +1938,8 @@ int get_c_indent(void) // #defines and so on go at the left when included in 'cinkeys', // excluding pragmas when customized in 'cinoptions' if (*theline == '#' && (*linecopy == '#' || in_cinkeys('#', ' ', true))) { - const char_u *const directive = (char_u *)skipwhite((char *)theline + 1); - if (curbuf->b_ind_pragma == 0 || STRNCMP(directive, "pragma", 6) != 0) { + const char *const directive = skipwhite(theline + 1); + if (curbuf->b_ind_pragma == 0 || strncmp(directive, "pragma", 6) != 0) { amount = curbuf->b_ind_hash_comment; goto theend; } @@ -2019,22 +2026,22 @@ int get_c_indent(void) } else if (what == COM_END) { // If our line starts with the middle comment string, line it // up with the comment opener per the 'comments' option. - if (STRNCMP(theline, lead_middle, lead_middle_len) == 0 - && STRNCMP(theline, lead_end, strlen(lead_end)) != 0) { + if (strncmp(theline, lead_middle, (size_t)lead_middle_len) == 0 + && strncmp(theline, lead_end, strlen(lead_end)) != 0) { done = true; if (curwin->w_cursor.lnum > 1) { // If the start comment string matches in the previous // line, use the indent of that line plus offset. If // the middle comment string matches in the previous // line, use the indent of that line. XXX - look = (char_u *)skipwhite(ml_get(curwin->w_cursor.lnum - 1)); - if (STRNCMP(look, lead_start, lead_start_len) == 0) { + look = skipwhite(ml_get(curwin->w_cursor.lnum - 1)); + if (strncmp(look, lead_start, (size_t)lead_start_len) == 0) { amount = get_indent_lnum(curwin->w_cursor.lnum - 1); - } else if (STRNCMP(look, lead_middle, lead_middle_len) == 0) { + } else if (strncmp(look, lead_middle, (size_t)lead_middle_len) == 0) { amount = get_indent_lnum(curwin->w_cursor.lnum - 1); break; - } else if (STRNCMP(ml_get(comment_pos->lnum) + comment_pos->col, - lead_start, lead_start_len) != 0) { + } else if (strncmp(ml_get(comment_pos->lnum) + comment_pos->col, + lead_start, (size_t)lead_start_len) != 0) { // If the start comment string doesn't match with the // start of the comment, skip this entry. XXX continue; @@ -2050,8 +2057,8 @@ int get_c_indent(void) // If our line starts with the end comment string, line it up // with the middle comment - if (STRNCMP(theline, lead_middle, lead_middle_len) != 0 - && STRNCMP(theline, lead_end, strlen(lead_end)) == 0) { + if (strncmp(theline, lead_middle, (size_t)lead_middle_len) != 0 + && strncmp(theline, lead_end, strlen(lead_end)) == 0) { amount = get_indent_lnum(curwin->w_cursor.lnum - 1); // XXX if (off != 0) { @@ -2088,10 +2095,10 @@ int get_c_indent(void) } if (amount == -1) { // use the comment opener if (!curbuf->b_ind_in_comment2) { - start = (char_u *)ml_get(comment_pos->lnum); + start = ml_get(comment_pos->lnum); look = start + comment_pos->col + 2; // skip / and * if (*look != NUL) { // if something after it - comment_pos->col = (colnr_T)((char_u *)skipwhite((char *)look) - start); + comment_pos->col = (colnr_T)(skipwhite(look) - start); } } getvcol(curwin, comment_pos, &col, NULL, NULL); @@ -2104,7 +2111,7 @@ int get_c_indent(void) goto theend; } // Are we looking at a ']' that has a match? - if (*skipwhite((char *)theline) == ']' + if (*skipwhite(theline) == ']' && (trypos = find_match_char('[', curbuf->b_ind_maxparen)) != NULL) { // align with the line containing the '['. amount = get_indent_lnum(trypos->lnum); @@ -2138,7 +2145,7 @@ int get_c_indent(void) } else { amount = -1; for (lnum = cur_curpos.lnum - 1; lnum > our_paren_pos.lnum; lnum--) { - l = (char_u *)skipwhite(ml_get(lnum)); + l = skipwhite(ml_get(lnum)); if (cin_nocode(l)) { // skip comment lines continue; } @@ -2184,7 +2191,7 @@ int get_c_indent(void) pos_T cursor_save = curwin->w_cursor; pos_T outermost; - char_u *line; + char *line; trypos = &our_paren_pos; do { @@ -2197,23 +2204,23 @@ int get_c_indent(void) curwin->w_cursor = cursor_save; - line = (char_u *)ml_get(outermost.lnum); + line = ml_get(outermost.lnum); is_if_for_while = cin_is_if_for_while_before_offset(line, &outermost.col); } amount = skip_label(our_paren_pos.lnum, &look); - look = (char_u *)skipwhite((char *)look); + look = skipwhite(look); if (*look == '(') { linenr_T save_lnum = curwin->w_cursor.lnum; - char_u *line; + char *line; int look_col; // Ignore a '(' in front of the line that has a match before // our matching '('. curwin->w_cursor.lnum = our_paren_pos.lnum; - line = (char_u *)get_cursor_line_ptr(); + line = get_cursor_line_ptr(); look_col = (int)(look - line); curwin->w_cursor.col = look_col + 1; if ((trypos = findmatchlimit(NULL, ')', 0, @@ -2225,7 +2232,7 @@ int get_c_indent(void) } curwin->w_cursor.lnum = save_lnum; - look = (char_u *)ml_get(our_paren_pos.lnum) + look_col; + look = ml_get(our_paren_pos.lnum) + look_col; } if (theline[0] == ')' || (curbuf->b_ind_unclosed == 0 && is_if_for_while == 0) @@ -2240,9 +2247,9 @@ int get_c_indent(void) // lines). if (theline[0] != ')') { cur_amount = MAXCOL; - l = (char_u *)ml_get(our_paren_pos.lnum); + l = ml_get(our_paren_pos.lnum); if (curbuf->b_ind_unclosed_wrapped - && cin_ends_in(l, (char_u *)"(", NULL)) { + && cin_ends_in(l, "(", NULL)) { // look for opening unmatched paren, indent one level // for each additional level n = 1; @@ -2357,13 +2364,13 @@ int get_c_indent(void) tryposBrace = &tryposCopy; trypos = tryposBrace; ourscope = trypos->lnum; - start = (char_u *)ml_get(ourscope); + start = ml_get(ourscope); // Now figure out how indented the line is in general. // If the brace was at the start of the line, we use that; // otherwise, check out the indentation of the line as // a whole and then add the "imaginary indent" to that. - look = (char_u *)skipwhite((char *)start); + look = skipwhite(start); if (*look == '{') { getvcol(curwin, trypos, &col, NULL, NULL); amount = col; @@ -2390,7 +2397,7 @@ int get_c_indent(void) // ldfd) { // } if ((curbuf->b_ind_js || curbuf->b_ind_keep_case_label) - && cin_iscase((char_u *)skipwhite(get_cursor_line_ptr()), false)) { + && cin_iscase(skipwhite(get_cursor_line_ptr()), false)) { amount = get_indent(); } else if (curbuf->b_ind_js) { amount = get_indent_lnum(lnum); @@ -2449,7 +2456,7 @@ int get_c_indent(void) if (start_brace == BRACE_AT_END) { // '{' is at end of line amount += curbuf->b_ind_open_imag; - l = (char_u *)skipwhite(get_cursor_line_ptr()); + l = skipwhite(get_cursor_line_ptr()); if (cin_is_cpp_namespace(l)) { amount += curbuf->b_ind_cpp_namespace; } else if (cin_is_cpp_extern_c(l)) { @@ -2522,7 +2529,7 @@ int get_c_indent(void) break; } - l = (char_u *)get_cursor_line_ptr(); + l = get_cursor_line_ptr(); // If we're in a comment or raw string now, skip to // the start of it. @@ -2633,7 +2640,7 @@ int get_c_indent(void) break; } - l = (char_u *)get_cursor_line_ptr(); + l = get_cursor_line_ptr(); // If we're in a comment or raw string now, skip // to the start of it. @@ -2676,7 +2683,7 @@ int get_c_indent(void) continue; } - l = (char_u *)get_cursor_line_ptr(); + l = get_cursor_line_ptr(); // If this is a switch() label, may line up relative to that. // If this is a C++ scope declaration, do the same. @@ -2750,8 +2757,8 @@ int get_c_indent(void) // -> y = y + 1; if (n) { amount = n; - l = after_label((char_u *)get_cursor_line_ptr()); - if (l != NULL && cin_is_cinword((char *)l)) { + l = after_label(get_cursor_line_ptr()); + if (l != NULL && cin_is_cinword(l)) { if (theline[0] == '{') { amount += curbuf->b_ind_open_extra; } else { @@ -2789,7 +2796,7 @@ int get_c_indent(void) // Ignore jump labels with nothing after them. if (!curbuf->b_ind_js && cin_islabel()) { - l = after_label((char_u *)get_cursor_line_ptr()); + l = after_label(get_cursor_line_ptr()); if (l == NULL || cin_nocode(l)) { continue; } @@ -2799,7 +2806,7 @@ int get_c_indent(void) // Ignore comment and empty lines. // (need to get the line again, cin_islabel() may have // unlocked it) - l = (char_u *)get_cursor_line_ptr(); + l = get_cursor_line_ptr(); if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, &amount) || cin_nocode(l)) { continue; @@ -2811,7 +2818,7 @@ int get_c_indent(void) n = 0; if (lookfor != LOOKFOR_TERM && curbuf->b_ind_cpp_baseclass > 0) { n = cin_is_cpp_baseclass(&cache_cpp_baseclass); - l = (char_u *)get_cursor_line_ptr(); + l = get_cursor_line_ptr(); } if (n) { if (lookfor == LOOKFOR_UNTERM) { @@ -2877,21 +2884,20 @@ int get_c_indent(void) // Line below current line is the one that starts a // (possibly broken) line ending in a comma. break; - } else { - amount = get_indent(); - if (curwin->w_cursor.lnum - 1 == ourscope) { - // line above is start of the scope, thus current - // line is the one that stars a (possibly broken) - // line ending in a comma. - break; - } + } + amount = get_indent(); + if (curwin->w_cursor.lnum - 1 == ourscope) { + // line above is start of the scope, thus current + // line is the one that stars a (possibly broken) + // line ending in a comma. + break; } } if (terminated == 0 || (lookfor != LOOKFOR_UNTERM && terminated == ',')) { if (lookfor != LOOKFOR_ENUM_OR_INIT - && (*skipwhite((char *)l) == '[' || l[STRLEN(l) - 1] == '[')) { + && (*skipwhite(l) == '[' || l[strlen(l) - 1] == '[')) { amount += ind_continuation; } // If we're in the middle of a paren thing, Go back to the line @@ -2923,7 +2929,7 @@ int get_c_indent(void) // case xx: if ( asdf && // asdf) curwin->w_cursor = *trypos; - l = (char_u *)get_cursor_line_ptr(); + l = get_cursor_line_ptr(); if (cin_iscase(l, false) || cin_isscopedecl(l)) { curwin->w_cursor.lnum++; curwin->w_cursor.col = 0; @@ -2938,8 +2944,8 @@ int get_c_indent(void) // here; if (terminated == ',') { while (curwin->w_cursor.lnum > 1) { - l = (char_u *)ml_get(curwin->w_cursor.lnum - 1); - if (*l == NUL || l[STRLEN(l) - 1] != '\\') { + l = ml_get(curwin->w_cursor.lnum - 1); + if (*l == NUL || l[strlen(l) - 1] != '\\') { break; } curwin->w_cursor.lnum--; @@ -2967,7 +2973,7 @@ int get_c_indent(void) // in the same line (scope is the same). Probably: // { 1, 2 }, // -> { 3, 4 } - if (*skipwhite((char *)l) != '{') { + if (*skipwhite(l) != '{') { amount += curbuf->b_ind_open_extra; } @@ -2982,7 +2988,7 @@ int get_c_indent(void) // Check if we are after an "if", "while", etc. // Also allow " } else". - if (cin_is_cinword((char *)l) || cin_iselse((char_u *)skipwhite((char *)l))) { + if (cin_is_cinword(l) || cin_iselse(skipwhite(l))) { // Found an unterminated line after an if (), line up // with the last one. // if (cond) @@ -3024,7 +3030,7 @@ int get_c_indent(void) // do // x = 1; // -> here - l = (char_u *)skipwhite(get_cursor_line_ptr()); + l = skipwhite(get_cursor_line_ptr()); if (cin_isdo(l)) { if (whilelevel == 0) { break; @@ -3042,7 +3048,7 @@ int get_c_indent(void) // not the one from "if () {". if (*l == '}') { curwin->w_cursor.col = - (colnr_T)(l - (char_u *)get_cursor_line_ptr()) + 1; + (colnr_T)(l - get_cursor_line_ptr()) + 1; } if ((trypos = find_start_brace()) == NULL @@ -3095,12 +3101,12 @@ int get_c_indent(void) // line up with this line, remember its indent // 100 + // NOLINT(whitespace/tab) // -> here; // NOLINT(whitespace/tab) - l = (char_u *)get_cursor_line_ptr(); + l = get_cursor_line_ptr(); amount = cur_amount; - n = (int)STRLEN(l); + n = (int)strlen(l); if (terminated == ',' - && (*skipwhite((char *)l) == ']' + && (*skipwhite(l) == ']' || (n >= 2 && l[n - 2] == ']'))) { break; } @@ -3127,7 +3133,7 @@ int get_c_indent(void) // 4 * // 5, // 6, - if (cin_iscomment((char_u *)skipwhite((char *)l))) { + if (cin_iscomment(skipwhite(l))) { break; } lookfor = LOOKFOR_COMMA; @@ -3147,7 +3153,7 @@ int get_c_indent(void) } else { if (lookfor == LOOKFOR_INITIAL && *l != NUL - && l[STRLEN(l) - 1] == '\\') { + && l[strlen(l) - 1] == '\\') { // XXX cont_amount = cin_get_equal_amount(curwin->w_cursor.lnum); } @@ -3162,7 +3168,7 @@ int get_c_indent(void) } // Check if we are after a while (cond); // If so: Ignore until the matching "do". - } else if (cin_iswhileofdo_end(terminated)) { // XXX + } else if (cin_iswhileofdo_end((uint8_t)terminated)) { // XXX // Found an unterminated line after a while ();, line up // with the last one. // while (cond); @@ -3196,14 +3202,14 @@ int get_c_indent(void) // Skip single break line, if before a switch label. It // may be lined up with the case label. if (lookfor == LOOKFOR_NOBREAK - && cin_isbreak((char_u *)skipwhite(get_cursor_line_ptr()))) { + && cin_isbreak(skipwhite(get_cursor_line_ptr()))) { lookfor = LOOKFOR_ANY; continue; } // Handle "do {" line. if (whilelevel > 0) { - l = cin_skipcomment((char_u *)get_cursor_line_ptr()); + l = cin_skipcomment(get_cursor_line_ptr()); if (cin_isdo(l)) { amount = get_indent(); // XXX whilelevel--; @@ -3253,7 +3259,7 @@ int get_c_indent(void) // asdfasdf); // here; term_again: - l = (char_u *)get_cursor_line_ptr(); + l = get_cursor_line_ptr(); if (find_last_paren(l, '(', ')') && (trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL) { // Check if we are on a case label now. This is @@ -3261,7 +3267,7 @@ term_again: // case xx: if ( asdf && // asdf) curwin->w_cursor = *trypos; - l = (char_u *)get_cursor_line_ptr(); + l = get_cursor_line_ptr(); if (cin_iscase(l, false) || cin_isscopedecl(l)) { curwin->w_cursor.lnum++; curwin->w_cursor.col = 0; @@ -3287,7 +3293,7 @@ term_again: amount += curbuf->b_ind_open_extra; } // See remark above: "Only add b_ind_open_extra.." - l = (char_u *)skipwhite((char *)l); + l = skipwhite(l); if (*l == '{') { amount -= curbuf->b_ind_open_extra; } @@ -3313,13 +3319,13 @@ term_again: // If we're at the end of a block, skip to the start of // that block. - l = (char_u *)get_cursor_line_ptr(); + l = get_cursor_line_ptr(); if (find_last_paren(l, '{', '}') // XXX && (trypos = find_start_brace()) != NULL) { curwin->w_cursor = *trypos; // if not "else {" check for terminated again // but skip block for "} else {" - l = cin_skipcomment((char_u *)get_cursor_line_ptr()); + l = cin_skipcomment(get_cursor_line_ptr()); if (*l == '}' || !cin_iselse(l)) { goto term_again; } @@ -3366,10 +3372,10 @@ term_again: // contains { or }: "void f() {\n if (1)" if (cur_curpos.lnum < curbuf->b_ml.ml_line_count && !cin_nocode(theline) - && vim_strchr((char *)theline, '{') == NULL - && vim_strchr((char *)theline, '}') == NULL - && !cin_ends_in(theline, (char_u *)":", NULL) - && !cin_ends_in(theline, (char_u *)",", NULL) + && vim_strchr(theline, '{') == NULL + && vim_strchr(theline, '}') == NULL + && !cin_ends_in(theline, ":", NULL) + && !cin_ends_in(theline, ",", NULL) && cin_isfuncdecl(NULL, cur_curpos.lnum + 1, cur_curpos.lnum + 1) && !cin_isterminated(theline, false, true)) { amount = curbuf->b_ind_func_type; @@ -3383,7 +3389,7 @@ term_again: curwin->w_cursor.lnum--; curwin->w_cursor.col = 0; - l = (char_u *)get_cursor_line_ptr(); + l = get_cursor_line_ptr(); // If we're in a comment or raw string now, skip to the start // of it. @@ -3399,7 +3405,7 @@ term_again: n = 0; if (curbuf->b_ind_cpp_baseclass != 0) { n = cin_is_cpp_baseclass(&cache_cpp_baseclass); - l = (char_u *)get_cursor_line_ptr(); + l = get_cursor_line_ptr(); } if (n) { // XXX @@ -3428,8 +3434,8 @@ term_again: // ... // } foo, // bar; - if (cin_ends_in(l, (char_u *)",", NULL) - || (*l != NUL && (n = l[STRLEN(l) - 1]) == '\\')) { + if (cin_ends_in(l, ",", NULL) + || (*l != NUL && (n = (uint8_t)l[strlen(l) - 1]) == '\\')) { // take us back to opening paren if (find_last_paren(l, '(', ')') && (trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL) { @@ -3442,8 +3448,8 @@ term_again: // bla", // here; while (n == 0 && curwin->w_cursor.lnum > 1) { - l = (char_u *)ml_get(curwin->w_cursor.lnum - 1); - if (*l == NUL || l[STRLEN(l) - 1] != '\\') { + l = ml_get(curwin->w_cursor.lnum - 1); + if (*l == NUL || l[strlen(l) - 1] != '\\') { break; } curwin->w_cursor.lnum--; @@ -3466,11 +3472,11 @@ term_again: if (cin_isfuncdecl(NULL, cur_curpos.lnum, 0)) { // XXX break; } - l = (char_u *)get_cursor_line_ptr(); + l = get_cursor_line_ptr(); // Finding the closing '}' of a previous function. Put // current line at the left margin. For when 'cino' has "fs". - if (*skipwhite((char *)l) == '}') { + if (*skipwhite(l) == '}') { break; } @@ -3479,7 +3485,7 @@ term_again: // comments) align at column 0. For example: // char *string_array[] = { "foo", // / * x * / "b};ar" }; / * foobar * / - if (cin_ends_in(l, (char_u *)"};", NULL)) { + if (cin_ends_in(l, "};", NULL)) { break; } @@ -3487,7 +3493,7 @@ term_again: // array constant: // something = [ // 234, <- extra indent - if (cin_ends_in(l, (char_u *)"[", NULL)) { + if (cin_ends_in(l, "[", NULL)) { amount = get_indent() + ind_continuation; break; } @@ -3495,18 +3501,18 @@ term_again: // Find a line only has a semicolon that belongs to a previous // line ending in '}', e.g. before an #endif. Don't increase // indent then. - if (*(look = (char_u *)skipwhite((char *)l)) == ';' && cin_nocode(look + 1)) { + if (*(look = skipwhite(l)) == ';' && cin_nocode(look + 1)) { pos_T curpos_save = curwin->w_cursor; while (curwin->w_cursor.lnum > 1) { - look = (char_u *)ml_get(--curwin->w_cursor.lnum); + look = ml_get(--curwin->w_cursor.lnum); if (!(cin_nocode(look) || cin_ispreproc_cont(&look, &curwin->w_cursor.lnum, &amount))) { break; } } if (curwin->w_cursor.lnum > 0 - && cin_ends_in(look, (char_u *)"}", NULL)) { + && cin_ends_in(look, "}", NULL)) { break; } @@ -3526,13 +3532,13 @@ term_again: // int foo, // bar; // indent_to_0 here; - if (cin_ends_in(l, (char_u *)";", NULL)) { - l = (char_u *)ml_get(curwin->w_cursor.lnum - 1); - if (cin_ends_in(l, (char_u *)",", NULL) - || (*l != NUL && l[STRLEN(l) - 1] == '\\')) { + if (cin_ends_in(l, ";", NULL)) { + l = ml_get(curwin->w_cursor.lnum - 1); + if (cin_ends_in(l, ",", NULL) + || (*l != NUL && l[strlen(l) - 1] == '\\')) { break; } - l = (char_u *)get_cursor_line_ptr(); + l = get_cursor_line_ptr(); } // Doesn't look like anything interesting -- so just @@ -3560,8 +3566,8 @@ term_again: // char *foo = "asdf{backslash} // here"; if (cur_curpos.lnum > 1) { - l = (char_u *)ml_get(cur_curpos.lnum - 1); - if (*l != NUL && l[STRLEN(l) - 1] == '\\') { + l = ml_get(cur_curpos.lnum - 1); + if (*l != NUL && l[strlen(l) - 1] == '\\') { cur_amount = cin_get_equal_amount(cur_curpos.lnum - 1); if (cur_amount > 0) { amount = cur_amount; @@ -3587,9 +3593,9 @@ laterend: static int find_match(int lookfor, linenr_T ourscope) { - const char_u *look; + const char *look; pos_T *theirscope; - const char_u *mightbeif; + const char *mightbeif; int elselevel; int whilelevel; @@ -3607,7 +3613,7 @@ static int find_match(int lookfor, linenr_T ourscope) curwin->w_cursor.lnum--; curwin->w_cursor.col = 0; - look = cin_skipcomment((char_u *)get_cursor_line_ptr()); + look = cin_skipcomment(get_cursor_line_ptr()); if (!cin_iselse(look) && !cin_isif(look) && !cin_isdo(look) // XXX @@ -3639,7 +3645,7 @@ static int find_match(int lookfor, linenr_T ourscope) // if it was an "else" (that's not an "else if") // then we need to go back to another if, so // increment elselevel - look = cin_skipcomment((char_u *)get_cursor_line_ptr()); + look = cin_skipcomment(get_cursor_line_ptr()); if (cin_iselse(look)) { mightbeif = cin_skipcomment(look + 4); if (!cin_isif(mightbeif)) { @@ -3656,7 +3662,7 @@ static int find_match(int lookfor, linenr_T ourscope) } // If it's an "if" decrement elselevel - look = cin_skipcomment((char_u *)get_cursor_line_ptr()); + look = cin_skipcomment(get_cursor_line_ptr()); if (cin_isif(look)) { elselevel--; // NOLINT(readability/braces) // When looking for an "if" ignore "while"s that diff --git a/src/nvim/input.c b/src/nvim/input.c index 681d9d5f9c..96214d45c2 100644 --- a/src/nvim/input.c +++ b/src/nvim/input.c @@ -4,24 +4,33 @@ // input.c: high level functions for prompting the user or input // like yes/no or number prompts. -#include <inttypes.h> #include <stdbool.h> +#include <string.h> +#include "nvim/ascii.h" +#include "nvim/event/multiqueue.h" #include "nvim/func_attr.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" #include "nvim/input.h" +#include "nvim/keycodes.h" #include "nvim/mbyte.h" #include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/os/input.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "input.c.generated.h" +# include "input.c.generated.h" // IWYU pragma: export #endif -/// Ask for a reply from the user, 'y' or 'n' +/// Ask for a reply from the user, a 'y' or a 'n', with prompt "str" (which +/// should have been translated already). /// /// No other characters are accepted, the message is repeated until a valid /// reply is entered or <C-c> is hit. diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index da1063f699..6de3b0a9d0 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -4,11 +4,16 @@ // insexpand.c: functions for Insert mode completion #include <assert.h> -#include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" @@ -18,36 +23,48 @@ #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/eval/userfunc.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" +#include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/insexpand.h" #include "nvim/keycodes.h" +#include "nvim/macros.h" +#include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/move.h" #include "nvim/option.h" +#include "nvim/os/fs.h" #include "nvim/os/input.h" #include "nvim/os/time.h" #include "nvim/path.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" #include "nvim/regexp.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/spell.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/tag.h" #include "nvim/textformat.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" -#include "nvim/window.h" // Definitions used for CTRL-X submode. // Note: If you change CTRL-X submode, you must also maintain ctrl_x_msgs[] @@ -55,30 +72,31 @@ #define CTRL_X_WANT_IDENT 0x100 -#define CTRL_X_NORMAL 0 ///< CTRL-N CTRL-P completion, default -#define CTRL_X_NOT_DEFINED_YET 1 -#define CTRL_X_SCROLL 2 -#define CTRL_X_WHOLE_LINE 3 -#define CTRL_X_FILES 4 -#define CTRL_X_TAGS (5 + CTRL_X_WANT_IDENT) -#define CTRL_X_PATH_PATTERNS (6 + CTRL_X_WANT_IDENT) -#define CTRL_X_PATH_DEFINES (7 + CTRL_X_WANT_IDENT) -#define CTRL_X_FINISHED 8 -#define CTRL_X_DICTIONARY (9 + CTRL_X_WANT_IDENT) -#define CTRL_X_THESAURUS (10 + CTRL_X_WANT_IDENT) -#define CTRL_X_CMDLINE 11 -#define CTRL_X_FUNCTION 12 -#define CTRL_X_OMNI 13 -#define CTRL_X_SPELL 14 -#define CTRL_X_LOCAL_MSG 15 ///< only used in "ctrl_x_msgs" -#define CTRL_X_EVAL 16 ///< for builtin function complete() -#define CTRL_X_CMDLINE_CTRL_X 17 ///< CTRL-X typed in CTRL_X_CMDLINE +enum { + CTRL_X_NORMAL = 0, ///< CTRL-N CTRL-P completion, default + CTRL_X_NOT_DEFINED_YET = 1, + CTRL_X_SCROLL = 2, + CTRL_X_WHOLE_LINE = 3, + CTRL_X_FILES = 4, + CTRL_X_TAGS = (5 + CTRL_X_WANT_IDENT), + CTRL_X_PATH_PATTERNS = (6 + CTRL_X_WANT_IDENT), + CTRL_X_PATH_DEFINES = (7 + CTRL_X_WANT_IDENT), + CTRL_X_FINISHED = 8, + CTRL_X_DICTIONARY = (9 + CTRL_X_WANT_IDENT), + CTRL_X_THESAURUS = (10 + CTRL_X_WANT_IDENT), + CTRL_X_CMDLINE = 11, + CTRL_X_FUNCTION = 12, + CTRL_X_OMNI = 13, + CTRL_X_SPELL = 14, + CTRL_X_LOCAL_MSG = 15, ///< only used in "ctrl_x_msgs" + CTRL_X_EVAL = 16, ///< for builtin function complete() + CTRL_X_CMDLINE_CTRL_X = 17, ///< CTRL-X typed in CTRL_X_CMDLINE +}; #define CTRL_X_MSG(i) ctrl_x_msgs[(i) & ~CTRL_X_WANT_IDENT] /// Message for CTRL-X mode, index is ctrl_x_mode. -static char *ctrl_x_msgs[] = -{ +static char *ctrl_x_msgs[] = { N_(" Keyword completion (^N^P)"), // CTRL_X_NORMAL, ^P/^N compl. N_(" ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)"), NULL, // CTRL_X_SCROLL: depends on state @@ -152,7 +170,7 @@ typedef struct { pos_T first_match_pos; ///< first match position pos_T last_match_pos; ///< last match position bool found_all; ///< found all matches of a certain type. - char_u *dict; ///< dictionary file to search + char *dict; ///< dictionary file to search int dict_f; ///< "dict" is an exact file name or not } ins_compl_next_state_T; @@ -215,11 +233,11 @@ static bool compl_interrupted = false; static bool compl_restarting = false; ///< don't insert match -///< When the first completion is done "compl_started" is set. When it's -///< false the word to be completed must be located. +/// When the first completion is done "compl_started" is set. When it's +/// false the word to be completed must be located. static bool compl_started = false; -///< Which Ctrl-X mode are we in? +/// Which Ctrl-X mode are we in? static int ctrl_x_mode = CTRL_X_NORMAL; static int compl_matches = 0; ///< number of completion matches @@ -240,7 +258,6 @@ static expand_T compl_xp; // List of flags for method of completion. static int compl_cont_status = 0; - #define CONT_ADDING 1 ///< "normal" or "adding" expansion #define CONT_INTRPT (2 + 4) ///< a ^X interrupted the current expansion ///< it's set only iff N_ADDS is set @@ -420,7 +437,7 @@ void compl_status_clear(void) compl_cont_status = 0; } -// @return true if completion is using the forward direction matches +/// @return true if completion is using the forward direction matches static bool compl_dir_forward(void) { return compl_direction == FORWARD; @@ -452,7 +469,7 @@ bool check_compl_option(bool dict_opt) msg_attr((dict_opt ? _("'dictionary' option is empty") : _("'thesaurus' option is empty")), HL_ATTR(HLF_E)); - if (emsg_silent == 0) { + if (emsg_silent == 0 && !in_assert_fails) { vim_beep(BO_COMPL); setcursor(); ui_flush(); @@ -572,8 +589,8 @@ bool ins_compl_accept_char(int c) /// Get the completed text by inferring the case of the originally typed text. /// If the result is in allocated memory "tofree" is set to it. -static char_u *ins_compl_infercase_gettext(char_u *str, int char_len, int compl_char_len, - int min_len, char **tofree) +static char *ins_compl_infercase_gettext(const char *str, int char_len, int compl_char_len, + int min_len, char **tofree) { bool has_lower = false; bool was_letter = false; @@ -581,7 +598,7 @@ static char_u *ins_compl_infercase_gettext(char_u *str, int char_len, int compl_ // Allocate wide character array for the completion and fill it. int *const wca = xmalloc((size_t)char_len * sizeof(*wca)); { - const char_u *p = str; + const char *p = str; for (int i = 0; i < char_len; i++) { wca[i] = mb_ptr2char_adv(&p); } @@ -589,7 +606,7 @@ static char_u *ins_compl_infercase_gettext(char_u *str, int char_len, int compl_ // Rule 1: Were any chars converted to lower? { - const char_u *p = (char_u *)compl_orig_text; + const char *p = compl_orig_text; for (int i = 0; i < min_len; i++) { const int c = mb_ptr2char_adv(&p); if (mb_islower(c)) { @@ -608,7 +625,7 @@ static char_u *ins_compl_infercase_gettext(char_u *str, int char_len, int compl_ // Rule 2: No lower case, 2nd consecutive letter converted to // upper case. if (!has_lower) { - const char_u *p = (char_u *)compl_orig_text; + const char *p = compl_orig_text; for (int i = 0; i < min_len; i++) { const int c = mb_ptr2char_adv(&p); if (was_letter && mb_isupper(c) && mb_islower(wca[i])) { @@ -624,7 +641,7 @@ static char_u *ins_compl_infercase_gettext(char_u *str, int char_len, int compl_ // Copy the original case of the part we typed. { - const char_u *p = (char_u *)compl_orig_text; + const char *p = compl_orig_text; for (int i = 0; i < min_len; i++) { const int c = mb_ptr2char_adv(&p); if (mb_islower(c)) { @@ -637,7 +654,7 @@ static char_u *ins_compl_infercase_gettext(char_u *str, int char_len, int compl_ // Generate encoding specific output from wide character array. garray_T gap; - char *p = (char *)IObuff; + char *p = IObuff; int i = 0; ga_init(&gap, 1, 500); while (i < char_len) { @@ -646,7 +663,7 @@ static char_u *ins_compl_infercase_gettext(char_u *str, int char_len, int compl_ assert(gap.ga_data != NULL); // suppress clang "Dereference of NULL pointer" p = (char *)gap.ga_data + gap.ga_len; gap.ga_len += utf_char2bytes(wca[i++], p); - } else if ((p - (char *)IObuff) + 6 >= IOSIZE) { + } else if ((p - IObuff) + 6 >= IOSIZE) { // Multi-byte characters can occupy up to five bytes more than // ASCII characters, and we also need one byte for NUL, so when // getting to six bytes from the edge of IObuff switch to using a @@ -667,7 +684,7 @@ static char_u *ins_compl_infercase_gettext(char_u *str, int char_len, int compl_ } *p = NUL; - return (char_u *)IObuff; + return IObuff; } /// This is like ins_compl_add(), but if 'ic' and 'inf' are set, then the @@ -676,11 +693,11 @@ static char_u *ins_compl_infercase_gettext(char_u *str, int char_len, int compl_ /// the rest of the word to be in -- webb /// /// @param[in] cont_s_ipos next ^X<> will set initial_pos -int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname, Direction dir, +int ins_compl_add_infercase(char *str_arg, int len, bool icase, char *fname, Direction dir, bool cont_s_ipos) FUNC_ATTR_NONNULL_ARG(1) { - char_u *str = str_arg; + char *str = str_arg; int char_len; // count multi-byte characters int compl_char_len; int flags = 0; @@ -691,7 +708,7 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname, // Find actual length of completion. { - const char_u *p = str; + const char *p = str; char_len = 0; while (*p != NUL) { MB_PTR_ADV(p); @@ -701,7 +718,7 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname, // Find actual length of original text. { - const char_u *p = (char_u *)compl_orig_text; + const char *p = compl_orig_text; compl_char_len = 0; while (*p != NUL) { MB_PTR_ADV(p); @@ -722,7 +739,7 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname, flags |= CP_ICASE; } - int res = ins_compl_add((char *)str, len, (char *)fname, NULL, false, NULL, dir, flags, false); + int res = ins_compl_add(str, len, fname, NULL, false, NULL, dir, flags, false); xfree(tofree); return res; } @@ -785,7 +802,7 @@ static int ins_compl_add(char *const str, int len, char *const fname, char *cons match = compl_first_match; do { if (!match_at_original_text(match) - && STRNCMP(match->cp_str, str, len) == 0 + && strncmp(match->cp_str, str, (size_t)len) == 0 && ((int)strlen(match->cp_str) <= len || match->cp_str[len] == NUL)) { FREE_CPTEXT(cptext, cptext_allocated); return NOTDONE; @@ -877,7 +894,7 @@ static int ins_compl_add(char *const str, int len, char *const fname, char *cons /// @param match completion match /// @param str character string to check /// @param len length of "str" -static bool ins_compl_equal(compl_T *match, char_u *str, size_t len) +static bool ins_compl_equal(compl_T *match, char *str, size_t len) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { if (match->cp_flags & CP_EQUAL) { @@ -886,13 +903,13 @@ static bool ins_compl_equal(compl_T *match, char_u *str, size_t len) if (match->cp_flags & CP_ICASE) { return STRNICMP(match->cp_str, str, len) == 0; } - return STRNCMP(match->cp_str, str, len) == 0; + return strncmp(match->cp_str, str, len) == 0; } /// Reduce the longest common string for match "match". static void ins_compl_longest_match(compl_T *match) { - char_u *p, *s; + char *p, *s; int c1, c2; int had_match; @@ -916,11 +933,11 @@ static void ins_compl_longest_match(compl_T *match) } // Reduce the text if this match differs from compl_leader. - p = (char_u *)compl_leader; - s = (char_u *)match->cp_str; + p = compl_leader; + s = match->cp_str; while (*p != NUL) { - c1 = utf_ptr2char((char *)p); - c2 = utf_ptr2char((char *)s); + c1 = utf_ptr2char(p); + c2 = utf_ptr2char(s); if ((match->cp_flags & CP_ICASE) ? (mb_tolower(c1) != mb_tolower(c2)) @@ -1135,7 +1152,7 @@ static int ins_compl_build_pum(void) do { if (!match_at_original_text(compl) && (compl_leader == NULL - || ins_compl_equal(compl, (char_u *)compl_leader, (size_t)lead_len))) { + || ins_compl_equal(compl, compl_leader, (size_t)lead_len))) { compl_match_arraysize++; } compl = compl->cp_next; @@ -1160,7 +1177,7 @@ static int ins_compl_build_pum(void) do { if (!match_at_original_text(compl) && (compl_leader == NULL - || ins_compl_equal(compl, (char_u *)compl_leader, (size_t)lead_len))) { + || ins_compl_equal(compl, compl_leader, (size_t)lead_len))) { if (!shown_match_ok) { if (compl == compl_shown_match || did_find_shown_match) { // This item is the shown match or this is the @@ -1177,16 +1194,16 @@ static int ins_compl_build_pum(void) } if (compl->cp_text[CPT_ABBR] != NULL) { - compl_match_array[i].pum_text = (char_u *)compl->cp_text[CPT_ABBR]; + compl_match_array[i].pum_text = compl->cp_text[CPT_ABBR]; } else { - compl_match_array[i].pum_text = (char_u *)compl->cp_str; + compl_match_array[i].pum_text = compl->cp_str; } - compl_match_array[i].pum_kind = (char_u *)compl->cp_text[CPT_KIND]; - compl_match_array[i].pum_info = (char_u *)compl->cp_text[CPT_INFO]; + compl_match_array[i].pum_kind = compl->cp_text[CPT_KIND]; + compl_match_array[i].pum_info = compl->cp_text[CPT_INFO]; if (compl->cp_text[CPT_MENU] != NULL) { - compl_match_array[i++].pum_extra = (char_u *)compl->cp_text[CPT_MENU]; + compl_match_array[i++].pum_extra = compl->cp_text[CPT_MENU]; } else { - compl_match_array[i++].pum_extra = (char_u *)compl->cp_fname; + compl_match_array[i++].pum_extra = compl->cp_fname; } } @@ -1240,8 +1257,8 @@ void ins_compl_show_pum(void) } else { // popup menu already exists, only need to find the current item. for (int i = 0; i < compl_match_arraysize; i++) { - if (compl_match_array[i].pum_text == (char_u *)compl_shown_match->cp_str - || compl_match_array[i].pum_text == (char_u *)compl_shown_match->cp_text[CPT_ABBR]) { + if (compl_match_array[i].pum_text == compl_shown_match->cp_str + || compl_match_array[i].pum_text == compl_shown_match->cp_text[CPT_ABBR]) { cur = i; break; } @@ -1277,10 +1294,10 @@ void ins_compl_show_pum(void) /// /// @param flags DICT_FIRST and/or DICT_EXACT /// @param thesaurus Thesaurus completion -static void ins_compl_dictionaries(char_u *dict_start, char_u *pat, int flags, int thesaurus) +static void ins_compl_dictionaries(char *dict_start, char *pat, int flags, int thesaurus) { - char *dict = (char *)dict_start; - char_u *ptr; + char *dict = dict_start; + char *ptr; char *buf; regmatch_T regmatch; char **files; @@ -1311,16 +1328,16 @@ static void ins_compl_dictionaries(char_u *dict_start, char_u *pat, int flags, i // to only match at the start of a line. Otherwise just match the // pattern. Also need to double backslashes. if (ctrl_x_mode_line_or_eval()) { - char_u *pat_esc = vim_strsave_escaped(pat, (char_u *)"\\"); + char *pat_esc = vim_strsave_escaped(pat, "\\"); - size_t len = STRLEN(pat_esc) + 10; + size_t len = strlen(pat_esc) + 10; ptr = xmalloc(len); - vim_snprintf((char *)ptr, len, "^\\s*\\zs\\V%s", pat_esc); - regmatch.regprog = vim_regcomp((char *)ptr, RE_MAGIC); + vim_snprintf(ptr, len, "^\\s*\\zs\\V%s", pat_esc); + regmatch.regprog = vim_regcomp(ptr, RE_MAGIC); xfree(pat_esc); xfree(ptr); } else { - regmatch.regprog = vim_regcomp((char *)pat, p_magic ? RE_MAGIC : 0); + regmatch.regprog = vim_regcomp(pat, magic_isset() ? RE_MAGIC : 0); if (regmatch.regprog == NULL) { goto theend; } @@ -1355,10 +1372,10 @@ static void ins_compl_dictionaries(char_u *dict_start, char_u *pat, int flags, i } else { ptr = pat; } - spell_dump_compl((char *)ptr, regmatch.rm_ic, &dir, 0); + spell_dump_compl(ptr, regmatch.rm_ic, &dir, 0); } else if (count > 0) { // avoid warning for using "files" uninit ins_compl_files(count, files, thesaurus, flags, - ®match, (char_u *)buf, &dir); + ®match, buf, &dir); if (flags != DICT_EXACT) { FreeWild(count, files); } @@ -1378,12 +1395,12 @@ theend: /// skipping the word at 'skip_word'. /// /// @return OK on success. -static int thesaurus_add_words_in_line(char *fname, char_u **buf_arg, int dir, char_u *skip_word) +static int thesaurus_add_words_in_line(char *fname, char **buf_arg, int dir, const char *skip_word) { int status = OK; // Add the other matches on the line - char_u *ptr = *buf_arg; + char *ptr = *buf_arg; while (!got_int) { // Find start of the next word. Skip white // space and punctuation. @@ -1391,7 +1408,7 @@ static int thesaurus_add_words_in_line(char *fname, char_u **buf_arg, int dir, c if (*ptr == NUL || *ptr == NL) { break; } - char_u *wstart = ptr; + char *wstart = ptr; // Find end of the word. // Japanese words may have characters in @@ -1400,7 +1417,7 @@ static int thesaurus_add_words_in_line(char *fname, char_u **buf_arg, int dir, c while (*ptr != NUL) { const int l = utfc_ptr2len((const char *)ptr); - if (l < 2 && !vim_iswordc(*ptr)) { + if (l < 2 && !vim_iswordc((uint8_t)(*ptr))) { break; } ptr += l; @@ -1409,7 +1426,7 @@ static int thesaurus_add_words_in_line(char *fname, char_u **buf_arg, int dir, c // Add the word. Skip the regexp match. if (wstart != skip_word) { status = ins_compl_add_infercase(wstart, (int)(ptr - wstart), p_ic, - (char_u *)fname, dir, false); + fname, dir, false); if (status == FAIL) { break; } @@ -1423,21 +1440,21 @@ static int thesaurus_add_words_in_line(char *fname, char_u **buf_arg, int dir, c /// Process "count" dictionary/thesaurus "files" and add the text matching /// "regmatch". static void ins_compl_files(int count, char **files, int thesaurus, int flags, regmatch_T *regmatch, - char_u *buf, Direction *dir) + char *buf, Direction *dir) FUNC_ATTR_NONNULL_ARG(2, 7) { - char_u *ptr; + char *ptr; int i; FILE *fp; int add_r; for (i = 0; i < count && !got_int && !compl_interrupted; i++) { fp = os_fopen(files[i], "r"); // open dictionary file - if (flags != DICT_EXACT) { + if (flags != DICT_EXACT && !shortmess(SHM_COMPLETIONSCAN)) { msg_hist_off = true; // reset in msg_trunc_attr() - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Scanning dictionary: %s"), files[i]); - (void)msg_trunc_attr((char *)IObuff, true, HL_ATTR(HLF_R)); + (void)msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R)); } if (fp == NULL) { @@ -1448,21 +1465,20 @@ static void ins_compl_files(int count, char **files, int thesaurus, int flags, r // Check each line for a match. while (!got_int && !compl_interrupted && !vim_fgets(buf, LSIZE, fp)) { ptr = buf; - while (vim_regexec(regmatch, (char *)buf, (colnr_T)(ptr - buf))) { - ptr = (char_u *)regmatch->startp[0]; + while (vim_regexec(regmatch, buf, (colnr_T)(ptr - buf))) { + ptr = regmatch->startp[0]; if (ctrl_x_mode_line_or_eval()) { ptr = find_line_end(ptr); } else { ptr = find_word_end(ptr); } - add_r = ins_compl_add_infercase((char_u *)regmatch->startp[0], - (int)(ptr - (char_u *)regmatch->startp[0]), - p_ic, (char_u *)files[i], *dir, false); + add_r = ins_compl_add_infercase(regmatch->startp[0], + (int)(ptr - regmatch->startp[0]), + p_ic, files[i], *dir, false); if (thesaurus) { // For a thesaurus, add all the words in the line ptr = buf; - add_r = thesaurus_add_words_in_line(files[i], &ptr, *dir, - (char_u *)regmatch->startp[0]); + add_r = thesaurus_add_words_in_line(files[i], &ptr, *dir, regmatch->startp[0]); } if (add_r == OK) { // if dir was BACKWARD then honor it just once @@ -1485,24 +1501,24 @@ static void ins_compl_files(int count, char **files, int thesaurus, int flags, r /// Find the start of the next word. /// Returns a pointer to the first char of the word. Also stops at a NUL. -char_u *find_word_start(char_u *ptr) +char *find_word_start(char *ptr) FUNC_ATTR_PURE { while (*ptr != NUL && *ptr != '\n' && mb_get_class(ptr) <= 1) { - ptr += utfc_ptr2len((char *)ptr); + ptr += utfc_ptr2len(ptr); } return ptr; } /// Find the end of the word. Assumes it starts inside a word. /// Returns a pointer to just after the word. -char_u *find_word_end(char_u *ptr) +char *find_word_end(char *ptr) FUNC_ATTR_PURE { const int start_class = mb_get_class(ptr); if (start_class > 1) { while (*ptr != NUL) { - ptr += utfc_ptr2len((char *)ptr); + ptr += utfc_ptr2len(ptr); if (mb_get_class(ptr) != start_class) { break; } @@ -1512,12 +1528,13 @@ char_u *find_word_end(char_u *ptr) } /// Find the end of the line, omitting CR and NL at the end. -/// Returns a pointer to just after the line. -static char_u *find_line_end(char_u *ptr) +/// +/// @return a pointer to just after the line. +static char *find_line_end(char *ptr) { - char_u *s; + char *s; - s = ptr + STRLEN(ptr); + s = ptr + strlen(ptr); while (s > ptr && (s[-1] == CAR || s[-1] == NL)) { s--; } @@ -1788,13 +1805,13 @@ static void ins_compl_set_original_text(char *str) /// matches. void ins_compl_addfrommatch(void) { - char_u *p; + char *p; int len = (int)curwin->w_cursor.col - (int)compl_col; int c; compl_T *cp; assert(compl_shown_match != NULL); - p = (char_u *)compl_shown_match->cp_str; - if ((int)STRLEN(p) <= len) { // the match is too short + p = compl_shown_match->cp_str; + if ((int)strlen(p) <= len) { // the match is too short // When still at the original match use the first entry that matches // the leader. if (!match_at_original_text(compl_shown_match)) { @@ -1805,17 +1822,17 @@ void ins_compl_addfrommatch(void) for (cp = compl_shown_match->cp_next; cp != NULL && !is_first_match(cp); cp = cp->cp_next) { if (compl_leader == NULL - || ins_compl_equal(cp, (char_u *)compl_leader, STRLEN(compl_leader))) { - p = (char_u *)cp->cp_str; + || ins_compl_equal(cp, compl_leader, strlen(compl_leader))) { + p = cp->cp_str; break; } } - if (p == NULL || (int)STRLEN(p) <= len) { + if (p == NULL || (int)strlen(p) <= len) { return; } } p += len; - c = utf_ptr2char((char *)p); + c = utf_ptr2char(p); ins_compl_addleader(c); } @@ -1957,9 +1974,9 @@ static bool ins_compl_stop(const int c, const int prev_mode, bool retval) // of the original text that has changed. // When using the longest match, edited the match or used // CTRL-E then don't use the current match. - char_u *ptr; + char *ptr; if (compl_curr_match != NULL && compl_used_match && c != Ctrl_E) { - ptr = (char_u *)compl_curr_match->cp_str; + ptr = compl_curr_match->cp_str; } else { ptr = NULL; } @@ -2008,17 +2025,17 @@ static bool ins_compl_stop(const int c, const int prev_mode, bool retval) // but only do this, if the Popup is still visible if (c == Ctrl_E) { ins_compl_delete(); - char_u *p = NULL; + char *p = NULL; if (compl_leader != NULL) { - p = (char_u *)compl_leader; + p = compl_leader; } else if (compl_first_match != NULL) { - p = (char_u *)compl_orig_text; + p = compl_orig_text; } if (p != NULL) { const int compl_len = get_compl_len(); - const int len = (int)STRLEN(p); + const int len = (int)strlen(p); if (len > compl_len) { - ins_bytes_len((char *)p + compl_len, (size_t)(len - compl_len)); + ins_bytes_len(p + compl_len, (size_t)(len - compl_len)); } } retval = true; @@ -2164,24 +2181,24 @@ bool ins_compl_prep(int c) /// Fix the redo buffer for the completion leader replacing some of the typed /// text. This inserts backspaces and appends the changed text. /// "ptr" is the known leader text or NUL. -static void ins_compl_fixRedoBufForLeader(char_u *ptr_arg) +static void ins_compl_fixRedoBufForLeader(char *ptr_arg) { int len; - char_u *p; - char_u *ptr = ptr_arg; + char *p; + char *ptr = ptr_arg; if (ptr == NULL) { if (compl_leader != NULL) { - ptr = (char_u *)compl_leader; + ptr = compl_leader; } else { return; // nothing to do } } if (compl_orig_text != NULL) { - p = (char_u *)compl_orig_text; + p = compl_orig_text; for (len = 0; p[len] != NUL && p[len] == ptr[len]; len++) {} if (len > 0) { - len -= utf_head_off((char *)p, (char *)p + len); + len -= utf_head_off(p, p + len); } for (p += len; *p != NUL; MB_PTR_ADV(p)) { AppendCharToRedobuff(K_BS); @@ -2189,7 +2206,7 @@ static void ins_compl_fixRedoBufForLeader(char_u *ptr_arg) } else { len = 0; } - AppendToRedobuffLit((char *)ptr + len, -1); + AppendToRedobuffLit(ptr + len, -1); } /// Loops through the list of windows, loaded-buffers or non-loaded-buffers @@ -2224,30 +2241,129 @@ static buf_T *ins_compl_next_buf(buf_T *buf, int flag) return buf; } +static Callback cfu_cb; ///< 'completefunc' callback function +static Callback ofu_cb; ///< 'omnifunc' callback function +static Callback tsrfu_cb; ///< 'thesaurusfunc' callback function + +/// Copy a global callback function to a buffer local callback. +static void copy_global_to_buflocal_cb(Callback *globcb, Callback *bufcb) +{ + callback_free(bufcb); + if (globcb->type != kCallbackNone) { + callback_copy(bufcb, globcb); + } +} + +/// Parse the 'completefunc' option value and set the callback function. +/// Invoked when the 'completefunc' option is set. The option value can be a +/// name of a function (string), or function(<name>) or funcref(<name>) or a +/// lambda expression. +void set_completefunc_option(char **errmsg) +{ + if (option_set_callback_func(curbuf->b_p_cfu, &cfu_cb) == FAIL) { + *errmsg = e_invarg; + return; + } + + set_buflocal_cfu_callback(curbuf); +} + +/// Copy the global 'completefunc' callback function to the buffer-local +/// 'completefunc' callback for "buf". +void set_buflocal_cfu_callback(buf_T *buf) +{ + copy_global_to_buflocal_cb(&cfu_cb, &buf->b_cfu_cb); +} + +/// Parse the 'omnifunc' option value and set the callback function. +/// Invoked when the 'omnifunc' option is set. The option value can be a +/// name of a function (string), or function(<name>) or funcref(<name>) or a +/// lambda expression. +void set_omnifunc_option(buf_T *buf, char **errmsg) +{ + if (option_set_callback_func(buf->b_p_ofu, &ofu_cb) == FAIL) { + *errmsg = e_invarg; + return; + } + set_buflocal_ofu_callback(buf); +} + +/// Copy the global 'omnifunc' callback function to the buffer-local 'omnifunc' +/// callback for "buf". +void set_buflocal_ofu_callback(buf_T *buf) +{ + copy_global_to_buflocal_cb(&ofu_cb, &buf->b_ofu_cb); +} + +/// Parse the 'thesaurusfunc' option value and set the callback function. +/// Invoked when the 'thesaurusfunc' option is set. The option value can be a +/// name of a function (string), or function(<name>) or funcref(<name>) or a +/// lambda expression. +void set_thesaurusfunc_option(char **errmsg) +{ + int retval; + + if (*curbuf->b_p_tsrfu != NUL) { + // buffer-local option set + retval = option_set_callback_func(curbuf->b_p_tsrfu, &curbuf->b_tsrfu_cb); + } else { + // global option set + retval = option_set_callback_func(p_tsrfu, &tsrfu_cb); + } + + if (retval == FAIL) { + *errmsg = e_invarg; + } +} + +/// Mark the global 'completefunc' 'omnifunc' and 'thesaurusfunc' callbacks with +/// "copyID" so that they are not garbage collected. +bool set_ref_in_insexpand_funcs(int copyID) +{ + bool abort = set_ref_in_callback(&cfu_cb, copyID, NULL, NULL); + abort = abort || set_ref_in_callback(&ofu_cb, copyID, NULL, NULL); + abort = abort || set_ref_in_callback(&tsrfu_cb, copyID, NULL, NULL); + + return abort; +} + /// Get the user-defined completion function name for completion "type" -static char_u *get_complete_funcname(int type) +static char *get_complete_funcname(int type) { switch (type) { case CTRL_X_FUNCTION: - return (char_u *)curbuf->b_p_cfu; + return curbuf->b_p_cfu; case CTRL_X_OMNI: - return (char_u *)curbuf->b_p_ofu; + return curbuf->b_p_ofu; case CTRL_X_THESAURUS: - return *curbuf->b_p_tsrfu == NUL ? (char_u *)p_tsrfu : (char_u *)curbuf->b_p_tsrfu; + return *curbuf->b_p_tsrfu == NUL ? p_tsrfu : curbuf->b_p_tsrfu; default: - return (char_u *)""; + return ""; } } -/// Execute user defined complete function 'completefunc' or 'omnifunc', and -/// get matches in "matches". +/// Get the callback to use for insert mode completion. +static Callback *get_insert_callback(int type) +{ + if (type == CTRL_X_FUNCTION) { + return &curbuf->b_cfu_cb; + } + if (type == CTRL_X_OMNI) { + return &curbuf->b_ofu_cb; + } + // CTRL_X_THESAURUS + return (*curbuf->b_p_tsrfu != NUL) ? &curbuf->b_tsrfu_cb : &tsrfu_cb; +} + +/// Execute user defined complete function 'completefunc', 'omnifunc' or +/// 'thesaurusfunc', and get matches in "matches". /// -/// @param type CTRL_X_OMNI or CTRL_X_FUNCTION -static void expand_by_function(int type, char_u *base) +/// @param type either CTRL_X_OMNI or CTRL_X_FUNCTION or CTRL_X_THESAURUS +static void expand_by_function(int type, char *base) { list_T *matchlist = NULL; dict_T *matchdict = NULL; - char_u *funcname; + char *funcname; pos_T pos; typval_T rettv; const int save_State = State; @@ -2264,7 +2380,7 @@ static void expand_by_function(int type, char_u *base) args[1].v_type = VAR_STRING; args[2].v_type = VAR_UNKNOWN; args[0].vval.v_number = 0; - args[1].vval.v_string = base != NULL ? (char *)base : ""; + args[1].vval.v_string = base != NULL ? base : ""; pos = curwin->w_cursor; // Lock the text to avoid weird things from happening. Also disallow @@ -2272,8 +2388,10 @@ static void expand_by_function(int type, char_u *base) // Insert mode in another buffer. textlock++; + Callback *cb = get_insert_callback(type); + // Call a function, which returns a list or dict. - if (call_vim_function((char *)funcname, 2, args, &rettv) == OK) { + if (callback_call(cb, 2, args, &rettv)) { switch (rettv.v_type) { case VAR_LIST: matchlist = rettv.vval.v_list; @@ -2518,12 +2636,12 @@ void f_complete_check(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } /// Return Insert completion mode name string -static char_u *ins_compl_mode(void) +static char *ins_compl_mode(void) { if (ctrl_x_mode_not_defined_yet() || ctrl_x_mode_scroll() || compl_started) { - return (char_u *)ctrl_x_mode_names[ctrl_x_mode & ~CTRL_X_WANT_IDENT]; + return ctrl_x_mode_names[ctrl_x_mode & ~CTRL_X_WANT_IDENT]; } - return (char_u *)""; + return ""; } /// Assign the sequence number to all the completion matches which don't have @@ -2612,8 +2730,7 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) int ret = OK; if (what_flag & CI_WHAT_MODE) { - ret = tv_dict_add_str(retdict, S_LEN("mode"), - (char *)ins_compl_mode()); + ret = tv_dict_add_str(retdict, S_LEN("mode"), ins_compl_mode()); } if (ret == OK && (what_flag & CI_WHAT_PUM_VISIBLE)) { @@ -2637,6 +2754,7 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) tv_dict_add_str(di, S_LEN("kind"), EMPTY_IF_NULL(match->cp_text[CPT_KIND])); tv_dict_add_str(di, S_LEN("info"), EMPTY_IF_NULL(match->cp_text[CPT_INFO])); if (match->cp_user_data.v_type == VAR_UNKNOWN) { + // Add an empty string for backwards compatibility tv_dict_add_str(di, S_LEN("user_data"), ""); } else { tv_dict_add_tv(di, S_LEN("user_data"), &match->cp_user_data); @@ -2740,7 +2858,7 @@ static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_ar // Remember the first match so that the loop stops when we // wrap and come back there a second time. st->set_match_pos = true; - } else if (vim_strchr("buwU", *st->e_cpt) != NULL + } else if (vim_strchr("buwU", (uint8_t)(*st->e_cpt)) != NULL && (st->ins_buf = ins_compl_next_buf(st->ins_buf, *st->e_cpt)) != curbuf) { // Scan a buffer, but not the current one. if (st->ins_buf->b_ml.ml_mfp != NULL) { // loaded buffer @@ -2756,17 +2874,19 @@ static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_ar goto done; } compl_type = CTRL_X_DICTIONARY; - st->dict = (char_u *)st->ins_buf->b_fname; + st->dict = st->ins_buf->b_fname; st->dict_f = DICT_EXACT; } - msg_hist_off = true; // reset in msg_trunc_attr() - vim_snprintf((char *)IObuff, IOSIZE, _("Scanning: %s"), - st->ins_buf->b_fname == NULL - ? buf_spname(st->ins_buf) - : st->ins_buf->b_sfname == NULL - ? st->ins_buf->b_fname - : st->ins_buf->b_sfname); - (void)msg_trunc_attr((char *)IObuff, true, HL_ATTR(HLF_R)); + if (!shortmess(SHM_COMPLETIONSCAN)) { + msg_hist_off = true; // reset in msg_trunc_attr() + vim_snprintf(IObuff, IOSIZE, _("Scanning: %s"), + st->ins_buf->b_fname == NULL + ? buf_spname(st->ins_buf) + : st->ins_buf->b_sfname == NULL + ? st->ins_buf->b_fname + : st->ins_buf->b_sfname); + (void)msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R)); + } } else if (*st->e_cpt == NUL) { status = INS_COMPL_CPT_END; } else { @@ -2779,7 +2899,7 @@ static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_ar compl_type = CTRL_X_THESAURUS; } if (*++st->e_cpt != ',' && *st->e_cpt != NUL) { - st->dict = (char_u *)st->e_cpt; + st->dict = st->e_cpt; st->dict_f = DICT_FIRST; } } else if (*st->e_cpt == 'i') { @@ -2787,16 +2907,18 @@ static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_ar } else if (*st->e_cpt == 'd') { compl_type = CTRL_X_PATH_DEFINES; } else if (*st->e_cpt == ']' || *st->e_cpt == 't') { - msg_hist_off = true; // reset in msg_trunc_attr() compl_type = CTRL_X_TAGS; - vim_snprintf((char *)IObuff, IOSIZE, "%s", _("Scanning tags.")); - (void)msg_trunc_attr((char *)IObuff, true, HL_ATTR(HLF_R)); + if (!shortmess(SHM_COMPLETIONSCAN)) { + msg_hist_off = true; // reset in msg_trunc_attr() + vim_snprintf(IObuff, IOSIZE, "%s", _("Scanning tags.")); + (void)msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R)); + } } else { compl_type = -1; } // in any case e_cpt is advanced to the next entry - (void)copy_option_part(&st->e_cpt, (char *)IObuff, IOSIZE, ","); + (void)copy_option_part(&st->e_cpt, IObuff, IOSIZE, ","); st->found_all = true; if (compl_type == -1) { @@ -2813,7 +2935,7 @@ done: /// included files. static void get_next_include_file_completion(int compl_type) { - find_pattern_in_path((char_u *)compl_pattern, compl_direction, + find_pattern_in_path(compl_pattern, compl_direction, strlen(compl_pattern), false, false, ((compl_type == CTRL_X_PATH_DEFINES && !(compl_cont_status & CONT_SOL)) @@ -2823,17 +2945,17 @@ static void get_next_include_file_completion(int compl_type) /// Get the next set of words matching "compl_pattern" in dictionary or /// thesaurus files. -static void get_next_dict_tsr_completion(int compl_type, char_u *dict, int dict_f) +static void get_next_dict_tsr_completion(int compl_type, char *dict, int dict_f) { if (thesaurus_func_complete(compl_type)) { - expand_by_function(compl_type, (char_u *)compl_pattern); + expand_by_function(compl_type, compl_pattern); } else { ins_compl_dictionaries(dict != NULL ? dict : (compl_type == CTRL_X_THESAURUS - ? (*curbuf->b_p_tsr == NUL ? p_tsr : (char_u *)curbuf->b_p_tsr) + ? (*curbuf->b_p_tsr == NUL ? p_tsr : curbuf->b_p_tsr) : (*curbuf->b_p_dict == - NUL ? (char_u *)p_dict : (char_u *)curbuf->b_p_dict)), - (char_u *)compl_pattern, + NUL ? p_dict : curbuf->b_p_dict)), + compl_pattern, dict != NULL ? dict_f : 0, compl_type == CTRL_X_THESAURUS); } @@ -2844,7 +2966,7 @@ static void get_next_tag_completion(void) { // set p_ic according to p_ic, p_scs and pat for find_tags(). const int save_p_ic = p_ic; - p_ic = ignorecase((char_u *)compl_pattern); + p_ic = ignorecase(compl_pattern); // Find up to TAG_MANY matches. Avoids that an enormous number // of matches is found when compl_pattern is empty @@ -2872,11 +2994,11 @@ static void get_next_filename_completion(void) } // May change home directory back to "~". - tilde_replace((char_u *)compl_pattern, num_matches, matches); + tilde_replace(compl_pattern, num_matches, matches); #ifdef BACKSLASH_IN_FILENAME if (curbuf->b_p_csl[0] != NUL) { for (int i = 0; i < num_matches; i++) { - char_u *ptr = matches[i]; + char *ptr = matches[i]; while (*ptr != NUL) { if (curbuf->b_p_csl[0] == 's' && *ptr == '\\') { *ptr = '/'; @@ -2896,7 +3018,7 @@ static void get_next_cmdline_completion(void) { char **matches; int num_matches; - if (expand_cmdline(&compl_xp, (char_u *)compl_pattern, + if (expand_cmdline(&compl_xp, compl_pattern, (int)strlen(compl_pattern), &num_matches, &matches) == EXPAND_OK) { ins_compl_add_matches(num_matches, matches, false); @@ -2907,7 +3029,7 @@ static void get_next_cmdline_completion(void) static void get_next_spell_completion(linenr_T lnum) { char **matches; - int num_matches = expand_spelling(lnum, (char_u *)compl_pattern, &matches); + int num_matches = expand_spelling(lnum, compl_pattern, &matches); if (num_matches > 0) { ins_compl_add_matches(num_matches, matches, p_ic); } else { @@ -2921,27 +3043,27 @@ static void get_next_spell_completion(linenr_T lnum) /// @param cur_match_pos current match position /// @param match_len /// @param cont_s_ipos next ^X<> will set initial_pos -static char_u *ins_comp_get_next_word_or_line(buf_T *ins_buf, pos_T *cur_match_pos, int *match_len, - bool *cont_s_ipos) +static char *ins_comp_get_next_word_or_line(buf_T *ins_buf, pos_T *cur_match_pos, int *match_len, + bool *cont_s_ipos) { *match_len = 0; - char_u *ptr = (char_u *)ml_get_buf(ins_buf, cur_match_pos->lnum, false) + cur_match_pos->col; + char *ptr = ml_get_buf(ins_buf, cur_match_pos->lnum, false) + cur_match_pos->col; int len; if (ctrl_x_mode_line_or_eval()) { if (compl_status_adding()) { if (cur_match_pos->lnum >= ins_buf->b_ml.ml_line_count) { return NULL; } - ptr = (char_u *)ml_get_buf(ins_buf, cur_match_pos->lnum + 1, false); + ptr = ml_get_buf(ins_buf, cur_match_pos->lnum + 1, false); if (!p_paste) { - ptr = (char_u *)skipwhite((char *)ptr); + ptr = skipwhite(ptr); } } - len = (int)STRLEN(ptr); + len = (int)strlen(ptr); } else { - char_u *tmp_ptr = ptr; + char *tmp_ptr = ptr; - if (compl_status_adding() && compl_length <= (int)STRLEN(tmp_ptr)) { + if (compl_status_adding() && compl_length <= (int)strlen(tmp_ptr)) { tmp_ptr += compl_length; // Skip if already inside a word. if (vim_iswordp(tmp_ptr)) { @@ -2958,10 +3080,10 @@ static char_u *ins_comp_get_next_word_or_line(buf_T *ins_buf, pos_T *cur_match_p if (cur_match_pos->lnum < ins_buf->b_ml.ml_line_count) { // Try next line, if any. the new word will be "join" as if the // normal command "J" was used. IOSIZE is always greater than - // compl_length, so the next STRNCPY always works -- Acevedo - STRNCPY(IObuff, ptr, len); // NOLINT(runtime/printf) - ptr = (char_u *)ml_get_buf(ins_buf, cur_match_pos->lnum + 1, false); - tmp_ptr = ptr = (char_u *)skipwhite((char *)ptr); + // compl_length, so the next strncpy always works -- Acevedo + strncpy(IObuff, ptr, (size_t)len); // NOLINT(runtime/printf) + ptr = ml_get_buf(ins_buf, cur_match_pos->lnum + 1, false); + tmp_ptr = ptr = skipwhite(ptr); // Find start of next word. tmp_ptr = find_word_start(tmp_ptr); // Find end of next word. @@ -2983,12 +3105,12 @@ static char_u *ins_comp_get_next_word_or_line(buf_T *ins_buf, pos_T *cur_match_p if (tmp_ptr - ptr >= IOSIZE - len) { tmp_ptr = ptr + IOSIZE - len - 1; } - STRLCPY(IObuff + len, ptr, IOSIZE - len); + xstrlcpy(IObuff + len, ptr, (size_t)(IOSIZE - len)); len += (int)(tmp_ptr - ptr); *cont_s_ipos = true; } IObuff[len] = NUL; - ptr = (char_u *)IObuff; + ptr = IObuff; } if (len == compl_length) { return NULL; @@ -3037,10 +3159,10 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_ // has added a word that was at the beginning of the line. if (ctrl_x_mode_line_or_eval() || (compl_cont_status & CONT_SOL)) { found_new_match = search_for_exact_line(st->ins_buf, st->cur_match_pos, - compl_direction, (char_u *)compl_pattern); + compl_direction, compl_pattern); } else { found_new_match = searchit(NULL, st->ins_buf, st->cur_match_pos, - NULL, compl_direction, (char_u *)compl_pattern, 1L, + NULL, compl_direction, compl_pattern, 1L, SEARCH_KEEP + SEARCH_NFMSG, RE_LAST, NULL); } msg_silent--; @@ -3084,13 +3206,13 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_ continue; } int len; - char_u *ptr = ins_comp_get_next_word_or_line(st->ins_buf, st->cur_match_pos, - &len, &cont_s_ipos); + char *ptr = ins_comp_get_next_word_or_line(st->ins_buf, st->cur_match_pos, + &len, &cont_s_ipos); if (ptr == NULL) { continue; } if (ins_compl_add_infercase(ptr, len, p_ic, - st->ins_buf == curbuf ? NULL : (char_u *)st->ins_buf->b_sfname, + st->ins_buf == curbuf ? NULL : st->ins_buf->b_sfname, 0, cont_s_ipos) != NOTDONE) { found_new_match = OK; break; @@ -3137,7 +3259,7 @@ static bool get_next_completion_match(int type, ins_compl_next_state_T *st, pos_ case CTRL_X_FUNCTION: case CTRL_X_OMNI: - expand_by_function(type, (char_u *)compl_pattern); + expand_by_function(type, compl_pattern); break; case CTRL_X_SPELL: @@ -3190,7 +3312,7 @@ static int ins_compl_get_exp(pos_T *ini) assert(st.ins_buf != NULL); compl_old_match = compl_curr_match; // remember the last current match - st.cur_match_pos = (compl_dir_forward() ? &st.last_match_pos : &st.first_match_pos); + st.cur_match_pos = compl_dir_forward() ? &st.last_match_pos : &st.first_match_pos; // For ^N/^P loop over all the flags/windows/buffers in 'complete' for (;;) { @@ -3281,7 +3403,7 @@ static int ins_compl_get_exp(pos_T *ini) static void ins_compl_update_shown_match(void) { while (!ins_compl_equal(compl_shown_match, - (char_u *)compl_leader, STRLEN(compl_leader)) + compl_leader, strlen(compl_leader)) && compl_shown_match->cp_next != NULL && !is_first_match(compl_shown_match->cp_next)) { compl_shown_match = compl_shown_match->cp_next; @@ -3290,10 +3412,10 @@ static void ins_compl_update_shown_match(void) // If we didn't find it searching forward, and compl_shows_dir is // backward, find the last match. if (compl_shows_dir_backward() - && !ins_compl_equal(compl_shown_match, (char_u *)compl_leader, STRLEN(compl_leader)) + && !ins_compl_equal(compl_shown_match, compl_leader, strlen(compl_leader)) && (compl_shown_match->cp_next == NULL || is_first_match(compl_shown_match->cp_next))) { - while (!ins_compl_equal(compl_shown_match, (char_u *)compl_leader, STRLEN(compl_leader)) + while (!ins_compl_equal(compl_shown_match, compl_leader, strlen(compl_leader)) && compl_shown_match->cp_prev != NULL && !is_first_match(compl_shown_match->cp_prev)) { compl_shown_match = compl_shown_match->cp_prev; @@ -3360,9 +3482,9 @@ static void ins_compl_show_filename(void) } } msg_hist_off = true; - vim_snprintf((char *)IObuff, IOSIZE, "%s %s%s", lead, + vim_snprintf(IObuff, IOSIZE, "%s %s%s", lead, s > compl_shown_match->cp_fname ? "<" : "", s); - msg((char *)IObuff); + msg(IObuff); msg_hist_off = false; redraw_cmdline = false; // don't overwrite! } @@ -3438,7 +3560,7 @@ static int find_next_completion_match(bool allow_get_expansion, int todo, bool a if (!match_at_original_text(compl_shown_match) && compl_leader != NULL && !ins_compl_equal(compl_shown_match, - (char_u *)compl_leader, STRLEN(compl_leader))) { + compl_leader, strlen(compl_leader))) { todo++; } else { // Remember a matching item. @@ -3559,17 +3681,6 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match return num_matches; } -void pum_ext_select_item(int item, bool insert, bool finish) -{ - if (!pum_visible() || item < -1 || item >= compl_match_arraysize) { - return; - } - pum_want.active = true; - pum_want.item = item; - pum_want.insert = insert; - pum_want.finish = finish; -} - /// Call this while finding completions, to check whether the user has hit a key /// that should change the currently displayed completion, or exit completion /// mode. Also, when compl_pending is not zero, show a completion as soon as @@ -3707,30 +3818,30 @@ static int get_normal_compl_info(char *line, int startcol, colnr_T curs_col) { if ((compl_cont_status & CONT_SOL) || ctrl_x_mode_path_defines()) { if (!compl_status_adding()) { - while (--startcol >= 0 && vim_isIDc(line[startcol])) {} + while (--startcol >= 0 && vim_isIDc((uint8_t)line[startcol])) {} compl_col += ++startcol; compl_length = curs_col - startcol; } if (p_ic) { - compl_pattern = (char *)str_foldcase((char_u *)line + compl_col, compl_length, NULL, 0); + compl_pattern = str_foldcase(line + compl_col, compl_length, NULL, 0); } else { compl_pattern = xstrnsave(line + compl_col, (size_t)compl_length); } } else if (compl_status_adding()) { - char_u *prefix = (char_u *)"\\<"; + char *prefix = "\\<"; // we need up to 2 extra chars for the prefix - compl_pattern = xmalloc(quote_meta(NULL, (char_u *)line + compl_col, compl_length) + 2); - if (!vim_iswordp((char_u *)line + compl_col) + compl_pattern = xmalloc(quote_meta(NULL, line + compl_col, compl_length) + 2); + if (!vim_iswordp(line + compl_col) || (compl_col > 0 - && (vim_iswordp(mb_prevptr((char_u *)line, (char_u *)line + compl_col))))) { - prefix = (char_u *)""; + && (vim_iswordp(mb_prevptr(line, line + compl_col))))) { + prefix = ""; } STRCPY(compl_pattern, prefix); - (void)quote_meta((char_u *)compl_pattern + STRLEN(prefix), - (char_u *)line + compl_col, compl_length); + (void)quote_meta(compl_pattern + strlen(prefix), + line + compl_col, compl_length); } else if (--startcol < 0 - || !vim_iswordp(mb_prevptr((char_u *)line, (char_u *)line + startcol + 1))) { + || !vim_iswordp(mb_prevptr(line, line + startcol + 1))) { // Match any word of at least two chars compl_pattern = xstrdup("\\<\\k\\k"); compl_col += curs_col; @@ -3739,10 +3850,10 @@ static int get_normal_compl_info(char *line, int startcol, colnr_T curs_col) // Search the point of change class of multibyte character // or not a word single byte character backward. startcol -= utf_head_off(line, line + startcol); - int base_class = mb_get_class((char_u *)line + startcol); + int base_class = mb_get_class(line + startcol); while (--startcol >= 0) { int head_off = utf_head_off(line, line + startcol); - if (base_class != mb_get_class((char_u *)line + startcol - head_off)) { + if (base_class != mb_get_class(line + startcol - head_off)) { break; } startcol -= head_off; @@ -3755,13 +3866,12 @@ static int get_normal_compl_info(char *line, int startcol, colnr_T curs_col) // xmalloc(7) is enough -- Acevedo compl_pattern = xmalloc(7); STRCPY(compl_pattern, "\\<"); - (void)quote_meta((char_u *)compl_pattern + 2, (char_u *)line + compl_col, 1); + (void)quote_meta(compl_pattern + 2, line + compl_col, 1); STRCAT(compl_pattern, "\\k"); } else { - compl_pattern = xmalloc(quote_meta(NULL, (char_u *)line + compl_col, - compl_length) + 2); + compl_pattern = xmalloc(quote_meta(NULL, line + compl_col, compl_length) + 2); STRCPY(compl_pattern, "\\<"); - (void)quote_meta((char_u *)compl_pattern + 2, (char_u *)line + compl_col, compl_length); + (void)quote_meta(compl_pattern + 2, line + compl_col, compl_length); } } @@ -3779,7 +3889,7 @@ static int get_wholeline_compl_info(char *line, colnr_T curs_col) compl_length = 0; } if (p_ic) { - compl_pattern = (char *)str_foldcase((char_u *)line + compl_col, compl_length, NULL, 0); + compl_pattern = str_foldcase(line + compl_col, compl_length, NULL, 0); } else { compl_pattern = xstrnsave(line + compl_col, (size_t)compl_length); } @@ -3789,17 +3899,17 @@ static int get_wholeline_compl_info(char *line, colnr_T curs_col) /// Get the pattern, column and length for filename completion. /// Sets the global variables: compl_col, compl_length and compl_pattern. -static int get_filename_compl_info(char_u *line, int startcol, colnr_T curs_col) +static int get_filename_compl_info(char *line, int startcol, colnr_T curs_col) { // Go back to just before the first filename character. if (startcol > 0) { - char_u *p = line + startcol; + char *p = line + startcol; MB_PTR_BACK(line, p); - while (p > line && vim_isfilec(utf_ptr2char((char *)p))) { + while (p > line && vim_isfilec(utf_ptr2char(p))) { MB_PTR_BACK(line, p); } - if (p == line && vim_isfilec(utf_ptr2char((char *)p))) { + if (p == line && vim_isfilec(utf_ptr2char(p))) { startcol = 0; } else { startcol = (int)(p - line) + 1; @@ -3808,7 +3918,7 @@ static int get_filename_compl_info(char_u *line, int startcol, colnr_T curs_col) compl_col += startcol; compl_length = (int)curs_col - startcol; - compl_pattern = addstar((char *)line + compl_col, (size_t)compl_length, EXPAND_FILES); + compl_pattern = addstar(line + compl_col, (size_t)compl_length, EXPAND_FILES); return OK; } @@ -3818,7 +3928,7 @@ static int get_filename_compl_info(char_u *line, int startcol, colnr_T curs_col) static int get_cmdline_compl_info(char *line, colnr_T curs_col) { compl_pattern = xstrnsave(line, (size_t)curs_col); - set_cmd_context(&compl_xp, (char_u *)compl_pattern, (int)strlen(compl_pattern), curs_col, false); + set_cmd_context(&compl_xp, compl_pattern, (int)strlen(compl_pattern), curs_col, false); if (compl_xp.xp_context == EXPAND_UNSUCCESSFUL || compl_xp.xp_context == EXPAND_NOTHING) { // No completion possible, use an empty pattern to get a @@ -3843,7 +3953,7 @@ static int get_userdefined_compl_info(colnr_T curs_col) const int save_State = State; // Call 'completefunc' or 'omnifunc' and get pattern length as a string - char_u *funcname = get_complete_funcname(ctrl_x_mode); + char *funcname = get_complete_funcname(ctrl_x_mode); if (*funcname == NUL) { semsg(_(e_notset), ctrl_x_mode_function() ? "completefunc" : "omnifunc"); return FAIL; @@ -3858,7 +3968,8 @@ static int get_userdefined_compl_info(colnr_T curs_col) pos_T pos = curwin->w_cursor; textlock++; - colnr_T col = (colnr_T)call_func_retnr((char *)funcname, 2, args); + Callback *cb = get_insert_callback(ctrl_x_mode); + colnr_T col = (colnr_T)callback_call_retnr(cb, 2, args); textlock--; State = save_State; @@ -3940,18 +4051,18 @@ static int get_spell_compl_info(int startcol, colnr_T curs_col) /// become invalid and needs to be fetched again. /// /// @return OK on success. -static int compl_get_info(char_u *line, int startcol, colnr_T curs_col, bool *line_invalid) +static int compl_get_info(char *line, int startcol, colnr_T curs_col, bool *line_invalid) { if (ctrl_x_mode_normal() || ((ctrl_x_mode & CTRL_X_WANT_IDENT) && !thesaurus_func_complete(ctrl_x_mode))) { - return get_normal_compl_info((char *)line, startcol, curs_col); + return get_normal_compl_info(line, startcol, curs_col); } else if (ctrl_x_mode_line_or_eval()) { - return get_wholeline_compl_info((char *)line, curs_col); + return get_wholeline_compl_info(line, curs_col); } else if (ctrl_x_mode_files()) { return get_filename_compl_info(line, startcol, curs_col); } else if (ctrl_x_mode == CTRL_X_CMDLINE) { - return get_cmdline_compl_info((char *)line, curs_col); + return get_cmdline_compl_info(line, curs_col); } else if (ctrl_x_mode_function() || ctrl_x_mode_omni() || thesaurus_func_complete(ctrl_x_mode)) { if (get_userdefined_compl_info(curs_col) == FAIL) { @@ -3979,7 +4090,7 @@ static int compl_get_info(char_u *line, int startcol, colnr_T curs_col, bool *li /// the same line as the cursor then fix it (the line has been split because it /// was longer than 'tw'). if SOL is set then skip the previous pattern, a word /// at the beginning of the line has been inserted, we'll look for that. -static void ins_compl_continue_search(char_u *line) +static void ins_compl_continue_search(char *line) { // it is a continued search compl_cont_status &= ~CONT_INTRPT; // remove INTRPT @@ -3991,7 +4102,7 @@ static void ins_compl_continue_search(char_u *line) // 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((char *)line); + compl_col = (colnr_T)getwhitecols(line); compl_startpos.col = compl_col; compl_startpos.lnum = curwin->w_cursor.lnum; compl_cont_status &= ~CONT_SOL; // clear SOL if present @@ -4001,9 +4112,7 @@ static void ins_compl_continue_search(char_u *line) // mode but first we need to redefine compl_startpos if (compl_cont_status & CONT_S_IPOS) { compl_cont_status |= CONT_SOL; - compl_startpos.col = (colnr_T)((char_u *)skipwhite((char *)line - + compl_length - + compl_startpos.col) - line); + compl_startpos.col = (colnr_T)(skipwhite(line + compl_length + compl_startpos.col) - line); } compl_col = compl_startpos.col; } @@ -4050,7 +4159,7 @@ static int ins_compl_start(void) && compl_cont_mode == ctrl_x_mode) { // this same ctrl-x_mode was interrupted previously. Continue the // completion. - ins_compl_continue_search((char_u *)line); + ins_compl_continue_search(line); } else { compl_cont_status &= CONT_LOCAL; } @@ -4070,7 +4179,7 @@ static int ins_compl_start(void) // Work out completion pattern and original text -- webb bool line_invalid = false; - if (compl_get_info((char_u *)line, startcol, curs_col, &line_invalid) == FAIL) { + if (compl_get_info(line, startcol, curs_col, &line_invalid) == FAIL) { if (ctrl_x_mode_function() || ctrl_x_mode_omni() || thesaurus_func_complete(ctrl_x_mode)) { // restore did_ai, so that adding comment leader works @@ -4169,18 +4278,18 @@ static void ins_compl_show_statusmsg(void) if (compl_curr_match->cp_number != -1) { // Space for 10 text chars. + 2x10-digit no.s = 31. // Translations may need more than twice that. - static char_u match_ref[81]; + static char match_ref[81]; if (compl_matches > 0) { - vim_snprintf((char *)match_ref, sizeof(match_ref), + vim_snprintf(match_ref, sizeof(match_ref), _("match %d of %d"), compl_curr_match->cp_number, compl_matches); } else { - vim_snprintf((char *)match_ref, sizeof(match_ref), + vim_snprintf(match_ref, sizeof(match_ref), _("match %d"), compl_curr_match->cp_number); } - edit_submode_extra = (char *)match_ref; + edit_submode_extra = match_ref; edit_submode_highl = HLF_R; if (dollar_vcol >= 0) { curs_columns(curwin, false); @@ -4304,7 +4413,7 @@ static void show_pum(int prev_w_wrow, int prev_w_leftcol) // If dest is not NULL the chars. are copied there quoting (with // a backslash) the metachars, and dest would be NUL terminated. // Returns the length (needed) of dest -static unsigned quote_meta(char_u *dest, char_u *src, int len) +static unsigned quote_meta(char *dest, char *src, int len) { unsigned m = (unsigned)len + 1; // one extra for the NUL @@ -4318,7 +4427,7 @@ static unsigned quote_meta(char_u *dest, char_u *src, int len) } FALLTHROUGH; case '~': - if (!p_magic) { // quote these only if magic is set + if (!magic_isset()) { // quote these only if magic is set break; } FALLTHROUGH; @@ -4339,7 +4448,7 @@ static unsigned quote_meta(char_u *dest, char_u *src, int len) *dest++ = *src; } // Copy remaining bytes of a multibyte character. - const int mb_len = utfc_ptr2len((char *)src) - 1; + const int mb_len = utfc_ptr2len(src) - 1; if (mb_len > 0 && len >= mb_len) { for (int i = 0; i < mb_len; i++) { len--; @@ -4361,6 +4470,9 @@ static unsigned quote_meta(char_u *dest, char_u *src, int len) void free_insexpand_stuff(void) { XFREE_CLEAR(compl_orig_text); + callback_free(&cfu_cb); + callback_free(&ofu_cb); + callback_free(&tsrfu_cb); } #endif diff --git a/src/nvim/insexpand.h b/src/nvim/insexpand.h index 8e183455ca..83ba14e0d2 100644 --- a/src/nvim/insexpand.h +++ b/src/nvim/insexpand.h @@ -1,15 +1,10 @@ #ifndef NVIM_INSEXPAND_H #define NVIM_INSEXPAND_H -#include "nvim/vim.h" +#include <stdbool.h> -/// state for pum_ext_select_item. -EXTERN struct { - bool active; - int item; - bool insert; - bool finish; -} pum_want; +#include "nvim/macros.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "insexpand.h.generated.h" diff --git a/src/nvim/keycodes.c b/src/nvim/keycodes.c index 61dc2ac035..e19806e464 100644 --- a/src/nvim/keycodes.c +++ b/src/nvim/keycodes.c @@ -4,16 +4,25 @@ #include <assert.h> #include <inttypes.h> #include <limits.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> #include "nvim/ascii.h" #include "nvim/charset.h" -#include "nvim/edit.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/vars.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/keycodes.h" +#include "nvim/log.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -46,8 +55,7 @@ static const struct modmasktable { #define MOD_KEYS_ENTRY_SIZE 5 -static char_u modifier_keys_table[] = -{ +static uint8_t modifier_keys_table[] = { // mod mask with modifier without modifier MOD_MASK_SHIFT, '&', '9', '@', '1', // begin MOD_MASK_SHIFT, '&', '0', '@', '2', // cancel @@ -347,8 +355,7 @@ static struct mousetable { int button; // Which mouse button is it? bool is_click; // Is it a mouse button click event? bool is_drag; // Is it a mouse drag event? -} mouse_table[] = -{ +} mouse_table[] = { { KE_LEFTMOUSE, MOUSE_LEFT, true, false }, { KE_LEFTDRAG, MOUSE_LEFT, false, true }, { KE_LEFTRELEASE, MOUSE_LEFT, false, false }, @@ -396,22 +403,24 @@ int name_to_mod_mask(int c) int simplify_key(const int key, int *modifiers) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - if (*modifiers & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT)) { - // TAB is a special case. - if (key == TAB && (*modifiers & MOD_MASK_SHIFT)) { - *modifiers &= ~MOD_MASK_SHIFT; - return K_S_TAB; - } - const int key0 = KEY2TERMCAP0(key); - const int key1 = KEY2TERMCAP1(key); - for (int i = 0; modifier_keys_table[i] != NUL; i += MOD_KEYS_ENTRY_SIZE) { - if (key0 == modifier_keys_table[i + 3] - && key1 == modifier_keys_table[i + 4] - && (*modifiers & modifier_keys_table[i])) { - *modifiers &= ~modifier_keys_table[i]; - return TERMCAP2KEY(modifier_keys_table[i + 1], - modifier_keys_table[i + 2]); - } + if (!(*modifiers & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT))) { + return key; + } + + // TAB is a special case. + if (key == TAB && (*modifiers & MOD_MASK_SHIFT)) { + *modifiers &= ~MOD_MASK_SHIFT; + return K_S_TAB; + } + const int key0 = KEY2TERMCAP0(key); + const int key1 = KEY2TERMCAP1(key); + for (int i = 0; modifier_keys_table[i] != NUL; i += MOD_KEYS_ENTRY_SIZE) { + if (key0 == modifier_keys_table[i + 3] + && key1 == modifier_keys_table[i + 4] + && (*modifiers & modifier_keys_table[i])) { + *modifiers &= ~modifier_keys_table[i]; + return TERMCAP2KEY(modifier_keys_table[i + 1], + modifier_keys_table[i + 2]); } } return key; @@ -465,7 +474,7 @@ char_u *get_special_key_name(int c, int modifiers) int i, idx; int table_idx; - char_u *s; + char *s; string[0] = '<'; idx = 1; @@ -534,7 +543,7 @@ char_u *get_special_key_name(int c, int modifiers) } else { s = transchar(c); while (*s) { - string[idx++] = *s++; + string[idx++] = (uint8_t)(*s++); } } } @@ -563,7 +572,7 @@ char_u *get_special_key_name(int c, int modifiers) /// @param[out] did_simplify found <C-H>, etc. /// /// @return Number of characters added to dst, zero for no match. -unsigned int trans_special(const char_u **const srcp, const size_t src_len, char_u *const dst, +unsigned int trans_special(const char **const srcp, const size_t src_len, char *const dst, const int flags, const bool escape_ks, bool *const did_simplify) FUNC_ATTR_NONNULL_ARG(1, 3) FUNC_ATTR_WARN_UNUSED_RESULT { @@ -573,7 +582,7 @@ unsigned int trans_special(const char_u **const srcp, const size_t src_len, char return 0; } - return special_to_buf(key, modifiers, escape_ks, dst); + return special_to_buf(key, modifiers, escape_ks, (char_u *)dst); } /// Put the character sequence for "key" with "modifiers" into "dst" and return @@ -616,15 +625,15 @@ unsigned int special_to_buf(int key, int modifiers, bool escape_ks, char_u *dst) /// @param[out] did_simplify FSK_SIMPLIFY and found <C-H>, etc. /// /// @return Key and modifiers or 0 if there is no match. -int find_special_key(const char_u **const srcp, const size_t src_len, int *const modp, +int find_special_key(const char **const srcp, const size_t src_len, int *const modp, const int flags, bool *const did_simplify) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1, 3) { - const char_u *last_dash; - const char_u *end_of_name; - const char_u *src; - const char_u *bp; - const char_u *const end = *srcp + src_len - 1; + const char *last_dash; + const char *end_of_name; + const char *src; + const char *bp; + const char *const end = *srcp + src_len - 1; const bool in_string = flags & FSK_IN_STRING; int modifiers; int bit; @@ -650,7 +659,7 @@ int find_special_key(const char_u **const srcp, const size_t src_len, int *const if (*bp == '-') { last_dash = bp; if (bp + 1 <= end) { - l = utfc_ptr2len_len((char *)bp + 1, (int)(end - bp) + 1); + l = utfc_ptr2len_len(bp + 1, (int)(end - bp) + 1); // Anything accepted, like <C-?>. // <C-"> or <M-"> are not special in strings as " is // the string delimiter. With a backslash it works: <M-\"> @@ -665,7 +674,7 @@ int find_special_key(const char_u **const srcp, const size_t src_len, int *const if (end - bp > 3 && bp[0] == 't' && bp[1] == '_') { bp += 3; // skip t_xx, xx may be '-' or '>' } else if (end - bp > 4 && STRNICMP(bp, "char-", 5) == 0) { - vim_str2nr((char *)bp + 5, NULL, &l, STR2NR_ALL, NULL, NULL, 0, true); + vim_str2nr(bp + 5, NULL, &l, STR2NR_ALL, NULL, NULL, 0, true); if (l == 0) { emsg(_(e_invarg)); return 0; @@ -682,7 +691,7 @@ int find_special_key(const char_u **const srcp, const size_t src_len, int *const modifiers = 0x0; for (bp = src + 1; bp < last_dash; bp++) { if (*bp != '-') { - bit = name_to_mod_mask(*bp); + bit = name_to_mod_mask((uint8_t)(*bp)); if (bit == 0x0) { break; // Illegal modifier name } @@ -695,7 +704,7 @@ int find_special_key(const char_u **const srcp, const size_t src_len, int *const if (STRNICMP(last_dash + 1, "char-", 5) == 0 && ascii_isdigit(last_dash[6])) { // <Char-123> or <Char-033> or <Char-0x33> - vim_str2nr((char *)last_dash + 6, NULL, &l, STR2NR_ALL, NULL, &n, 0, true); + vim_str2nr(last_dash + 6, NULL, &l, STR2NR_ALL, NULL, &n, 0, true); if (l == 0) { emsg(_(e_invarg)); return 0; @@ -709,12 +718,12 @@ int find_special_key(const char_u **const srcp, const size_t src_len, int *const // Special case for a double-quoted string off = l = 2; } else { - l = utfc_ptr2len((char *)last_dash + 1); + l = utfc_ptr2len(last_dash + 1); } if (modifiers != 0 && last_dash[l + 1] == '>') { - key = utf_ptr2char((char *)last_dash + off); + key = utf_ptr2char(last_dash + off); } else { - key = get_special_key_code(last_dash + off); + key = get_special_key_code((char_u *)last_dash + off); if (!(flags & FSK_KEEP_X_KEY)) { key = handle_x_keys(key); } @@ -878,11 +887,11 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co { ssize_t i; size_t slen; - char_u key; + char key; size_t dlen = 0; - const char_u *src; - const char_u *const end = (char_u *)from + from_len - 1; - char_u *result; // buffer for resulting string + const char *src; + const char *const end = from + from_len - 1; + char *result; // buffer for resulting string const bool do_backslash = !(cpo_flags & FLAG_CPO_BSLASH); // backslash is a special character const bool do_special = !(flags & REPTERM_NO_SPECIAL); @@ -894,12 +903,12 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co const size_t buf_len = allocated ? from_len * 6 + 1 : 128; result = allocated ? xmalloc(buf_len) : *bufp; - src = (char_u *)from; + src = from; // Check for #n at start only: function key n if ((flags & REPTERM_FROM_PART) && from_len > 1 && src[0] == '#' && ascii_isdigit(src[1])) { // function key - result[dlen++] = K_SPECIAL; + result[dlen++] = (char)K_SPECIAL; result[dlen++] = 'k'; if (src[1] == '0') { result[dlen++] = ';'; // #0 is F10 is "k;" @@ -916,7 +925,7 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co } // Check for special <> keycodes, like "<C-S-LeftMouse>" if (do_special && ((flags & REPTERM_DO_LT) || ((end - src) >= 3 - && STRNCMP(src, "<lt>", 4) != 0))) { + && strncmp(src, "<lt>", 4) != 0))) { // Replace <SID> by K_SNR <script-nr> _. // (room: 5 * 6 = 30 bytes; needed: 3 + <nr> + 1 <= 14) if (end - src >= 4 && STRNICMP(src, "<SID>", 5) == 0) { @@ -924,12 +933,12 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co emsg(_(e_usingsid)); } else { src += 5; - result[dlen++] = K_SPECIAL; - result[dlen++] = KS_EXTRA; + result[dlen++] = (char)K_SPECIAL; + result[dlen++] = (char)KS_EXTRA; result[dlen++] = KE_SNR; - snprintf((char *)result + dlen, buf_len - dlen, "%" PRId64, + snprintf(result + dlen, buf_len - dlen, "%" PRId64, (int64_t)current_sctx.sc_sid); - dlen += STRLEN(result + dlen); + dlen += strlen(result + dlen); result[dlen++] = '_'; continue; } @@ -945,7 +954,8 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co } if (do_special) { - char_u *p, *s, len; + char *p, *s; + int len; // Replace <Leader> by the value of "mapleader". // Replace <LocalLeader> by the value of "maplocalleader". @@ -963,8 +973,8 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co if (len != 0) { // Allow up to 8 * 6 characters for "mapleader". - if (p == NULL || *p == NUL || STRLEN(p) > 8 * 6) { - s = (char_u *)"\\"; + if (p == NULL || *p == NUL || strlen(p) > 8 * 6) { + s = "\\"; } else { s = p; } @@ -995,9 +1005,9 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co for (i = utfc_ptr2len_len((char *)src, (int)(end - src) + 1); i > 0; i--) { // If the character is K_SPECIAL, replace it with K_SPECIAL // KS_SPECIAL KE_FILLER. - if (*src == K_SPECIAL) { - result[dlen++] = K_SPECIAL; - result[dlen++] = KS_SPECIAL; + if (*src == (char)K_SPECIAL) { + result[dlen++] = (char)K_SPECIAL; + result[dlen++] = (char)KS_SPECIAL; result[dlen++] = KE_FILLER; } else { result[dlen++] = *src; @@ -1049,8 +1059,8 @@ char *vim_strsave_escape_ks(char *p) // Need a buffer to hold up to three times as much. Four in case of an // illegal utf-8 byte: // 0xc0 -> 0xc3 - 0x80 -> 0xc3 K_SPECIAL KS_SPECIAL KE_FILLER - char_u *res = xmalloc(strlen(p) * 4 + 1); - char_u *d = res; + char *res = xmalloc(strlen(p) * 4 + 1); + char_u *d = (char_u *)res; for (char_u *s = (char_u *)p; *s != NUL;) { if (s[0] == K_SPECIAL && s[1] != NUL && s[2] != NUL) { // Copy special key unmodified. @@ -1066,7 +1076,7 @@ char *vim_strsave_escape_ks(char *p) } *d = NUL; - return (char *)res; + return res; } /// Remove escaping from K_SPECIAL characters. Reverse of diff --git a/src/nvim/keycodes.h b/src/nvim/keycodes.h index c4d984ee17..7842dee92c 100644 --- a/src/nvim/keycodes.h +++ b/src/nvim/keycodes.h @@ -1,6 +1,10 @@ #ifndef NVIM_KEYCODES_H #define NVIM_KEYCODES_H +#include <stddef.h> + +#include "nvim/ascii.h" +#include "nvim/option_defs.h" #include "nvim/strings.h" // Keycode definitions for special keys. diff --git a/src/nvim/lib/ringbuf.h b/src/nvim/lib/ringbuf.h index 4fd110a531..907018a06e 100644 --- a/src/nvim/lib/ringbuf.h +++ b/src/nvim/lib/ringbuf.h @@ -25,14 +25,9 @@ #define _RINGBUF_LENGTH(rb) \ ((rb)->first == NULL ? 0 \ - : ((rb)->next == (rb)->first) ? (size_t)((rb)->buf_end - (rb)->buf) + 1 \ - : ((rb)->next > \ - (rb)->first) ? (size_t)((rb)->next - \ - (rb)->first) \ - : (size_t)((rb)-> \ - next - (rb)->buf + \ - (rb)->buf_end - \ - (rb)->first + 1)) + : ((rb)->next == (rb)->first) ? (size_t)((rb)->buf_end - (rb)->buf) + 1 \ + : ((rb)->next > (rb)->first) ? (size_t)((rb)->next - (rb)->first) \ + : (size_t)((rb)->next - (rb)->buf + (rb)->buf_end - (rb)->first + 1)) #define _RINGBUF_NEXT(rb, var) \ ((var) == (rb)->buf_end ? (rb)->buf : (var) + 1) diff --git a/src/nvim/linematch.c b/src/nvim/linematch.c new file mode 100644 index 0000000000..a9dac40731 --- /dev/null +++ b/src/nvim/linematch.c @@ -0,0 +1,384 @@ +// 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 <assert.h> +#include <stdbool.h> +#include <stddef.h> +#include <string.h> + +#include "nvim/linematch.h" +#include "nvim/macros.h" +#include "nvim/memory.h" + +// struct for running the diff linematch algorithm +typedef struct { + int *df_decision; // to keep track of this path traveled + int df_lev_score; // to keep track of the total score of this path + size_t df_path_idx; // current index of this path +} diffcmppath_T; + +#define LN_MAX_BUFS 8 + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "linematch.c.generated.h" +#endif + +static size_t line_len(const char *s) +{ + char *end = strchr(s, '\n'); + if (end) { + return (size_t)(end - s); + } + return strlen(s); +} + +/// Same as matching_chars but ignore whitespace +/// +/// @param s1 +/// @param s2 +static int matching_chars_iwhite(const char *s1, const char *s2) +{ + // the newly processed strings that will be compared + // delete the white space characters, and/or replace all upper case with lower + char *strsproc[2]; + const char *strsorig[2] = { s1, s2 }; + for (int k = 0; k < 2; k++) { + size_t d = 0; + size_t i = 0; + size_t slen = line_len(strsorig[k]); + strsproc[k] = xmalloc((slen + 1) * sizeof(char)); + while (d + i < slen) { + char e = strsorig[k][i + d]; + if (e != ' ' && e != '\t') { + strsproc[k][i] = e; + i++; + } else { + d++; + } + } + strsproc[k][i] = '\0'; + } + int matching = matching_chars(strsproc[0], strsproc[1]); + xfree(strsproc[0]); + xfree(strsproc[1]); + return matching; +} + +/// update the path of a point in the diff linematch algorithm +/// @param diffcmppath +/// @param score +/// @param to +/// @param from +/// @param choice +static void update_path_flat(diffcmppath_T *diffcmppath, int score, size_t to, size_t from, + const int choice) +{ + size_t path_idx = diffcmppath[from].df_path_idx; + + for (size_t k = 0; k < path_idx; k++) { + diffcmppath[to].df_decision[k] = diffcmppath[from].df_decision[k]; + } + + diffcmppath[to].df_decision[path_idx] = choice; + diffcmppath[to].df_lev_score = score; + diffcmppath[to].df_path_idx = path_idx + 1; +} + +#define MATCH_CHAR_MAX_LEN 800 + +/// Return matching characters between "s1" and "s2" whilst respecting sequence order. +/// Consider the case of two strings 'AAACCC' and 'CCCAAA', the +/// return value from this function will be 3, either to match +/// the 3 C's, or the 3 A's. +/// +/// Examples: +/// matching_chars("aabc", "acba") -> 2 // 'a' and 'b' in common +/// matching_chars("123hello567", "he123ll567o") -> 8 // '123', 'll' and '567' in common +/// matching_chars("abcdefg", "gfedcba") -> 1 // all characters in common, +/// // but only at most 1 in sequence +/// +/// @param s1 +/// @param s2 +static int matching_chars(const char *s1, const char *s2) +{ + size_t s1len = MIN(MATCH_CHAR_MAX_LEN - 1, line_len(s1)); + size_t s2len = MIN(MATCH_CHAR_MAX_LEN - 1, line_len(s2)); + int matrix[2][MATCH_CHAR_MAX_LEN] = { 0 }; + bool icur = 1; // save space by storing only two rows for i axis + for (size_t i = 0; i < s1len; i++) { + icur = !icur; + int *e1 = matrix[icur]; + int *e2 = matrix[!icur]; + for (size_t j = 0; j < s2len; j++) { + // skip char in s1 + if (e2[j + 1] > e1[j + 1]) { + e1[j + 1] = e2[j + 1]; + } + // skip char in s2 + if (e1[j] > e1[j + 1]) { + e1[j + 1] = e1[j]; + } + // compare char in s1 and s2 + if ((s1[i] == s2[j]) && (e2[j] + 1) > e1[j + 1]) { + e1[j + 1] = e2[j] + 1; + } + } + } + return matrix[icur][s2len]; +} + +/// count the matching characters between a variable number of strings "sp" +/// mark the strings that have already been compared to extract them later +/// without re-running the character match counting. +/// @param sp +/// @param fomvals +/// @param n +static int count_n_matched_chars(const char **sp, const size_t n, bool iwhite) +{ + int matched_chars = 0; + int matched = 0; + for (size_t i = 0; i < n; i++) { + for (size_t j = i + 1; j < n; j++) { + if (sp[i] != NULL && sp[j] != NULL) { + matched++; + // TODO(lewis6991): handle whitespace ignoring higher up in the stack + matched_chars += iwhite ? matching_chars_iwhite(sp[i], sp[j]) + : matching_chars(sp[i], sp[j]); + } + } + } + + // prioritize a match of 3 (or more lines) equally to a match of 2 lines + if (matched >= 2) { + matched_chars *= 2; + matched_chars /= matched; + } + + return matched_chars; +} + +void fastforward_buf_to_lnum(const char **s, long lnum) +{ + for (long i = 0; i < lnum - 1; i++) { + *s = strchr(*s, '\n'); + (*s)++; + } +} + +/// try all the different ways to compare these lines and use the one that +/// results in the most matching characters +/// @param df_iters +/// @param paths +/// @param npaths +/// @param path_idx +/// @param choice +/// @param diffcmppath +/// @param diff_len +/// @param ndiffs +/// @param diff_blk +static void try_possible_paths(const int *df_iters, const size_t *paths, const int npaths, + const int path_idx, int *choice, diffcmppath_T *diffcmppath, + const int *diff_len, const size_t ndiffs, const char **diff_blk, + bool iwhite) +{ + if (path_idx == npaths) { + if ((*choice) > 0) { + int from_vals[LN_MAX_BUFS]; + const int *to_vals = df_iters; + const char *current_lines[LN_MAX_BUFS]; + for (size_t k = 0; k < ndiffs; k++) { + from_vals[k] = df_iters[k]; + // get the index at all of the places + if ((*choice) & (1 << k)) { + from_vals[k]--; + const char *p = diff_blk[k]; + fastforward_buf_to_lnum(&p, df_iters[k]); + current_lines[k] = p; + } else { + current_lines[k] = NULL; + } + } + size_t unwrapped_idx_from = unwrap_indexes(from_vals, diff_len, ndiffs); + size_t unwrapped_idx_to = unwrap_indexes(to_vals, diff_len, ndiffs); + int matched_chars = count_n_matched_chars(current_lines, ndiffs, iwhite); + int score = diffcmppath[unwrapped_idx_from].df_lev_score + matched_chars; + if (score > diffcmppath[unwrapped_idx_to].df_lev_score) { + update_path_flat(diffcmppath, score, unwrapped_idx_to, unwrapped_idx_from, *choice); + } + } else { + // initialize the 0, 0, 0 ... choice + size_t i = 0; + while (i < ndiffs && df_iters[i] == 0) { + i++; + if (i == ndiffs) { + diffcmppath[0].df_lev_score = 0; + diffcmppath[0].df_path_idx = 0; + } + } + } + return; + } + size_t bit_place = paths[path_idx]; + *(choice) |= (1 << bit_place); // set it to 1 + try_possible_paths(df_iters, paths, npaths, path_idx + 1, choice, + diffcmppath, diff_len, ndiffs, diff_blk, iwhite); + *(choice) &= ~(1 << bit_place); // set it to 0 + try_possible_paths(df_iters, paths, npaths, path_idx + 1, choice, + diffcmppath, diff_len, ndiffs, diff_blk, iwhite); +} + +/// unwrap indexes to access n dimensional tensor +/// @param values +/// @param diff_len +/// @param ndiffs +static size_t unwrap_indexes(const int *values, const int *diff_len, const size_t ndiffs) +{ + size_t num_unwrap_scalar = 1; + for (size_t k = 0; k < ndiffs; k++) { + num_unwrap_scalar *= (size_t)diff_len[k] + 1; + } + + size_t path_idx = 0; + for (size_t k = 0; k < ndiffs; k++) { + num_unwrap_scalar /= (size_t)diff_len[k] + 1; + + // (k == 0) space optimization + int n = k == 0 ? values[k] % 2 : values[k]; + path_idx += num_unwrap_scalar * (size_t)n; + } + return path_idx; +} + +/// populate the values of the linematch algorithm tensor, and find the best +/// decision for how to compare the relevant lines from each of the buffers at +/// each point in the tensor +/// @param df_iters +/// @param ch_dim +/// @param diffcmppath +/// @param diff_len +/// @param ndiffs +/// @param diff_blk +static void populate_tensor(int *df_iters, const size_t ch_dim, diffcmppath_T *diffcmppath, + const int *diff_len, const size_t ndiffs, const char **diff_blk, + bool iwhite) +{ + if (ch_dim == ndiffs) { + int npaths = 0; + size_t paths[LN_MAX_BUFS]; + + for (size_t j = 0; j < ndiffs; j++) { + if (df_iters[j] > 0) { + paths[npaths] = j; + npaths++; + } + } + int choice = 0; + size_t unwrapper_idx_to = unwrap_indexes(df_iters, diff_len, ndiffs); + diffcmppath[unwrapper_idx_to].df_lev_score = -1; + try_possible_paths(df_iters, paths, npaths, 0, &choice, diffcmppath, + diff_len, ndiffs, diff_blk, iwhite); + return; + } + + for (int i = 0; i <= diff_len[ch_dim]; i++) { + df_iters[ch_dim] = i; + populate_tensor(df_iters, ch_dim + 1, diffcmppath, diff_len, + ndiffs, diff_blk, iwhite); + } +} + +/// algorithm to find an optimal alignment of lines of a diff block with 2 or +/// more files. The algorithm is generalized to work for any number of files +/// which corresponds to another dimension added to the tensor used in the +/// algorithm +/// +/// for questions and information about the linematch algorithm please contact +/// Jonathon White (jonathonwhite@protonmail.com) +/// +/// for explanation, a summary of the algorithm in 3 dimensions (3 files +/// compared) follows +/// +/// The 3d case (for 3 buffers) of the algorithm implemented when diffopt +/// 'linematch' is enabled. The algorithm constructs a 3d tensor to +/// compare a diff between 3 buffers. The dimensions of the tensor are +/// the length of the diff in each buffer plus 1 A path is constructed by +/// moving from one edge of the cube/3d tensor to the opposite edge. +/// Motions from one cell of the cube to the next represent decisions. In +/// a 3d cube, there are a total of 7 decisions that can be made, +/// represented by the enum df_path3_choice which is defined in +/// buffer_defs.h a comparison of buffer 0 and 1 represents a motion +/// toward the opposite edge of the cube with components along the 0 and +/// 1 axes. a comparison of buffer 0, 1, and 2 represents a motion +/// toward the opposite edge of the cube with components along the 0, 1, +/// and 2 axes. A skip of buffer 0 represents a motion along only the 0 +/// axis. For each action, a point value is awarded, and the path is +/// saved for reference later, if it is found to have been the optimal +/// path. The optimal path has the highest score. The score is +/// calculated as the summation of the total characters matching between +/// all of the lines which were compared. The structure of the algorithm +/// is that of a dynamic programming problem. We can calculate a point +/// i,j,k in the cube as a function of i-1, j-1, and k-1. To find the +/// score and path at point i,j,k, we must determine which path we want +/// to use, this is done by looking at the possibilities and choosing +/// the one which results in the local highest score. The total highest +/// scored path is, then in the end represented by the cell in the +/// opposite corner from the start location. The entire algorithm +/// consists of populating the 3d cube with the optimal paths from which +/// it may have came. +/// +/// Optimizations: +/// As the function to calculate the cell of a tensor at point i,j,k is a +/// function of the cells at i-1, j-1, k-1, the whole tensor doesn't need +/// to be stored in memory at once. In the case of the 3d cube, only two +/// slices (along k and j axis) are stored in memory. For the 2d matrix +/// (for 2 files), only two rows are stored at a time. The next/previous +/// slice (or row) is always calculated from the other, and they alternate +/// at each iteration. +/// In the 3d case, 3 arrays are populated to memorize the score (matched +/// characters) of the 3 buffers, so a redundant calculation of the +/// scores does not occur +/// @param diff_blk +/// @param diff_len +/// @param ndiffs +/// @param [out] [allocated] decisions +/// @return the length of decisions +size_t linematch_nbuffers(const char **diff_blk, const int *diff_len, const size_t ndiffs, + int **decisions, bool iwhite) +{ + assert(ndiffs <= LN_MAX_BUFS); + + size_t memsize = 1; + size_t memsize_decisions = 0; + for (size_t i = 0; i < ndiffs; i++) { + assert(diff_len[i] >= 0); + memsize *= i == 0 ? 2 : (size_t)(diff_len[i] + 1); + memsize_decisions += (size_t)diff_len[i]; + } + + // create the flattened path matrix + diffcmppath_T *diffcmppath = xmalloc(sizeof(diffcmppath_T) * memsize); + // allocate memory here + for (size_t i = 0; i < memsize; i++) { + diffcmppath[i].df_decision = xmalloc(memsize_decisions * sizeof(int)); + } + + // memory for avoiding repetitive calculations of score + int df_iters[LN_MAX_BUFS]; + populate_tensor(df_iters, 0, diffcmppath, diff_len, ndiffs, diff_blk, iwhite); + + const size_t u = unwrap_indexes(diff_len, diff_len, ndiffs); + const size_t best_path_idx = diffcmppath[u].df_path_idx; + const int *best_path_decisions = diffcmppath[u].df_decision; + + *decisions = xmalloc(sizeof(int) * best_path_idx); + for (size_t i = 0; i < best_path_idx; i++) { + (*decisions)[i] = best_path_decisions[i]; + } + + for (size_t i = 0; i < memsize; i++) { + xfree(diffcmppath[i].df_decision); + } + xfree(diffcmppath); + + return best_path_idx; +} diff --git a/src/nvim/linematch.h b/src/nvim/linematch.h new file mode 100644 index 0000000000..052d438617 --- /dev/null +++ b/src/nvim/linematch.h @@ -0,0 +1,10 @@ +#ifndef NVIM_LINEMATCH_H +#define NVIM_LINEMATCH_H + +#include <stddef.h> + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "linematch.h.generated.h" +#endif + +#endif // NVIM_LINEMATCH_H diff --git a/src/nvim/locale.c b/src/nvim/locale.c index c472d9ba66..c3cfd3bedb 100644 --- a/src/nvim/locale.c +++ b/src/nvim/locale.c @@ -3,8 +3,10 @@ // locale.c: functions for language/locale configuration -#include "auto/config.h" +#include <stdbool.h> +#include <stdio.h> +#include "auto/config.h" #ifdef HAVE_LOCALE_H # include <locale.h> #endif @@ -13,8 +15,11 @@ #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/eval.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/garray.h" +#include "nvim/gettext.h" #include "nvim/locale.h" +#include "nvim/macros.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/option.h" @@ -23,6 +28,7 @@ #include "nvim/path.h" #include "nvim/profile.h" #include "nvim/types.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "locale.c.generated.h" @@ -42,7 +48,7 @@ static char *get_locale_val(int what) /// @return true when "lang" starts with a valid language name. /// Rejects NULL, empty string, "C", "C.UTF-8" and others. -static bool is_valid_mess_lang(char *lang) +static bool is_valid_mess_lang(const char *lang) { return lang != NULL && ASCII_ISALPHA(lang[0]) && ASCII_ISALPHA(lang[1]); } @@ -284,12 +290,11 @@ static char **find_locales(void) // Find all available locales by running command "locale -a". If this // doesn't work we won't have completion. - char *locale_a = (char *)get_cmd_output((char_u *)"locale -a", NULL, - kShellOptSilent, NULL); + char *locale_a = get_cmd_output("locale -a", NULL, kShellOptSilent, NULL); if (locale_a == NULL) { return NULL; } - ga_init(&locales_ga, sizeof(char_u *), 20); + ga_init(&locales_ga, sizeof(char *), 20); // Transform locale_a string where each locale is separated by "\n" // into an array of locale strings. @@ -303,7 +308,7 @@ static char **find_locales(void) xfree(locale_a); // Guarantee that .ga_data is NULL terminated ga_grow(&locales_ga, 1); - ((char_u **)locales_ga.ga_data)[locales_ga.ga_len] = NULL; + ((char **)locales_ga.ga_data)[locales_ga.ga_len] = NULL; return locales_ga.ga_data; } # endif @@ -312,23 +317,26 @@ static char **find_locales(void) static void init_locales(void) { # ifndef MSWIN - if (!did_init_locales) { - did_init_locales = true; - locales = find_locales(); + if (did_init_locales) { + return; } + + did_init_locales = true; + locales = find_locales(); # endif } # if defined(EXITFREE) void free_locales(void) { - int i; - if (locales != NULL) { - for (i = 0; locales[i] != NULL; i++) { - xfree(locales[i]); - } - XFREE_CLEAR(locales); + if (locales == NULL) { + return; + } + + for (int i = 0; locales[i] != NULL; i++) { + xfree(locales[i]); } + XFREE_CLEAR(locales); } # endif diff --git a/src/nvim/log.c b/src/nvim/log.c index 9bdf327430..2c214aa32d 100644 --- a/src/nvim/log.c +++ b/src/nvim/log.c @@ -9,21 +9,27 @@ // #include <assert.h> +#include <errno.h> #include <inttypes.h> #include <stdarg.h> #include <stdbool.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> #include <uv.h> #include "auto/config.h" +#include "nvim/ascii.h" #include "nvim/eval.h" +#include "nvim/globals.h" #include "nvim/log.h" -#include "nvim/main.h" +#include "nvim/memory.h" #include "nvim/message.h" #include "nvim/os/os.h" +#include "nvim/os/stdpaths_defs.h" #include "nvim/os/time.h" #include "nvim/path.h" -#include "nvim/types.h" /// Cached location of the expanded log file path decided by log_path_init(). static char log_file_path[MAXPATHL + 1] = { 0 }; diff --git a/src/nvim/log.h b/src/nvim/log.h index cbee0e0f81..14d46c2ea7 100644 --- a/src/nvim/log.h +++ b/src/nvim/log.h @@ -11,6 +11,7 @@ // NVIM_PROBE(nvim_foo_bar, 1, string.data); #if defined(HAVE_SYS_SDT_H) # include <sys/sdt.h> // NOLINT + # define NVIM_PROBE(name, n, ...) STAP_PROBE##n(neovim, name, __VA_ARGS__) #else # define NVIM_PROBE(name, n, ...) diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index bdb0719809..6160b84485 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -4,14 +4,14 @@ #include <assert.h> #include <lauxlib.h> #include <lua.h> -#include <lualib.h> #include <stdbool.h> +#include <stddef.h> #include <stdint.h> +#include <stdlib.h> +#include <string.h> #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/assert.h" -#include "nvim/func_attr.h" #include "nvim/memory.h" // FIXME: vim.h is not actually needed, but otherwise it states MAXPATHL is // redefined @@ -19,12 +19,16 @@ #include "nvim/ascii.h" #include "nvim/eval/decode.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/eval/typval_encode.h" #include "nvim/eval/userfunc.h" -#include "nvim/globals.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" #include "nvim/lua/converter.h" #include "nvim/lua/executor.h" #include "nvim/macros.h" #include "nvim/message.h" +#include "nvim/types.h" #include "nvim/vim.h" /// Determine, which keys lua table contains @@ -387,7 +391,7 @@ nlua_pop_typval_table_processing_end: case LUA_TFUNCTION: { LuaRef func = nlua_ref_global(lstate, -1); - char *name = (char *)register_luafunc(func); + char *name = register_luafunc(func); cur.tv->v_type = VAR_FUNC; cur.tv->vval.v_string = xstrdup(name); @@ -565,6 +569,7 @@ static bool typval_conv_special = false; #define TYPVAL_ENCODE_FIRST_ARG_TYPE lua_State *const #define TYPVAL_ENCODE_FIRST_ARG_NAME lstate #include "nvim/eval/typval_encode.c.h" + #undef TYPVAL_ENCODE_SCOPE #undef TYPVAL_ENCODE_NAME #undef TYPVAL_ENCODE_FIRST_ARG_TYPE @@ -906,9 +911,8 @@ Float nlua_pop_Float(lua_State *lstate, Error *err) lua_pop(lstate, 1); if (table_props.type != kObjectTypeFloat) { return 0; - } else { - return (Float)table_props.val; } + return (Float)table_props.val; } /// Convert lua table to array without determining whether it is array diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index f3821f149a..5ffd90fddd 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1,18 +1,22 @@ // 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 <assert.h> +#include <inttypes.h> #include <lauxlib.h> #include <lua.h> #include <lualib.h> +#include <stddef.h> +#include <string.h> #include <tree_sitter/api.h> +#include <uv.h> +#include "klib/kvec.h" #include "luv/luv.h" #include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" #include "nvim/ascii.h" -#include "nvim/assert.h" #include "nvim/buffer_defs.h" #include "nvim/change.h" #include "nvim/cursor.h" @@ -20,30 +24,39 @@ #include "nvim/eval.h" #include "nvim/eval/funcs.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" +#include "nvim/event/defs.h" #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" #include "nvim/event/time.h" #include "nvim/ex_cmds.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_getln.h" -#include "nvim/extmark.h" -#include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/keycodes.h" #include "nvim/lua/converter.h" #include "nvim/lua/executor.h" #include "nvim/lua/stdlib.h" #include "nvim/lua/treesitter.h" #include "nvim/macros.h" -#include "nvim/map.h" +#include "nvim/main.h" #include "nvim/memline.h" +#include "nvim/memory.h" #include "nvim/message.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/option_defs.h" +#include "nvim/os/fileio.h" #include "nvim/os/os.h" +#include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/runtime.h" -#include "nvim/screen.h" +#include "nvim/strings.h" #include "nvim/ui.h" -#include "nvim/ui_compositor.h" #include "nvim/undo.h" #include "nvim/usercmd.h" #include "nvim/version.h" @@ -192,8 +205,8 @@ static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags if (status) { if (status == LUA_ERRMEM && !(flags & LUVF_CALLBACK_NOEXIT)) { // consider out of memory errors unrecoverable, just like xmalloc() - mch_errmsg(e_outofmem); - mch_errmsg("\n"); + os_errmsg(e_outofmem); + os_errmsg("\n"); preserve_exit(); } const char *error = lua_tostring(lstate, -1); @@ -245,8 +258,8 @@ static int nlua_luv_thread_common_cfpcall(lua_State *lstate, int nargs, int nres 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"); + os_errmsg(e_outofmem); + os_errmsg("\n"); lua_close(lstate); #ifdef MSWIN ExitThread(0); @@ -310,6 +323,36 @@ static int nlua_thr_api_nvim__get_runtime(lua_State *lstate) return 1; } +/// Copies args starting at `lua_arg0` to Lua `_G.arg`, and sets `_G.arg[0]` to the scriptname. +/// +/// Example (arg[0] => "foo.lua", arg[1] => "--arg1", …): +/// nvim -l foo.lua --arg1 --arg2 +/// +/// @note Lua CLI sets args before "-e" as _negative_ `_G.arg` indices, but we currently don't. +/// +/// @see https://www.lua.org/pil/1.4.html +/// @see https://github.com/premake/premake-core/blob/1c1304637f4f5e50ba8c57aae8d1d80ec3b7aaf2/src/host/premake.c#L563-L594 +/// +/// @returns number of args +static int nlua_init_argv(lua_State *const L, char **argv, int argc, int lua_arg0) +{ + int i = 0; + lua_newtable(L); // _G.arg + + if (lua_arg0 > 0) { + lua_pushstring(L, argv[lua_arg0 - 1]); + lua_rawseti(L, -2, 0); // _G.arg[0] = "foo.lua" + + for (; lua_arg0 >= 0 && i + lua_arg0 < argc; i++) { + lua_pushstring(L, argv[i + lua_arg0]); + lua_rawseti(L, -2, i + 1); // _G.arg[i+1] = "--foo" + } + } + + lua_setglobal(L, "arg"); + return i; +} + static void nlua_schedule_event(void **argv) { LuaRef cb = (LuaRef)(ptrdiff_t)argv[0]; @@ -400,7 +443,7 @@ static int nlua_wait(lua_State *lstate) bool fast_only = false; if (lua_top >= 4) { - fast_only = lua_toboolean(lstate, 4); + fast_only = lua_toboolean(lstate, 4); } MultiQueue *loop_events = fast_only || in_fast_callback > 0 @@ -585,8 +628,8 @@ static bool nlua_init_packages(lua_State *lstate) 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"); + os_errmsg((char *)lua_tostring(lstate, -1)); + os_errmsg("\n"); return false; } @@ -640,7 +683,7 @@ ok: } LuaRef ui_event_cb = nlua_ref_global(lstate, 3); - ui_comp_add_cb(ns_id, ui_event_cb, ext_widgets); + ui_add_cb(ns_id, ui_event_cb, ext_widgets); return 0; } @@ -654,7 +697,7 @@ static int nlua_ui_detach(lua_State *lstate) return luaL_error(lstate, "invalid ns_id"); } - ui_comp_remove_cb(ns_id); + ui_remove_cb(ns_id); return 0; } @@ -752,10 +795,8 @@ static bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL return true; } -/// Initialize global lua interpreter -/// -/// Crashes Nvim if initialization fails. -void nlua_init(void) +/// Initializes global Lua interpreter, or exits Nvim on failure. +void nlua_init(char **argv, int argc, int lua_arg0) { #ifdef NLUA_TRACK_REFS const char *env = os_getenv("NVIM_LUA_NOTRACK"); @@ -766,20 +807,19 @@ void nlua_init(void) lua_State *lstate = luaL_newstate(); if (lstate == NULL) { - mch_errmsg(_("E970: Failed to initialize lua interpreter\n")); + os_errmsg(_("E970: Failed to initialize lua interpreter\n")); os_exit(1); } luaL_openlibs(lstate); if (!nlua_state_init(lstate)) { - mch_errmsg(_("E970: Failed to initialize builtin lua modules\n")); + os_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(); + nlua_init_argv(lstate, argv, argc, lua_arg0); } static lua_State *nlua_thread_acquire_vm(void) @@ -1012,8 +1052,8 @@ static int nlua_require(lua_State *const lstate) time_push(&rel_time, &start_time); int status = lua_pcall(lstate, 1, 1, 0); if (status == 0) { - vim_snprintf((char *)IObuff, IOSIZE, "require('%s')", name); - time_msg((char *)IObuff, &start_time); + vim_snprintf(IObuff, IOSIZE, "require('%s')", name); + time_msg(IObuff, &start_time); } time_pop(rel_time); @@ -1177,6 +1217,7 @@ static int nlua_rpc(lua_State *lstate, bool request) api_set_error(&err, kErrorTypeValidation, "Invalid channel: %" PRIu64, chan_id); } + api_free_array(args); // TODO(bfredl): no } check_err: @@ -1301,7 +1342,7 @@ void nlua_typval_eval(const String str, typval_T *const arg, typval_T *const ret const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str.size + 1; char *lcmd; if (lcmd_len < IOSIZE) { - lcmd = (char *)IObuff; + lcmd = IObuff; } else { lcmd = xmalloc(lcmd_len); } @@ -1311,7 +1352,7 @@ void nlua_typval_eval(const String str, typval_T *const arg, typval_T *const ret #undef EVALHEADER nlua_typval_exec(lcmd, lcmd_len, "luaeval()", arg, 1, true, ret_tv); - if (lcmd != (char *)IObuff) { + if (lcmd != IObuff) { xfree(lcmd); } } @@ -1325,7 +1366,7 @@ void nlua_typval_call(const char *str, size_t len, typval_T *const args, int arg const size_t lcmd_len = sizeof(CALLHEADER) - 1 + len + sizeof(CALLSUFFIX) - 1; char *lcmd; if (lcmd_len < IOSIZE) { - lcmd = (char *)IObuff; + lcmd = IObuff; } else { lcmd = xmalloc(lcmd_len); } @@ -1338,7 +1379,7 @@ void nlua_typval_call(const char *str, size_t len, typval_T *const args, int arg nlua_typval_exec(lcmd, lcmd_len, "v:lua", args, argcount, false, ret_tv); - if (lcmd != (char *)IObuff) { + if (lcmd != IObuff) { xfree(lcmd); } } @@ -1399,11 +1440,11 @@ int nlua_source_using_linegetter(LineGetter fgetline, void *cookie, char *name) estack_push(ETYPE_SCRIPT, name, 0); garray_T ga; - char_u *line = NULL; + char *line = NULL; - ga_init(&ga, (int)sizeof(char_u *), 10); - while ((line = (char_u *)fgetline(0, cookie, 0, false)) != NULL) { - GA_APPEND(char_u *, &ga, line); + ga_init(&ga, (int)sizeof(char *), 10); + while ((line = fgetline(0, cookie, 0, false)) != NULL) { + GA_APPEND(char *, &ga, line); } char *code = ga_concat_strings_sep(&ga, "\n"); size_t len = strlen(code); @@ -1604,7 +1645,7 @@ void ex_luado(exarg_T *const eap) + (sizeof(DOEND) - 1)); char *lcmd; if (lcmd_len < IOSIZE) { - lcmd = (char *)IObuff; + lcmd = IObuff; } else { lcmd = xmalloc(lcmd_len + 1); } @@ -1672,21 +1713,51 @@ void ex_luafile(exarg_T *const eap) nlua_exec_file((const char *)eap->arg); } -/// execute lua code from a file. +/// Executes Lua code from a file or "-" (stdin). /// -/// Note: we call the lua global loadfile as opposed to calling luaL_loadfile -/// in case loadfile has been overridden in the users environment. +/// Calls the Lua `loadfile` global as opposed to `luaL_loadfile` in case `loadfile` was overridden +/// in the user environment. /// -/// @param path path of the file +/// @param path Path to the file, may be "-" (stdin) during startup. /// -/// @return true if everything ok, false if there was an error (echoed) +/// @return true on success, false on error (echoed) or user canceled (CTRL-c) while reading "-" +/// (stdin). bool nlua_exec_file(const char *path) FUNC_ATTR_NONNULL_ALL { lua_State *const lstate = global_lstate; + if (!strequal(path, "-")) { + lua_getglobal(lstate, "loadfile"); + lua_pushstring(lstate, path); + } else { + FileDescriptor *stdin_dup = file_open_stdin(); + + StringBuilder sb = KV_INITIAL_VALUE; + kv_resize(sb, 64); + ptrdiff_t read_size = -1; + // Read all input from stdin, unless interrupted (ctrl-c). + while (true) { + if (got_int) { // User canceled. + return false; + } + read_size = file_read(stdin_dup, IObuff, 64); + if (read_size < 0) { // Error. + return false; + } + if (read_size > 0) { + kv_concat_len(sb, IObuff, (size_t)read_size); + } + if (read_size < 64) { // EOF. + break; + } + } + kv_push(sb, NUL); + file_free(stdin_dup, false); - lua_getglobal(lstate, "loadfile"); - lua_pushstring(lstate, path); + lua_getglobal(lstate, "loadstring"); + lua_pushstring(lstate, sb.items); + kv_destroy(sb); + } if (nlua_pcall(lstate, 1, 2)) { nlua_error(lstate, _("E5111: Error calling lua: %.*s")); @@ -1758,7 +1829,7 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_setfield(lstate, -2, "_ts_get_minimum_language_version"); } -int nlua_expand_pat(expand_T *xp, char_u *pat, int *num_results, char ***results) +int nlua_expand_pat(expand_T *xp, char *pat, int *num_results, char ***results) { lua_State *const lstate = global_lstate; int ret = OK; @@ -1771,7 +1842,7 @@ int nlua_expand_pat(expand_T *xp, char_u *pat, int *num_results, char ***results luaL_checktype(lstate, -1, LUA_TFUNCTION); // [ vim, vim._expand_pat, buf ] - lua_pushlstring(lstate, (const char *)pat, STRLEN(pat)); + lua_pushlstring(lstate, (const char *)pat, strlen(pat)); if (nlua_pcall(lstate, 1, 2) != 0) { nlua_error(lstate, @@ -1806,7 +1877,7 @@ int nlua_expand_pat(expand_T *xp, char_u *pat, int *num_results, char ***results goto cleanup_array; } - GA_APPEND(char_u *, &result_array, (char_u *)string_to_cstr(v.data.string)); + GA_APPEND(char *, &result_array, string_to_cstr(v.data.string)); } xp->xp_pattern += prefix_len; @@ -1832,7 +1903,7 @@ static int nlua_is_thread(lua_State *lstate) return 1; } -bool nlua_is_table_from_lua(typval_T *const arg) +bool nlua_is_table_from_lua(const typval_T *const arg) { if (arg->v_type == VAR_DICT) { return arg->vval.v_dict->lua_table_ref != LUA_NOREF; @@ -1843,7 +1914,7 @@ bool nlua_is_table_from_lua(typval_T *const arg) } } -char_u *nlua_register_table_as_callable(typval_T *const arg) +char *nlua_register_table_as_callable(const typval_T *const arg) { LuaRef table_ref = LUA_NOREF; if (arg->v_type == VAR_DICT) { @@ -1879,7 +1950,7 @@ char_u *nlua_register_table_as_callable(typval_T *const arg) LuaRef func = nlua_ref_global(lstate, -1); - char_u *name = register_luafunc(func); + char *name = register_luafunc(func); lua_pop(lstate, 1); // [] assert(top == lua_gettop(lstate)); @@ -1889,8 +1960,8 @@ char_u *nlua_register_table_as_callable(typval_T *const arg) void nlua_execute_on_key(int c) { - char_u buf[NUMBUFLEN]; - size_t buf_len = special_to_buf(c, mod_mask, false, buf); + char buf[NUMBUFLEN]; + size_t buf_len = special_to_buf(c, mod_mask, false, (char_u *)buf); lua_State *const lstate = global_lstate; @@ -1906,7 +1977,7 @@ void nlua_execute_on_key(int c) luaL_checktype(lstate, -1, LUA_TFUNCTION); // [ vim, vim._on_key, buf ] - lua_pushlstring(lstate, (const char *)buf, buf_len); + lua_pushlstring(lstate, buf, buf_len); int save_got_int = got_int; got_int = false; // avoid interrupts when the key typed is Ctrl-C @@ -1985,6 +2056,9 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview) nlua_pushref(lstate, preview ? cmd->uc_preview_luaref : cmd->uc_luaref); lua_newtable(lstate); + lua_pushstring(lstate, cmd->uc_name); + lua_setfield(lstate, -2, "name"); + lua_pushboolean(lstate, eap->forceit == 1); lua_setfield(lstate, -2, "bang"); @@ -2177,3 +2251,80 @@ plain: kv_printf(str, "<Lua %d>", ref); return str.items; } + +char *nlua_read_secure(const char *path) +{ + lua_State *const lstate = global_lstate; + const int top = lua_gettop(lstate); + + lua_getglobal(lstate, "vim"); + lua_getfield(lstate, -1, "secure"); + lua_getfield(lstate, -1, "read"); + lua_pushstring(lstate, path); + if (nlua_pcall(lstate, 1, 1)) { + nlua_error(lstate, _("Error executing vim.secure.read: %.*s")); + lua_settop(lstate, top); + return NULL; + } + + size_t len = 0; + const char *contents = lua_tolstring(lstate, -1, &len); + char *buf = NULL; + if (contents != NULL) { + // Add one to include trailing null byte + buf = xcalloc(len + 1, sizeof(char)); + memcpy(buf, contents, len + 1); + } + + lua_settop(lstate, top); + return buf; +} + +bool nlua_trust(const char *action, const char *path) +{ + lua_State *const lstate = global_lstate; + const int top = lua_gettop(lstate); + + lua_getglobal(lstate, "vim"); + lua_getfield(lstate, -1, "secure"); + lua_getfield(lstate, -1, "trust"); + + lua_newtable(lstate); + lua_pushstring(lstate, "action"); + lua_pushstring(lstate, action); + lua_settable(lstate, -3); + if (path == NULL) { + lua_pushstring(lstate, "bufnr"); + lua_pushnumber(lstate, 0); + lua_settable(lstate, -3); + } else { + lua_pushstring(lstate, "path"); + lua_pushstring(lstate, path); + lua_settable(lstate, -3); + } + + if (nlua_pcall(lstate, 1, 2)) { + nlua_error(lstate, _("Error executing vim.secure.trust: %.*s")); + lua_settop(lstate, top); + return false; + } + + bool success = lua_toboolean(lstate, -2); + const char *msg = lua_tostring(lstate, -1); + if (msg != NULL) { + if (success) { + if (strcmp(action, "allow") == 0) { + smsg("Allowed \"%s\" in trust database.", msg); + } else if (strcmp(action, "deny") == 0) { + smsg("Denied \"%s\" in trust database.", msg); + } else if (strcmp(action, "remove") == 0) { + smsg("Removed \"%s\" from trust database.", msg); + } + } else { + semsg(e_trustfile, msg); + } + } + + lua_settop(lstate, top); + return success; +} diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index 78346fd81f..c6747833e5 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -3,13 +3,17 @@ #include <lauxlib.h> #include <lua.h> +#include <stdbool.h> #include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" #include "nvim/assert.h" #include "nvim/eval/typval.h" #include "nvim/ex_cmds_defs.h" #include "nvim/func_attr.h" #include "nvim/lua/converter.h" +#include "nvim/macros.h" +#include "nvim/types.h" #include "nvim/usercmd.h" // Generated by msgpack-gen.lua diff --git a/src/nvim/lua/spell.c b/src/nvim/lua/spell.c index 31a2b2d19f..d510d25e90 100644 --- a/src/nvim/lua/spell.c +++ b/src/nvim/lua/spell.c @@ -1,15 +1,25 @@ // 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 <assert.h> #include <lauxlib.h> +#include <limits.h> #include <lua.h> - +#include <stdbool.h> +#include <stddef.h> + +#include "nvim/ascii.h" +#include "nvim/buffer_defs.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" #include "nvim/lua/spell.h" +#include "nvim/message.h" #include "nvim/spell.h" -#include "nvim/vim.h" +#include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "lua/spell.c.generated.h" +# include "lua/spell.c.generated.h" // IWYU pragma: export #endif int nlua_spell_check(lua_State *lstate) @@ -51,7 +61,7 @@ int nlua_spell_check(lua_State *lstate) while (*str != NUL) { attr = HLF_COUNT; - len = spell_check(curwin, (char_u *)str, &attr, &capcol, false); + len = spell_check(curwin, (char *)str, &attr, &capcol, false); assert(len <= INT_MAX); if (attr != HLF_COUNT) { diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c index 2f46f6ff65..6ebca6d97e 100644 --- a/src/nvim/lua/stdlib.c +++ b/src/nvim/lua/stdlib.c @@ -1,67 +1,56 @@ // 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 <assert.h> #include <lauxlib.h> #include <lua.h> -#include <lualib.h> - +#include <stdarg.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> +#include <sys/types.h> + +#include "auto/config.h" #include "cjson/lua_cjson.h" -#include "luv/luv.h" #include "mpack/lmpack.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" #include "nvim/ascii.h" -#include "nvim/assert.h" #include "nvim/buffer_defs.h" -#include "nvim/change.h" -#include "nvim/cursor.h" #include "nvim/eval.h" -#include "nvim/eval/userfunc.h" -#include "nvim/event/loop.h" -#include "nvim/event/time.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_eval.h" -#include "nvim/ex_getln.h" -#include "nvim/extmark.h" -#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" -#include "nvim/macros.h" #include "nvim/map.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" -#include "nvim/message.h" -#include "nvim/msgpack_rpc/channel.h" -#include "nvim/os/os.h" +#include "nvim/memory.h" +#include "nvim/pos.h" #include "nvim/regexp.h" -#include "nvim/regexp_defs.h" -#include "nvim/screen.h" #include "nvim/types.h" -#include "nvim/undo.h" -#include "nvim/version.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/stdlib.c.generated.h" #endif -static int regex_match(lua_State *lstate, regprog_T **prog, char_u *str) +static int regex_match(lua_State *lstate, regprog_T **prog, char *str) { regmatch_T rm; rm.regprog = *prog; rm.rm_ic = false; - bool match = vim_regexec(&rm, (char *)str, 0); + bool match = vim_regexec(&rm, str, 0); *prog = rm.regprog; if (match) { - lua_pushinteger(lstate, (lua_Integer)(rm.startp[0] - (char *)str)); - lua_pushinteger(lstate, (lua_Integer)(rm.endp[0] - (char *)str)); + lua_pushinteger(lstate, (lua_Integer)(rm.startp[0] - str)); + lua_pushinteger(lstate, (lua_Integer)(rm.endp[0] - str)); return 2; } return 0; @@ -71,7 +60,7 @@ static int regex_match_str(lua_State *lstate) { regprog_T **prog = regex_check(lstate); const char *str = luaL_checkstring(lstate, 2); - int nret = regex_match(lstate, prog, (char_u *)str); + int nret = regex_match(lstate, prog, (char *)str); if (!*prog) { return luaL_error(lstate, "regex: internal error"); @@ -111,14 +100,14 @@ static int regex_match_line(lua_State *lstate) return luaL_error(lstate, "invalid row"); } - char_u *line = (char_u *)ml_get_buf(buf, rownr + 1, false); - size_t len = STRLEN(line); + char *line = ml_get_buf(buf, rownr + 1, false); + size_t len = strlen(line); if (start < 0 || (size_t)start > len) { return luaL_error(lstate, "invalid start"); } - char_u save = NUL; + char save = NUL; if (end >= 0) { if ((size_t)end > len || end < start) { return luaL_error(lstate, "invalid end"); @@ -187,7 +176,7 @@ int nlua_str_utfindex(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL } size_t codepoints = 0, codeunits = 0; - mb_utflen((const char_u *)s1, (size_t)idx, &codepoints, &codeunits); + mb_utflen(s1, (size_t)idx, &codepoints, &codeunits); lua_pushinteger(lstate, (long)codepoints); lua_pushinteger(lstate, (long)codeunits); @@ -209,7 +198,7 @@ static int nlua_str_utf_pos(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL size_t idx = 1; size_t clen; for (size_t i = 0; i < s1_len && s1[i] != NUL; i += clen) { - clen = (size_t)utf_ptr2len_len((const char_u *)(s1) + i, (int)(s1_len - i)); + clen = (size_t)utf_ptr2len_len(s1 + i, (int)(s1_len - i)); lua_pushinteger(lstate, (long)i + 1); lua_rawseti(lstate, -2, (int)idx); idx++; @@ -277,8 +266,7 @@ int nlua_str_byteindex(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL use_utf16 = lua_toboolean(lstate, 3); } - ssize_t byteidx = mb_utf_index_to_bytes((const char_u *)s1, s1_len, - (size_t)idx, use_utf16); + ssize_t byteidx = mb_utf_index_to_bytes(s1, s1_len, (size_t)idx, use_utf16); if (byteidx == -1) { return luaL_error(lstate, "index out of range"); } @@ -378,15 +366,14 @@ int nlua_setvar(lua_State *lstate) if (di == NULL) { // Doesn't exist, nothing to do return 0; - } else { - // Notify watchers - if (watched) { - tv_dict_watcher_notify(dict, key.data, NULL, &di->di_tv); - } - - // Delete the entry - tv_dict_item_remove(dict, di); } + // Notify watchers + if (watched) { + tv_dict_watcher_notify(dict, key.data, NULL, &di->di_tv); + } + + // Delete the entry + tv_dict_item_remove(dict, di); } else { // Update the key typval_T tv; @@ -495,8 +482,6 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL return 1; } -#if defined(HAVE_ICONV) - /// Convert string from one encoding to another static int nlua_iconv(lua_State *lstate) { @@ -515,14 +500,14 @@ static int nlua_iconv(lua_State *lstate) size_t str_len = 0; const char *str = lua_tolstring(lstate, 1, &str_len); - char_u *from = (char_u *)enc_canonize(enc_skip((char *)lua_tolstring(lstate, 2, NULL))); - char_u *to = (char_u *)enc_canonize(enc_skip((char *)lua_tolstring(lstate, 3, NULL))); + char *from = enc_canonize(enc_skip((char *)lua_tolstring(lstate, 2, NULL))); + char *to = enc_canonize(enc_skip((char *)lua_tolstring(lstate, 3, NULL))); vimconv_T vimconv; vimconv.vc_type = CONV_NONE; - convert_setup_ext(&vimconv, (char *)from, false, (char *)to, false); + convert_setup_ext(&vimconv, from, false, to, false); - char_u *ret = (char_u *)string_convert(&vimconv, (char *)str, &str_len); + char *ret = string_convert(&vimconv, (char *)str, &str_len); convert_setup(&vimconv, NULL, NULL); @@ -532,15 +517,13 @@ static int nlua_iconv(lua_State *lstate) if (ret == NULL) { lua_pushnil(lstate); } else { - lua_pushlstring(lstate, (char *)ret, str_len); + lua_pushlstring(lstate, ret, str_len); xfree(ret); } return 1; } -#endif - void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread) { if (!is_thread) { @@ -587,12 +570,10 @@ void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread) luaopen_spell(lstate); lua_setfield(lstate, -2, "spell"); -#if defined(HAVE_ICONV) // vim.iconv // depends on p_ambw, p_emoji lua_pushcfunction(lstate, &nlua_iconv); lua_setfield(lstate, -2, "iconv"); -#endif } // vim.mpack diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 79b11eca4a..56f4daed1a 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -6,23 +6,28 @@ // trees and nodes, and could be broken out as a reusable lua package #include <assert.h> -#include <inttypes.h> #include <lauxlib.h> +#include <limits.h> #include <lua.h> -#include <lualib.h> #include <stdbool.h> #include <stdint.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> #include <uv.h> #include "klib/kvec.h" #include "nvim/api/private/helpers.h" -#include "nvim/buffer.h" -#include "nvim/log.h" +#include "nvim/buffer_defs.h" +#include "nvim/globals.h" #include "nvim/lua/treesitter.h" +#include "nvim/macros.h" #include "nvim/map.h" #include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/pos.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "tree_sitter/api.h" #define TS_META_PARSER "treesitter_parser" @@ -177,19 +182,19 @@ int tslua_add_language(lua_State *L) uv_lib_t lib; if (uv_dlopen(path, &lib)) { - snprintf((char *)IObuff, IOSIZE, "Failed to load parser: uv_dlopen: %s", - uv_dlerror(&lib)); + snprintf(IObuff, IOSIZE, "Failed to load parser for language '%s': uv_dlopen: %s", + lang_name, uv_dlerror(&lib)); uv_dlclose(&lib); - lua_pushstring(L, (char *)IObuff); + lua_pushstring(L, IObuff); return lua_error(L); } TSLanguage *(*lang_parser)(void); if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { - snprintf((char *)IObuff, IOSIZE, "Failed to load parser: uv_dlsym: %s", + snprintf(IObuff, IOSIZE, "Failed to load parser: uv_dlsym: %s", uv_dlerror(&lib)); uv_dlclose(&lib); - lua_pushstring(L, (char *)IObuff); + lua_pushstring(L, IObuff); return lua_error(L); } @@ -333,7 +338,7 @@ static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position return ""; } char *line = ml_get_buf(bp, (linenr_T)position.row + 1, false); - size_t len = STRLEN(line); + size_t len = strlen(line); if (position.column > len) { *bytes_read = 0; return ""; diff --git a/src/nvim/lua/xdiff.c b/src/nvim/lua/xdiff.c index b2b5dfedee..857b159af5 100644 --- a/src/nvim/lua/xdiff.c +++ b/src/nvim/lua/xdiff.c @@ -1,21 +1,26 @@ // 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 <errno.h> #include <lauxlib.h> #include <lua.h> -#include <lualib.h> -#include <stdio.h> -#include <stdlib.h> +#include <stdbool.h> #include <string.h> +#include "luaconf.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/linematch.h" #include "nvim/lua/converter.h" #include "nvim/lua/executor.h" #include "nvim/lua/xdiff.h" +#include "nvim/macros.h" +#include "nvim/memory.h" #include "nvim/vim.h" #include "xdiff/xdiff.h" +#define COMPARED_BUFFER0 (1 << 0) +#define COMPARED_BUFFER1 (1 << 1) + typedef enum { kNluaXdiffModeUnified = 0, kNluaXdiffModeOnHunkCB, @@ -25,12 +30,81 @@ typedef enum { typedef struct { lua_State *lstate; Error *err; + mmfile_t *ma; + mmfile_t *mb; + bool linematch; + bool iwhite; } hunkpriv_t; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/xdiff.c.generated.h" #endif +static void lua_pushhunk(lua_State *lstate, long start_a, long count_a, long start_b, long count_b) +{ + // Mimic extra offsets done by xdiff, see: + // src/xdiff/xemit.c:284 + // src/xdiff/xutils.c:(356,368) + if (count_a > 0) { + start_a += 1; + } + if (count_b > 0) { + start_b += 1; + } + lua_createtable(lstate, 0, 0); + lua_pushinteger(lstate, start_a); + lua_rawseti(lstate, -2, 1); + lua_pushinteger(lstate, count_a); + lua_rawseti(lstate, -2, 2); + lua_pushinteger(lstate, start_b); + lua_rawseti(lstate, -2, 3); + lua_pushinteger(lstate, count_b); + lua_rawseti(lstate, -2, 4); + lua_rawseti(lstate, -2, (signed)lua_objlen(lstate, -2) + 1); +} + +static void get_linematch_results(lua_State *lstate, mmfile_t *ma, mmfile_t *mb, long start_a, + long count_a, long start_b, long count_b, bool iwhite) +{ + // get the pointer to char of the start of the diff to pass it to linematch algorithm + const char *diff_begin[2] = { ma->ptr, mb->ptr }; + int diff_length[2] = { (int)count_a, (int)count_b }; + + fastforward_buf_to_lnum(&diff_begin[0], start_a + 1); + fastforward_buf_to_lnum(&diff_begin[1], start_b + 1); + + int *decisions = NULL; + size_t decisions_length = linematch_nbuffers(diff_begin, diff_length, 2, &decisions, iwhite); + + long lnuma = start_a, lnumb = start_b; + + long hunkstarta = lnuma; + long hunkstartb = lnumb; + long hunkcounta = 0; + long hunkcountb = 0; + for (size_t i = 0; i < decisions_length; i++) { + if (i && (decisions[i - 1] != decisions[i])) { + lua_pushhunk(lstate, hunkstarta, hunkcounta, hunkstartb, hunkcountb); + + hunkstarta = lnuma; + hunkstartb = lnumb; + hunkcounta = 0; + hunkcountb = 0; + // create a new hunk + } + if (decisions[i] & COMPARED_BUFFER0) { + lnuma++; + hunkcounta++; + } + if (decisions[i] & COMPARED_BUFFER1) { + lnumb++; + hunkcountb++; + } + } + lua_pushhunk(lstate, hunkstarta, hunkcounta, hunkstartb, hunkcountb); + xfree(decisions); +} + static int write_string(void *priv, mmbuffer_t *mb, int nbuf) { luaL_Buffer *buf = (luaL_Buffer *)priv; @@ -52,30 +126,15 @@ static int write_string(void *priv, mmbuffer_t *mb, int nbuf) // hunk_func callback used when opts.hunk_lines = true static int hunk_locations_cb(long start_a, long count_a, long start_b, long count_b, void *cb_data) { - // Mimic extra offsets done by xdiff, see: - // src/xdiff/xemit.c:284 - // src/xdiff/xutils.c:(356,368) - if (count_a > 0) { - start_a += 1; - } - if (count_b > 0) { - start_b += 1; + hunkpriv_t *priv = (hunkpriv_t *)cb_data; + lua_State *lstate = priv->lstate; + if (priv->linematch) { + get_linematch_results(lstate, priv->ma, priv->mb, start_a, count_a, start_b, count_b, + priv->iwhite); + } else { + lua_pushhunk(lstate, start_a, count_a, start_b, count_b); } - lua_State *lstate = (lua_State *)cb_data; - lua_createtable(lstate, 0, 0); - - lua_pushinteger(lstate, start_a); - lua_rawseti(lstate, -2, 1); - lua_pushinteger(lstate, count_a); - lua_rawseti(lstate, -2, 2); - lua_pushinteger(lstate, start_b); - lua_rawseti(lstate, -2, 3); - lua_pushinteger(lstate, count_b); - lua_rawseti(lstate, -2, 4); - - lua_rawseti(lstate, -2, (signed)lua_objlen(lstate, -2) + 1); - return 0; } @@ -149,7 +208,7 @@ static bool check_xdiff_opt(ObjectType actType, ObjectType expType, const char * } static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg, xpparam_t *params, - Error *err) + bool *linematch, Error *err) { const DictionaryOf(LuaRef) opts = nlua_pop_Dictionary(lstate, true, err); @@ -205,6 +264,11 @@ static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg, goto exit_1; } cfg->interhunkctxlen = v->data.integer; + } else if (strequal("linematch", k.data)) { + *linematch = api_object_to_bool(*v, "linematch", false, err); + if (ERROR_SET(err)) { + goto exit_1; + } } else { struct { const char *name; @@ -244,10 +308,8 @@ static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg, if (had_on_hunk) { mode = kNluaXdiffModeOnHunkCB; - cfg->hunk_func = call_on_hunk_cb; } else if (had_result_type_indices) { mode = kNluaXdiffModeLocations; - cfg->hunk_func = hunk_locations_cb; } exit_1: @@ -268,6 +330,7 @@ int nlua_xdl_diff(lua_State *lstate) xdemitconf_t cfg; xpparam_t params; xdemitcb_t ecb; + bool linematch = false; CLEAR_FIELD(cfg); CLEAR_FIELD(params); @@ -280,7 +343,7 @@ int nlua_xdl_diff(lua_State *lstate) return luaL_argerror(lstate, 3, "expected table"); } - mode = process_xdl_diff_opts(lstate, &cfg, ¶ms, &err); + mode = process_xdl_diff_opts(lstate, &cfg, ¶ms, &linematch, &err); if (ERROR_SET(&err)) { goto exit_0; @@ -288,7 +351,7 @@ int nlua_xdl_diff(lua_State *lstate) } luaL_Buffer buf; - hunkpriv_t *priv = NULL; + hunkpriv_t priv; switch (mode) { case kNluaXdiffModeUnified: luaL_buffinit(lstate, &buf); @@ -296,14 +359,24 @@ int nlua_xdl_diff(lua_State *lstate) ecb.out_line = write_string; break; case kNluaXdiffModeOnHunkCB: - priv = xmalloc(sizeof(*priv)); - priv->lstate = lstate; - priv->err = &err; - ecb.priv = priv; + cfg.hunk_func = call_on_hunk_cb; + priv = (hunkpriv_t) { + .lstate = lstate, + .err = &err, + }; + ecb.priv = &priv; break; case kNluaXdiffModeLocations: + cfg.hunk_func = hunk_locations_cb; + priv = (hunkpriv_t) { + .lstate = lstate, + .ma = &ma, + .mb = &mb, + .linematch = linematch, + .iwhite = (params.flags & XDF_IGNORE_WHITESPACE) > 0 + }; + ecb.priv = &priv; lua_createtable(lstate, 0, 0); - ecb.priv = lstate; break; } @@ -314,8 +387,6 @@ int nlua_xdl_diff(lua_State *lstate) } } - XFREE_CLEAR(priv); - exit_0: if (ERROR_SET(&err)) { luaL_where(lstate, 1); diff --git a/src/nvim/macros.h b/src/nvim/macros.h index 9b7562e86f..242e3c381a 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -11,7 +11,6 @@ #else # ifndef INIT # define INIT(...) __VA_ARGS__ -# define COMMA , # endif #endif diff --git a/src/nvim/main.c b/src/nvim/main.c index 5687e0a6a9..bbe877356d 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -3,43 +3,51 @@ #define EXTERN #include <assert.h> -#include <msgpack.h> +#include <limits.h> +#include <msgpack/pack.h> #include <stdbool.h> #include <stdint.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> +#include "auto/config.h" #include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/channel.h" -#include "nvim/charset.h" #include "nvim/decoration.h" #include "nvim/decoration_provider.h" #include "nvim/diff.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/event/multiqueue.h" +#include "nvim/event/stream.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/garray.h" +#include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/grid.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/insexpand.h" +#include "nvim/keycodes.h" #include "nvim/locale.h" #include "nvim/log.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" #include "nvim/main.h" -#include "nvim/mapping.h" #include "nvim/mark.h" -#include "nvim/mbyte.h" +#include "nvim/memfile_defs.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" @@ -48,23 +56,26 @@ #include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/option_defs.h" #include "nvim/optionstr.h" #include "nvim/os/fileio.h" #include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os/os_defs.h" +#include "nvim/os/stdpaths_defs.h" #include "nvim/os/time.h" -#include "nvim/os_unix.h" #include "nvim/path.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/quickfix.h" #include "nvim/runtime.h" #include "nvim/shada.h" #include "nvim/sign.h" -#include "nvim/state.h" +#include "nvim/statusline.h" #include "nvim/strings.h" #include "nvim/syntax.h" +#include "nvim/terminal.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/ui_client.h" #include "nvim/ui_compositor.h" @@ -74,8 +85,8 @@ #ifdef MSWIN # include "nvim/os/os_win_console.h" #endif +#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/api/ui.h" #include "nvim/event/loop.h" @@ -84,22 +95,22 @@ #include "nvim/msgpack_rpc/helpers.h" #include "nvim/msgpack_rpc/server.h" #include "nvim/os/signal.h" -#ifndef MSWIN -# include "nvim/os/pty_process_unix.h" -#endif -#include "nvim/api/extmark.h" // values for "window_layout" -#define WIN_HOR 1 // "-o" horizontally split windows -#define WIN_VER 2 // "-O" vertically split windows -#define WIN_TABS 3 // "-p" windows on tab pages +enum { + WIN_HOR = 1, // "-o" horizontally split windows + WIN_VER = 2, // "-O" vertically split windows + WIN_TABS = 3, // "-p" windows on tab pages +}; // Values for edit_type. -#define EDIT_NONE 0 // no edit type yet -#define EDIT_FILE 1 // file name argument[s] given, use argument list -#define EDIT_STDIN 2 // read file from stdin -#define EDIT_TAG 3 // tag name argument given, use tagname -#define EDIT_QF 4 // start in quickfix mode +enum { + EDIT_NONE = 0, // no edit type yet + EDIT_FILE = 1, // file name argument[s] given, use argument list + EDIT_STDIN = 2, // read file from stdin + EDIT_TAG = 3, // tag name argument given, use tagname + EDIT_QF = 4, // start in quickfix mode +}; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "main.c.generated.h" @@ -228,10 +239,10 @@ int main(int argc, char **argv) argv0 = argv[0]; - char_u *fname = NULL; // file name from command line + char *fname = NULL; // file name from command line mparm_T params; // various parameters passed between // main() and other functions. - char_u *cwd = NULL; // current working dir on startup + char *cwd = NULL; // current working dir on startup time_init(); // Many variables are in `params` so that we can pass them around easily. @@ -265,8 +276,7 @@ int main(int argc, char **argv) // argument list "global_alist". command_line_scan(¶ms); - nlua_init(); - + nlua_init(argv, argc, params.lua_arg0); TIME_MSG("init lua interpreter"); if (embedded_mode) { @@ -276,9 +286,35 @@ int main(int argc, char **argv) } } - server_init(params.listen_addr); +#ifdef MSWIN + // on windows we use CONIN special file, thus we don't know this yet. + bool has_term = true; +#else + bool has_term = (stdin_isatty || stdout_isatty || stderr_isatty); +#endif + bool use_builtin_ui = (has_term && !headless_mode && !embedded_mode && !silent_mode); + + // don't bind the server yet, if we are using builtin ui. + // This will be done when nvim server has been forked from the ui process + if (!use_builtin_ui) { + server_init(params.listen_addr); + } + if (params.remote) { - remote_request(¶ms, params.remote, params.server_addr, argc, argv); + remote_request(¶ms, params.remote, params.server_addr, argc, argv, + use_builtin_ui); + } + + bool remote_ui = (ui_client_channel_id != 0); + + if (use_builtin_ui && !remote_ui) { + ui_client_forward_stdin = !stdin_isatty; + uint64_t rv = ui_client_start_server(params.argc, params.argv); + if (!rv) { + os_errmsg("Failed to start Nvim server!\n"); + getout(1); + } + ui_client_channel_id = rv; } if (GARGCOUNT > 0) { @@ -329,22 +365,20 @@ int main(int argc, char **argv) debug_break_level = params.use_debug_break_level; // Read ex-commands if invoked with "-es". - if (!params.input_isatty && !params.input_neverscript - && silent_mode && exmode_active) { - input_start(STDIN_FILENO); + if (!stdin_isatty && !params.input_istext && silent_mode && exmode_active) { + input_start(); + } + + if (ui_client_channel_id) { + ui_client_run(remote_ui); // NORETURN } // Wait for UIs to set up Nvim or show early messages // and prompts (--cmd, swapfile dialog, …). bool use_remote_ui = (embedded_mode && !headless_mode); - bool use_builtin_ui = (!headless_mode && !embedded_mode && !silent_mode); - if (use_remote_ui || use_builtin_ui) { + if (use_remote_ui) { TIME_MSG("waiting for UI"); - if (use_remote_ui) { - remote_ui_wait_for_attach(); - } else { - ui_builtin_start(); - } + remote_ui_wait_for_attach(); TIME_MSG("done waiting for UI"); firstwin->w_prev_height = firstwin->w_height; // may have changed } @@ -355,12 +389,13 @@ int main(int argc, char **argv) win_new_screensize(); TIME_MSG("clear screen"); - if (ui_client_channel_id) { - ui_client_init(ui_client_channel_id); - ui_client_execute(ui_client_channel_id); - abort(); // unreachable + // Handle "foo | nvim". EDIT_FILE may be overwritten now. #6299 + if (edit_stdin(¶ms)) { + params.edit_type = EDIT_STDIN; } + open_script_files(¶ms); + // Default mappings (incl. menus) Error err = ERROR_INIT; Object o = NLUA_EXEC_STATIC("return vim._init_default_mappings()", @@ -369,19 +404,18 @@ int main(int argc, char **argv) api_clear_error(&err); assert(o.type == kObjectTypeNil); api_free_object(o); + 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"); + bool vimrc_none = strequal(params.use_vimrc, "NONE"); // Reset 'loadplugins' for "-u NONE" before "--cmd" arguments. // Allows for setting 'loadplugins' there. - if (vimrc_none) { - // When using --clean we still want to load plugins - p_lpl = params.clean; - } + // For --clean we still want to load plugins. + p_lpl = vimrc_none ? params.clean : p_lpl; // Execute --cmd arguments. exe_pre_commands(¶ms); @@ -468,7 +502,7 @@ int main(int argc, char **argv) // writing end of the pipe doesn't like, e.g., in case stdin and stderr // are the same terminal: "cat | vim -". // Using autocommands here may cause trouble... - if ((params.edit_type == EDIT_STDIN || stdin_fd >= 0) && !recoverymode) { + if (params.edit_type == EDIT_STDIN && !recoverymode) { read_stdin(); } @@ -511,7 +545,9 @@ int main(int argc, char **argv) if (params.diff_mode) { // set options in each window for "nvim -d". FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - diff_win_options(wp, true); + if (!wp->w_arg_idx_invalid) { + diff_win_options(wp, true); + } } } @@ -520,7 +556,7 @@ int main(int argc, char **argv) // Need to jump to the tag before executing the '-c command'. // Makes "vim -c '/return' -t main" work. - handle_tag((char_u *)params.tagname); + handle_tag(params.tagname); // Execute any "+", "-c" and "-S" arguments. if (params.n_commands > 0) { @@ -544,6 +580,13 @@ int main(int argc, char **argv) TIME_MSG("UIEnter autocommands"); } +#ifdef MSWIN + if (use_builtin_ui) { + os_icon_init(); + } + os_title_save(); +#endif + // Adjust default register name for "unnamed" in 'clipboard'. Can only be // done after the clipboard is available and all initial commands that may // modify the 'clipboard' setting have run; i.e. just before entering the @@ -569,6 +612,12 @@ int main(int argc, char **argv) (void)eval_has_provider("clipboard"); } + if (params.luaf != NULL) { + bool lua_ok = nlua_exec_file(params.luaf); + TIME_MSG("executing Lua -l script"); + getout(lua_ok ? 0 : 1); + } + TIME_MSG("before starting main loop"); ILOG("starting main loop"); @@ -586,15 +635,19 @@ void os_exit(int r) { exiting = true; - ui_flush(); - ui_call_stop(); - ml_close_all(true); // remove all memfiles + if (ui_client_channel_id) { + ui_client_stop(); + } else { + ui_flush(); + ui_call_stop(); + ml_close_all(true); // remove all memfiles + } if (!event_teardown() && r == 0) { r = 1; // Exit with error if main_loop did not teardown gracefully. } - if (input_global_fd() >= 0) { - stream_set_blocking(input_global_fd(), true); // normalize stream (#2598) + if (used_stdin) { + stream_set_blocking(STDIN_FILENO, true); // normalize stream (#2598) } ILOG("Nvim exit: %d", r); @@ -612,9 +665,8 @@ void getout(int exitval) { exiting = true; - // When running in Ex mode an error causes us to exit with a non-zero exit - // code. POSIX requires this, although it's not 100% clear from the - // standard. + // On error during Ex mode, exit with a non-zero code. + // POSIX requires this, although it's not 100% clear from the standard. if (exmode_active) { exitval += ex_exitval; } @@ -705,6 +757,7 @@ void getout(int exitval) if (did_emsg) { // give the user a chance to read the (error) message no_wait_return = false; + // TODO(justinmk): this may call getout(0), clobbering exitval... wait_return(false); } @@ -713,21 +766,25 @@ void getout(int exitval) // Apply 'titleold'. if (p_title && *p_titleold != NUL) { - ui_call_set_title(cstr_as_string((char *)p_titleold)); + ui_call_set_title(cstr_as_string(p_titleold)); } - cs_end(); if (garbage_collect_at_exit) { garbage_collect(false); } +#ifdef MSWIN + // Restore Windows console icon before exiting. + os_icon_set(NULL, NULL); + os_title_reset(); +#endif + 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. +/// Preserve files, print contents of `IObuff`, and exit 1. +/// +/// May be called from deadly_signal(). void preserve_exit(void) FUNC_ATTR_NORETURN { @@ -736,9 +793,9 @@ void preserve_exit(void) // Prevent repeated calls into this method. if (really_exiting) { - if (input_global_fd() >= 0) { + if (used_stdin) { // normalize stream (#2598) - stream_set_blocking(input_global_fd(), true); + stream_set_blocking(STDIN_FILENO, true); } exit(2); } @@ -746,15 +803,15 @@ void preserve_exit(void) really_exiting = true; // Ignore SIGHUP while we are already exiting. #9274 signal_reject_deadly(); - mch_errmsg(IObuff); - mch_errmsg("\n"); + os_errmsg(IObuff); + os_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"); + os_errmsg("Vim: preserving files...\r\n"); ui_flush(); ml_sync_all(false, false, true); // preserve all swap files break; @@ -763,7 +820,7 @@ void preserve_exit(void) ml_close_all(false); // close all memfiles, without deleting - mch_errmsg("Vim: Finished.\r\n"); + os_errmsg("Vim: Finished.\r\n"); getout(1); } @@ -812,16 +869,25 @@ static uint64_t server_connect(char *server_addr, const char **errmsg) /// Handle remote subcommands static void remote_request(mparm_T *params, int remote_args, char *server_addr, int argc, - char **argv) + char **argv, bool ui_only) { + bool is_ui = strequal(argv[remote_args], "--remote-ui"); + if (ui_only && !is_ui) { + // TODO(bfredl): this implies always starting the TUI. + // if we be smart we could delay this past should_exit + return; + } + 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 (is_ui) { if (!chan) { - emsg(connect_error); - exit(1); + os_errmsg("Remote ui failed to start: "); + os_errmsg(connect_error); + os_errmsg("\n"); + os_exit(1); } ui_client_channel_id = chan; @@ -845,15 +911,15 @@ static void remote_request(mparm_T *params, int remote_args, char *server_addr, Object o = nlua_exec(s, a, &err); api_free_array(a); if (ERROR_SET(&err)) { - mch_errmsg(err.msg); - mch_errmsg("\n"); + os_errmsg(err.msg); + os_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_errmsg("vim._cs_remote returned unexpected value\n"); os_exit(2); } @@ -863,28 +929,28 @@ static void remote_request(mparm_T *params, int remote_args, char *server_addr, 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_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_errmsg(rvobj.data.dictionary.items[i].value.data.string.data); + os_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_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_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_errmsg("vim._cs_remote didn't return a value for should_exit or tabbed, bailing\n"); os_exit(2); } api_free_object(o); @@ -900,14 +966,14 @@ static void remote_request(mparm_T *params, int remote_args, char *server_addr, /// Decides whether text (as opposed to commands) will be read from stdin. /// @see EDIT_STDIN -static bool edit_stdin(bool explicit, mparm_T *parmp) +static bool edit_stdin(mparm_T *parmp) { bool implicit = !headless_mode - && !embedded_mode - && (!exmode_active || parmp->input_neverscript) - && !parmp->input_isatty - && scriptin[0] == NULL; // `-s -` was not given. - return explicit || implicit; + && !(embedded_mode && stdin_fd <= 0) + && (!exmode_active || parmp->input_istext) + && !stdin_isatty + && parmp->scriptin == NULL; // `-s -` was not given. + return parmp->had_stdin_file || implicit; } /// Scan the command line arguments. @@ -916,7 +982,6 @@ static void command_line_scan(mparm_T *parmp) int argc = parmp->argc; char **argv = parmp->argv; int argv_idx; // index in argv[n][] - bool had_stdin_file = false; // found explicit "-" argument bool had_minmin = false; // found "--" argument int want_argument; // option argument with argument long n; @@ -936,9 +1001,9 @@ static void command_line_scan(mparm_T *parmp) } else { parmp->commands[parmp->n_commands++] = &(argv[0][1]); } - - // Optional argument. } else if (argv[0][0] == '-' && !had_minmin) { + // Optional argument. + want_argument = false; char c = argv[0][argv_idx++]; switch (c) { @@ -948,17 +1013,15 @@ static void command_line_scan(mparm_T *parmp) silent_mode = true; parmp->no_swap_file = true; } else { - if (parmp->edit_type != EDIT_NONE - && parmp->edit_type != EDIT_FILE - && parmp->edit_type != EDIT_STDIN) { + if (parmp->edit_type > EDIT_STDIN) { mainerr(err_too_many_args, argv[0]); } - had_stdin_file = true; + parmp->had_stdin_file = true; parmp->edit_type = EDIT_STDIN; } argv_idx = -1; // skip to next argument break; - case '-': // "--" don't take any more option arguments + case '-': // "--" No more option arguments. // "--help" give help message // "--version" give version message // "--noplugin[s]" skip plugins @@ -1054,7 +1117,7 @@ static void command_line_scan(mparm_T *parmp) break; case 'E': // "-E" Ex mode exmode_active = true; - parmp->input_neverscript = true; + parmp->input_istext = true; break; case 'f': // "-f" GUI: run in foreground. break; @@ -1066,10 +1129,6 @@ static void command_line_scan(mparm_T *parmp) p_hkmap = true; set_option_value_give_err("rl", 1L, NULL, 0); break; - case 'l': // "-l" lisp mode, 'lisp' and 'showmatch' on. - set_option_value_give_err("lisp", 1L, NULL, 0); - p_sm = true; - break; case 'M': // "-M" no changes or writing of files reset_modifiable(); FALLTHROUGH; @@ -1154,7 +1213,7 @@ static void command_line_scan(mparm_T *parmp) break; case 'w': // "-w{number}" set window height // "-w {scriptout}" write to script - if (ascii_isdigit(((char_u *)argv[0])[argv_idx])) { + if (ascii_isdigit((argv[0])[argv_idx])) { n = get_number_arg(argv[0], &argv_idx, 10); set_option_value_give_err("window", n, NULL, 0); break; @@ -1174,6 +1233,7 @@ static void command_line_scan(mparm_T *parmp) FALLTHROUGH; case 'S': // "-S {file}" execute Vim script case 'i': // "-i {shada}" use for ShaDa file + case 'l': // "-l {file}" Lua mode case 'u': // "-u {vimrc}" vim inits file case 'U': // "-U {gvimrc}" gvim inits file case 'W': // "-W {scriptout}" overwrite @@ -1254,38 +1314,34 @@ static void command_line_scan(mparm_T *parmp) set_option_value_give_err("shadafile", 0L, argv[0], 0); break; - case 's': { // "-s {scriptin}" read from script file - if (scriptin[0] != NULL) { + case 'l': // "-l" Lua script: args after "-l". + headless_mode = true; + silent_mode = true; + p_verbose = 1; + parmp->no_swap_file = true; + parmp->use_vimrc = parmp->use_vimrc ? parmp->use_vimrc : "NONE"; + if (p_shadafile == NULL || *p_shadafile == NUL) { + set_option_value_give_err("shadafile", 0L, "NONE", 0); + } + parmp->luaf = argv[0]; + argc--; + if (argc > 0) { // Lua args after "-l <file>". + parmp->lua_arg0 = parmp->argc - argc; + argc = 0; + } + break; + + case 's': // "-s {scriptin}" read from script file + if (parmp->scriptin != NULL) { scripterror: - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Attempt to open script file again: \"%s %s\"\n"), argv[-1], argv[0]); - mch_errmsg((const char *)IObuff); + os_errmsg(IObuff); os_exit(2); } - int error; - if (strequal(argv[0], "-")) { - const int stdin_dup_fd = os_dup(STDIN_FILENO); -#ifdef MSWIN - // Replace the original stdin with the console input handle. - os_replace_stdin_to_conin(); -#endif - FileDescriptor *const stdin_dup = file_open_fd_new(&error, stdin_dup_fd, - kFileReadOnly|kFileNonBlocking); - assert(stdin_dup != NULL); - scriptin[0] = stdin_dup; - } else if ((scriptin[0] = - file_open_new(&error, argv[0], kFileReadOnly|kFileNonBlocking, - 0)) == NULL) { - vim_snprintf((char *)IObuff, IOSIZE, - _("Cannot open for reading: \"%s\": %s\n"), - argv[0], os_strerror(error)); - mch_errmsg((const char *)IObuff); - os_exit(2); - } - save_typebuf(); + parmp->scriptin = argv[0]; break; - } case 't': // "-t {tag}" parmp->tagname = argv[0]; @@ -1298,7 +1354,7 @@ scripterror: case 'w': // "-w {nr}" 'window' value // "-w {scriptout}" append to script file - if (ascii_isdigit(*((char_u *)argv[0]))) { + if (ascii_isdigit(*(argv[0]))) { argv_idx = 0; n = get_number_arg(argv[0], &argv_idx, 10); set_option_value_give_err("window", n, NULL, 0); @@ -1307,26 +1363,18 @@ scripterror: } FALLTHROUGH; case 'W': // "-W {scriptout}" overwrite script file - if (scriptout != NULL) { + if (parmp->scriptout != NULL) { goto scripterror; } - if ((scriptout = os_fopen(argv[0], c == 'w' ? APPENDBIN : WRITEBIN)) - == NULL) { - mch_errmsg(_("Cannot open for script output: \"")); - mch_errmsg(argv[0]); - mch_errmsg("\"\n"); - os_exit(2); - } - break; + parmp->scriptout = argv[0]; + parmp->scriptout_append = (c == 'w'); } } } else { // File name argument. argv_idx = -1; // skip to next argument // Check for only one type of editing. - if (parmp->edit_type != EDIT_NONE - && parmp->edit_type != EDIT_FILE - && parmp->edit_type != EDIT_STDIN) { + if (parmp->edit_type > EDIT_STDIN) { mainerr(err_too_many_args, argv[0]); } parmp->edit_type = EDIT_FILE; @@ -1347,7 +1395,7 @@ scripterror: path_fix_case(p); #endif - int alist_fnum_flag = edit_stdin(had_stdin_file, parmp) + int alist_fnum_flag = edit_stdin(parmp) ? 1 // add buffer nr after exp. : 2; // add buffer number now and use curbuf alist_add(&global_alist, p, alist_fnum_flag); @@ -1362,8 +1410,8 @@ scripterror: } } - if (embedded_mode && silent_mode) { - mainerr(_("--embed conflicts with -es/-Es"), NULL); + if (embedded_mode && (silent_mode || parmp->luaf)) { + mainerr(_("--embed conflicts with -es/-Es/-l"), NULL); } // If there is a "+123" or "-c" command, set v:swapcommand to the first one. @@ -1375,11 +1423,6 @@ scripterror: xfree(swcmd); } - // Handle "foo | nvim". EDIT_FILE may be overwritten now. #6299 - if (edit_stdin(had_stdin_file, parmp)) { - parmp->edit_type = EDIT_STDIN; - } - TIME_MSG("parsing arguments"); } @@ -1396,6 +1439,8 @@ static void init_params(mparm_T *paramp, int argc, char **argv) paramp->listen_addr = NULL; paramp->server_addr = NULL; paramp->remote = 0; + paramp->luaf = NULL; + paramp->lua_arg0 = -1; } /// Initialize global startuptime file if "--startuptime" passed as an argument. @@ -1408,25 +1453,13 @@ static void init_startuptime(mparm_T *paramp) break; } } - - starttime = time(NULL); } static void check_and_set_isatty(mparm_T *paramp) { - stdin_isatty - = paramp->input_isatty = os_isatty(STDIN_FILENO); - stdout_isatty - = paramp->output_isatty = os_isatty(STDOUT_FILENO); - paramp->err_isatty = os_isatty(STDERR_FILENO); -#ifndef MSWIN - int tty_fd = paramp->input_isatty - ? STDIN_FILENO - : (paramp->output_isatty - ? STDOUT_FILENO - : (paramp->err_isatty ? STDERR_FILENO : -1)); - pty_process_save_termios(tty_fd); -#endif + stdin_isatty = os_isatty(STDIN_FILENO); + stdout_isatty = os_isatty(STDOUT_FILENO); + stderr_isatty = os_isatty(STDERR_FILENO); TIME_MSG("window checked"); } @@ -1452,9 +1485,9 @@ static void init_path(const char *exename) } /// Get filename from command line, if any. -static char_u *get_fname(mparm_T *parmp, char_u *cwd) +static char *get_fname(mparm_T *parmp, char *cwd) { - return (char_u *)alist_name(&GARGLIST[0]); + return alist_name(&GARGLIST[0]); } // Decide about window layout for diff mode after reading vimrc. @@ -1477,8 +1510,8 @@ static void handle_quickfix(mparm_T *paramp) if (paramp->use_ef != NULL) { set_string_option_direct("ef", -1, paramp->use_ef, OPT_FREE, SID_CARG); } - vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef); - if (qf_init(NULL, (char *)p_ef, p_efm, true, (char *)IObuff, p_menc) < 0) { + vim_snprintf(IObuff, IOSIZE, "cfile %s", p_ef); + if (qf_init(NULL, p_ef, p_efm, true, IObuff, p_menc) < 0) { msg_putchar('\n'); os_exit(3); } @@ -1488,13 +1521,13 @@ static void handle_quickfix(mparm_T *paramp) // Need to jump to the tag before executing the '-c command'. // Makes "vim -c '/return' -t main" work. -static void handle_tag(char_u *tagname) +static void handle_tag(char *tagname) { if (tagname != NULL) { swap_exists_did_quit = false; - vim_snprintf((char *)IObuff, IOSIZE, "ta %s", tagname); - do_cmdline_cmd((char *)IObuff); + vim_snprintf(IObuff, IOSIZE, "ta %s", tagname); + do_cmdline_cmd(IObuff); TIME_MSG("jumping to tag"); // If the user doesn't want to edit the file then we quit here. @@ -1526,6 +1559,38 @@ static void read_stdin(void) check_swap_exists_action(); } +static void open_script_files(mparm_T *parmp) +{ + if (parmp->scriptin) { + int error; + if (strequal(parmp->scriptin, "-")) { + FileDescriptor *stdin_dup = file_open_stdin(); + scriptin[0] = stdin_dup; + } else { + scriptin[0] = file_open_new(&error, parmp->scriptin, + kFileReadOnly|kFileNonBlocking, 0); + if (scriptin[0] == NULL) { + vim_snprintf((char *)IObuff, IOSIZE, + _("Cannot open for reading: \"%s\": %s\n"), + parmp->scriptin, os_strerror(error)); + os_errmsg(IObuff); + os_exit(2); + } + } + save_typebuf(); + } + + if (parmp->scriptout) { + scriptout = os_fopen(parmp->scriptout, parmp->scriptout_append ? APPENDBIN : WRITEBIN); + if (scriptout == NULL) { + os_errmsg(_("Cannot open for script output: \"")); + os_errmsg(parmp->scriptout); + os_errmsg("\"\n"); + os_exit(2); + } + } +} + // Create the requested number of windows and edit buffers in them. // Also does recovery if "recoverymode" set. static void create_windows(mparm_T *parmp) @@ -1644,7 +1709,7 @@ static void create_windows(mparm_T *parmp) /// If opened more than one window, start editing files in the other /// windows. make_windows() has already opened the windows. -static void edit_buffers(mparm_T *parmp, char_u *cwd) +static void edit_buffers(mparm_T *parmp, char *cwd) { int arg_idx; // index in argument list int i; @@ -1665,7 +1730,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) arg_idx = 1; for (i = 1; i < parmp->window_count; i++) { if (cwd != NULL) { - os_chdir((char *)cwd); + os_chdir(cwd); } // When w_arg_idx is -1 remove the window (see create_windows()). if (curwin->w_arg_idx == -1) { @@ -1768,17 +1833,19 @@ static void exe_pre_commands(mparm_T *parmp) int cnt = parmp->n_pre_commands; int i; - if (cnt > 0) { - curwin->w_cursor.lnum = 0; // just in case.. - estack_push(ETYPE_ARGS, _("pre-vimrc command line"), 0); - current_sctx.sc_sid = SID_CMDARG; - for (i = 0; i < cnt; i++) { - do_cmdline_cmd(cmds[i]); - } - estack_pop(); - current_sctx.sc_sid = 0; - TIME_MSG("--cmd commands"); + if (cnt <= 0) { + return; + } + + curwin->w_cursor.lnum = 0; // just in case.. + estack_push(ETYPE_ARGS, _("pre-vimrc command line"), 0); + current_sctx.sc_sid = SID_CMDARG; + for (i = 0; i < cnt; i++) { + do_cmdline_cmd(cmds[i]); } + estack_pop(); + current_sctx.sc_sid = 0; + TIME_MSG("--cmd commands"); } // Execute "+", "-c" and "-S" arguments. @@ -1884,28 +1951,29 @@ static bool do_user_initialization(void) return do_exrc; } - char_u *init_lua_path = (char_u *)stdpaths_user_conf_subpath("init.lua"); - char_u *user_vimrc = (char_u *)stdpaths_user_conf_subpath("init.vim"); + char *init_lua_path = stdpaths_user_conf_subpath("init.lua"); + char *user_vimrc = stdpaths_user_conf_subpath("init.vim"); // init.lua - if (os_path_exists((char *)init_lua_path) - && do_source((char *)init_lua_path, true, DOSO_VIMRC)) { - if (os_path_exists((char *)user_vimrc)) { + if (os_path_exists(init_lua_path) + && do_source(init_lua_path, true, DOSO_VIMRC)) { + if (os_path_exists(user_vimrc)) { semsg(_("E5422: Conflicting configs: \"%s\" \"%s\""), init_lua_path, user_vimrc); } xfree(user_vimrc); xfree(init_lua_path); - return false; + do_exrc = p_exrc; + return do_exrc; } xfree(init_lua_path); // init.vim - if (do_source((char *)user_vimrc, true, DOSO_VIMRC) != FAIL) { + if (do_source(user_vimrc, true, DOSO_VIMRC) != FAIL) { do_exrc = p_exrc; if (do_exrc) { - do_exrc = (path_full_compare(VIMRC_FILE, (char *)user_vimrc, false, true) != kEqualFiles); + do_exrc = (path_full_compare(VIMRC_FILE, user_vimrc, false, true) != kEqualFiles); } xfree(user_vimrc); return do_exrc; @@ -1949,6 +2017,41 @@ static bool do_user_initialization(void) return do_exrc; } +// Read initialization commands from ".nvim.lua", ".nvimrc", or ".exrc" in +// current directory. This is only done if the 'exrc' option is set. +// Only do this if VIMRC_FILE is not the same as vimrc file sourced in +// do_user_initialization. +static void do_exrc_initialization(void) +{ + char *str; + + if (os_path_exists(VIMRC_LUA_FILE)) { + str = nlua_read_secure(VIMRC_LUA_FILE); + if (str != NULL) { + Error err = ERROR_INIT; + nlua_exec(cstr_as_string(str), (Array)ARRAY_DICT_INIT, &err); + xfree(str); + if (ERROR_SET(&err)) { + semsg("Error detected while processing %s:", VIMRC_LUA_FILE); + semsg_multiline(err.msg); + api_clear_error(&err); + } + } + } else if (os_path_exists(VIMRC_FILE)) { + str = nlua_read_secure(VIMRC_FILE); + if (str != NULL) { + do_source_str(str, VIMRC_FILE); + xfree(str); + } + } else if (os_path_exists(EXRC_FILE)) { + str = nlua_read_secure(EXRC_FILE); + if (str != NULL) { + do_source_str(str, EXRC_FILE); + xfree(str); + } + } +} + /// Source startup scripts static void source_startup_scripts(const mparm_T *const parmp) FUNC_ATTR_NONNULL_ALL @@ -1967,35 +2070,8 @@ static void source_startup_scripts(const mparm_T *const parmp) do_system_initialization(); if (do_user_initialization()) { - // Read initialization commands from ".vimrc" or ".exrc" in current - // directory. This is only done if the 'exrc' option is set. - // Because of security reasons we disallow shell and write commands - // now, except for unix if the file is owned by the user or 'secure' - // option has been reset in environment of global "exrc" or "vimrc". - // Only do this if VIMRC_FILE is not the same as vimrc file sourced in - // do_user_initialization. -#if defined(UNIX) - // If vimrc file is not owned by user, set 'secure' mode. - if (!os_file_owned(VIMRC_FILE)) // NOLINT(readability/braces) -#endif - secure = p_secure; - - if (do_source(VIMRC_FILE, true, DOSO_VIMRC) == FAIL) { -#if defined(UNIX) - // if ".exrc" is not owned by user set 'secure' mode - if (!os_file_owned(EXRC_FILE)) { - secure = p_secure; - } else { - secure = 0; - } -#endif - (void)do_source(EXRC_FILE, false, DOSO_NONE); - } + do_exrc_initialization(); } - if (secure == 2) { - need_wait_return = true; - } - secure = 0; } TIME_MSG("sourcing vimrc file(s)"); } @@ -2010,19 +2086,20 @@ static int execute_env(char *env) FUNC_ATTR_NONNULL_ALL { const char *initstr = os_getenv(env); - if (initstr != NULL) { - estack_push(ETYPE_ENV, env, 0); - const sctx_T save_current_sctx = current_sctx; - current_sctx.sc_sid = SID_ENV; - current_sctx.sc_seq = 0; - current_sctx.sc_lnum = 0; - do_cmdline_cmd((char *)initstr); - - estack_pop(); - current_sctx = save_current_sctx; - return OK; - } - return FAIL; + if (initstr == NULL) { + return FAIL; + } + + estack_push(ETYPE_ENV, env, 0); + const sctx_T save_current_sctx = current_sctx; + current_sctx.sc_sid = SID_ENV; + current_sctx.sc_seq = 0; + current_sctx.sc_lnum = 0; + do_cmdline_cmd((char *)initstr); + + estack_pop(); + current_sctx = save_current_sctx; + return OK; } /// Prints the following then exits: @@ -2038,17 +2115,17 @@ static void mainerr(const char *errstr, const char *str) signal_stop(); // kill us with CTRL-C here, if you like - mch_errmsg(prgname); - mch_errmsg(": "); - mch_errmsg(_(errstr)); + os_errmsg(prgname); + os_errmsg(": "); + os_errmsg(_(errstr)); if (str != NULL) { - mch_errmsg(": \""); - mch_errmsg(str); - mch_errmsg("\""); + os_errmsg(": \""); + os_errmsg((char *)str); + os_errmsg("\""); } - mch_errmsg(_("\nMore info with \"")); - mch_errmsg(prgname); - mch_errmsg(" -h\"\n"); + os_errmsg(_("\nMore info with \"")); + os_errmsg(prgname); + os_errmsg(" -h\"\n"); os_exit(1); } @@ -2057,8 +2134,8 @@ static void mainerr(const char *errstr, const char *str) static void version(void) { // TODO(bfred): not like this? - nlua_init(); - info_message = true; // use mch_msg(), not mch_errmsg() + nlua_init(NULL, 0, -1); + info_message = true; // use os_msg(), not os_errmsg() list_version(); msg_putchar('\n'); msg_didout = false; @@ -2069,47 +2146,48 @@ static void usage(void) { signal_stop(); // kill us with CTRL-C here, if you like - mch_msg(_("Usage:\n")); - mch_msg(_(" nvim [options] [file ...] Edit file(s)\n")); - mch_msg(_(" nvim [options] -t <tag> Edit file where tag is defined\n")); - mch_msg(_(" nvim [options] -q [errorfile] Edit file with first error\n")); - mch_msg(_("\nOptions:\n")); - mch_msg(_(" -- Only file names after this\n")); - mch_msg(_(" + Start at end of file\n")); - mch_msg(_(" --cmd <cmd> Execute <cmd> before any config\n")); - mch_msg(_(" +<cmd>, -c <cmd> Execute <cmd> after config and first file\n")); - mch_msg("\n"); - mch_msg(_(" -b Binary mode\n")); - mch_msg(_(" -d Diff mode\n")); - mch_msg(_(" -e, -E Ex mode\n")); - mch_msg(_(" -es, -Es Silent (batch) mode\n")); - mch_msg(_(" -h, --help Print this help message\n")); - mch_msg(_(" -i <shada> Use this shada file\n")); - mch_msg(_(" -m Modifications (writing files) not allowed\n")); - mch_msg(_(" -M Modifications in text not allowed\n")); - mch_msg(_(" -n No swap file, use memory only\n")); - mch_msg(_(" -o[N] Open N windows (default: one per file)\n")); - mch_msg(_(" -O[N] Open N vertical windows (default: one per file)\n")); - mch_msg(_(" -p[N] Open N tab pages (default: one per file)\n")); - mch_msg(_(" -r, -L List swap files\n")); - mch_msg(_(" -r <file> Recover edit state for this file\n")); - mch_msg(_(" -R Read-only mode\n")); - mch_msg(_(" -S <session> Source <session> after loading the first file\n")); - mch_msg(_(" -s <scriptin> Read Normal mode commands from <scriptin>\n")); - mch_msg(_(" -u <config> Use this config file\n")); - mch_msg(_(" -v, --version Print version information\n")); - mch_msg(_(" -V[N][file] Verbose [level][file]\n")); - mch_msg("\n"); - mch_msg(_(" --api-info Write msgpack-encoded API metadata to stdout\n")); - mch_msg(_(" --clean \"Factory defaults\" (skip user config and plugins, shada)\n")); - mch_msg(_(" --embed Use stdin/stdout as a msgpack-rpc channel\n")); - 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")); + os_msg(_("Usage:\n")); + os_msg(_(" nvim [options] [file ...] Edit file(s)\n")); + os_msg(_(" nvim [options] -t <tag> Edit file where tag is defined\n")); + os_msg(_(" nvim [options] -q [errorfile] Edit file with first error\n")); + os_msg(_("\nOptions:\n")); + os_msg(_(" -- Only file names after this\n")); + os_msg(_(" + Start at end of file\n")); + os_msg(_(" --cmd <cmd> Execute <cmd> before any config\n")); + os_msg(_(" +<cmd>, -c <cmd> Execute <cmd> after config and first file\n")); + os_msg(_(" -l <script> [args...] Execute Lua <script> (with optional args)\n")); + os_msg("\n"); + os_msg(_(" -b Binary mode\n")); + os_msg(_(" -d Diff mode\n")); + os_msg(_(" -e, -E Ex mode\n")); + os_msg(_(" -es, -Es Silent (batch) mode\n")); + os_msg(_(" -h, --help Print this help message\n")); + os_msg(_(" -i <shada> Use this shada file\n")); + os_msg(_(" -m Modifications (writing files) not allowed\n")); + os_msg(_(" -M Modifications in text not allowed\n")); + os_msg(_(" -n No swap file, use memory only\n")); + os_msg(_(" -o[N] Open N windows (default: one per file)\n")); + os_msg(_(" -O[N] Open N vertical windows (default: one per file)\n")); + os_msg(_(" -p[N] Open N tab pages (default: one per file)\n")); + os_msg(_(" -r, -L List swap files\n")); + os_msg(_(" -r <file> Recover edit state for this file\n")); + os_msg(_(" -R Read-only mode\n")); + os_msg(_(" -S <session> Source <session> after loading the first file\n")); + os_msg(_(" -s <scriptin> Read Normal mode commands from <scriptin>\n")); + os_msg(_(" -u <config> Use this config file\n")); + os_msg(_(" -v, --version Print version information\n")); + os_msg(_(" -V[N][file] Verbose [level][file]\n")); + os_msg("\n"); + os_msg(_(" --api-info Write msgpack-encoded API metadata to stdout\n")); + os_msg(_(" --clean \"Factory defaults\" (skip user config and plugins, shada)\n")); + os_msg(_(" --embed Use stdin/stdout as a msgpack-rpc channel\n")); + os_msg(_(" --headless Don't start a user interface\n")); + os_msg(_(" --listen <address> Serve RPC API from this address\n")); + os_msg(_(" --noplugin Don't load plugins\n")); + os_msg(_(" --remote[-subcommand] Execute commands remotely on a server\n")); + os_msg(_(" --server <address> Specify RPC server to send commands to\n")); + os_msg(_(" --startuptime <file> Write startup timing messages to <file>\n")); + os_msg(_("\nSee \":help startup-options\" for all options.\n")); } // Check the result of the ATTENTION dialog: diff --git a/src/nvim/main.h b/src/nvim/main.h index 0c497a7c0e..2d54837872 100644 --- a/src/nvim/main.h +++ b/src/nvim/main.h @@ -1,6 +1,8 @@ #ifndef NVIM_MAIN_H #define NVIM_MAIN_H +#include <stdbool.h> + #include "nvim/event/loop.h" // Maximum number of commands from + or -c arguments. @@ -21,15 +23,15 @@ typedef struct { char cmds_tofree[MAX_ARG_CMDS]; // commands that need free() int n_pre_commands; // no. of commands from --cmd char *pre_commands[MAX_ARG_CMDS]; // commands from --cmd argument + char *luaf; // Lua script filename from "-l" + int lua_arg0; // Lua script args start index. int edit_type; // type of editing to do char *tagname; // tag from -t argument char *use_ef; // 'errorfile' from -q argument - bool input_isatty; // stdin is a terminal - bool output_isatty; // stdout is a terminal - bool err_isatty; // stderr is a terminal - bool input_neverscript; // never treat stdin as script (-E/-Es) + bool input_istext; // stdin is text, not executable (-E/-Es) + int no_swap_file; // "-n" argument used int use_debug_break_level; int window_count; // number of windows to use @@ -40,6 +42,10 @@ typedef struct { char *listen_addr; // --listen {address} int remote; // --remote-[subcmd] {file1} {file2} char *server_addr; // --server {address} + char *scriptin; // -s {filename} + char *scriptout; // -w/-W {filename} + bool scriptout_append; // append (-w) instead of overwrite (-W) + bool had_stdin_file; // explicit - as a file to edit } mparm_T; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/map.c b/src/nvim/map.c index 24478c6091..191a459863 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -8,17 +8,16 @@ // khash.h does not make its own copy of the key or value. // -#include <lauxlib.h> -#include <lua.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> +#include "auto/config.h" #include "klib/khash.h" +#include "nvim/gettext.h" #include "nvim/map.h" #include "nvim/map_defs.h" #include "nvim/memory.h" -#include "nvim/vim.h" #define cstr_t_hash kh_str_hash_func #define cstr_t_eq kh_str_hash_equal diff --git a/src/nvim/map.h b/src/nvim/map.h index f5f30f5a85..92f0b32255 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -2,12 +2,17 @@ #define NVIM_MAP_H #include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include "klib/khash.h" #include "nvim/api/private/defs.h" #include "nvim/extmark_defs.h" +#include "nvim/gettext.h" #include "nvim/highlight_defs.h" #include "nvim/map_defs.h" #include "nvim/tui/input_defs.h" +#include "nvim/types.h" #include "nvim/ui_client.h" #if defined(__NetBSD__) diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c index 001bbf7e48..831d1299a8 100644 --- a/src/nvim/mapping.c +++ b/src/nvim/mapping.c @@ -5,32 +5,43 @@ #include <assert.h> #include <inttypes.h> +#include <lauxlib.h> +#include <limits.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include "nvim/api/private/converter.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" -#include "nvim/assert.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" +#include "nvim/cmdexpand.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" -#include "nvim/ex_docmd.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_session.h" -#include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" #include "nvim/keycodes.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" #include "nvim/mapping.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/option.h" +#include "nvim/option_defs.h" +#include "nvim/pos.h" #include "nvim/regexp.h" #include "nvim/runtime.h" -#include "nvim/ui.h" +#include "nvim/search.h" +#include "nvim/strings.h" #include "nvim/vim.h" /// List used for abbreviations. @@ -150,7 +161,7 @@ static void showmap(mapblock_T *mp, bool local) { size_t len = 1; - if (message_filtered((char *)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(mp->m_desc))) { return; } @@ -174,9 +185,9 @@ static void showmap(mapblock_T *mp, bool local) } // Display the LHS. Get length of what we write. - len = (size_t)msg_outtrans_special((char *)mp->m_keys, true, 0); + len = (size_t)msg_outtrans_special(mp->m_keys, true, 0); do { - msg_putchar(' '); // padd with blanks + msg_putchar(' '); // pad with blanks len++; } while (len < 12); @@ -263,7 +274,7 @@ static bool set_maparg_lhs_rhs(const char *const orig_lhs, const size_t orig_lhs return false; } mapargs->lhs_len = strlen(replaced); - STRLCPY(mapargs->lhs, replaced, sizeof(mapargs->lhs)); + xstrlcpy(mapargs->lhs, replaced, sizeof(mapargs->lhs)); if (did_simplify) { replaced = replace_termcodes(orig_lhs, orig_lhs_len, &bufarg, flags | REPTERM_NO_SIMPLIFY, NULL, cpo_flags); @@ -271,7 +282,7 @@ static bool set_maparg_lhs_rhs(const char *const orig_lhs, const size_t orig_lhs return false; } mapargs->alt_lhs_len = strlen(replaced); - STRLCPY(mapargs->alt_lhs, replaced, sizeof(mapargs->alt_lhs)); + xstrlcpy(mapargs->alt_lhs, replaced, sizeof(mapargs->alt_lhs)); } else { mapargs->alt_lhs_len = 0; } @@ -289,10 +300,10 @@ static void set_maparg_rhs(const char *const orig_rhs, const size_t orig_rhs_len 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); + mapargs->orig_rhs = xcalloc(mapargs->orig_rhs_len + 1, sizeof(char)); + xstrlcpy(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 NUL-char + mapargs->rhs = xcalloc(1, sizeof(char)); // single NUL-char mapargs->rhs_len = 0; mapargs->rhs_is_noop = true; } else { @@ -308,7 +319,7 @@ static void set_maparg_rhs(const char *const orig_rhs, const size_t orig_rhs_len } else { 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 = xcalloc(1, sizeof(char)); 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, @@ -336,53 +347,53 @@ static void set_maparg_rhs(const char *const orig_rhs, const size_t orig_rhs_len /// @param[out] mapargs MapArguments struct holding all extracted argument /// values. /// @return 0 on success, 1 if invalid arguments are detected. -static int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *mapargs) +static int str_to_mapargs(const char *strargs, bool is_unmap, MapArguments *mapargs) { - const char_u *to_parse = strargs; - to_parse = (char_u *)skipwhite((char *)to_parse); + const char *to_parse = strargs; + to_parse = skipwhite(to_parse); CLEAR_POINTER(mapargs); // Accept <buffer>, <nowait>, <silent>, <expr>, <script>, and <unique> in // any order. while (true) { - if (STRNCMP(to_parse, "<buffer>", 8) == 0) { - to_parse = (char_u *)skipwhite((char *)to_parse + 8); + if (strncmp(to_parse, "<buffer>", 8) == 0) { + to_parse = skipwhite(to_parse + 8); mapargs->buffer = true; continue; } - if (STRNCMP(to_parse, "<nowait>", 8) == 0) { - to_parse = (char_u *)skipwhite((char *)to_parse + 8); + if (strncmp(to_parse, "<nowait>", 8) == 0) { + to_parse = skipwhite(to_parse + 8); mapargs->nowait = true; continue; } - if (STRNCMP(to_parse, "<silent>", 8) == 0) { - to_parse = (char_u *)skipwhite((char *)to_parse + 8); + if (strncmp(to_parse, "<silent>", 8) == 0) { + to_parse = skipwhite(to_parse + 8); mapargs->silent = true; continue; } // Ignore obsolete "<special>" modifier. - if (STRNCMP(to_parse, "<special>", 9) == 0) { - to_parse = (char_u *)skipwhite((char *)to_parse + 9); + if (strncmp(to_parse, "<special>", 9) == 0) { + to_parse = skipwhite(to_parse + 9); continue; } - if (STRNCMP(to_parse, "<script>", 8) == 0) { - to_parse = (char_u *)skipwhite((char *)to_parse + 8); + if (strncmp(to_parse, "<script>", 8) == 0) { + to_parse = skipwhite(to_parse + 8); mapargs->script = true; continue; } - if (STRNCMP(to_parse, "<expr>", 6) == 0) { - to_parse = (char_u *)skipwhite((char *)to_parse + 6); + if (strncmp(to_parse, "<expr>", 6) == 0) { + to_parse = skipwhite(to_parse + 6); mapargs->expr = true; continue; } - if (STRNCMP(to_parse, "<unique>", 8) == 0) { - to_parse = (char_u *)skipwhite((char *)to_parse + 8); + if (strncmp(to_parse, "<unique>", 8) == 0) { + to_parse = skipwhite(to_parse + 8); mapargs->unique = true; continue; } @@ -399,7 +410,7 @@ static int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *ma // // With :unmap, literal white space is included in the {lhs}; there is no // separate {rhs}. - const char *lhs_end = (char *)to_parse; + const char *lhs_end = to_parse; bool do_backslash = (vim_strchr(p_cpo, CPO_BSLASH) == NULL); while (*lhs_end && (is_unmap || !ascii_iswhite(*lhs_end))) { if ((lhs_end[0] == Ctrl_V || (do_backslash && lhs_end[0] == '\\')) @@ -411,20 +422,20 @@ static int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *ma // {lhs_end} is a pointer to the "terminating whitespace" after {lhs}. // Use that to initialize {rhs_start}. - const char_u *rhs_start = (char_u *)skipwhite((char *)lhs_end); + const char *rhs_start = skipwhite((char *)lhs_end); // Given {lhs} might be larger than MAXMAPLEN before replace_termcodes // (e.g. "<Space>" is longer than ' '), so first copy into a buffer. - size_t orig_lhs_len = (size_t)((char_u *)lhs_end - to_parse); + size_t orig_lhs_len = (size_t)(lhs_end - to_parse); if (orig_lhs_len >= 256) { return 1; } - char_u lhs_to_replace[256]; - STRLCPY(lhs_to_replace, to_parse, orig_lhs_len + 1); + char lhs_to_replace[256]; + xstrlcpy(lhs_to_replace, to_parse, orig_lhs_len + 1); - size_t orig_rhs_len = STRLEN(rhs_start); - if (!set_maparg_lhs_rhs((char *)lhs_to_replace, orig_lhs_len, - (char *)rhs_start, orig_rhs_len, LUA_NOREF, + size_t orig_rhs_len = strlen(rhs_start); + if (!set_maparg_lhs_rhs(lhs_to_replace, orig_lhs_len, + rhs_start, orig_rhs_len, LUA_NOREF, CPO_TO_CPO_FLAGS, mapargs)) { return 1; } @@ -454,16 +465,16 @@ static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table, } } - mp->m_keys = (uint8_t *)xstrdup(keys); + mp->m_keys = xstrdup(keys); mp->m_str = args->rhs; - mp->m_orig_str = (char *)args->orig_rhs; + mp->m_orig_str = args->orig_rhs; mp->m_luaref = args->rhs_lua; if (!simplified) { args->rhs = NULL; args->orig_rhs = NULL; args->rhs_lua = LUA_NOREF; } - mp->m_keylen = (int)STRLEN(mp->m_keys); + mp->m_keylen = (int)strlen(mp->m_keys); mp->m_noremap = noremap; mp->m_nowait = args->nowait; mp->m_silent = args->silent; @@ -489,7 +500,7 @@ static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table, mp->m_next = *abbr_table; *abbr_table = mp; } else { - const int n = MAP_HASH(mp->m_mode, mp->m_keys[0]); + const int n = MAP_HASH(mp->m_mode, (uint8_t)mp->m_keys[0]); mp->m_next = map_table[n]; map_table[n] = mp; } @@ -508,7 +519,7 @@ static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table, static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T *buf) { mapblock_T *mp, **mpp; - const char_u *p; + const char *p; int n; int retval = 0; mapblock_T **abbr_table; @@ -545,7 +556,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, goto theend; } - const char_u *lhs = (char_u *)&args->lhs; + const char *lhs = (char *)&args->lhs; const bool did_simplify = args->alt_lhs_len != 0; // The following is done twice if we have two versions of keys @@ -559,11 +570,11 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, if (!did_simplify) { break; } - lhs = (char_u *)&args->alt_lhs; + lhs = (char *)&args->alt_lhs; len = (int)args->alt_lhs_len; } else if (did_simplify && do_print) { // when printing always use the not-simplified map - lhs = (char_u *)&args->alt_lhs; + lhs = (char *)&args->alt_lhs; len = (int)args->alt_lhs_len; } @@ -583,9 +594,9 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, const int first = vim_iswordp(lhs); int last = first; - p = lhs + utfc_ptr2len((char *)lhs); + p = (char *)lhs + utfc_ptr2len((char *)lhs); n = 1; - while (p < lhs + len) { + while (p < (char *)lhs + len) { n++; // nr of (multi-byte) chars last = vim_iswordp(p); // type of last char if (same == -1 && last != first) { @@ -632,7 +643,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, // check entries with the same mode if ((mp->m_mode & mode) != 0 && mp->m_keylen == len - && STRNCMP(mp->m_keys, lhs, (size_t)len) == 0) { + && strncmp(mp->m_keys, lhs, (size_t)len) == 0) { if (is_abbrev) { semsg(_("E224: global abbreviation already exists for %s"), mp->m_keys); @@ -666,7 +677,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, did_local = true; } else { n = mp->m_keylen; - if (STRNCMP(mp->m_keys, lhs, (size_t)(n < len ? n : len)) == 0) { + if (strncmp(mp->m_keys, lhs, (size_t)(n < len ? n : len)) == 0) { showmap(mp, true); did_local = true; } @@ -685,9 +696,9 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, for (int round = 0; (round == 0 || maptype == MAPTYPE_UNMAP) && round <= 1 && !did_it && !got_int; round++) { int hash_start, hash_end; - if (has_lhs || is_abbrev) { + if ((round == 0 && has_lhs) || is_abbrev) { // just use one hash - hash_start = is_abbrev ? 0 : MAP_HASH(mode, lhs[0]); + hash_start = is_abbrev ? 0 : MAP_HASH(mode, (uint8_t)lhs[0]); hash_end = hash_start + 1; } else { // need to loop over all hash lists @@ -710,12 +721,12 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, } else { // do we have a match? if (round) { // second round: Try unmap "rhs" string n = (int)strlen(mp->m_str); - p = (char_u *)mp->m_str; + p = mp->m_str; } else { n = mp->m_keylen; p = mp->m_keys; } - if (STRNCMP(p, lhs, (size_t)(n < len ? n : len)) == 0) { + if (strncmp(p, lhs, (size_t)(n < len ? n : len)) == 0) { if (maptype == MAPTYPE_UNMAP) { // Delete entry. // Only accept a full match. For abbreviations @@ -768,7 +779,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, XFREE_CLEAR(mp->m_orig_str); } mp->m_str = args->rhs; - mp->m_orig_str = (char *)args->orig_rhs; + mp->m_orig_str = args->orig_rhs; mp->m_luaref = args->rhs_lua; if (!keyround1_simplified) { args->rhs = NULL; @@ -797,7 +808,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, } // May need to put this entry into another hash list. - int new_hash = MAP_HASH(mp->m_mode, mp->m_keys[0]); + int new_hash = MAP_HASH(mp->m_mode, (uint8_t)mp->m_keys[0]); if (!is_abbrev && new_hash != hash) { *mpp = mp->m_next; mp->m_next = map_table[new_hash]; @@ -904,7 +915,7 @@ theend: /// - 4 for out of mem (deprecated, WON'T HAPPEN) /// - 5 for entry not unique /// -int do_map(int maptype, char_u *arg, int mode, bool is_abbrev) +int do_map(int maptype, char *arg, int mode, bool is_abbrev) { MapArguments parsed_args; int result = str_to_mapargs(arg, maptype == MAPTYPE_UNMAP, &parsed_args); @@ -1024,7 +1035,7 @@ void map_clear_mode(buf_T *buf, int mode, bool local, bool abbr) continue; } // May need to put this entry into another hash list. - new_hash = MAP_HASH(mp->m_mode, mp->m_keys[0]); + new_hash = MAP_HASH(mp->m_mode, (uint8_t)mp->m_keys[0]); if (!abbr && new_hash != hash) { *mpp = mp->m_next; if (local) { @@ -1059,9 +1070,9 @@ bool map_to_exists(const char *const str, const char *const modechars, const boo int retval; char *buf = NULL; - const char_u *const rhs = (char_u *)replace_termcodes(str, strlen(str), - &buf, REPTERM_DO_LT, - NULL, CPO_TO_CPO_FLAGS); + const char *const rhs = replace_termcodes(str, strlen(str), + &buf, REPTERM_DO_LT, + NULL, CPO_TO_CPO_FLAGS); #define MAPMODE(mode, modechars, chr, modeflags) \ do { \ @@ -1079,7 +1090,7 @@ bool map_to_exists(const char *const str, const char *const modechars, const boo MAPMODE(mode, modechars, 'c', MODE_CMDLINE); #undef MAPMODE - retval = map_to_exists_mode((char *)rhs, mode, abbr); + retval = map_to_exists_mode(rhs, mode, abbr); xfree(buf); return retval; @@ -1188,7 +1199,7 @@ static char_u *translate_mapping(char_u *str, int cpo_flags) } if (c) { - ga_append(&ga, (char)c); + ga_append(&ga, (uint8_t)c); } } ga_append(&ga, NUL); @@ -1201,7 +1212,7 @@ static char_u *translate_mapping(char_u *str, int cpo_flags) /// @param forceit true if '!' given /// @param isabbrev true if abbreviation /// @param isunmap true if unmap/unabbrev command -char_u *set_context_in_map_cmd(expand_T *xp, char *cmd, char_u *arg, bool forceit, bool isabbrev, +char_u *set_context_in_map_cmd(expand_T *xp, char *cmd, char *arg, bool forceit, bool isabbrev, bool isunmap, cmdidx_T cmdidx) { if (forceit && cmdidx != CMD_map && cmdidx != CMD_unmap) { @@ -1219,38 +1230,38 @@ char_u *set_context_in_map_cmd(expand_T *xp, char *cmd, char_u *arg, bool forcei xp->xp_context = EXPAND_MAPPINGS; expand_buffer = false; for (;;) { - if (STRNCMP(arg, "<buffer>", 8) == 0) { + if (strncmp(arg, "<buffer>", 8) == 0) { expand_buffer = true; - arg = (char_u *)skipwhite((char *)arg + 8); + arg = skipwhite(arg + 8); continue; } - if (STRNCMP(arg, "<unique>", 8) == 0) { - arg = (char_u *)skipwhite((char *)arg + 8); + if (strncmp(arg, "<unique>", 8) == 0) { + arg = skipwhite(arg + 8); continue; } - if (STRNCMP(arg, "<nowait>", 8) == 0) { - arg = (char_u *)skipwhite((char *)arg + 8); + if (strncmp(arg, "<nowait>", 8) == 0) { + arg = skipwhite(arg + 8); continue; } - if (STRNCMP(arg, "<silent>", 8) == 0) { - arg = (char_u *)skipwhite((char *)arg + 8); + if (strncmp(arg, "<silent>", 8) == 0) { + arg = skipwhite(arg + 8); continue; } - if (STRNCMP(arg, "<special>", 9) == 0) { - arg = (char_u *)skipwhite((char *)arg + 9); + if (strncmp(arg, "<special>", 9) == 0) { + arg = skipwhite(arg + 9); continue; } - if (STRNCMP(arg, "<script>", 8) == 0) { - arg = (char_u *)skipwhite((char *)arg + 8); + if (strncmp(arg, "<script>", 8) == 0) { + arg = skipwhite(arg + 8); continue; } - if (STRNCMP(arg, "<expr>", 6) == 0) { - arg = (char_u *)skipwhite((char *)arg + 6); + if (strncmp(arg, "<expr>", 6) == 0) { + arg = skipwhite(arg + 6); continue; } break; } - xp->xp_pattern = (char *)arg; + xp->xp_pattern = arg; } return NULL; @@ -1259,98 +1270,140 @@ char_u *set_context_in_map_cmd(expand_T *xp, char *cmd, char_u *arg, bool forcei /// Find all mapping/abbreviation names that match regexp "regmatch". /// For command line expansion of ":[un]map" and ":[un]abbrev" in all modes. /// @return OK if matches found, FAIL otherwise. -int ExpandMappings(regmatch_T *regmatch, int *num_file, char ***file) +int ExpandMappings(char *pat, regmatch_T *regmatch, int *numMatches, char ***matches) { - mapblock_T *mp; - int hash; - int count; - int round; - char *p; - int i; - - *num_file = 0; // return values in case of FAIL - *file = NULL; - - // round == 1: Count the matches. - // round == 2: Build the array to keep the matches. - for (round = 1; round <= 2; round++) { - count = 0; - - for (i = 0; i < 7; i++) { - if (i == 0) { - p = "<silent>"; - } else if (i == 1) { - p = "<unique>"; - } else if (i == 2) { - p = "<script>"; - } else if (i == 3) { - p = "<expr>"; - } else if (i == 4 && !expand_buffer) { - p = "<buffer>"; - } else if (i == 5) { - p = "<nowait>"; - } else if (i == 6) { - p = "<special>"; - } else { + const bool fuzzy = cmdline_fuzzy_complete(pat); + + *numMatches = 0; // return values in case of FAIL + *matches = NULL; + + garray_T ga; + if (!fuzzy) { + ga_init(&ga, sizeof(char *), 3); + } else { + ga_init(&ga, sizeof(fuzmatch_str_T), 3); + } + + // First search in map modifier arguments + for (int i = 0; i < 7; i++) { + char *p; + if (i == 0) { + p = "<silent>"; + } else if (i == 1) { + p = "<unique>"; + } else if (i == 2) { + p = "<script>"; + } else if (i == 3) { + p = "<expr>"; + } else if (i == 4 && !expand_buffer) { + p = "<buffer>"; + } else if (i == 5) { + p = "<nowait>"; + } else if (i == 6) { + p = "<special>"; + } else { + continue; + } + + bool match; + int score = 0; + if (!fuzzy) { + match = vim_regexec(regmatch, p, (colnr_T)0); + } else { + score = fuzzy_match_str(p, pat); + match = (score != 0); + } + + if (!match) { + continue; + } + + if (fuzzy) { + GA_APPEND(fuzmatch_str_T, &ga, ((fuzmatch_str_T){ + .idx = ga.ga_len, + .str = xstrdup(p), + .score = score, + })); + } else { + GA_APPEND(char *, &ga, xstrdup(p)); + } + } + + for (int hash = 0; hash < 256; hash++) { + mapblock_T *mp; + if (expand_isabbrev) { + if (hash > 0) { // only one abbrev list + break; // for (hash) + } + mp = first_abbr; + } else if (expand_buffer) { + mp = curbuf->b_maphash[hash]; + } else { + mp = maphash[hash]; + } + for (; mp; mp = mp->m_next) { + if (!(mp->m_mode & expand_mapmodes)) { continue; } - if (vim_regexec(regmatch, p, (colnr_T)0)) { - if (round == 1) { - count++; - } else { - (*file)[count++] = xstrdup(p); - } + char *p = (char *)translate_mapping((char_u *)mp->m_keys, CPO_TO_CPO_FLAGS); + if (p == NULL) { + continue; } - } - for (hash = 0; hash < 256; hash++) { - if (expand_isabbrev) { - if (hash > 0) { // only one abbrev list - break; // for (hash) - } - mp = first_abbr; - } else if (expand_buffer) { - mp = curbuf->b_maphash[hash]; + bool match; + int score = 0; + if (!fuzzy) { + match = vim_regexec(regmatch, p, (colnr_T)0); } else { - mp = maphash[hash]; + score = fuzzy_match_str(p, pat); + match = (score != 0); } - for (; mp; mp = mp->m_next) { - if (mp->m_mode & expand_mapmodes) { - p = (char *)translate_mapping(mp->m_keys, CPO_TO_CPO_FLAGS); - if (p != NULL && vim_regexec(regmatch, p, (colnr_T)0)) { - if (round == 1) { - count++; - } else { - (*file)[count++] = p; - p = NULL; - } - } - xfree(p); - } - } // for (mp) - } // for (hash) - if (count == 0) { // no match found - break; // for (round) - } + if (!match) { + xfree(p); + continue; + } - if (round == 1) { - *file = xmalloc((size_t)count * sizeof(char_u *)); - } - } // for (round) + if (fuzzy) { + GA_APPEND(fuzmatch_str_T, &ga, ((fuzmatch_str_T){ + .idx = ga.ga_len, + .str = p, + .score = score, + })); + } else { + GA_APPEND(char *, &ga, p); + } + } // for (mp) + } // for (hash) + + if (ga.ga_len == 0) { + return FAIL; + } + if (!fuzzy) { + *matches = ga.ga_data; + *numMatches = ga.ga_len; + } else { + fuzzymatches_to_strmatches(ga.ga_data, matches, ga.ga_len, false); + *numMatches = ga.ga_len; + } + + int count = *numMatches; if (count > 1) { // Sort the matches - sort_strings(*file, count); + // Fuzzy matching already sorts the matches + if (!fuzzy) { + sort_strings(*matches, count); + } // Remove multiple entries - char **ptr1 = *file; + char **ptr1 = *matches; char **ptr2 = ptr1 + 1; char **ptr3 = ptr1 + count; while (ptr2 < ptr3) { - if (strcmp(*ptr1, *ptr2)) { + if (strcmp(*ptr1, *ptr2) != 0) { *++ptr1 = *ptr2++; } else { xfree(*ptr2++); @@ -1359,7 +1412,7 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char ***file) } } - *num_file = count; + *numMatches = count; return count == 0 ? FAIL : OK; } @@ -1379,12 +1432,12 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char ***file) // Then there must be white space before the abbr. // // Return true if there is an abbreviation, false if not. -bool check_abbr(int c, char_u *ptr, int col, int mincol) +bool check_abbr(int c, char *ptr, int col, int mincol) { int len; int scol; // starting column of the abbr. int j; - char_u *s; + char *s; char_u tb[MB_MAXBYTES + 4]; mapblock_T *mp; mapblock_T *mp2; @@ -1410,7 +1463,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) { bool vim_abbr; - char_u *p = mb_prevptr(ptr, ptr + col); + char *p = mb_prevptr(ptr, ptr + col); if (!vim_iswordp(p)) { vim_abbr = true; // Vim added abbr. } else { @@ -1423,7 +1476,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) while (p > ptr + mincol) { p = mb_prevptr(ptr, p); if (ascii_isspace(*p) || (!vim_abbr && is_id != vim_iswordp(p))) { - p += utfc_ptr2len((char *)p); + p += utfc_ptr2len(p); break; } clen++; @@ -1447,20 +1500,20 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) mp->m_next == NULL ? (mp = mp2, mp2 = NULL) : (mp = mp->m_next)) { int qlen = mp->m_keylen; - char *q = (char *)mp->m_keys; + char *q = mp->m_keys; int match; if (strchr((const char *)mp->m_keys, K_SPECIAL) != NULL) { // Might have K_SPECIAL escaped mp->m_keys. - q = xstrdup((char *)mp->m_keys); + q = xstrdup(mp->m_keys); vim_unescape_ks((char_u *)q); - qlen = (int)STRLEN(q); + qlen = (int)strlen(q); } // find entries with right mode and keys match = (mp->m_mode & State) && qlen == len - && !STRNCMP(q, ptr, (size_t)len); - if (q != (char *)mp->m_keys) { + && !strncmp(q, ptr, (size_t)len); + if (q != mp->m_keys) { xfree(q); } if (match) { @@ -1497,9 +1550,9 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) int newlen = utf_char2bytes(c, (char *)tb + j); tb[j + newlen] = NUL; // Need to escape K_SPECIAL. - char_u *escaped = (char_u *)vim_strsave_escape_ks((char *)tb + j); + char *escaped = vim_strsave_escape_ks((char *)tb + j); if (escaped != NULL) { - newlen = (int)STRLEN(escaped); + newlen = (int)strlen(escaped); memmove(tb + j, escaped, (size_t)newlen); j += newlen; xfree(escaped); @@ -1509,17 +1562,23 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) // insert the last typed char (void)ins_typebuf((char *)tb, 1, 0, true, mp->m_silent); } - if (mp->m_expr) { - s = (char_u *)eval_map_expr(mp, c); + + // copy values here, calling eval_map_expr() may make "mp" invalid! + const int noremap = mp->m_noremap; + const bool silent = mp->m_silent; + const bool expr = mp->m_expr; + + if (expr) { + s = eval_map_expr(mp, c); } else { - s = (char_u *)mp->m_str; + s = mp->m_str; } if (s != NULL) { // insert the to string - (void)ins_typebuf((char *)s, mp->m_noremap, 0, true, mp->m_silent); + (void)ins_typebuf(s, noremap, 0, true, silent); // no abbrev. for these chars - typebuf.tb_no_abbr_cnt += (int)STRLEN(s) + j + 1; - if (mp->m_expr) { + typebuf.tb_no_abbr_cnt += (int)strlen(s) + j + 1; + if (expr) { xfree(s); } } @@ -1528,7 +1587,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) tb[1] = NUL; len = clen; // Delete characters instead of bytes while (len-- > 0) { // delete the from string - (void)ins_typebuf((char *)tb, 1, 0, true, mp->m_silent); + (void)ins_typebuf((char *)tb, 1, 0, true, silent); } return true; } @@ -1538,6 +1597,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) /// Evaluate the RHS of a mapping or abbreviations and take care of escaping /// special characters. +/// Careful: after this "mp" will be invalid if the mapping was deleted. /// /// @param c NUL or typed character for abbreviation char *eval_map_expr(mapblock_T *mp, int c) @@ -1552,6 +1612,8 @@ char *eval_map_expr(mapblock_T *mp, int c) vim_unescape_ks((char_u *)expr); } + const bool replace_keycodes = mp->m_replace_keycodes; + // Forbid changing text or using ":normal" to avoid most of the bad side // effects. Also restore the cursor position. textlock++; @@ -1588,8 +1650,8 @@ char *eval_map_expr(mapblock_T *mp, int c) char *res = NULL; - if (mp->m_replace_keycodes) { - replace_termcodes(p, STRLEN(p), &res, REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS); + if (replace_keycodes) { + replace_termcodes(p, strlen(p), &res, REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS); } else { // Escape K_SPECIAL in the result to be able to use the string as typeahead. res = vim_strsave_escape_ks(p); @@ -1606,8 +1668,8 @@ char *eval_map_expr(mapblock_T *mp, int c) int makemap(FILE *fd, buf_T *buf) { mapblock_T *mp; - char_u c1, c2, c3; - char_u *p; + char c1, c2, c3; + char *p; char *cmd; int abbr; int hash; @@ -1645,8 +1707,8 @@ int makemap(FILE *fd, buf_T *buf) if (mp->m_luaref != LUA_NOREF) { continue; } - for (p = (char_u *)mp->m_str; *p != NUL; p++) { - if (p[0] == K_SPECIAL && p[1] == KS_EXTRA + for (p = mp->m_str; *p != NUL; p++) { + if ((uint8_t)p[0] == K_SPECIAL && (uint8_t)p[1] == KS_EXTRA && p[2] == KE_SNR) { break; } @@ -1791,7 +1853,7 @@ int makemap(FILE *fd, buf_T *buf) if (putc(' ', fd) < 0 || put_escstr(fd, mp->m_keys, 0) == FAIL || putc(' ', fd) < 0 - || put_escstr(fd, (char_u *)mp->m_str, 1) == FAIL + || put_escstr(fd, mp->m_str, 1) == FAIL || put_eol(fd) < 0) { return FAIL; } @@ -1817,9 +1879,9 @@ int makemap(FILE *fd, buf_T *buf) // "what": 0 for :map lhs, 1 for :map rhs, 2 for :set // // return FAIL for failure, OK otherwise -int put_escstr(FILE *fd, char_u *strstart, int what) +int put_escstr(FILE *fd, char *strstart, int what) { - char_u *str = strstart; + char_u *str = (char_u *)strstart; int c; // :map xx <Nop> @@ -1896,7 +1958,7 @@ int put_escstr(FILE *fd, char_u *strstart, int what) } } else if (c < ' ' || c > '~' || c == '|' || (what == 0 && c == ' ') - || (what == 1 && str == strstart && c == ' ') + || (what == 1 && str == (char_u *)strstart && c == ' ') || (what != 2 && c == '<')) { if (putc(Ctrl_V, fd) < 0) { return FAIL; @@ -1918,14 +1980,14 @@ int put_escstr(FILE *fd, char_u *strstart, int what) /// @param abbr do abbreviations /// @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 *rhs_lua) +char *check_map(char *keys, int mode, int exact, int ign_mod, int abbr, mapblock_T **mp_ptr, + int *local_ptr, int *rhs_lua) { int len, minlen; mapblock_T *mp; *rhs_lua = LUA_NOREF; - len = (int)STRLEN(keys); + len = (int)strlen(keys); for (int local = 1; local >= 0; local--) { // loop over all hash lists for (int hash = 0; hash < 256; hash++) { @@ -1946,15 +2008,15 @@ char_u *check_map(char_u *keys, int mode, int exact, int ign_mod, int abbr, mapb for (; mp != NULL; mp = mp->m_next) { // skip entries with wrong mode, wrong length and not matching ones if ((mp->m_mode & mode) && (!exact || mp->m_keylen == len)) { - char_u *s = mp->m_keys; + char *s = mp->m_keys; int keylen = mp->m_keylen; if (ign_mod && keylen >= 3 - && s[0] == K_SPECIAL && s[1] == KS_MODIFIER) { + && (uint8_t)s[0] == K_SPECIAL && (uint8_t)s[1] == KS_MODIFIER) { s += 3; keylen -= 3; } minlen = keylen < len ? keylen : len; - if (STRNCMP(s, keys, minlen) == 0) { + if (strncmp(s, keys, (size_t)minlen) == 0) { if (mp_ptr != NULL) { *mp_ptr = mp; } @@ -1962,7 +2024,7 @@ char_u *check_map(char_u *keys, int mode, int exact, int ign_mod, int abbr, mapb *local_ptr = local; } *rhs_lua = mp->m_luaref; - return mp->m_luaref == LUA_NOREF ? (char_u *)mp->m_str : NULL; + return mp->m_luaref == LUA_NOREF ? mp->m_str : NULL; } } } @@ -2007,7 +2069,7 @@ static Dictionary mapblock_fill_dict(const mapblock_T *const mp, const char *lhs FUNC_ATTR_NONNULL_ARG(1) { Dictionary dict = ARRAY_DICT_INIT; - char *const lhs = str2special_save((const char *)mp->m_keys, compatible, !compatible); + char *const lhs = str2special_save(mp->m_keys, compatible, !compatible); char *const mapmode = map_mode_to_chars(mp->m_mode); varnumber_T noremap_value; @@ -2090,13 +2152,13 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) const int flags = REPTERM_FROM_PART | REPTERM_DO_LT; const int mode = get_map_mode((char **)&which, 0); - char_u *keys_simplified - = (char_u *)replace_termcodes(keys, strlen(keys), &keys_buf, flags, &did_simplify, - CPO_TO_CPO_FLAGS); + char *keys_simplified = replace_termcodes(keys, strlen(keys), &keys_buf, flags, &did_simplify, + CPO_TO_CPO_FLAGS); mapblock_T *mp = NULL; int buffer_local; LuaRef rhs_lua; - char_u *rhs = check_map(keys_simplified, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua); + char *rhs = check_map(keys_simplified, mode, exact, false, abbr, &mp, &buffer_local, + &rhs_lua); if (did_simplify) { // When the lhs is being simplified the not-simplified keys are // preferred for printing, like in do_map(). @@ -2104,7 +2166,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) strlen(keys), &alt_keys_buf, flags | REPTERM_NO_SIMPLIFY, NULL, CPO_TO_CPO_FLAGS); - rhs = check_map((char_u *)alt_keys_buf, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua); + rhs = check_map(alt_keys_buf, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua); } if (!get_dict) { @@ -2113,7 +2175,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) if (*rhs == NUL) { rettv->vval.v_string = xstrdup("<Nop>"); } else { - rettv->vval.v_string = str2special_save((char *)rhs, false, false); + rettv->vval.v_string = str2special_save(rhs, false, false); } } else if (rhs_lua != LUA_NOREF) { rettv->vval.v_string = nlua_funcref_str(mp->m_luaref); @@ -2122,7 +2184,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) // Return a dictionary. if (mp != NULL && (rhs != NULL || rhs_lua != LUA_NOREF)) { Dictionary dict = mapblock_fill_dict(mp, - did_simplify ? (char *)keys_simplified : NULL, + did_simplify ? keys_simplified : NULL, buffer_local, true); (void)object_to_vim(DICTIONARY_OBJ(dict), rettv, NULL); api_free_dictionary(dict); @@ -2332,14 +2394,14 @@ void langmap_init(void) /// changed at any time! void langmap_set(void) { - char_u *p; - char_u *p2; + char *p; + char *p2; int from, to; ga_clear(&langmap_mapga); // clear the previous map first langmap_init(); // back to one-to-one map - for (p = (char_u *)p_langmap; p[0] != NUL;) { + for (p = p_langmap; p[0] != NUL;) { for (p2 = p; p2[0] != NUL && p2[0] != ',' && p2[0] != ';'; MB_PTR_ADV(p2)) { if (p2[0] == '\\' && p2[1] != NUL) { @@ -2359,7 +2421,7 @@ void langmap_set(void) if (p[0] == '\\' && p[1] != NUL) { p++; } - from = utf_ptr2char((char *)p); + from = utf_ptr2char(p); to = NUL; if (p2 == NULL) { MB_PTR_ADV(p); @@ -2367,14 +2429,14 @@ void langmap_set(void) if (p[0] == '\\') { p++; } - to = utf_ptr2char((char *)p); + to = utf_ptr2char(p); } } else { if (p2[0] != ',') { if (p2[0] == '\\') { p2++; } - to = utf_ptr2char((char *)p2); + to = utf_ptr2char(p2); } } if (to == NUL) { @@ -2398,8 +2460,7 @@ void langmap_set(void) p = p2; if (p[0] != NUL) { if (p[0] != ',') { - semsg(_("E358: 'langmap': Extra characters after semicolon: %s"), - p); + semsg(_("E358: 'langmap': Extra characters after semicolon: %s"), p); return; } p++; @@ -2419,7 +2480,7 @@ static void do_exmap(exarg_T *eap, int isabbrev) switch (do_map((*cmdp == 'n') ? MAPTYPE_NOREMAP : (*cmdp == 'u') ? MAPTYPE_UNMAP : MAPTYPE_MAP, - (char_u *)eap->arg, mode, isabbrev)) { + eap->arg, mode, isabbrev)) { case 1: emsg(_(e_invarg)); break; @@ -2438,8 +2499,7 @@ void ex_abbreviate(exarg_T *eap) /// ":map" and friends. void ex_map(exarg_T *eap) { - // If we are sourcing .exrc or .vimrc in current directory we - // print the mappings for security reasons. + // If we are in a secure mode we print the mappings for security reasons. if (secure) { secure = 2; msg_outtrans(eap->cmd); @@ -2540,7 +2600,7 @@ void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mod } int mode_val; // integer value of the mapping mode, to be passed to do_map() char *p = (mode.size) ? mode.data : "m"; - if (STRNCMP(p, "!", 2) == 0) { + if (strncmp(p, "!", 2) == 0) { mode_val = get_map_mode(&p, true); // mapmode-ic } else { mode_val = get_map_mode(&p, false); diff --git a/src/nvim/mapping.h b/src/nvim/mapping.h index 156187b5d8..58e28810bc 100644 --- a/src/nvim/mapping.h +++ b/src/nvim/mapping.h @@ -1,6 +1,10 @@ #ifndef NVIM_MAPPING_H #define NVIM_MAPPING_H +#include <stdbool.h> +#include <stddef.h> + +#include "lauxlib.h" #include "nvim/buffer_defs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/types.h" @@ -28,11 +32,11 @@ struct map_arguments { /// vim limits this to MAXMAPLEN characters, allowing us to use a static /// buffer. Setting lhs_len to a value larger than MAXMAPLEN can signal /// that {lhs} was too long and truncated. - char_u lhs[MAXMAPLEN + 1]; + char lhs[MAXMAPLEN + 1]; size_t lhs_len; /// Unsimplifed {lhs} of the mapping. If no simplification has been done then alt_lhs_len is 0. - char_u alt_lhs[MAXMAPLEN + 1]; + char alt_lhs[MAXMAPLEN + 1]; size_t alt_lhs_len; char *rhs; /// The {rhs} of the mapping. @@ -40,7 +44,7 @@ struct map_arguments { LuaRef rhs_lua; /// lua function as {rhs} bool rhs_is_noop; /// True when the {rhs} should be <Nop>. - char_u *orig_rhs; /// The original text of the {rhs}. + char *orig_rhs; /// The original text of the {rhs}. size_t orig_rhs_len; char *desc; /// map description }; diff --git a/src/nvim/mark.c b/src/nvim/mark.c index f41793c8a6..f1a1f25e6c 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -4,20 +4,27 @@ // mark.c: functions for setting marks and jumping to them #include <assert.h> -#include <inttypes.h> #include <limits.h> +#include <stdint.h> +#include <stdio.h> #include <string.h> #include "nvim/ascii.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" #include "nvim/edit.h" -#include "nvim/eval.h" -#include "nvim/ex_cmds.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/extmark.h" +#include "nvim/extmark_defs.h" #include "nvim/fold.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" @@ -25,16 +32,15 @@ #include "nvim/message.h" #include "nvim/move.h" #include "nvim/normal.h" -#include "nvim/option.h" +#include "nvim/option_defs.h" #include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os/time.h" #include "nvim/path.h" #include "nvim/quickfix.h" #include "nvim/sign.h" #include "nvim/strings.h" #include "nvim/textobject.h" -#include "nvim/ui.h" +#include "nvim/undo_defs.h" #include "nvim/vim.h" // This file contains routines to maintain and manipulate marks. @@ -545,6 +551,7 @@ MarkMoveRes mark_move_to(fmark_T *fm, MarkMove flags) // Need to change buffer fm_copy = *fm; // Copy, autocommand may change it fm = &fm_copy; + // Jump to the file with the mark res |= switch_to_mark_buf(fm, !(flags & kMarkJumpList)); // Failed switching buffer if (res & kMarkMoveFailed) { @@ -562,6 +569,7 @@ MarkMoveRes mark_move_to(fmark_T *fm, MarkMove flags) // Move the cursor while keeping track of what changed for the caller pos_T prev_pos = curwin->w_cursor; pos_T pos = fm->mark; + // Set lnum again, autocommands my have changed it curwin->w_cursor = fm->mark; if (flags & kMarkBeginLine) { beginline(BL_WHITE | BL_FIX); @@ -645,29 +653,31 @@ fmark_T *getnextmark(pos_T *startpos, int dir, int begin_line) // until the mark is used to avoid a long startup delay. static void fname2fnum(xfmark_T *fm) { - if (fm->fname != NULL) { - // First expand "~/" in the file name to the home directory. - // Don't expand the whole name, it may contain other '~' chars. + if (fm->fname == NULL) { + return; + } + + // First expand "~/" in the file name to the home directory. + // Don't expand the whole name, it may contain other '~' chars. #ifdef BACKSLASH_IN_FILENAME - if (fm->fname[0] == '~' && (fm->fname[1] == '/' || fm->fname[1] == '\\')) { + if (fm->fname[0] == '~' && (fm->fname[1] == '/' || fm->fname[1] == '\\')) { #else - if (fm->fname[0] == '~' && (fm->fname[1] == '/')) { + if (fm->fname[0] == '~' && (fm->fname[1] == '/')) { #endif - expand_env("~/", NameBuff, MAXPATHL); - int len = (int)strlen(NameBuff); - STRLCPY(NameBuff + len, fm->fname + 2, MAXPATHL - len); - } else { - STRLCPY(NameBuff, fm->fname, MAXPATHL); - } + expand_env("~/", NameBuff, MAXPATHL); + int len = (int)strlen(NameBuff); + xstrlcpy(NameBuff + len, fm->fname + 2, (size_t)(MAXPATHL - len)); + } else { + xstrlcpy(NameBuff, fm->fname, MAXPATHL); + } - // Try to shorten the file name. - os_dirname((char_u *)IObuff, IOSIZE); - char *p = path_shorten_fname(NameBuff, IObuff); + // Try to shorten the file name. + os_dirname(IObuff, IOSIZE); + char *p = path_shorten_fname(NameBuff, IObuff); - // buflist_new() will call fmarks_check_names() - (void)buflist_new(NameBuff, p, (linenr_T)1, 0); - } + // buflist_new() will call fmarks_check_names() + (void)buflist_new(NameBuff, p, (linenr_T)1, 0); } // Check all file marks for a name that matches the file name in buf. @@ -675,7 +685,7 @@ static void fname2fnum(xfmark_T *fm) // Used for marks that come from the .shada file. void fmarks_check_names(buf_T *buf) { - char_u *name = (char_u *)buf->b_ffname; + char *name = buf->b_ffname; int i; if (buf->b_ffname == NULL) { @@ -683,12 +693,12 @@ void fmarks_check_names(buf_T *buf) } for (i = 0; i < NGLOBALMARKS; i++) { - fmarks_check_one(&namedfm[i], (char *)name, buf); + fmarks_check_one(&namedfm[i], name, buf); } FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { for (i = 0; i < wp->w_jumplistlen; i++) { - fmarks_check_one(&wp->w_jumplist[i], (char *)name, buf); + fmarks_check_one(&wp->w_jumplist[i], name, buf); } } } @@ -773,12 +783,12 @@ void clrallmarks(buf_T *const buf) // Get name of file from a filemark. // When it's in the current buffer, return the text at the mark. // Returns an allocated string. -char_u *fm_getname(fmark_T *fmark, int lead_len) +char *fm_getname(fmark_T *fmark, int lead_len) { if (fmark->fnum == curbuf->b_fnum) { // current buffer - return (char_u *)mark_line(&(fmark->mark), lead_len); + return mark_line(&(fmark->mark), lead_len); } - return (char_u *)buflist_nr2name(fmark->fnum, false, true); + return buflist_nr2name(fmark->fnum, false, true); } /// Return the line at mark "mp". Truncate to fit in window. @@ -810,9 +820,9 @@ static char *mark_line(pos_T *mp, int lead_len) // print the marks void ex_marks(exarg_T *eap) { - char_u *arg = (char_u *)eap->arg; + char *arg = eap->arg; int i; - char_u *name; + char *name; pos_T *posp, *startp, *endp; if (arg != NULL && *arg == NUL) { @@ -827,7 +837,7 @@ void ex_marks(exarg_T *eap) if (namedfm[i].fmark.fnum != 0) { name = fm_getname(&namedfm[i].fmark, 15); } else { - name = (char_u *)namedfm[i].fname; + name = namedfm[i].fname; } if (name != NULL) { show_one_mark(i >= NMARKS ? i - NMARKS + '0' : i + 'A', @@ -859,11 +869,11 @@ void ex_marks(exarg_T *eap) } /// @param current in current file -static void show_one_mark(int c, char_u *arg, pos_T *p, char_u *name_arg, int current) +static void show_one_mark(int c, char *arg, pos_T *p, char *name_arg, int current) { static bool did_title = false; bool mustfree = false; - char_u *name = name_arg; + char *name = name_arg; if (c == -1) { // finish up if (did_title) { @@ -876,14 +886,14 @@ static void show_one_mark(int c, char_u *arg, pos_T *p, char_u *name_arg, int cu } } } else if (!got_int - && (arg == NULL || vim_strchr((char *)arg, c) != NULL) + && (arg == NULL || vim_strchr(arg, c) != NULL) && p->lnum != 0) { // don't output anything if 'q' typed at --more-- prompt if (name == NULL && current) { - name = (char_u *)mark_line(p, 15); + name = mark_line(p, 15); mustfree = true; } - if (!message_filtered((char *)name)) { + if (!message_filtered(name)) { if (!did_title) { // Highlight title msg_puts_title(_("\nmark line col file/text")); @@ -891,10 +901,10 @@ static void show_one_mark(int c, char_u *arg, pos_T *p, char_u *name_arg, int cu } msg_putchar('\n'); if (!got_int) { - snprintf((char *)IObuff, IOSIZE, " %c %6" PRIdLINENR " %4d ", c, p->lnum, p->col); - msg_outtrans((char *)IObuff); + snprintf(IObuff, IOSIZE, " %c %6" PRIdLINENR " %4d ", c, p->lnum, p->col); + msg_outtrans(IObuff); if (name != NULL) { - msg_outtrans_attr((char *)name, current ? HL_ATTR(HLF_D) : 0); + msg_outtrans_attr(name, current ? HL_ATTR(HLF_D) : 0); } } } @@ -907,7 +917,7 @@ static void show_one_mark(int c, char_u *arg, pos_T *p, char_u *name_arg, int cu // ":delmarks[!] [marks]" void ex_delmarks(exarg_T *eap) { - char_u *p; + char *p; int from, to; int i; int lower; @@ -923,14 +933,14 @@ void ex_delmarks(exarg_T *eap) emsg(_(e_argreq)); } else { // clear specified marks only - for (p = (char_u *)eap->arg; *p != NUL; p++) { + for (p = eap->arg; *p != NUL; p++) { lower = ASCII_ISLOWER(*p); digit = ascii_isdigit(*p); if (lower || digit || ASCII_ISUPPER(*p)) { if (p[1] == '-') { // clear range of marks - from = *p; - to = p[2]; + from = (uint8_t)(*p); + to = (uint8_t)p[2]; if (!(lower ? ASCII_ISLOWER(p[2]) : (digit ? ascii_isdigit(p[2]) : ASCII_ISUPPER(p[2]))) @@ -941,7 +951,7 @@ void ex_delmarks(exarg_T *eap) p += 2; } else { // clear one lower case mark - from = to = *p; + from = to = (uint8_t)(*p); } for (i = from; i <= to; i++) { @@ -996,7 +1006,7 @@ void ex_jumps(exarg_T *eap) msg_puts_title(_("\n jump line col file/text")); for (i = 0; i < curwin->w_jumplistlen && !got_int; i++) { if (curwin->w_jumplist[i].fmark.mark.lnum != 0) { - name = (char *)fm_getname(&curwin->w_jumplist[i].fmark, 16); + 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. @@ -1014,11 +1024,11 @@ void ex_jumps(exarg_T *eap) xfree(name); break; } - snprintf((char *)IObuff, IOSIZE, "%c %2d %5" PRIdLINENR " %4d ", + snprintf(IObuff, IOSIZE, "%c %2d %5" PRIdLINENR " %4d ", i == curwin->w_jumplistidx ? '>' : ' ', i > curwin->w_jumplistidx ? i - curwin->w_jumplistidx : curwin->w_jumplistidx - i, curwin->w_jumplist[i].fmark.mark.lnum, curwin->w_jumplist[i].fmark.mark.col); - msg_outtrans((char *)IObuff); + msg_outtrans(IObuff); msg_outtrans_attr(name, curwin->w_jumplist[i].fmark.fnum == curbuf->b_fnum ? HL_ATTR(HLF_D) : 0); @@ -1042,7 +1052,7 @@ void ex_clearjumps(exarg_T *eap) void ex_changes(exarg_T *eap) { int i; - char_u *name; + char *name; // Highlight title msg_puts_title(_("\nchange line col text")); @@ -1058,9 +1068,9 @@ void ex_changes(exarg_T *eap) i > curwin->w_changelistidx ? i - curwin->w_changelistidx : curwin->w_changelistidx - i, (long)curbuf->b_changelist[i].mark.lnum, curbuf->b_changelist[i].mark.col); - msg_outtrans((char *)IObuff); - name = (char_u *)mark_line(&curbuf->b_changelist[i].mark, 17); - msg_outtrans_attr((char *)name, HL_ATTR(HLF_D)); + msg_outtrans(IObuff); + name = mark_line(&curbuf->b_changelist[i].mark, 17); + msg_outtrans_attr(name, HL_ATTR(HLF_D)); xfree(name); os_breakcheck(); } @@ -1370,18 +1380,21 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, linenr_T lnum_amount, long c // When deleting lines, this may create duplicate marks in the // jumplist. They will be removed here for the specified window. -// When "checktail" is true, removes tail jump if it matches current position. -void cleanup_jumplist(win_T *wp, bool checktail) +// When "loadfiles" is true first ensure entries have the "fnum" field set +// (this may be a bit slow). +void cleanup_jumplist(win_T *wp, bool loadfiles) { int i; - // Load all the files from the jump list. This is - // needed to properly clean up duplicate entries, but will take some - // time. - for (i = 0; i < wp->w_jumplistlen; i++) { - if ((wp->w_jumplist[i].fmark.fnum == 0) - && (wp->w_jumplist[i].fmark.mark.lnum != 0)) { - fname2fnum(&wp->w_jumplist[i]); + if (loadfiles) { + // If specified, load all the files from the jump list. This is + // needed to properly clean up duplicate entries, but will take some + // time. + for (i = 0; i < wp->w_jumplistlen; i++) { + if ((wp->w_jumplist[i].fmark.fnum == 0) + && (wp->w_jumplist[i].fmark.mark.lnum != 0)) { + fname2fnum(&wp->w_jumplist[i]); + } } } @@ -1429,8 +1442,8 @@ void cleanup_jumplist(win_T *wp, bool checktail) // When pointer is below last jump, remove the jump if it matches the current // line. This avoids useless/phantom jumps. #9805 - if (checktail && wp->w_jumplistlen - && wp->w_jumplistidx == wp->w_jumplistlen) { + if (loadfiles // otherwise (i.e.: Shada), last entry should be kept + && wp->w_jumplistlen && wp->w_jumplistidx == wp->w_jumplistlen) { const xfmark_T *fm_last = &wp->w_jumplist[wp->w_jumplistlen - 1]; if (fm_last->fmark.fnum == curbuf->b_fnum && fm_last->fmark.mark.lnum == wp->w_cursor.lnum) { @@ -1479,9 +1492,8 @@ const void *mark_jumplist_iter(const void *const iter, const win_T *const win, x *fm = *iter_mark; if (iter_mark == &(win->w_jumplist[win->w_jumplistlen - 1])) { return NULL; - } else { - return iter_mark + 1; } + return iter_mark + 1; } /// Iterate over global marks @@ -1698,18 +1710,18 @@ void mark_mb_adjustpos(buf_T *buf, pos_T *lp) FUNC_ATTR_NONNULL_ALL { if (lp->col > 0 || lp->coladd > 1) { - const char_u *const p = (char_u *)ml_get_buf(buf, lp->lnum, false); - if (*p == NUL || (int)STRLEN(p) < lp->col) { + const char *const p = ml_get_buf(buf, lp->lnum, false); + if (*p == NUL || (int)strlen(p) < lp->col) { lp->col = 0; } else { - lp->col -= utf_head_off((char *)p, (char *)p + lp->col); + lp->col -= utf_head_off(p, p + lp->col); } // Reset "coladd" when the cursor would be on the right half of a // double-wide character. if (lp->coladd == 1 && p[lp->col] != TAB - && vim_isprintc(utf_ptr2char((char *)p + lp->col)) - && ptr2cells((char *)p + lp->col) > 1) { + && vim_isprintc(utf_ptr2char(p + lp->col)) + && ptr2cells(p + lp->col) > 1) { lp->coladd = 0; } } diff --git a/src/nvim/mark.h b/src/nvim/mark.h index 6da976e8d3..af0abba864 100644 --- a/src/nvim/mark.h +++ b/src/nvim/mark.h @@ -1,9 +1,12 @@ #ifndef NVIM_MARK_H #define NVIM_MARK_H +#include <stdbool.h> +#include <stddef.h> + #include "nvim/ascii.h" #include "nvim/buffer_defs.h" -#include "nvim/ex_cmds_defs.h" // for exarg_T +#include "nvim/ex_cmds_defs.h" #include "nvim/extmark_defs.h" #include "nvim/func_attr.h" #include "nvim/macros.h" @@ -78,12 +81,13 @@ static inline int mark_local_index(const char name) : -1)))); } -static inline bool lt(pos_T, pos_T) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool equalpos(pos_T, pos_T) +static inline bool lt(pos_T a, pos_T b) + REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; +static inline bool equalpos(pos_T a, pos_T b) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ltoreq(pos_T, pos_T) +static inline bool ltoreq(pos_T a, pos_T b) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline void clearpos(pos_T *) +static inline void clearpos(pos_T *a) REAL_FATTR_ALWAYS_INLINE; /// Return true if position a is before (less than) position b. diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c index ad1680322c..2036ddd21d 100644 --- a/src/nvim/marktree.c +++ b/src/nvim/marktree.c @@ -48,10 +48,15 @@ // at the repo root. #include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> #include "klib/kvec.h" #include "nvim/garray.h" #include "nvim/marktree.h" +#include "nvim/memory.h" +#include "nvim/pos.h" #define T MT_BRANCH_FACTOR #define ILEN (sizeof(mtnode_t) + (2 * T) * sizeof(void *)) diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h index e2e05eebd5..5ce4b2cd24 100644 --- a/src/nvim/marktree.h +++ b/src/nvim/marktree.h @@ -2,14 +2,19 @@ #define NVIM_MARKTREE_H #include <assert.h> +#include <stdbool.h> +#include <stddef.h> #include <stdint.h> #include "nvim/assert.h" #include "nvim/garray.h" #include "nvim/map.h" +#include "nvim/map_defs.h" #include "nvim/pos.h" #include "nvim/types.h" +struct mtnode_s; + #define MT_MAX_DEPTH 20 #define MT_BRANCH_FACTOR 10 diff --git a/src/nvim/match.c b/src/nvim/match.c index b422dc0ba8..6663dfd7ec 100644 --- a/src/nvim/match.c +++ b/src/nvim/match.c @@ -3,22 +3,39 @@ // match.c: functions for highlighting matches +#include <assert.h> +#include <inttypes.h> #include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include "nvim/ascii.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/drawscreen.h" -#include "nvim/eval.h" #include "nvim/eval/funcs.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/eval/window.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/fold.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" +#include "nvim/macros.h" #include "nvim/match.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option_defs.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/regexp.h" -#include "nvim/runtime.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -408,7 +425,7 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_ const int called_emsg_before = called_emsg; // for :{range}s/pat only highlight inside the range - if (lnum < search_first_line || lnum > search_last_line) { + if ((lnum < search_first_line || lnum > search_last_line) && cur == NULL) { shl->lnum = 0; return; } @@ -444,16 +461,16 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_ } 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; + char *ml; matchcol = shl->rm.startpos[0].col; - ml = (char_u *)ml_get_buf(shl->buf, lnum, false) + matchcol; + ml = ml_get_buf(shl->buf, lnum, false) + matchcol; if (*ml == NUL) { matchcol++; shl->lnum = 0; break; } - matchcol += utfc_ptr2len((char *)ml); + matchcol += utfc_ptr2len(ml); } else { matchcol = shl->rm.endpos[0].col; } @@ -580,9 +597,10 @@ static void check_cur_search_hl(win_T *wp, match_T *shl) } /// 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, +/// +/// @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 **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 @@ -613,7 +631,7 @@ bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char_u **l // Need to get the line again, a multi-line regexp may have made it // invalid. - *line = (char_u *)ml_get_buf(wp->w_buffer, lnum, false); + *line = ml_get_buf(wp->w_buffer, lnum, false); if (shl->lnum != 0 && shl->lnum <= lnum) { if (shl->lnum == lnum) { @@ -636,7 +654,7 @@ bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char_u **l // Highlight one character for an empty match. if (shl->startcol == shl->endcol) { if ((*line)[shl->endcol] != NUL) { - shl->endcol += utfc_ptr2len((char *)(*line) + shl->endcol); + shl->endcol += utfc_ptr2len(*line + shl->endcol); } else { shl->endcol++; } @@ -660,9 +678,11 @@ bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char_u **l /// 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! +/// "on_last_col" is set to true with non-zero search_attr and the next column +/// is endcol. /// 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, +int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char **line, match_T *search_hl, + int *has_match_conc, int *match_conc, int lcs_eol_one, bool *on_last_col, bool *search_attr_from_match) { matchitem_T *cur = wp->w_match_head; // points to the match list @@ -690,7 +710,7 @@ int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char_u **line, match if (shl->startcol != MAXCOL && col >= shl->startcol && col < shl->endcol) { - int next_col = col + utfc_ptr2len((char *)(*line) + col); + int next_col = col + utfc_ptr2len(*line + col); if (shl->endcol < next_col) { shl->endcol = next_col; @@ -721,7 +741,7 @@ int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char_u **line, match // Need to get the line again, a multi-line regexp // may have made it invalid. - *line = (char_u *)ml_get_buf(wp->w_buffer, lnum, false); + *line = ml_get_buf(wp->w_buffer, lnum, false); if (shl->lnum == lnum) { shl->startcol = shl->rm.startpos[0].col; @@ -738,7 +758,7 @@ int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char_u **line, match if (shl->startcol == shl->endcol) { // highlight empty match, try again after it - char *p = (char *)(*line) + shl->endcol; + char *p = *line + shl->endcol; if (*p == NUL) { shl->endcol++; @@ -775,6 +795,7 @@ int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char_u **line, match } if (shl->attr_cur != 0) { search_attr = shl->attr_cur; + *on_last_col = col + 1 >= shl->endcol; *search_attr_from_match = shl != search_hl; } if (shl != search_hl && cur != NULL) { @@ -805,17 +826,17 @@ bool get_prevcol_hl_flag(win_T *wp, match_T *search_hl, long curcol) || (prevcol > (long)search_hl->startcol && search_hl->endcol == MAXCOL))) { return true; - } else { - cur = wp->w_match_head; - while (cur != NULL) { - if (!cur->mit_hl.is_addpos && (prevcol == (long)cur->mit_hl.startcol - || (prevcol > (long)cur->mit_hl.startcol - && cur->mit_hl.endcol == MAXCOL))) { - return true; - } - cur = cur->mit_next; + } + cur = wp->w_match_head; + while (cur != NULL) { + if (!cur->mit_hl.is_addpos && (prevcol == (long)cur->mit_hl.startcol + || (prevcol > (long)cur->mit_hl.startcol + && cur->mit_hl.endcol == MAXCOL))) { + return true; } + cur = cur->mit_next; } + return false; } @@ -859,12 +880,14 @@ static int matchadd_dict_arg(typval_T *tv, const char **conceal_char, win_T **wi *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; - } + if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("window"))) == NULL) { + return OK; + } + + *win = find_win_by_nr_or_id(&di->di_tv); + if (*win == NULL) { + emsg(_(e_invalwindow)); + return FAIL; } return OK; @@ -1206,7 +1229,7 @@ void ex_match(exarg_T *eap) semsg(_(e_invarg2), eap->arg); return; } - end = skip_regexp(p + 1, *p, true, NULL); + end = skip_regexp(p + 1, *p, true); if (!eap->skip) { if (*end != NUL && !ends_excmd(*skipwhite(end + 1))) { xfree(g); diff --git a/src/nvim/math.c b/src/nvim/math.c index b427688083..31c6b5af69 100644 --- a/src/nvim/math.c +++ b/src/nvim/math.c @@ -10,7 +10,7 @@ #include "nvim/math.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "math.c.generated.h" +# include "math.c.generated.h" // IWYU pragma: export #endif int xfpclassify(double d) diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 33d652a51f..8b50ba719a 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -25,36 +25,51 @@ /// Vim scripts may contain an ":scriptencoding" command. This has an effect /// for some commands, like ":menutrans". -#include <inttypes.h> +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <iconv.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include <wchar.h> #include <wctype.h> -#include "nvim/ascii.h" -#include "nvim/vim.h" -#ifdef HAVE_LOCALE_H -# include <locale.h> -#endif +#include "auto/config.h" #include "nvim/arabic.h" +#include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" -#include "nvim/eval.h" -#include "nvim/fileio.h" -#include "nvim/func_attr.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/grid_defs.h" #include "nvim/iconv.h" +#include "nvim/keycodes.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/mbyte.h" +#include "nvim/mbyte_defs.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/option_defs.h" #include "nvim/os/os.h" -#include "nvim/path.h" +#include "nvim/os/os_defs.h" +#include "nvim/pos.h" #include "nvim/screen.h" -#include "nvim/spell.h" #include "nvim/strings.h" +#include "nvim/types.h" +#include "nvim/vim.h" + +#ifdef HAVE_LOCALE_H +# include <locale.h> +#endif typedef struct { int rangeStart; @@ -68,11 +83,12 @@ struct interval { long last; }; +// uncrustify:off #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mbyte.c.generated.h" - # include "unicode_tables.generated.h" #endif +// uncrustify:on static char e_list_item_nr_is_not_list[] = N_("E1109: List item %d is not a List"); @@ -84,8 +100,8 @@ static char e_list_item_nr_cell_width_invalid[] = N_("E1112: List item %d cell width invalid"); static char e_overlapping_ranges_for_nr[] = N_("E1113: Overlapping ranges for 0x%lx"); -static char e_only_values_of_0x100_and_higher_supported[] - = N_("E1114: Only values of 0x100 and higher supported"); +static char e_only_values_of_0x80_and_higher_supported[] + = N_("E1114: Only values of 0x80 and higher supported"); // To speed up BYTELEN(); keep a lookup table to quickly get the length in // bytes of a UTF-8 character from the first byte of a UTF-8 string. Bytes @@ -136,8 +152,7 @@ const uint8_t utf8len_tab_zero[] = { // "iso-8859-n" is handled by enc_canonize() directly. static struct { const char *name; int prop; int codepage; } -enc_canon_table[] = -{ +enc_canon_table[] = { #define IDX_LATIN_1 0 { "latin1", ENC_8BIT + ENC_LATIN1, 1252 }, #define IDX_ISO_2 1 @@ -270,8 +285,7 @@ enc_canon_table[] = // Aliases for encoding names. static struct { const char *name; int canon; } -enc_alias_table[] = -{ +enc_alias_table[] = { { "ansi", IDX_LATIN_1 }, { "iso-8859-1", IDX_LATIN_1 }, { "latin2", IDX_ISO_2 }, @@ -353,15 +367,15 @@ static int enc_canon_search(const char *name) // Find canonical encoding "name" in the list and return its properties. // Returns 0 if not found. -int enc_canon_props(const char_u *name) +int enc_canon_props(const char *name) FUNC_ATTR_PURE { int i = enc_canon_search((char *)name); if (i >= 0) { return enc_canon_table[i].prop; - } else if (STRNCMP(name, "2byte-", 6) == 0) { + } else if (strncmp(name, "2byte-", 6) == 0) { return ENC_DBCS; - } else if (STRNCMP(name, "8bit-", 5) == 0 || STRNCMP(name, "iso-8859-", 9) == 0) { + } else if (strncmp(name, "8bit-", 5) == 0 || strncmp(name, "iso-8859-", 9) == 0) { return ENC_8BIT; } return 0; @@ -381,10 +395,10 @@ int bomb_size(void) if (*curbuf->b_p_fenc == NUL || strcmp(curbuf->b_p_fenc, "utf-8") == 0) { n = 3; - } else if (STRNCMP(curbuf->b_p_fenc, "ucs-2", 5) == 0 - || STRNCMP(curbuf->b_p_fenc, "utf-16", 6) == 0) { + } else if (strncmp(curbuf->b_p_fenc, "ucs-2", 5) == 0 + || strncmp(curbuf->b_p_fenc, "utf-16", 6) == 0) { n = 2; - } else if (STRNCMP(curbuf->b_p_fenc, "ucs-4", 5) == 0) { + } else if (strncmp(curbuf->b_p_fenc, "ucs-4", 5) == 0) { n = 4; } } @@ -392,9 +406,9 @@ int bomb_size(void) } // Remove all BOM from "s" by moving remaining text. -void remove_bom(char_u *s) +void remove_bom(char *s) { - char *p = (char *)s; + char *p = s; while ((p = strchr(p, 0xef)) != NULL) { if ((uint8_t)p[1] == 0xbb && (uint8_t)p[2] == 0xbf) { @@ -410,25 +424,25 @@ void remove_bom(char_u *s) // 1 for punctuation // 2 for an (ASCII) word character // >2 for other word characters -int mb_get_class(const char_u *p) +int mb_get_class(const char *p) FUNC_ATTR_PURE { return mb_get_class_tab(p, curbuf->b_chartab); } -int mb_get_class_tab(const char_u *p, const uint64_t *const chartab) +int mb_get_class_tab(const char *p, const uint64_t *const chartab) FUNC_ATTR_PURE { - if (MB_BYTE2LEN(p[0]) == 1) { + if (MB_BYTE2LEN((uint8_t)p[0]) == 1) { if (p[0] == NUL || ascii_iswhite(p[0])) { return 0; } - if (vim_iswordc_tab(p[0], chartab)) { + if (vim_iswordc_tab((uint8_t)p[0], chartab)) { return 2; } return 1; } - return utf_class_tab(utf_ptr2char((char *)p), chartab); + return utf_class_tab(utf_ptr2char(p), chartab); } // Return true if "c" is in "table". @@ -468,12 +482,16 @@ static bool intable(const struct interval *table, size_t n_items, int c) /// gen_unicode_tables.lua, which must be manually invoked as needed. int utf_char2cells(int c) { - if (c >= 0x100) { + // Use the value from setcellwidths() at 0x80 and higher, unless the + // character is not printable. + if (c >= 0x80 && vim_isprintc(c)) { int n = cw_value(c); if (n != 0) { return n; } + } + if (c >= 0x100) { if (!utf_printable(c)) { return 6; // unprintable, displays <xxxx> } @@ -520,13 +538,13 @@ int utf_ptr2cells(const char *p) /// Like utf_ptr2cells(), but limit string length to "size". /// For an empty string or truncated character returns 1. -int utf_ptr2cells_len(const char_u *p, int size) +int utf_ptr2cells_len(const char *p, int size) { int c; // Need to convert to a wide character. - if (size > 0 && *p >= 0x80) { - if (utf_ptr2len_len(p, size) < utf8len_tab[*p]) { + if (size > 0 && (uint8_t)(*p) >= 0x80) { + if (utf_ptr2len_len(p, size) < utf8len_tab[(uint8_t)(*p)]) { return 1; // truncated } c = utf_ptr2char((char *)p); @@ -552,8 +570,8 @@ size_t mb_string2cells(const char *str) { size_t clen = 0; - for (const char_u *p = (char_u *)str; *p != NUL; p += utfc_ptr2len((char *)p)) { - clen += (size_t)utf_ptr2cells((char *)p); + for (const char *p = str; *p != NUL; p += utfc_ptr2len(p)) { + clen += (size_t)utf_ptr2cells(p); } return clen; @@ -570,9 +588,9 @@ size_t mb_string2cells_len(const char *str, size_t size) { size_t clen = 0; - for (const char_u *p = (char_u *)str; *p != NUL && p < (char_u *)str + size; - p += utfc_ptr2len_len((char *)p, (int)size + (int)(p - (char_u *)str))) { - clen += (size_t)utf_ptr2cells((char *)p); + for (const char *p = str; *p != NUL && p < str + size; + p += utfc_ptr2len_len(p, (int)size + (int)(p - str))) { + clen += (size_t)utf_ptr2cells(p); } return clen; @@ -585,7 +603,7 @@ size_t mb_string2cells_len(const char *str, size_t size) /// For an overlong sequence this may return zero. /// Does not include composing characters for obvious reasons. /// -/// @param[in] p String to convert. +/// @param[in] p_in String to convert. /// /// @return Unicode codepoint or byte value. int utf_ptr2char(const char *const p_in) @@ -685,23 +703,23 @@ static int utf_safe_read_char_adv(const char_u **s, size_t *n) // Get character at **pp and advance *pp to the next character. // Note: composing characters are skipped! -int mb_ptr2char_adv(const char_u **const pp) +int mb_ptr2char_adv(const char **const pp) { int c; - c = utf_ptr2char((char *)(*pp)); - *pp += utfc_ptr2len((char *)(*pp)); + c = utf_ptr2char(*pp); + *pp += utfc_ptr2len(*pp); return c; } // Get character at **pp and advance *pp to the next character. // Note: composing characters are returned as separate characters. -int mb_cptr2char_adv(const char_u **pp) +int mb_cptr2char_adv(const char **pp) { int c; - c = utf_ptr2char((char *)(*pp)); - *pp += utf_ptr2len((char *)(*pp)); + c = utf_ptr2char(*pp); + *pp += utf_ptr2len(*pp); return c; } @@ -730,26 +748,25 @@ bool utf_composinglike(const char *p1, const char *p2) /// space at least for #MAX_MCO + 1 elements. /// /// @return leading character. -int utfc_ptr2char(const char *p_in, int *pcc) +int utfc_ptr2char(const char *p, int *pcc) { - uint8_t *p = (uint8_t *)p_in; int i = 0; - int c = utf_ptr2char((char *)p); - int len = utf_ptr2len((char *)p); + int c = utf_ptr2char(p); + int len = utf_ptr2len(p); // Only accept a composing char when the first char isn't illegal. - if ((len > 1 || *p < 0x80) - && p[len] >= 0x80 - && utf_composinglike((char *)p, (char *)p + len)) { - int cc = utf_ptr2char((char *)p + len); + if ((len > 1 || (uint8_t)(*p) < 0x80) + && (uint8_t)p[len] >= 0x80 + && utf_composinglike(p, p + len)) { + int cc = utf_ptr2char(p + len); for (;;) { pcc[i++] = cc; if (i == MAX_MCO) { break; } - len += utf_ptr2len((char *)p + len); - if (p[len] < 0x80 || !utf_iscomposing(cc = utf_ptr2char((char *)p + len))) { + len += utf_ptr2len(p + len); + if ((uint8_t)p[len] < 0x80 || !utf_iscomposing(cc = utf_ptr2char(p + len))) { break; } } @@ -766,7 +783,7 @@ int utfc_ptr2char(const char *p_in, int *pcc) // composing characters. Use no more than p[maxlen]. // // @param [out] pcc: composing chars, last one is 0 -int utfc_ptr2char_len(const char_u *p, int *pcc, int maxlen) +int utfc_ptr2char_len(const char *p, int *pcc, int maxlen) { assert(maxlen > 0); @@ -775,15 +792,15 @@ int utfc_ptr2char_len(const char_u *p, int *pcc, int maxlen) int len = utf_ptr2len_len(p, maxlen); // Is it safe to use utf_ptr2char()? bool safe = len > 1 && len <= maxlen; - int c = safe ? utf_ptr2char((char *)p) : *p; + int c = safe ? utf_ptr2char(p) : (uint8_t)(*p); // Only accept a composing char when the first char isn't illegal. - if ((safe || c < 0x80) && len < maxlen && p[len] >= 0x80) { + if ((safe || c < 0x80) && len < maxlen && (uint8_t)p[len] >= 0x80) { for (; i < MAX_MCO; i++) { int len_cc = utf_ptr2len_len(p + len, maxlen - len); safe = len_cc > 1 && len_cc <= maxlen - len; - if (!safe || (pcc[i] = utf_ptr2char((char *)p + len)) < 0x80 - || !(i == 0 ? utf_composinglike((char *)p, (char *)p + len) : utf_iscomposing(pcc[i]))) { + if (!safe || (pcc[i] = utf_ptr2char(p + len)) < 0x80 + || !(i == 0 ? utf_composinglike(p, p + len) : utf_iscomposing(pcc[i]))) { break; } len += len_cc; @@ -835,13 +852,13 @@ int utf_byte2len(int b) // Returns 1 for an illegal byte sequence (also in incomplete byte seq.). // Returns number > "size" for an incomplete byte sequence. // Never returns zero. -int utf_ptr2len_len(const char_u *p, int size) +int utf_ptr2len_len(const char *p, int size) { int len; int i; int m; - len = utf8len_tab[*p]; + len = utf8len_tab[(uint8_t)(*p)]; if (len == 1) { return 1; // NUL, ascii or illegal lead byte } @@ -861,21 +878,20 @@ int utf_ptr2len_len(const char_u *p, int size) /// Return the number of bytes occupied by a UTF-8 character in a string. /// This includes following composing characters. /// Returns zero for NUL. -int utfc_ptr2len(const char *const p_in) +int utfc_ptr2len(const char *const p) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - uint8_t *p = (uint8_t *)p_in; - uint8_t b0 = *p; + uint8_t b0 = (uint8_t)(*p); if (b0 == NUL) { return 0; } - if (b0 < 0x80 && p[1] < 0x80) { // be quick for ASCII + if (b0 < 0x80 && (uint8_t)p[1] < 0x80) { // be quick for ASCII return 1; } // Skip over first UTF-8 char, stopping at a NUL byte. - int len = utf_ptr2len((char *)p); + int len = utf_ptr2len(p); // Check for illegal byte. if (len == 1 && b0 >= 0x80) { @@ -886,13 +902,13 @@ int utfc_ptr2len(const char *const p_in) // skip all of them (otherwise the cursor would get stuck). int prevlen = 0; for (;;) { - if (p[len] < 0x80 || !utf_composinglike((char *)p + prevlen, (char *)p + len)) { + if ((uint8_t)p[len] < 0x80 || !utf_composinglike(p + prevlen, p + len)) { return len; } // Skip over composing char. prevlen = len; - len += utf_ptr2len((char *)p + len); + len += utf_ptr2len(p + len); } } @@ -913,7 +929,7 @@ int utfc_ptr2len_len(const char *p, int size) } // Skip over first UTF-8 char, stopping at a NUL byte. - len = utf_ptr2len_len((char_u *)p, size); + len = utf_ptr2len_len(p, size); // Check for illegal byte and incomplete byte sequence. if ((len == 1 && (uint8_t)p[0] >= 0x80) || len > size) { @@ -932,7 +948,7 @@ int utfc_ptr2len_len(const char *p, int size) // Next character length should not go beyond size to ensure that // utf_composinglike(...) does not read beyond size. - len_next_char = utf_ptr2len_len((char_u *)p + len, size - len); + len_next_char = utf_ptr2len_len(p + len, size - len); if (len_next_char > size - len) { break; } @@ -1024,8 +1040,7 @@ bool utf_printable(int c) { // Sorted list of non-overlapping intervals. // 0xd800-0xdfff is reserved for UTF-16, actually illegal. - static struct interval nonprint[] = - { + static struct interval nonprint[] = { { 0x070f, 0x070f }, { 0x180b, 0x180e }, { 0x200b, 0x200f }, { 0x202a, 0x202e }, { 0x2060, 0x206f }, { 0xd800, 0xdfff }, { 0xfeff, 0xfeff }, { 0xfff9, 0xfffb }, { 0xfffe, 0xffff } @@ -1189,9 +1204,8 @@ static int utf_convert(int a, const convertStruct *const table, size_t n_items) && a <= table[start].rangeEnd && (a - table[start].rangeStart) % table[start].step == 0) { return a + table[start].offset; - } else { - return a; } + return a; } // Return the folded-case equivalent of "a", which is a UCS-4 character. Uses @@ -1219,12 +1233,9 @@ int mb_toupper(int a) return TOUPPER_ASC(a); } -#if defined(__STDC_ISO_10646__) - // If towupper() is available and handles Unicode, use it. if (!(cmp_flags & CMP_INTERNAL)) { return (int)towupper((wint_t)a); } -#endif // For characters below 128 use locale sensitive toupper(). if (a < 128) { @@ -1250,12 +1261,9 @@ int mb_tolower(int a) return TOLOWER_ASC(a); } -#if defined(__STDC_ISO_10646__) - // If towlower() is available and handles Unicode, use it. if (!(cmp_flags & CMP_INTERNAL)) { return (int)towlower((wint_t)a); } -#endif // For characters below 128 use locale sensitive tolower(). if (a < 128) { @@ -1452,7 +1460,7 @@ int utf16_to_utf8(const wchar_t *utf16, int utf16len, char **utf8) /// @param len maximum length (an earlier NUL terminates) /// @param[out] codepoints incremented with UTF-32 code point size /// @param[out] codeunits incremented with UTF-16 code unit size -void mb_utflen(const char_u *s, size_t len, size_t *codepoints, size_t *codeunits) +void mb_utflen(const char *s, size_t len, size_t *codepoints, size_t *codeunits) FUNC_ATTR_NONNULL_ALL { size_t count = 0, extra = 0; @@ -1461,7 +1469,7 @@ void mb_utflen(const char_u *s, size_t len, size_t *codepoints, size_t *codeunit clen = (size_t)utf_ptr2len_len(s + i, (int)(len - i)); // NB: gets the byte value of invalid sequence bytes. // we only care whether the char fits in the BMP or not - int c = (clen > 1) ? utf_ptr2char((char *)s + i) : s[i]; + int c = (clen > 1) ? utf_ptr2char(s + i) : (uint8_t)s[i]; count++; if (c > 0xFFFF) { extra++; @@ -1471,7 +1479,7 @@ void mb_utflen(const char_u *s, size_t len, size_t *codepoints, size_t *codeunit *codeunits += count + extra; } -ssize_t mb_utf_index_to_bytes(const char_u *s, size_t len, size_t index, bool use_utf16_units) +ssize_t mb_utf_index_to_bytes(const char *s, size_t len, size_t index, bool use_utf16_units) FUNC_ATTR_NONNULL_ALL { size_t count = 0; @@ -1483,7 +1491,7 @@ ssize_t mb_utf_index_to_bytes(const char_u *s, size_t len, size_t index, bool us clen = (size_t)utf_ptr2len_len(s + i, (int)(len - i)); // NB: gets the byte value of invalid sequence bytes. // we only care whether the char fits in the BMP or not - int c = (clen > 1) ? utf_ptr2char((char *)s + i) : s[i]; + int c = (clen > 1) ? utf_ptr2char(s + i) : (uint8_t)s[i]; count++; if (use_utf16_units && c > 0xFFFF) { count++; @@ -1530,14 +1538,14 @@ void show_utf8(void) { int len; int rlen = 0; - char_u *line; + char *line; int clen; int i; // Get the byte length of the char under the cursor, including composing // characters. - line = (char_u *)get_cursor_pos_ptr(); - len = utfc_ptr2len((char *)line); + line = get_cursor_pos_ptr(); + len = utfc_ptr2len(line); if (len == 0) { msg("NUL"); return; @@ -1551,10 +1559,10 @@ void show_utf8(void) STRCPY(IObuff + rlen, "+ "); rlen += 2; } - clen = utf_ptr2len((char *)line + i); + clen = utf_ptr2len(line + i); } - sprintf((char *)IObuff + rlen, "%02x ", - (line[i] == NL) ? NUL : line[i]); // NUL is stored as NL + sprintf(IObuff + rlen, "%02x ", // NOLINT(runtime/printf) + (line[i] == NL) ? NUL : (uint8_t)line[i]); // NUL is stored as NL clen--; rlen += (int)strlen(IObuff + rlen); if (rlen > IOSIZE - 20) { @@ -1562,7 +1570,7 @@ void show_utf8(void) } } - msg((char *)IObuff); + msg(IObuff); } /// Return offset from "p" to the start of a character, including composing characters. @@ -1780,11 +1788,12 @@ void mb_copy_char(const char **const fp, char **const tp) *fp += l; } -/// Return the offset from "p" to the first byte of a character. When "p" is +/// Return the offset from "p_in" to the first byte of a character. When "p_in" 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 mb_off_next(const char *base, const char *p_in) { + const uint8_t *p = (uint8_t *)p_in; int i; int j; @@ -1796,7 +1805,7 @@ int mb_off_next(const char_u *base, const char_u *p) for (i = 0; (p[i] & 0xc0) == 0x80; i++) {} if (i > 0) { // Check for illegal sequence. - for (j = 0; p - j > base; j++) { + for (j = 0; p - j > (uint8_t *)base; j++) { if ((p[-j] & 0xc0) != 0x80) { break; } @@ -1876,13 +1885,13 @@ int utf_cp_head_off(const char_u *base, const char_u *p) void utf_find_illegal(void) { pos_T pos = curwin->w_cursor; - char_u *p; + char *p; int len; vimconv_T vimconv; - char_u *tofree = NULL; + char *tofree = NULL; vimconv.vc_type = CONV_NONE; - if (enc_canon_props((char_u *)curbuf->b_p_fenc) & ENC_8BIT) { + if (enc_canon_props(curbuf->b_p_fenc) & ENC_8BIT) { // 'encoding' is "utf-8" but we are editing a 8-bit encoded file, // possibly a utf-8 file with illegal bytes. Setup for conversion // from utf-8 to 'fileencoding'. @@ -1891,10 +1900,10 @@ void utf_find_illegal(void) curwin->w_cursor.coladd = 0; for (;;) { - p = (char_u *)get_cursor_pos_ptr(); + p = get_cursor_pos_ptr(); if (vimconv.vc_type != CONV_NONE) { xfree(tofree); - tofree = (char_u *)string_convert(&vimconv, (char *)p, NULL); + tofree = string_convert(&vimconv, p, NULL); if (tofree == NULL) { break; } @@ -1904,17 +1913,16 @@ void utf_find_illegal(void) while (*p != NUL) { // Illegal means that there are not enough trail bytes (checked by // utf_ptr2len()) or too many of them (overlong sequence). - len = utf_ptr2len((char *)p); - if (*p >= 0x80 && (len == 1 - || utf_char2len(utf_ptr2char((char *)p)) != len)) { + len = utf_ptr2len(p); + if ((uint8_t)(*p) >= 0x80 && (len == 1 || utf_char2len(utf_ptr2char(p)) != len)) { if (vimconv.vc_type == CONV_NONE) { - curwin->w_cursor.col += (colnr_T)(p - (char_u *)get_cursor_pos_ptr()); + curwin->w_cursor.col += (colnr_T)(p - get_cursor_pos_ptr()); } else { int l; len = (int)(p - tofree); - for (p = (char_u *)get_cursor_pos_ptr(); *p != NUL && len-- > 0; p += l) { - l = utf_ptr2len((char *)p); + for (p = get_cursor_pos_ptr(); *p != NUL && len-- > 0; p += l) { + l = utf_ptr2len(p); curwin->w_cursor.col += l; } } @@ -1981,7 +1989,7 @@ void mb_check_adjust_col(void *win_) // Column 0 is always valid. if (oldcol != 0) { char *p = ml_get_buf(win->w_buffer, win->w_cursor.lnum, false); - colnr_T len = (colnr_T)STRLEN(p); + colnr_T len = (colnr_T)strlen(p); // Empty line or invalid column? if (len == 0 || oldcol < 0) { @@ -2008,7 +2016,7 @@ void mb_check_adjust_col(void *win_) /// @param line start of the string /// /// @return a pointer to the character before "*p", if there is one. -char_u *mb_prevptr(char_u *line, char_u *p) +char *mb_prevptr(char *line, char *p) { if (p > line) { MB_PTR_BACK(line, p); @@ -2018,9 +2026,9 @@ char_u *mb_prevptr(char_u *line, char_u *p) /// Return the character length of "str". Each multi-byte character (with /// following composing characters) counts as one. -int mb_charlen(const char_u *str) +int mb_charlen(const char *str) { - const char_u *p = str; + const char *p = str; int count; if (p == NULL) { @@ -2028,20 +2036,20 @@ int mb_charlen(const char_u *str) } for (count = 0; *p != NUL; count++) { - p += utfc_ptr2len((char *)p); + p += utfc_ptr2len(p); } return count; } /// Like mb_charlen() but for a string with specified length. -int mb_charlen_len(const char_u *str, int len) +int mb_charlen_len(const char *str, int len) { - const char_u *p = str; + const char *p = str; int count; for (count = 0; *p != NUL && p < str + len; count++) { - p += utfc_ptr2len((char *)p); + p += utfc_ptr2len(p); } return count; @@ -2097,10 +2105,10 @@ const char *mb_unescape(const char **const pp) /// Skip the Vim specific head of a 'encoding' name. char *enc_skip(char *p) { - if (STRNCMP(p, "2byte-", 6) == 0) { + if (strncmp(p, "2byte-", 6) == 0) { return p + 6; } - if (STRNCMP(p, "8bit-", 5) == 0) { + if (strncmp(p, "8bit-", 5) == 0) { return p + 5; } return p; @@ -2121,7 +2129,7 @@ char *enc_canonize(char *enc) } // copy "enc" to allocated memory, with room for two '-' - char *r = xmalloc(STRLEN(enc) + 3); + char *r = xmalloc(strlen(enc) + 3); // Make it all lower case and replace '_' with '-'. p = r; for (s = enc; *s != NUL; s++) { @@ -2137,24 +2145,24 @@ char *enc_canonize(char *enc) p = enc_skip(r); // Change "microsoft-cp" to "cp". Used in some spell files. - if (STRNCMP(p, "microsoft-cp", 12) == 0) { + if (strncmp(p, "microsoft-cp", 12) == 0) { STRMOVE(p, p + 10); } // "iso8859" -> "iso-8859" - if (STRNCMP(p, "iso8859", 7) == 0) { + if (strncmp(p, "iso8859", 7) == 0) { STRMOVE(p + 4, p + 3); p[3] = '-'; } // "iso-8859n" -> "iso-8859-n" - if (STRNCMP(p, "iso-8859", 8) == 0 && p[8] != '-') { + if (strncmp(p, "iso-8859", 8) == 0 && p[8] != '-') { STRMOVE(p + 9, p + 8); p[8] = '-'; } // "latin-N" -> "latinN" - if (STRNCMP(p, "latin-", 6) == 0) { + if (strncmp(p, "latin-", 6) == 0) { STRMOVE(p + 5, p + 6); } @@ -2192,7 +2200,7 @@ static int enc_alias_search(const char *name) // Get the canonicalized encoding of the current locale. // Returns an allocated string when successful, NULL when not. -char_u *enc_locale(void) +char *enc_locale(void) { int i; char buf[50]; @@ -2228,7 +2236,7 @@ char_u *enc_locale(void) const char *p = vim_strchr(s, '.'); if (p != NULL) { if (p > s + 2 && !STRNICMP(p + 1, "EUC", 3) - && !isalnum((int)p[4]) && p[4] != '-' && p[-3] == '_') { + && !isalnum((uint8_t)p[4]) && p[4] != '-' && p[-3] == '_') { // Copy "XY.EUC" to "euc-XY" to buf[10]. memmove(buf, "euc-", 4); buf[4] = (char)(ASCII_ISALNUM(p[-2]) ? TOLOWER_ASC(p[-2]) : 0); @@ -2252,20 +2260,18 @@ enc_locale_copy_enc: buf[i] = NUL; } - return (char_u *)enc_canonize(buf); + return enc_canonize(buf); } -#if defined(HAVE_ICONV) - // Call iconv_open() with a check if iconv() works properly (there are broken // versions). // Returns (void *)-1 if failed. // (should return iconv_t, but that causes problems with prototypes). -void *my_iconv_open(char_u *to, char_u *from) +void *my_iconv_open(char *to, char *from) { iconv_t fd; -# define ICONV_TESTLEN 400 - char_u tobuf[ICONV_TESTLEN]; +#define ICONV_TESTLEN 400 + char tobuf[ICONV_TESTLEN]; char *p; size_t tolen; static WorkingStatus iconv_working = kUnknown; @@ -2273,7 +2279,7 @@ void *my_iconv_open(char_u *to, char_u *from) if (iconv_working == kBroken) { return (void *)-1; // detected a broken iconv() previously } - fd = iconv_open(enc_skip((char *)to), enc_skip((char *)from)); + fd = iconv_open(enc_skip(to), enc_skip(from)); if (fd != (iconv_t)-1 && iconv_working == kUnknown) { // Do a dummy iconv() call to check if it actually works. There is a @@ -2281,7 +2287,7 @@ void *my_iconv_open(char_u *to, char_u *from) // because it's wide-spread. The symptoms are that after outputting // the initial shift state the "to" pointer is NULL and conversion // stops for no apparent reason after about 8160 characters. - p = (char *)tobuf; + p = tobuf; tolen = ICONV_TESTLEN; (void)iconv(fd, NULL, NULL, &p, &tolen); if (p == NULL) { @@ -2301,8 +2307,8 @@ void *my_iconv_open(char_u *to, char_u *from) // sequence and set "*unconvlenp" to the length of it. // Returns the converted string in allocated memory. NULL for an error. // If resultlenp is not NULL, sets it to the result length in bytes. -static char_u *iconv_string(const vimconv_T *const vcp, char_u *str, size_t slen, - size_t *unconvlenp, size_t *resultlenp) +static char *iconv_string(const vimconv_T *const vcp, const char *str, size_t slen, + size_t *unconvlenp, size_t *resultlenp) { const char *from; size_t fromlen; @@ -2310,11 +2316,11 @@ static char_u *iconv_string(const vimconv_T *const vcp, char_u *str, size_t slen size_t tolen; size_t len = 0; size_t done = 0; - char_u *result = NULL; - char_u *p; + char *result = NULL; + char *p; int l; - from = (char *)str; + from = str; fromlen = slen; for (;;) { if (len == 0 || ICONV_ERRNO == ICONV_E2BIG) { @@ -2329,7 +2335,7 @@ static char_u *iconv_string(const vimconv_T *const vcp, char_u *str, size_t slen result = p; } - to = (char *)result + done; + to = result + done; tolen = len - done - 2; // Avoid a warning for systems with a wrong iconv() prototype by // casting the second argument to void *. @@ -2369,17 +2375,15 @@ static char_u *iconv_string(const vimconv_T *const vcp, char_u *str, size_t slen break; } // Not enough room or skipping illegal sequence. - done = (size_t)(to - (char *)result); + done = (size_t)(to - result); } if (resultlenp != NULL && result != NULL) { - *resultlenp = (size_t)(to - (char *)result); + *resultlenp = (size_t)(to - result); } return result; } -#endif // HAVE_ICONV - /// Setup "vcp" for conversion from "from" to "to". /// The names must have been made canonical with enc_canonize(). /// vcp->vc_type must have been initialized to CONV_NONE. @@ -2404,11 +2408,9 @@ int convert_setup_ext(vimconv_T *vcp, char *from, bool from_unicode_is_utf8, cha int to_is_utf8; // Reset to no conversion. -#ifdef HAVE_ICONV if (vcp->vc_type == CONV_ICONV && vcp->vc_fd != (iconv_t)-1) { iconv_close(vcp->vc_fd); } -#endif *vcp = (vimconv_T)MBYTE_NONE_CONV; // No conversion when one of the names is empty or they are equal. @@ -2417,8 +2419,8 @@ int convert_setup_ext(vimconv_T *vcp, char *from, bool from_unicode_is_utf8, cha return OK; } - from_prop = enc_canon_props((char_u *)from); - to_prop = enc_canon_props((char_u *)to); + from_prop = enc_canon_props(from); + to_prop = enc_canon_props(to); if (from_unicode_is_utf8) { from_is_utf8 = from_prop & ENC_UNICODE; } else { @@ -2444,18 +2446,15 @@ int convert_setup_ext(vimconv_T *vcp, char *from, bool from_unicode_is_utf8, cha } else if (from_is_utf8 && (to_prop & ENC_LATIN9)) { // Internal utf-8 -> latin9 conversion. vcp->vc_type = CONV_TO_LATIN9; - } -#ifdef HAVE_ICONV - else { // NOLINT(readability/braces) + } else { // Use iconv() for conversion. - vcp->vc_fd = (iconv_t)my_iconv_open(to_is_utf8 ? (char_u *)"utf-8" : (char_u *)to, - from_is_utf8 ? (char_u *)"utf-8" : (char_u *)from); + vcp->vc_fd = (iconv_t)my_iconv_open(to_is_utf8 ? "utf-8" : to, + from_is_utf8 ? "utf-8" : from); if (vcp->vc_fd != (iconv_t)-1) { vcp->vc_type = CONV_ICONV; vcp->vc_factor = 4; // could be longer too... } } -#endif if (vcp->vc_type == CONV_NONE) { return FAIL; } @@ -2470,14 +2469,13 @@ int convert_setup_ext(vimconv_T *vcp, char *from, bool from_unicode_is_utf8, cha /// When something goes wrong, NULL is returned and "*lenp" is unchanged. char *string_convert(const vimconv_T *const vcp, char *ptr, size_t *lenp) { - return (char *)string_convert_ext(vcp, (char_u *)ptr, lenp, NULL); + return string_convert_ext(vcp, ptr, lenp, NULL); } // Like string_convert(), but when "unconvlenp" is not NULL and there are is // an incomplete sequence at the end it is not converted and "*unconvlenp" is // set to the number of remaining bytes. -char_u *string_convert_ext(const vimconv_T *const vcp, char_u *ptr, size_t *lenp, - size_t *unconvlenp) +char *string_convert_ext(const vimconv_T *const vcp, char *ptr, size_t *lenp, size_t *unconvlenp) { char_u *retval = NULL; char_u *d; @@ -2486,12 +2484,12 @@ char_u *string_convert_ext(const vimconv_T *const vcp, char_u *ptr, size_t *lenp size_t len; if (lenp == NULL) { - len = STRLEN(ptr); + len = strlen(ptr); } else { len = *lenp; } if (len == 0) { - return (char_u *)xstrdup(""); + return xstrdup(""); } switch (vcp->vc_type) { @@ -2499,7 +2497,7 @@ char_u *string_convert_ext(const vimconv_T *const vcp, char_u *ptr, size_t *lenp retval = xmalloc(len * 2 + 1); d = retval; for (size_t i = 0; i < len; i++) { - c = ptr[i]; + c = (uint8_t)ptr[i]; if (c < 0x80) { *d++ = (char_u)c; } else { @@ -2517,7 +2515,7 @@ char_u *string_convert_ext(const vimconv_T *const vcp, char_u *ptr, size_t *lenp retval = xmalloc(len * 3 + 1); d = retval; for (size_t i = 0; i < len; i++) { - c = ptr[i]; + c = (uint8_t)ptr[i]; switch (c) { case 0xa4: c = 0x20ac; break; // euro @@ -2553,7 +2551,7 @@ char_u *string_convert_ext(const vimconv_T *const vcp, char_u *ptr, size_t *lenp if (l == 0) { *d++ = NUL; } else if (l == 1) { - uint8_t l_w = utf8len_tab_zero[ptr[i]]; + uint8_t l_w = utf8len_tab_zero[(uint8_t)ptr[i]]; if (l_w == 0) { // Illegal utf-8 byte cannot be converted @@ -2565,9 +2563,9 @@ char_u *string_convert_ext(const vimconv_T *const vcp, char_u *ptr, size_t *lenp *unconvlenp = len - i; break; } - *d++ = ptr[i]; + *d++ = (uint8_t)ptr[i]; } else { - c = utf_ptr2char((char *)ptr + i); + c = utf_ptr2char(ptr + i); if (vcp->vc_type == CONV_TO_LATIN9) { switch (c) { case 0x20ac: @@ -2619,14 +2617,12 @@ char_u *string_convert_ext(const vimconv_T *const vcp, char_u *ptr, size_t *lenp } break; -#ifdef HAVE_ICONV case CONV_ICONV: // conversion with vcp->vc_fd - retval = iconv_string(vcp, ptr, len, unconvlenp, lenp); + retval = (char_u *)iconv_string(vcp, ptr, len, unconvlenp, lenp); break; -#endif } - return retval; + return (char *)retval; } /// Table set by setcellwidths(). @@ -2705,7 +2701,7 @@ void f_setcellwidths(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (li_tv->v_type != VAR_LIST || li_tv->vval.v_list == NULL) { semsg(_(e_list_item_nr_is_not_list), item); - xfree(ptrs); + xfree((void *)ptrs); return; } @@ -2721,25 +2717,25 @@ void f_setcellwidths(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } if (i == 0) { n1 = lili_tv->vval.v_number; - if (n1 < 0x100) { - emsg(_(e_only_values_of_0x100_and_higher_supported)); - xfree(ptrs); + if (n1 < 0x80) { + emsg(_(e_only_values_of_0x80_and_higher_supported)); + xfree((void *)ptrs); return; } } else if (i == 1 && lili_tv->vval.v_number < n1) { semsg(_(e_list_item_nr_range_invalid), item); - xfree(ptrs); + xfree((void *)ptrs); return; } else if (i == 2 && (lili_tv->vval.v_number < 1 || lili_tv->vval.v_number > 2)) { semsg(_(e_list_item_nr_cell_width_invalid), item); - xfree(ptrs); + xfree((void *)ptrs); return; } } if (i != 3) { semsg(_(e_list_item_nr_does_not_contain_3_numbers), item); - xfree(ptrs); + xfree((void *)ptrs); return; } @@ -2758,7 +2754,7 @@ void f_setcellwidths(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) const varnumber_T n1 = TV_LIST_ITEM_TV(lili)->vval.v_number; if (item > 0 && n1 <= table[item - 1].last) { semsg(_(e_overlapping_ranges_for_nr), (long)n1); - xfree(ptrs); + xfree((void *)ptrs); xfree(table); return; } @@ -2769,7 +2765,7 @@ void f_setcellwidths(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) table[item].width = (char)TV_LIST_ITEM_TV(lili)->vval.v_number; } - xfree(ptrs); + xfree((void *)ptrs); cw_interval_T *const cw_table_save = cw_table; const size_t cw_table_size_save = cw_table_size; @@ -2791,11 +2787,26 @@ void f_setcellwidths(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) redraw_all_later(UPD_NOT_VALID); } +/// "getcellwidths()" function +void f_getcellwidths(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tv_list_alloc_ret(rettv, (ptrdiff_t)cw_table_size); + + for (size_t i = 0; i < cw_table_size; i++) { + list_T *entry = tv_list_alloc(3); + tv_list_append_number(entry, (varnumber_T)cw_table[i].first); + tv_list_append_number(entry, (varnumber_T)cw_table[i].last); + tv_list_append_number(entry, (varnumber_T)cw_table[i].width); + + tv_list_append_list(rettv->vval.v_list, entry); + } +} + void f_charclass(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (tv_check_for_string_arg(argvars, 0) == FAIL || argvars[0].vval.v_string == NULL) { return; } - rettv->vval.v_number = mb_get_class((const char_u *)argvars[0].vval.v_string); + rettv->vval.v_number = mb_get_class(argvars[0].vval.v_string); } diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h index b499f33cc6..780f33e05b 100644 --- a/src/nvim/mbyte.h +++ b/src/nvim/mbyte.h @@ -8,8 +8,8 @@ #include "nvim/eval/typval.h" #include "nvim/func_attr.h" #include "nvim/mbyte_defs.h" -#include "nvim/os/os_defs.h" // For indirect -#include "nvim/types.h" // for char_u +#include "nvim/os/os_defs.h" +#include "nvim/types.h" // Return byte length of character that starts with byte "b". // Returns 1 for a single-byte character. diff --git a/src/nvim/mbyte_defs.h b/src/nvim/mbyte_defs.h index 53b01a211f..e913e20f9f 100644 --- a/src/nvim/mbyte_defs.h +++ b/src/nvim/mbyte_defs.h @@ -46,9 +46,7 @@ typedef enum { typedef struct { int vc_type; ///< Zero or more ConvFlags. int vc_factor; ///< Maximal expansion factor. -#ifdef HAVE_ICONV iconv_t vc_fd; ///< Value for CONV_ICONV. -#endif bool vc_fail; ///< What to do with invalid characters: if true, fail, ///< otherwise use '?'. } vimconv_T; diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c index bb9be0766e..46be9ccea5 100644 --- a/src/nvim/memfile.c +++ b/src/nvim/memfile.c @@ -43,19 +43,25 @@ #include <inttypes.h> #include <limits.h> #include <stdbool.h> +#include <stdio.h> #include <string.h> -#include "nvim/ascii.h" #include "nvim/assert.h" +#include "nvim/buffer_defs.h" #include "nvim/fileio.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/macros.h" #include "nvim/memfile.h" +#include "nvim/memfile_defs.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/os/fs_defs.h" #include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os_unix.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/vim.h" #define MEMFILE_PAGE_SIZE 4096 /// default page size @@ -752,7 +758,7 @@ void mf_free_fnames(memfile_T *mfp) void mf_set_fnames(memfile_T *mfp, char *fname) { mfp->mf_fname = fname; - mfp->mf_ffname = (char_u *)FullName_save(mfp->mf_fname, false); + mfp->mf_ffname = FullName_save(mfp->mf_fname, false); } /// Make name of memfile's swapfile a full path. @@ -760,11 +766,13 @@ void mf_set_fnames(memfile_T *mfp, char *fname) /// Used before doing a :cd void mf_fullname(memfile_T *mfp) { - if (mfp != NULL && mfp->mf_fname != NULL && mfp->mf_ffname != NULL) { - xfree(mfp->mf_fname); - mfp->mf_fname = (char *)mfp->mf_ffname; - mfp->mf_ffname = NULL; + if (mfp == NULL || mfp->mf_fname == NULL || mfp->mf_ffname == NULL) { + return; } + + xfree(mfp->mf_fname); + mfp->mf_fname = mfp->mf_ffname; + mfp->mf_ffname = NULL; } /// Return true if there are any translations pending for memfile. @@ -815,8 +823,10 @@ static bool mf_do_open(memfile_T *mfp, char *fname, int flags) /// The number of buckets in the hashtable is increased by a factor of /// MHT_GROWTH_FACTOR when the average number of items per bucket /// exceeds 2 ^ MHT_LOG_LOAD_FACTOR. -#define MHT_LOG_LOAD_FACTOR 6 -#define MHT_GROWTH_FACTOR 2 // must be a power of two +enum { + MHT_LOG_LOAD_FACTOR = 6, + MHT_GROWTH_FACTOR = 2, // must be a power of two +}; /// Initialize an empty hash table. static void mf_hash_init(mf_hashtab_T *mht) diff --git a/src/nvim/memfile_defs.h b/src/nvim/memfile_defs.h index d1e8fd0fb4..53152c28f8 100644 --- a/src/nvim/memfile_defs.h +++ b/src/nvim/memfile_defs.h @@ -89,7 +89,7 @@ typedef struct mf_blocknr_trans_item { /// A memory file. typedef struct memfile { char *mf_fname; /// name of the file - char_u *mf_ffname; /// idem, full path + char *mf_ffname; /// idem, full path int mf_fd; /// file descriptor bhdr_T *mf_free_first; /// first block header in free list bhdr_T *mf_used_first; /// mru block header in used list diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 56342942db..10a8195e7a 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -39,19 +39,31 @@ #include <fcntl.h> #include <inttypes.h> #include <stdbool.h> +#include <stdio.h> #include <string.h> +#include <time.h> +#include <uv.h> +#include "auto/config.h" +#include "klib/kvec.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/change.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/fileio.h" -#include "nvim/func_attr.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" #include "nvim/input.h" +#include "nvim/macros.h" #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -60,19 +72,21 @@ #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/option.h" +#include "nvim/os/fs.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/process.h" -#include "nvim/os_unix.h" +#include "nvim/os/time.h" #include "nvim/path.h" -#include "nvim/sha256.h" +#include "nvim/pos.h" +#include "nvim/screen.h" #include "nvim/spell.h" #include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/version.h" #include "nvim/vim.h" -#include "nvim/window.h" #ifndef UNIX // it's in os/unix_defs.h for Unix # include <time.h> @@ -83,10 +97,12 @@ typedef struct pointer_block PTR_BL; // contents of a pointer block typedef struct data_block DATA_BL; // contents of a data block typedef struct pointer_entry PTR_EN; // block/line-count pair -#define DATA_ID (('d' << 8) + 'a') // data block id -#define PTR_ID (('p' << 8) + 't') // pointer block id -#define BLOCK0_ID0 'b' // block 0 id 0 -#define BLOCK0_ID1 '0' // block 0 id 1 +enum { + DATA_ID = (('d' << 8) + 'a'), // data block id + PTR_ID = (('p' << 8) + 't'), // pointer block id + BLOCK0_ID0 = 'b', // block 0 id 0 + BLOCK0_ID1 = '0', // block 0 id 1 +}; // pointer to a block, used in a pointer block struct pointer_entry { @@ -115,7 +131,8 @@ struct data_block { unsigned db_free; // free space available unsigned db_txt_start; // byte where text starts unsigned db_txt_end; // byte just after data block - linenr_T db_line_count; // number of lines in this block + // linenr_T db_line_count; + long db_line_count; // number of lines in this block unsigned db_index[1]; // index for start of line (actually bigger) // followed by empty space up to db_txt_start // followed by the text in the lines until @@ -134,18 +151,22 @@ struct data_block { #define INDEX_SIZE (sizeof(unsigned)) // size of one db_index entry #define HEADER_SIZE (sizeof(DATA_BL) - INDEX_SIZE) // size of data block header -#define B0_FNAME_SIZE_ORG 900 // what it was in older versions -#define B0_FNAME_SIZE_NOCRYPT 898 // 2 bytes used for other things -#define B0_FNAME_SIZE_CRYPT 890 // 10 bytes used for other things -#define B0_UNAME_SIZE 40 -#define B0_HNAME_SIZE 40 +enum { + B0_FNAME_SIZE_ORG = 900, // what it was in older versions + B0_FNAME_SIZE_NOCRYPT = 898, // 2 bytes used for other things + B0_FNAME_SIZE_CRYPT = 890, // 10 bytes used for other things + B0_UNAME_SIZE = 40, + B0_HNAME_SIZE = 40, +}; // Restrict the numbers to 32 bits, otherwise most compilers will complain. // This won't detect a 64 bit machine that only swaps a byte in the top 32 // bits, but that is crazy anyway. -#define B0_MAGIC_LONG 0x30313233L -#define B0_MAGIC_INT 0x20212223L -#define B0_MAGIC_SHORT 0x10111213L -#define B0_MAGIC_CHAR 0x55 +enum { + B0_MAGIC_LONG = 0x30313233L, + B0_MAGIC_INT = 0x20212223L, + B0_MAGIC_SHORT = 0x10111213L, + B0_MAGIC_CHAR = 0x55, +}; // Block zero holds all info about the swap file. // @@ -158,19 +179,19 @@ struct data_block { // different machines. b0_magic_* is used to check the byte order and size of // variables, because the rest of the swap file is not portable. struct block0 { - char_u b0_id[2]; ///< ID for block 0: BLOCK0_ID0 and BLOCK0_ID1. - char_u b0_version[10]; // Vim version string - char_u b0_page_size[4]; // number of bytes per page - char_u b0_mtime[4]; // last modification time of file - char_u b0_ino[4]; // inode of b0_fname - char_u b0_pid[4]; // process id of creator (or 0) - char_u b0_uname[B0_UNAME_SIZE]; // name of user (uid if no name) - char_u b0_hname[B0_HNAME_SIZE]; // host name (if it has a name) - char_u b0_fname[B0_FNAME_SIZE_ORG]; // name of file being edited - long b0_magic_long; // check for byte order of long - int b0_magic_int; // check for byte order of int - short b0_magic_short; // check for byte order of short - char_u b0_magic_char; // check for last char + char_u b0_id[2]; ///< ID for block 0: BLOCK0_ID0 and BLOCK0_ID1. + char b0_version[10]; // Vim version string + char_u b0_page_size[4]; // number of bytes per page + char_u b0_mtime[4]; // last modification time of file + char_u b0_ino[4]; // inode of b0_fname + char_u b0_pid[4]; // process id of creator (or 0) + char_u b0_uname[B0_UNAME_SIZE]; // name of user (uid if no name) + char_u b0_hname[B0_HNAME_SIZE]; // host name (if it has a name) + char b0_fname[B0_FNAME_SIZE_ORG]; // name of file being edited + long b0_magic_long; // check for byte order of long + int b0_magic_int; // check for byte order of int + int16_t b0_magic_short; // check for byte order of short + char_u b0_magic_char; // check for last char }; // Note: b0_dirty and b0_flags are put at the end of the file name. For very @@ -205,10 +226,12 @@ struct block0 { static linenr_T lowest_marked = 0; // arguments for ml_find_line() -#define ML_DELETE 0x11 // delete line -#define ML_INSERT 0x12 // insert line -#define ML_FIND 0x13 // just find the line -#define ML_FLUSH 0x02 // flush locked block +enum { + ML_DELETE = 0x11, // delete line + ML_INSERT = 0x12, // insert line + ML_FIND = 0x13, // just find the line + ML_FLUSH = 0x02, // flush locked block +}; #define ML_SIMPLE(x) ((x) & 0x10) // DEL, INS or FIND // argument for ml_upd_block0() @@ -258,7 +281,6 @@ int ml_open(buf_T *buf) buf->b_ml.ml_mfp = mfp; buf->b_ml.ml_flags = ML_EMPTY; buf->b_ml.ml_line_count = 1; - curwin->w_nrwidth_line_count = 0; // fill block0 struct and write page 0 hp = mf_new(mfp, false, 1); @@ -271,15 +293,15 @@ int ml_open(buf_T *buf) b0p->b0_id[0] = BLOCK0_ID0; b0p->b0_id[1] = BLOCK0_ID1; b0p->b0_magic_long = B0_MAGIC_LONG; - b0p->b0_magic_int = (int)B0_MAGIC_INT; - b0p->b0_magic_short = (short)B0_MAGIC_SHORT; + b0p->b0_magic_int = B0_MAGIC_INT; + b0p->b0_magic_short = (int16_t)B0_MAGIC_SHORT; b0p->b0_magic_char = B0_MAGIC_CHAR; - xstrlcpy(xstpcpy((char *)b0p->b0_version, "VIM "), Version, 6); + xstrlcpy(xstpcpy(b0p->b0_version, "VIM "), Version, 6); long_to_char((long)mfp->mf_page_size, b0p->b0_page_size); if (!buf->b_spell) { b0p->b0_dirty = buf->b_changed ? B0_DIRTY : 0; - b0p->b0_flags = (uint8_t)(get_fileformat(buf) + 1); + b0p->b0_flags = (char)(get_fileformat(buf) + 1); set_b0_fname(b0p, buf); (void)os_get_username((char *)b0p->b0_uname, B0_UNAME_SIZE); b0p->b0_uname[B0_UNAME_SIZE - 1] = NUL; @@ -437,7 +459,7 @@ void ml_open_file(buf_T *buf) if (buf->b_spell) { char *fname = vim_tempname(); if (fname != NULL) { - (void)mf_open_file(mfp, (char *)fname); // consumes fname! + (void)mf_open_file(mfp, fname); // consumes fname! } buf->b_may_swap = false; return; @@ -616,9 +638,9 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf) // If there is no user name or it is too long, don't use "~/" int retval = os_get_username(uname, B0_UNAME_SIZE); size_t ulen = strlen(uname); - size_t flen = STRLEN(b0p->b0_fname); + size_t flen = strlen(b0p->b0_fname); if (retval == FAIL || ulen + flen > B0_FNAME_SIZE_CRYPT - 1) { - STRLCPY(b0p->b0_fname, buf->b_ffname, B0_FNAME_SIZE_CRYPT); + xstrlcpy(b0p->b0_fname, buf->b_ffname, B0_FNAME_SIZE_CRYPT); } else { memmove(b0p->b0_fname + ulen + 1, b0p->b0_fname + 1, flen); memmove(b0p->b0_fname + 1, uname, ulen); @@ -653,10 +675,10 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf) /// not set. static void set_b0_dir_flag(ZERO_BL *b0p, buf_T *buf) { - if (same_directory((char_u *)buf->b_ml.ml_mfp->mf_fname, (char_u *)buf->b_ffname)) { + if (same_directory(buf->b_ml.ml_mfp->mf_fname, buf->b_ffname)) { b0p->b0_flags |= B0_SAME_DIR; } else { - b0p->b0_flags &= (uint8_t) ~B0_SAME_DIR; + b0p->b0_flags = (char)(b0p->b0_flags & ~B0_SAME_DIR); } } @@ -666,8 +688,8 @@ static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf) const int size = B0_FNAME_SIZE_NOCRYPT; int n = (int)strlen(buf->b_p_fenc); - if ((int)STRLEN(b0p->b0_fname) + n + 1 > size) { - b0p->b0_flags &= (uint8_t) ~B0_HAS_FENC; + if ((int)strlen(b0p->b0_fname) + n + 1 > size) { + b0p->b0_flags = (char)(b0p->b0_flags & ~B0_HAS_FENC); } else { memmove((char *)b0p->b0_fname + size - n, buf->b_p_fenc, (size_t)n); @@ -676,6 +698,23 @@ static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf) } } +/// Return true if the process with number "b0p->b0_pid" is still running. +/// "swap_fname" is the name of the swap file, if it's from before a reboot then +/// the result is false; +static bool swapfile_process_running(const ZERO_BL *b0p, const char *swap_fname) +{ + FileInfo st; + double uptime; + // If the system rebooted after when the swap file was written then the + // process can't be running now. + if (os_fileinfo(swap_fname, &st) + && uv_uptime(&uptime) == 0 + && (Timestamp)st.stat.st_mtim.tv_sec < os_time() - (Timestamp)uptime) { + return false; + } + return os_proc_running((int)char_to_long(b0p->b0_pid)); +} + /// Try to recover curbuf from the .swp file. /// /// @param checkext if true, check the extension and detect whether it is a @@ -710,7 +749,7 @@ void ml_recover(bool checkext) int len = (int)strlen(fname); if (checkext && len >= 4 && STRNICMP(fname + len - 4, ".s", 2) == 0 - && vim_strchr("abcdefghijklmnopqrstuvw", TOLOWER_ASC(fname[len - 2])) != NULL + && vim_strchr("abcdefghijklmnopqrstuvw", TOLOWER_ASC((uint8_t)fname[len - 2])) != NULL && ASCII_ISALPHA(fname[len - 1])) { directly = true; fname_used = xstrdup(fname); // make a copy for mf_open() @@ -718,7 +757,7 @@ void ml_recover(bool checkext) directly = false; // count the number of matching swap files - len = recover_names((char_u *)fname, false, 0, NULL); + len = recover_names(fname, false, 0, NULL); if (len == 0) { // no swap files found semsg(_("E305: No swap file found for %s"), fname); goto theend; @@ -728,7 +767,7 @@ void ml_recover(bool checkext) i = 1; } else { // several swap files found, choose // list the names of the swap files - (void)recover_names((char_u *)fname, true, 0, NULL); + (void)recover_names(fname, true, 0, NULL); msg_putchar('\n'); msg_puts(_("Enter number of swap file to use (0 to quit): ")); i = get_number(false, NULL); @@ -737,7 +776,7 @@ void ml_recover(bool checkext) } } // get the swap file name that will be used - (void)recover_names((char_u *)fname, false, i, &fname_used); + (void)recover_names(fname, false, i, &fname_used); } if (fname_used == NULL) { goto theend; // user chose invalid number. @@ -788,7 +827,7 @@ void ml_recover(bool checkext) goto theend; } b0p = hp->bh_data; - if (STRNCMP(b0p->b0_version, "VIM 3.0", 7) == 0) { + if (strncmp(b0p->b0_version, "VIM 3.0", 7) == 0) { msg_start(); msg_outtrans_attr(mfp->mf_fname, MSG_HIST); msg_puts_attr(_(" cannot be used with this version of Vim.\n"), @@ -848,18 +887,18 @@ void ml_recover(bool checkext) // If .swp file name given directly, use name from swap file for buffer. if (directly) { expand_env((char *)b0p->b0_fname, NameBuff, MAXPATHL); - if (setfname(curbuf, (char *)NameBuff, NULL, true) == FAIL) { + if (setfname(curbuf, NameBuff, NULL, true) == FAIL) { goto theend; } } - home_replace(NULL, mfp->mf_fname, (char *)NameBuff, MAXPATHL, true); + home_replace(NULL, mfp->mf_fname, NameBuff, MAXPATHL, true); smsg(_("Using swap file \"%s\""), NameBuff); if (buf_spname(curbuf) != NULL) { - STRLCPY(NameBuff, buf_spname(curbuf), MAXPATHL); + xstrlcpy(NameBuff, buf_spname(curbuf), MAXPATHL); } else { - home_replace(NULL, curbuf->b_ffname, (char *)NameBuff, MAXPATHL, true); + home_replace(NULL, curbuf->b_ffname, NameBuff, MAXPATHL, true); } smsg(_("Original file \"%s\""), NameBuff); msg_putchar('\n'); @@ -883,8 +922,8 @@ void ml_recover(bool checkext) if (b0p->b0_flags & B0_HAS_FENC) { int fnsize = B0_FNAME_SIZE_NOCRYPT; - for (p = (char *)b0p->b0_fname + fnsize; (char_u *)p > b0p->b0_fname && p[-1] != NUL; p--) {} - b0_fenc = xstrnsave(p, (size_t)(b0p->b0_fname + fnsize - (char_u *)p)); + for (p = (char *)b0p->b0_fname + fnsize; p > b0p->b0_fname && p[-1] != NUL; p--) {} + b0_fenc = xstrnsave(p, (size_t)(b0p->b0_fname + fnsize - p)); } mf_put(mfp, hp, false, false); // release block 0 @@ -1116,7 +1155,14 @@ void ml_recover(bool checkext) } else { msg(_("Recovery completed. Buffer contents equals file contents.")); } - msg_puts(_("\nYou may want to delete the .swp file now.\n\n")); + msg_puts(_("\nYou may want to delete the .swp file now.")); + if (swapfile_process_running(b0p, fname_used)) { + // Warn there could be an active Vim on the same file, the user may + // want to kill it. + msg_puts(_("\nNote: process STILL RUNNING: ")); + msg_outnum(char_to_long(b0p->b0_pid)); + } + msg_puts("\n\n"); cmdline_row = msg_row; } redraw_curbuf_later(UPD_NOT_VALID); @@ -1155,24 +1201,24 @@ theend: /// @param list when true, list the swap file names /// @param nr when non-zero, return nr'th swap file name /// @param fname_out result when "nr" > 0 -int recover_names(char_u *fname, int list, int nr, char **fname_out) +int recover_names(char *fname, int list, int nr, char **fname_out) { int num_names; char *(names[6]); - char_u *tail; + char *tail; char *p; int file_count = 0; char **files; - char_u *fname_res = NULL; + char *fname_res = NULL; #ifdef HAVE_READLINK - char_u fname_buf[MAXPATHL]; + char fname_buf[MAXPATHL]; #endif if (fname != NULL) { #ifdef HAVE_READLINK // Expand symlink in the file name, because the swap file is created // with the actual file instead of with the symlink. - if (resolve_symlink((char *)fname, (char *)fname_buf) == OK) { + if (resolve_symlink(fname, fname_buf) == OK) { fname_res = fname_buf; } else #endif @@ -1193,7 +1239,7 @@ int recover_names(char_u *fname, int list, int nr, char **fname_out) // Isolate a directory name from *dirp and put it in dir_name (we know // it is large enough, so use 31000 for length). // Advance dirp to next directory name. - (void)copy_option_part(&dirp, (char *)dir_name, 31000, ","); + (void)copy_option_part(&dirp, dir_name, 31000, ","); if (dir_name[0] == '.' && dir_name[1] == NUL) { // check current dir if (fname == NULL) { @@ -1204,30 +1250,29 @@ int recover_names(char_u *fname, int list, int nr, char **fname_out) names[2] = xstrdup(".sw?"); num_names = 3; } else { - num_names = recov_file_names(names, (char *)fname_res, true); + num_names = recov_file_names(names, fname_res, true); } } else { // check directory dir_name if (fname == NULL) { - names[0] = concat_fnames((char *)dir_name, "*.sw?", true); + names[0] = concat_fnames(dir_name, "*.sw?", true); // For Unix names starting with a dot are special. MS-Windows // supports this too, on some file systems. - names[1] = concat_fnames((char *)dir_name, ".*.sw?", true); - names[2] = concat_fnames((char *)dir_name, ".sw?", true); + names[1] = concat_fnames(dir_name, ".*.sw?", true); + names[2] = concat_fnames(dir_name, ".sw?", true); num_names = 3; } else { - int len = (int)STRLEN(dir_name); + int len = (int)strlen(dir_name); p = dir_name + len; - if (after_pathsep((char *)dir_name, (char *)p) + if (after_pathsep(dir_name, p) && len > 1 && p[-1] == p[-2]) { // Ends with '//', Use Full path for swap name - tail = (char_u *)make_percent_swname((char *)dir_name, - (char *)fname_res); + tail = make_percent_swname(dir_name, fname_res); } else { - tail = (char_u *)path_tail((char *)fname_res); - tail = (char_u *)concat_fnames((char *)dir_name, (char *)tail, true); + tail = path_tail(fname_res); + tail = concat_fnames(dir_name, tail, true); } - num_names = recov_file_names(names, (char *)tail, false); + num_names = recov_file_names(names, tail, false); xfree(tail); } } @@ -1244,11 +1289,11 @@ int recover_names(char_u *fname, int list, int nr, char **fname_out) // not able to execute the shell). // Try finding a swap file by simply adding ".swp" to the file name. if (*dirp == NUL && file_count + num_files == 0 && fname != NULL) { - char_u *swapname = (char_u *)modname((char *)fname_res, ".swp", true); + char *swapname = modname(fname_res, ".swp", true); if (swapname != NULL) { - if (os_path_exists((char *)swapname)) { - files = xmalloc(sizeof(char_u *)); - files[0] = (char *)swapname; + if (os_path_exists(swapname)) { + files = xmalloc(sizeof(char *)); + files[0] = swapname; swapname = NULL; num_files = 1; } @@ -1262,7 +1307,7 @@ int recover_names(char_u *fname, int list, int nr, char **fname_out) for (int i = 0; i < num_files; i++) { // Do not expand wildcards, on Windows would try to expand // "%tmp%" in "%tmp%file" - if (path_full_compare((char *)p, files[i], true, false) & kEqualFiles) { + if (path_full_compare(p, files[i], true, false) & kEqualFiles) { // Remove the name from files[i]. Move further entries // down. When the array becomes empty free it here, since // FreeWild() won't be called below. @@ -1292,7 +1337,7 @@ int recover_names(char_u *fname, int list, int nr, char **fname_out) } } else { msg_puts(_(" In directory ")); - msg_home_replace((char *)dir_name); + msg_home_replace(dir_name); msg_puts(":\n"); } @@ -1303,7 +1348,7 @@ int recover_names(char_u *fname, int list, int nr, char **fname_out) msg_puts(". "); msg_puts((const char *)path_tail(files[i])); msg_putchar('\n'); - (void)swapfile_info((char_u *)files[i]); + (void)swapfile_info(files[i]); } } else { msg_puts(_(" -- none --\n")); @@ -1331,17 +1376,19 @@ char *make_percent_swname(const char *dir, const char *name) { char *d = NULL; char *f = fix_fname(name != NULL ? name : ""); - if (f != NULL) { - char *s = xstrdup(f); - for (d = s; *d != NUL; MB_PTR_ADV(d)) { - if (vim_ispathsep(*d)) { - *d = '%'; - } + if (f == NULL) { + return NULL; + } + + char *s = xstrdup(f); + for (d = s; *d != NUL; MB_PTR_ADV(d)) { + if (vim_ispathsep(*d)) { + *d = '%'; } - d = concat_fnames(dir, s, true); - xfree(s); - xfree(f); } + d = concat_fnames(dir, s, true); + xfree(s); + xfree(f); return d; } @@ -1363,7 +1410,7 @@ void get_b0_dict(const char *fname, dict_T *d) tv_dict_add_str(d, S_LEN("error"), "Magic number mismatch"); } else { // We have swap information. - tv_dict_add_str_len(d, S_LEN("version"), (char *)b0.b0_version, 10); + tv_dict_add_str_len(d, S_LEN("version"), b0.b0_version, 10); tv_dict_add_str_len(d, S_LEN("user"), (char *)b0.b0_uname, B0_UNAME_SIZE); tv_dict_add_str_len(d, S_LEN("host"), (char *)b0.b0_hname, @@ -1388,7 +1435,7 @@ void get_b0_dict(const char *fname, dict_T *d) /// Give information about an existing swap file. /// /// @return timestamp (0 when unknown). -static time_t swapfile_info(char_u *fname) +static time_t swapfile_info(char *fname) { assert(fname != NULL); int fd; @@ -1400,7 +1447,7 @@ static time_t swapfile_info(char_u *fname) // print the swap file date FileInfo file_info; - if (os_fileinfo((char *)fname, &file_info)) { + if (os_fileinfo(fname, &file_info)) { #ifdef UNIX // print name of owner of the file if (os_get_uname((uv_uid_t)file_info.stat.st_uid, uname, B0_UNAME_SIZE) == OK) { @@ -1414,15 +1461,15 @@ static time_t swapfile_info(char_u *fname) msg_puts(_(" dated: ")); #endif x = file_info.stat.st_mtim.tv_sec; - char ctime_buf[50]; - msg_puts(os_ctime_r(&x, ctime_buf, sizeof(ctime_buf))); + char ctime_buf[100]; // hopefully enough for every language + msg_puts(os_ctime_r(&x, ctime_buf, sizeof(ctime_buf), true)); } // print the original file name - fd = os_open((char *)fname, O_RDONLY, 0); + fd = os_open(fname, O_RDONLY, 0); if (fd >= 0) { if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) { - if (STRNCMP(b0.b0_version, "VIM 3.0", 7) == 0) { + if (strncmp(b0.b0_version, "VIM 3.0", 7) == 0) { msg_puts(_(" [from Vim version 3.0]")); } else if (ml_check_b0_id(&b0) == FAIL) { msg_puts(_(" [does not look like a Vim swap file]")); @@ -1456,7 +1503,7 @@ static time_t swapfile_info(char_u *fname) if (char_to_long(b0.b0_pid) != 0L) { msg_puts(_("\n process ID: ")); msg_outnum(char_to_long(b0.b0_pid)); - if (os_proc_running((int)char_to_long(b0.b0_pid))) { + if (swapfile_process_running(&b0, (const char *)fname)) { msg_puts(_(" (STILL RUNNING)")); process_still_running = true; } @@ -1511,14 +1558,27 @@ static bool swapfile_unchanged(char *fname) ret = false; } + // Host name must be known and must equal the current host name, otherwise + // comparing pid is meaningless. + if (*(b0.b0_hname) == NUL) { + ret = false; + } else { + char hostname[B0_HNAME_SIZE]; + os_get_hostname(hostname, B0_HNAME_SIZE); + hostname[B0_HNAME_SIZE - 1] = NUL; + b0.b0_hname[B0_HNAME_SIZE - 1] = NUL; // in case of corruption + if (STRICMP(b0.b0_hname, hostname) != 0) { + ret = false; + } + } + // process must be known and not running. - long pid = char_to_long(b0.b0_pid); - if (pid == 0L || os_proc_running((int)pid)) { + if (char_to_long(b0.b0_pid) == 0L || swapfile_process_running(&b0, fname)) { ret = false; } - // TODO(bram): Should we check if the swap file was created on the current - // system? And the current user? + // We do not check the user, it should be irrelevant for whether the swap + // file is still useful. close(fd); return ret; @@ -1543,7 +1603,7 @@ static int recov_file_names(char **names, char *path, int prepend_dot) names[num_names] = concat_fnames(path, ".sw?", false); if (num_names >= 1) { // check if we have the same name twice char *p = names[num_names - 1]; - int i = (int)STRLEN(names[num_names - 1]) - (int)STRLEN(names[num_names]); + int i = (int)strlen(names[num_names - 1]) - (int)strlen(names[num_names]); if (i > 0) { p += i; // file name has been expanded to full path } @@ -1685,10 +1745,10 @@ char *ml_get(linenr_T lnum) } /// @return pointer to position "pos". -char_u *ml_get_pos(const pos_T *pos) +char *ml_get_pos(const pos_T *pos) FUNC_ATTR_NONNULL_ALL { - return (char_u *)ml_get_buf(curbuf, pos->lnum, false) + pos->col; + return ml_get_buf(curbuf, pos->lnum, false) + pos->col; } /// @return codepoint at pos. pos must be either valid or have col set to MAXCOL! @@ -1699,7 +1759,7 @@ int gchar_pos(pos_T *pos) if (pos->col == MAXCOL) { return NUL; } - return utf_ptr2char((char *)ml_get_pos(pos)); + return utf_ptr2char(ml_get_pos(pos)); } /// @param will_change true mark the buffer dirty (chars in the line will be changed) @@ -1762,7 +1822,7 @@ errorret: DATA_BL *dp = hp->bh_data; char *ptr = (char *)dp + (dp->db_index[lnum - buf->b_ml.ml_locked_low] & DB_INDEX_MASK); - buf->b_ml.ml_line_ptr = (char_u *)ptr; + buf->b_ml.ml_line_ptr = ptr; buf->b_ml.ml_line_lnum = lnum; buf->b_ml.ml_flags &= ~ML_LINE_DIRTY; } @@ -1771,7 +1831,7 @@ errorret: ml_add_deleted_len_buf(buf, buf->b_ml.ml_line_ptr, -1); } - return (char *)buf->b_ml.ml_line_ptr; + return buf->b_ml.ml_line_ptr; } /// Check if a line that was just obtained by a call to ml_get @@ -1806,7 +1866,7 @@ int ml_append(linenr_T lnum, char *line, colnr_T len, bool newfile) if (curbuf->b_ml.ml_line_lnum != 0) { ml_flush_line(curbuf); } - return ml_append_int(curbuf, lnum, (char_u *)line, len, newfile, false); + return ml_append_int(curbuf, lnum, line, len, newfile, false); } /// Like ml_append() but for an arbitrary buffer. The buffer must already have @@ -1816,7 +1876,7 @@ int ml_append(linenr_T lnum, char *line, colnr_T len, bool newfile) /// @param line text of the new line /// @param len length of new line, including NUL, or 0 /// @param newfile flag, see above -int ml_append_buf(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, bool newfile) +int ml_append_buf(buf_T *buf, linenr_T lnum, char *line, colnr_T len, bool newfile) FUNC_ATTR_NONNULL_ARG(1) { if (buf->b_ml.ml_mfp == NULL) { @@ -1834,8 +1894,7 @@ int ml_append_buf(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, bool new /// @param len length of line, including NUL, or 0 /// @param newfile flag, see above /// @param mark mark the new line -static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, bool newfile, - int mark) +static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, bool newfile, int mark) { // lnum out of range if (lnum > buf->b_ml.ml_line_count || buf->b_ml.ml_mfp == NULL) { @@ -1847,7 +1906,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b } if (len == 0) { - len = (colnr_T)STRLEN(line) + 1; // space needed for the text + len = (colnr_T)strlen(line) + 1; // space needed for the text } int space_needed = len + (int)INDEX_SIZE; // space needed for text + index @@ -2135,13 +2194,13 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b buf->b_ml.ml_stack_top = stack_idx + 1; // truncate stack if (lineadd) { - --(buf->b_ml.ml_stack_top); + (buf->b_ml.ml_stack_top)--; // fix line count for rest of blocks in the stack ml_lineadd(buf, lineadd); // fix stack itself buf->b_ml.ml_stack[buf->b_ml.ml_stack_top].ip_high += lineadd; - ++(buf->b_ml.ml_stack_top); + (buf->b_ml.ml_stack_top)++; } // We are finished, break the loop here. @@ -2244,15 +2303,15 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b void ml_add_deleted_len(char *ptr, ssize_t len) { - ml_add_deleted_len_buf(curbuf, (char_u *)ptr, len); + ml_add_deleted_len_buf(curbuf, ptr, len); } -void ml_add_deleted_len_buf(buf_T *buf, char_u *ptr, ssize_t len) +void ml_add_deleted_len_buf(buf_T *buf, char *ptr, ssize_t len) { if (inhibit_delete_count) { return; } - ssize_t maxlen = (ssize_t)STRLEN(ptr); + ssize_t maxlen = (ssize_t)strlen(ptr); if (len == -1 || len > maxlen) { len = maxlen; } @@ -2309,10 +2368,10 @@ int ml_replace_buf(buf_T *buf, linenr_T lnum, char *line, bool copy) } if (readlen && kv_size(buf->update_callbacks)) { - ml_add_deleted_len_buf(buf, (char_u *)ml_get_buf(buf, lnum, false), -1); + ml_add_deleted_len_buf(buf, ml_get_buf(buf, lnum, false), -1); } - buf->b_ml.ml_line_ptr = (char_u *)line; + buf->b_ml.ml_line_ptr = line; buf->b_ml.ml_line_lnum = lnum; buf->b_ml.ml_flags = (buf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY; @@ -2386,7 +2445,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) // Line should always have an NL char internally (represented as NUL), // even if 'noeol' is set. assert(line_size >= 1); - ml_add_deleted_len_buf(buf, (char_u *)dp + line_start, line_size - 1); + ml_add_deleted_len_buf(buf, (char *)dp + line_start, line_size - 1); // special case: If there is only one line in the data block it becomes empty. // Then we have to remove the entry, pointing to this data block, from the @@ -2428,7 +2487,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) buf->b_ml.ml_stack[buf->b_ml.ml_stack_top].ip_high += buf->b_ml.ml_locked_lineadd; } - ++(buf->b_ml.ml_stack_top); + (buf->b_ml.ml_stack_top)++; break; } @@ -2574,7 +2633,7 @@ static void ml_flush_line(buf_T *buf) buf->flush_count++; linenr_T lnum = buf->b_ml.ml_line_lnum; - char_u *new_line = buf->b_ml.ml_line_ptr; + char *new_line = buf->b_ml.ml_line_ptr; bhdr_T *hp = ml_find_line(buf, lnum, ML_FIND); if (hp == NULL) { @@ -2583,14 +2642,14 @@ static void ml_flush_line(buf_T *buf) DATA_BL *dp = hp->bh_data; int idx = lnum - buf->b_ml.ml_locked_low; int start = ((dp->db_index[idx]) & DB_INDEX_MASK); - char_u *old_line = (char_u *)dp + start; + char *old_line = (char *)dp + start; int old_len; if (idx == 0) { // line is last in block old_len = (int)dp->db_txt_end - start; } else { // text of previous line follows old_len = (int)(dp->db_index[idx - 1] & DB_INDEX_MASK) - start; } - colnr_T new_len = (colnr_T)STRLEN(new_line) + 1; + colnr_T new_len = (colnr_T)strlen(new_line) + 1; int extra = new_len - old_len; // negative if lines gets smaller // if new line fits in data block, replace directly @@ -2698,11 +2757,11 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) && buf->b_ml.ml_locked_high >= lnum) { // remember to update pointer blocks and stack later if (action == ML_INSERT) { - ++(buf->b_ml.ml_locked_lineadd); - ++(buf->b_ml.ml_locked_high); + (buf->b_ml.ml_locked_lineadd)++; + (buf->b_ml.ml_locked_high)++; } else if (action == ML_DELETE) { - --(buf->b_ml.ml_locked_lineadd); - --(buf->b_ml.ml_locked_high); + (buf->b_ml.ml_locked_lineadd)--; + (buf->b_ml.ml_locked_high)--; } return buf->b_ml.ml_locked; } @@ -2908,7 +2967,7 @@ int resolve_symlink(const char *fname, char *buf) } // Put the result so far in tmp[], starting with the original name. - STRLCPY(tmp, fname, MAXPATHL); + xstrlcpy(tmp, fname, MAXPATHL); for (;;) { // Limit symlink depth to 100, catch recursive loops. @@ -2940,11 +2999,11 @@ int resolve_symlink(const char *fname, char *buf) // If it's relative, build a new path based on the directory // portion of the filename (if any) and the path the symlink // points to. - if (path_is_absolute((char_u *)buf)) { + if (path_is_absolute(buf)) { STRCPY(tmp, buf); } else { - char_u *tail = (char_u *)path_tail(tmp); - if (STRLEN(tail) + strlen(buf) >= MAXPATHL) { + char *tail = path_tail(tmp); + if (strlen(tail) + strlen(buf) >= MAXPATHL) { return FAIL; } STRCPY(tail, buf); @@ -2961,41 +3020,41 @@ int resolve_symlink(const char *fname, char *buf) /// 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 *makeswapname(char *fname, char *ffname, buf_T *buf, char *dir_name) { - char_u *fname_res = fname; + char *fname_res = fname; #ifdef HAVE_READLINK - char_u fname_buf[MAXPATHL]; + char fname_buf[MAXPATHL]; // Expand symlink in the file name, so that we put the swap file with the // actual file instead of with the symlink. - if (resolve_symlink((char *)fname, (char *)fname_buf) == OK) { + if (resolve_symlink(fname, (char *)fname_buf) == OK) { fname_res = fname_buf; } #endif - int len = (int)STRLEN(dir_name); + int len = (int)strlen(dir_name); - char_u *s = dir_name + len; - if (after_pathsep((char *)dir_name, (char *)s) + char *s = dir_name + len; + if (after_pathsep(dir_name, s) && len > 1 && s[-1] == s[-2]) { // Ends with '//', Use Full path - char_u *r = NULL; - s = (char_u *)make_percent_swname((char *)dir_name, (char *)fname_res); + char *r = NULL; + s = make_percent_swname(dir_name, fname_res); if (s != NULL) { - r = (char_u *)modname((char *)s, ".swp", false); + r = modname(s, ".swp", false); xfree(s); } return r; } // Prepend a '.' to the swap file name for the current directory. - char_u *r = (char_u *)modname((char *)fname_res, ".swp", - dir_name[0] == '.' && dir_name[1] == NUL); + char *r = modname(fname_res, ".swp", + dir_name[0] == '.' && dir_name[1] == NUL); if (r == NULL) { // out of memory return NULL; } - s = (char_u *)get_file_in_dir((char *)r, (char *)dir_name); + s = get_file_in_dir(r, dir_name); xfree(r); return s; } @@ -3042,14 +3101,14 @@ char *get_file_in_dir(char *fname, char *dname) /// /// @param buf buffer being edited /// @param fname swap file name -static void attention_message(buf_T *buf, char_u *fname) +static void attention_message(buf_T *buf, char *fname) { assert(buf->b_fname != NULL); no_wait_return++; (void)emsg(_("E325: ATTENTION")); msg_puts(_("\nFound a swap file by the name \"")); - msg_home_replace((char *)fname); + msg_home_replace(fname); msg_puts("\"\n"); const time_t swap_mtime = swapfile_info(fname); msg_puts(_("While opening file \"")); @@ -3062,7 +3121,7 @@ static void attention_message(buf_T *buf, char_u *fname) msg_puts(_(" dated: ")); time_t x = file_info.stat.st_mtim.tv_sec; char ctime_buf[50]; - msg_puts(os_ctime_r(&x, ctime_buf, sizeof(ctime_buf))); + msg_puts(os_ctime_r(&x, ctime_buf, sizeof(ctime_buf), true)); if (swap_mtime != 0 && x > swap_mtime) { msg_puts(_(" NEWER than swap file!\n")); } @@ -3078,7 +3137,7 @@ static void attention_message(buf_T *buf, char_u *fname) msg_outtrans(buf->b_fname); msg_puts(_("\"\n to recover the changes (see \":help recovery\").\n")); msg_puts(_(" If you did this already, delete the swap file \"")); - msg_outtrans((char *)fname); + msg_outtrans(fname); msg_puts(_("\"\n to avoid this message.\n")); cmdline_row = msg_row; no_wait_return--; @@ -3094,9 +3153,9 @@ static void attention_message(buf_T *buf, char_u *fname) /// 4: delete it /// 5: quit /// 6: abort -static int do_swapexists(buf_T *buf, char_u *fname) +static int do_swapexists(buf_T *buf, char *fname) { - set_vim_var_string(VV_SWAPNAME, (char *)fname, -1); + set_vim_var_string(VV_SWAPNAME, fname, -1); set_vim_var_string(VV_SWAPCHOICE, NULL, -1); // Trigger SwapExists autocommands with <afile> set to the file being @@ -3161,8 +3220,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ (void)copy_option_part(dirp, dir_name, dir_len, ","); // we try different names until we find one that does not exist yet - char *fname = (char *)makeswapname((char_u *)buf_fname, (char_u *)buf->b_ffname, buf, - (char_u *)dir_name); + char *fname = makeswapname(buf_fname, buf->b_ffname, buf, dir_name); for (;;) { if (fname == NULL) { // must be out of memory @@ -3209,7 +3267,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ if (b0.b0_flags & B0_SAME_DIR) { if (path_fnamecmp(path_tail(buf->b_ffname), path_tail((char *)b0.b0_fname)) != 0 - || !same_directory((char_u *)fname, (char_u *)buf->b_ffname)) { + || !same_directory(fname, buf->b_ffname)) { // Symlinks may point to the same file even // when the name differs, need to check the // inode too. @@ -3254,12 +3312,12 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ if (choice == 0 && swap_exists_action != SEA_NONE && has_autocmd(EVENT_SWAPEXISTS, buf_fname, buf)) { - choice = do_swapexists(buf, (char_u *)fname); + choice = do_swapexists(buf, fname); } if (choice == 0) { // Show info about the existing swap file. - attention_message(buf, (char_u *)fname); + attention_message(buf, fname); // We don't want a 'q' typed at the more-prompt // interrupt loading a file. @@ -3375,8 +3433,8 @@ 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 != B0_MAGIC_LONG - || b0p->b0_magic_int != (int)B0_MAGIC_INT - || b0p->b0_magic_short != (short)B0_MAGIC_SHORT + || b0p->b0_magic_int != B0_MAGIC_INT + || b0p->b0_magic_short != (int16_t)B0_MAGIC_SHORT || b0p->b0_magic_char != B0_MAGIC_CHAR; } @@ -3485,7 +3543,7 @@ static void long_to_char(long n, char_u *s) s[3] = (char_u)(n & 0xff); } -static long char_to_long(char_u *s) +static long char_to_long(const char_u *s) { long retval; @@ -3516,7 +3574,7 @@ void ml_setflags(buf_T *buf) if (hp->bh_bnum == 0) { b0p = hp->bh_data; b0p->b0_dirty = buf->b_changed ? B0_DIRTY : 0; - b0p->b0_flags = (uint8_t)((b0p->b0_flags & ~B0_FF_MASK) | (uint8_t)(get_fileformat(buf) + 1)); + b0p->b0_flags = (char)((b0p->b0_flags & ~B0_FF_MASK) | (uint8_t)(get_fileformat(buf) + 1)); add_b0_fenc(b0p, buf); hp->bh_flags |= BH_DIRTY; mf_sync(buf->b_ml.ml_mfp, MFS_ZERO); @@ -3525,8 +3583,10 @@ 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 +enum { + MLCS_MAXL = 800, // max no of lines in chunk + MLCS_MINL = 400, // should be half of MLCS_MAXL +}; /// Keep information for finding byte offset of a line /// @@ -3566,7 +3626,7 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype) buf->b_ml.ml_usedchunks = 1; buf->b_ml.ml_chunksize[0].mlcs_numlines = 1; buf->b_ml.ml_chunksize[0].mlcs_totalsize = - (long)STRLEN(buf->b_ml.ml_line_ptr) + 1; + (long)strlen(buf->b_ml.ml_line_ptr) + 1; return; } @@ -3906,7 +3966,7 @@ int inc(pos_T *lp) { // when searching position may be set to end of a line if (lp->col != MAXCOL) { - const char_u *const p = ml_get_pos(lp); + const char *const p = ml_get_pos(lp); if (*p != NUL) { // still within line, move to next char (may be NUL) const int l = utfc_ptr2len((char *)p); diff --git a/src/nvim/memline.h b/src/nvim/memline.h index 441adf3e87..f4190f0210 100644 --- a/src/nvim/memline.h +++ b/src/nvim/memline.h @@ -1,8 +1,8 @@ #ifndef NVIM_MEMLINE_H #define NVIM_MEMLINE_H -#include "nvim/buffer_defs.h" // for buf_T -#include "nvim/pos.h" // for pos_T, linenr_T, colnr_T +#include "nvim/buffer_defs.h" +#include "nvim/pos.h" #include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/memline_defs.h b/src/nvim/memline_defs.h index 552ec1e3a9..6bb9255909 100644 --- a/src/nvim/memline_defs.h +++ b/src/nvim/memline_defs.h @@ -56,7 +56,7 @@ typedef struct memline { int ml_flags; linenr_T ml_line_lnum; // line number of cached line, 0 if not valid - char_u *ml_line_ptr; // pointer to cached line + char *ml_line_ptr; // pointer to cached line size_t ml_line_offset; // cached byte offset of ml_line_lnum int ml_line_offset_ff; // fileformat of cached line diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 61c43d8f99..5356300382 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -6,24 +6,32 @@ #include <assert.h> #include <inttypes.h> #include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> #include <string.h> +#include <time.h> #include "nvim/api/extmark.h" #include "nvim/arglist.h" +#include "nvim/ascii.h" +#include "nvim/buffer_updates.h" #include "nvim/context.h" #include "nvim/decoration_provider.h" #include "nvim/eval.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/insexpand.h" #include "nvim/lua/executor.h" +#include "nvim/main.h" #include "nvim/mapping.h" #include "nvim/memfile.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/sign.h" #include "nvim/ui.h" -#include "nvim/ui_compositor.h" +#include "nvim/usercmd.h" #include "nvim/vim.h" #ifdef UNIT_TESTING @@ -113,8 +121,8 @@ void *xmalloc(size_t size) { void *ret = try_malloc(size); if (!ret) { - mch_errmsg(e_outofmem); - mch_errmsg("\n"); + os_errmsg(e_outofmem); + os_errmsg("\n"); preserve_exit(); } return ret; @@ -144,8 +152,8 @@ void *xcalloc(size_t count, size_t size) try_to_free_memory(); ret = calloc(allocated_count, allocated_size); if (!ret) { - mch_errmsg(e_outofmem); - mch_errmsg("\n"); + os_errmsg(e_outofmem); + os_errmsg("\n"); preserve_exit(); } } @@ -166,8 +174,8 @@ void *xrealloc(void *ptr, size_t size) try_to_free_memory(); ret = realloc(ptr, allocated_size); if (!ret) { - mch_errmsg(e_outofmem); - mch_errmsg("\n"); + os_errmsg(e_outofmem); + os_errmsg("\n"); preserve_exit(); } } @@ -186,7 +194,7 @@ void *xmallocz(size_t size) { size_t total_size = size + 1; if (total_size < size) { - mch_errmsg(_("Vim: Data too large to fit into virtual memory space\n")); + os_errmsg(_("Vim: Data too large to fit into virtual memory space\n")); preserve_exit(); } @@ -438,9 +446,8 @@ char *xstrdupnul(const char *const str) { if (str == NULL) { return xmallocz(0); - } else { - return xstrdup(str); } + return xstrdup(str); } /// A version of memchr that starts the search at `src + len`. @@ -546,7 +553,7 @@ static void arena_free_reuse_blks(void) } } -/// Finnish the allocations in an arena. +/// Finish the allocations in an arena. /// /// This does not immediately free the memory, but leaves existing allocated /// objects valid, and returns an opaque ArenaMem handle, which can be used to @@ -576,41 +583,48 @@ void alloc_block(Arena *arena) blk->prev = prev_blk; } +static size_t arena_align_offset(uint64_t off) +{ + return ((off + (ARENA_ALIGN - 1)) & ~(ARENA_ALIGN - 1)); +} + /// @param arena if NULL, do a global allocation. caller must then free the value! -/// @param size if zero, will still return a non-null pointer, but not a unique one +/// @param size if zero, will still return a non-null pointer, but not a usable or unique one void *arena_alloc(Arena *arena, size_t size, bool align) { if (!arena) { return xmalloc(size); } - if (align) { - arena->pos = (arena->pos + (ARENA_ALIGN - 1)) & ~(ARENA_ALIGN - 1); + if (!arena->cur_blk) { + alloc_block(arena); } - if (arena->pos + size > arena->size || !arena->cur_blk) { + size_t alloc_pos = align ? arena_align_offset(arena->pos) : arena->pos; + if (alloc_pos + size > arena->size) { if (size > (ARENA_BLOCK_SIZE - sizeof(struct consumed_blk)) >> 1) { // if allocation is too big, allocate a large block with the requested // size, but still with block pointer head. We do this even for // arena->size / 2, as there likely is space left for the next // small allocation in the current block. - if (!arena->cur_blk) { - // to simplify free-list management, arena->cur_blk must - // always be a normal, ARENA_BLOCK_SIZE sized, block - alloc_block(arena); - } arena_alloc_count++; - char *alloc = xmalloc(size + sizeof(struct consumed_blk)); + size_t hdr_size = sizeof(struct consumed_blk); + size_t aligned_hdr_size = (align ? arena_align_offset(hdr_size) : hdr_size); + char *alloc = xmalloc(size + aligned_hdr_size); + + // to simplify free-list management, arena->cur_blk must + // always be a normal, ARENA_BLOCK_SIZE sized, block struct consumed_blk *cur_blk = (struct consumed_blk *)arena->cur_blk; struct consumed_blk *fix_blk = (struct consumed_blk *)alloc; fix_blk->prev = cur_blk->prev; cur_blk->prev = fix_blk; - return (alloc + sizeof(struct consumed_blk)); + return alloc + aligned_hdr_size; } else { - alloc_block(arena); + alloc_block(arena); // resets arena->pos + alloc_pos = align ? arena_align_offset(arena->pos) : arena->pos; } } - char *mem = arena->cur_blk + arena->pos; - arena->pos += size; + char *mem = arena->cur_blk + alloc_pos; + arena->pos = alloc_pos + size; return mem; } @@ -646,7 +660,6 @@ char *arena_memdupz(Arena *arena, const char *buf, size_t size) # include "nvim/autocmd.h" # include "nvim/buffer.h" -# include "nvim/charset.h" # include "nvim/cmdhist.h" # include "nvim/diff.h" # include "nvim/edit.h" @@ -655,23 +668,16 @@ char *arena_memdupz(Arena *arena, const char *buf, size_t size) # include "nvim/ex_docmd.h" # include "nvim/ex_getln.h" # include "nvim/file_search.h" -# include "nvim/fold.h" # include "nvim/getchar.h" # include "nvim/grid.h" # include "nvim/mark.h" -# include "nvim/mbyte.h" -# include "nvim/memline.h" -# include "nvim/move.h" # include "nvim/ops.h" # include "nvim/option.h" # include "nvim/os/os.h" -# include "nvim/os_unix.h" -# include "nvim/path.h" # include "nvim/quickfix.h" # include "nvim/regexp.h" # include "nvim/search.h" # include "nvim/spell.h" -# include "nvim/syntax.h" # include "nvim/tag.h" # include "nvim/window.h" @@ -806,6 +812,11 @@ void free_all_mem(void) bufref_T bufref; set_bufref(&bufref, buf); nextbuf = buf->b_next; + + // Since options (in addition to other stuff) have been freed above we need to ensure no + // callbacks are called, so free them before closing the buffer. + buf_free_callbacks(buf); + close_buffer(NULL, buf, DOBUF_WIPE, false, false); // Didn't work, try next one. buf = bufref_valid(&bufref) ? nextbuf : firstbuf; @@ -822,7 +833,6 @@ void free_all_mem(void) decor_free_all_mem(); ui_free_all_mem(); - ui_comp_free_all_mem(); nlua_free_all_mem(); // should be last, in case earlier free functions deallocates arenas diff --git a/src/nvim/memory.h b/src/nvim/memory.h index f407192331..5b9798dc0d 100644 --- a/src/nvim/memory.h +++ b/src/nvim/memory.h @@ -1,10 +1,10 @@ #ifndef NVIM_MEMORY_H #define NVIM_MEMORY_H -#include <stdbool.h> // for bool -#include <stddef.h> // for size_t -#include <stdint.h> // for uint8_t -#include <time.h> // for time_t +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <time.h> #include "nvim/macros.h" @@ -39,13 +39,13 @@ extern MemRealloc mem_realloc; extern bool entered_free_all_mem; #endif -EXTERN size_t arena_alloc_count INIT(=0); +EXTERN size_t arena_alloc_count INIT(= 0); typedef struct consumed_blk { struct consumed_blk *prev; } *ArenaMem; -#define ARENA_ALIGN sizeof(void *) +#define ARENA_ALIGN MAX(sizeof(void *), sizeof(double)) typedef struct { char *cur_blk; diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 57116170aa..2a18b08d8d 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -5,29 +5,41 @@ // GUI/Motif support by Robert Webb #include <assert.h> -#include <inttypes.h> +#include <stdbool.h> +#include <stdint.h> #include <string.h> #include "nvim/ascii.h" #include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" #include "nvim/keycodes.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/menu.h" +#include "nvim/menu_defs.h" #include "nvim/message.h" +#include "nvim/option_defs.h" #include "nvim/popupmenu.h" -#include "nvim/screen.h" +#include "nvim/pos.h" #include "nvim/state.h" #include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" +#include "nvim/undo_defs.h" #include "nvim/vim.h" -#include "nvim/window.h" #define MENUDEPTH 10 // maximum depth of menus @@ -45,7 +57,7 @@ static char e_nomenu[] = N_("E329: No menu \"%s\""); static bool menu_is_winbar(const char *const name) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - return (STRNCMP(name, "WinBar", 6) == 0); + return (strncmp(name, "WinBar", 6) == 0); } static vimmenu_T **get_root_menu(const char *const name) @@ -77,17 +89,17 @@ void ex_menu(exarg_T *eap) arg = eap->arg; for (;;) { - if (STRNCMP(arg, "<script>", 8) == 0) { + if (strncmp(arg, "<script>", 8) == 0) { noremap = REMAP_SCRIPT; arg = skipwhite(arg + 8); continue; } - if (STRNCMP(arg, "<silent>", 8) == 0) { + if (strncmp(arg, "<silent>", 8) == 0) { silent = true; arg = skipwhite(arg + 8); continue; } - if (STRNCMP(arg, "<special>", 9) == 0) { + if (strncmp(arg, "<special>", 9) == 0) { // Ignore obsolete "<special>" modifier. arg = skipwhite(arg + 9); continue; @@ -97,7 +109,7 @@ void ex_menu(exarg_T *eap) // Locate an optional "icon=filename" argument // TODO(nvim): Currently this is only parsed. Should expose it to UIs. - if (STRNCMP(arg, "icon=", 5) == 0) { + if (strncmp(arg, "icon=", 5) == 0) { arg += 5; while (*arg != NUL && *arg != ' ') { if (*arg == '\\') { @@ -140,10 +152,10 @@ void ex_menu(exarg_T *eap) pri_tab[MENUDEPTH] = -1; // mark end of the table // Check for "disable" or "enable" argument. - if (STRNCMP(arg, "enable", 6) == 0 && ascii_iswhite(arg[6])) { + if (strncmp(arg, "enable", 6) == 0 && ascii_iswhite(arg[6])) { enable = kTrue; arg = skipwhite(arg + 6); - } else if (STRNCMP(arg, "disable", 7) == 0 && ascii_iswhite(arg[7])) { + } else if (strncmp(arg, "disable", 7) == 0 && ascii_iswhite(arg[7])) { enable = kFalse; arg = skipwhite(arg + 7); } @@ -905,10 +917,10 @@ char *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char *arg, bool for } if (!ascii_iswhite(*p)) { - if (STRNCMP(arg, "enable", 6) == 0 + if (strncmp(arg, "enable", 6) == 0 && (arg[6] == NUL || ascii_iswhite(arg[6]))) { p = arg + 6; - } else if (STRNCMP(arg, "disable", 7) == 0 + } else if (strncmp(arg, "disable", 7) == 0 && (arg[7] == NUL || ascii_iswhite(arg[7]))) { p = arg + 7; } else { @@ -949,7 +961,7 @@ char *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char *arg, bool for if (after_dot > arg) { size_t path_len = (size_t)(after_dot - arg); path_name = xmalloc(path_len); - STRLCPY(path_name, arg, path_len); + xstrlcpy(path_name, arg, path_len); } name = path_name; while (name != NULL && *name) { @@ -1064,9 +1076,9 @@ char *get_menu_names(expand_T *xp, int idx) if (menu->modes & expand_modes) { if (menu->children != NULL) { if (should_advance) { - STRLCPY(tbuffer, menu->en_dname, TBUFFER_LEN); + xstrlcpy(tbuffer, menu->en_dname, TBUFFER_LEN); } else { - STRLCPY(tbuffer, menu->dname, TBUFFER_LEN); + xstrlcpy(tbuffer, menu->dname, TBUFFER_LEN); if (menu->en_dname == NULL) { should_advance = true; } @@ -1327,7 +1339,7 @@ static char *menu_text(const char *str, int *mnemonic, char **actext) break; } if (mnemonic != NULL && p[1] != '&') { - *mnemonic = (char_u)p[1]; + *mnemonic = (uint8_t)p[1]; } STRMOVE(p, p + 1); p = p + 1; @@ -1350,14 +1362,14 @@ bool menu_is_menubar(const char *const name) bool menu_is_popup(const char *const name) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - return STRNCMP(name, "PopUp", 5) == 0; + return strncmp(name, "PopUp", 5) == 0; } // Return true if "name" is a toolbar menu name. bool menu_is_toolbar(const char *const name) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - return STRNCMP(name, "ToolBar", 7) == 0; + return strncmp(name, "ToolBar", 7) == 0; } /// Return true if the name is a menu separator identifier: Starts and ends @@ -1432,15 +1444,17 @@ void show_popupmenu(void) vimmenu_T *menu; for (menu = root_menu; menu != NULL; menu = menu->next) { - if (STRNCMP("PopUp", menu->name, 5) == 0 && STRNCMP(menu->name + 5, mode, mode_len) == 0) { + if (strncmp("PopUp", menu->name, 5) == 0 && strncmp(menu->name + 5, mode, mode_len) == 0) { break; } } // Only show a popup when it is defined and has entries - if (menu != NULL && menu->children != NULL) { - pum_show_popupmenu(menu); + if (menu == NULL || menu->children == NULL) { + return; } + + pum_show_popupmenu(menu); } /// Execute "menu". Use by ":emenu" and the window toolbar. @@ -1520,7 +1534,7 @@ void execute_menu(const exarg_T *eap, vimmenu_T *menu, int mode_idx) ex_normal_busy++; if (save_current_state(&save_state)) { - exec_normal_cmd((char_u *)menu->strings[idx], menu->noremap[idx], + exec_normal_cmd(menu->strings[idx], menu->noremap[idx], menu->silent[idx]); } restore_current_state(&save_state); @@ -1716,7 +1730,7 @@ void ex_menutranslate(exarg_T *eap) } // ":menutrans clear": clear all translations. - if (STRNCMP(arg, "clear", 5) == 0 && ends_excmd(*skipwhite(arg + 5))) { + if (strncmp(arg, "clear", 5) == 0 && ends_excmd(*skipwhite(arg + 5))) { GA_DEEP_CLEAR(&menutrans_ga, menutrans_T, FREE_MENUTRANS); // Delete all "menutrans_" global variables. diff --git a/src/nvim/menu.h b/src/nvim/menu.h index be294a1831..32959cf35f 100644 --- a/src/nvim/menu.h +++ b/src/nvim/menu.h @@ -1,11 +1,11 @@ #ifndef NVIM_MENU_H #define NVIM_MENU_H -#include <stdbool.h> // for bool +#include <stdbool.h> -#include "nvim/ex_cmds_defs.h" // for exarg_T +#include "nvim/ex_cmds_defs.h" #include "nvim/menu_defs.h" -#include "nvim/types.h" // for char_u and expand_T +#include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "menu.h.generated.h" diff --git a/src/nvim/menu_defs.h b/src/nvim/menu_defs.h index 5fdb222bde..79b267ae49 100644 --- a/src/nvim/menu_defs.h +++ b/src/nvim/menu_defs.h @@ -1,7 +1,7 @@ #ifndef NVIM_MENU_DEFS_H #define NVIM_MENU_DEFS_H -#include <stdbool.h> // for bool +#include <stdbool.h> /// Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode /// \addtogroup MENU_INDEX diff --git a/src/nvim/message.c b/src/nvim/message.c index a90e675423..7f29b19031 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -7,41 +7,50 @@ #include <inttypes.h> #include <stdarg.h> #include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" -#include "nvim/assert.h" +#include "nvim/buffer_defs.h" +#include "nvim/channel.h" #include "nvim/charset.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" -#include "nvim/ex_docmd.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/event/defs.h" +#include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_eval.h" -#include "nvim/ex_getln.h" #include "nvim/fileio.h" -#include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/indent.h" #include "nvim/input.h" #include "nvim/keycodes.h" +#include "nvim/log.h" #include "nvim/main.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/mouse.h" -#include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os/time.h" +#include "nvim/pos.h" #include "nvim/regexp.h" #include "nvim/runtime.h" +#include "nvim/screen.h" #include "nvim/strings.h" -#include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" #include "nvim/vim.h" @@ -59,8 +68,10 @@ struct msgchunk_S { }; // Magic chars used in confirm dialog strings -#define DLG_BUTTON_SEP '\n' -#define DLG_HOTKEY_CHAR '&' +enum { + DLG_BUTTON_SEP = '\n', + DLG_HOTKEY_CHAR = '&', +}; static int confirm_msg_used = false; // displaying confirm_msg #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -128,7 +139,6 @@ static bool msg_ext_history_visible = false; static bool msg_ext_keep_after_cmdline = false; static int msg_grid_pos_at_flush = 0; -static int msg_grid_scroll_discount = 0; static void ui_ext_msg_set_pos(int row, bool scrolled) { @@ -195,7 +205,7 @@ void msg_grid_validate(void) msg_grid_set_pos(max_rows, false); } - if (msg_grid.chars && cmdline_row < msg_grid_pos) { + if (msg_grid.chars && !msg_scrolled && cmdline_row < msg_grid_pos) { // TODO(bfredl): this should already be the case, but fails in some // "batched" executions where compute_cmdrow() use stale positions or // something. @@ -204,7 +214,7 @@ void msg_grid_validate(void) } /// Displays the string 's' on the status line -/// When terminal not initialized (yet) mch_errmsg(..) is used. +/// When terminal not initialized (yet) os_errmsg(..) is used. /// /// @return true if wait_return() not called int msg(char *s) @@ -318,7 +328,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) || (*s != '<' && last_msg_hist != NULL && last_msg_hist->msg != NULL - && strcmp(s, last_msg_hist->msg))) { + && strcmp(s, last_msg_hist->msg) != 0)) { add_msg_hist(s, -1, attr, multiline); } @@ -481,10 +491,10 @@ int smsg(const char *s, ...) va_list arglist; va_start(arglist, s); - vim_vsnprintf((char *)IObuff, IOSIZE, s, arglist); + vim_vsnprintf(IObuff, IOSIZE, s, arglist); va_end(arglist); - return msg((char *)IObuff); + return msg(IObuff); } int smsg_attr(int attr, const char *s, ...) @@ -493,7 +503,7 @@ int smsg_attr(int attr, const char *s, ...) va_list arglist; va_start(arglist, s); - vim_vsnprintf((char *)IObuff, IOSIZE, s, arglist); + vim_vsnprintf(IObuff, IOSIZE, s, arglist); va_end(arglist); return msg_attr((const char *)IObuff, attr); } @@ -504,7 +514,7 @@ int smsg_attr_keep(int attr, const char *s, ...) va_list arglist; va_start(arglist, s); - vim_vsnprintf((char *)IObuff, IOSIZE, s, arglist); + vim_vsnprintf(IObuff, IOSIZE, s, arglist); va_end(arglist); return msg_attr_keep((const char *)IObuff, attr, true, false); } @@ -663,6 +673,13 @@ static bool emsg_multiline(const char *s, bool multiline) return true; } + if (in_assert_fails && emsg_assert_fails_msg == NULL) { + emsg_assert_fails_msg = xstrdup(s); + emsg_assert_fails_lnum = SOURCING_LNUM; + xfree(emsg_assert_fails_context); + emsg_assert_fails_context = xstrdup(SOURCING_NAME == NULL ? "" : SOURCING_NAME); + } + // set "v:errmsg", also when using ":silent! cmd" set_vim_var_string(VV_ERRMSG, s, -1); @@ -747,7 +764,7 @@ static bool emsg_multiline(const char *s, bool multiline) /// emsg() - display an error message /// /// Rings the bell, if appropriate, and calls message() to do the real work -/// When terminal not initialized (yet) mch_errmsg(..) is used. +/// When terminal not initialized (yet) os_errmsg(..) is used. /// /// @return true if wait_return() not called bool emsg(const char *s) @@ -864,10 +881,10 @@ void msg_schedule_semsg(const char *const fmt, ...) { va_list ap; va_start(ap, fmt); - vim_vsnprintf((char *)IObuff, IOSIZE, fmt, ap); + vim_vsnprintf(IObuff, IOSIZE, fmt, ap); va_end(ap); - char *s = xstrdup((char *)IObuff); + char *s = xstrdup(IObuff); loop_schedule_deferred(&main_loop, event_create(msg_semsg_event, 1, s)); } @@ -902,11 +919,13 @@ char *msg_trunc_attr(char *s, bool force, int attr) /// @note: May change the message by replacing a character with '<'. char *msg_may_trunc(bool force, char *s) { - int room; + if (ui_has(kUIMessages)) { + return s; + } - room = (Rows - cmdline_row - 1) * Columns + sc_col - 1; + int room = (Rows - cmdline_row - 1) * Columns + sc_col - 1; if ((force || (shortmess(SHM_TRUNC) && !exmode_active)) - && (int)STRLEN(s) - room > 0) { + && (int)strlen(s) - room > 0) { int size = vim_strsize(s); // There may be room anyway when there are multibyte chars. @@ -1513,7 +1532,7 @@ char *msg_outtrans_one(char *p, int attr) msg_outtrans_len_attr(p, l, attr); return p + l; } - msg_puts_attr((const char *)transchar_byte(*p), attr); + msg_puts_attr((const char *)transchar_byte((uint8_t)(*p)), attr); return p + 1; } @@ -2133,7 +2152,7 @@ static void msg_puts_display(const char *str, int maxlen, int attr, int recurse) msg_ext_last_attr = attr; } // Concat pieces with the same highlight - size_t len = STRNLEN(str, maxlen); // -V781 + size_t len = strnlen(str, (size_t)maxlen); // -V781 ga_concat_len(&msg_ext_last_chunk, (char *)str, len); msg_ext_cur_len += len; return; @@ -2436,6 +2455,7 @@ void msg_reset_scroll(void) } msg_scrolled = 0; msg_scrolled_at_flush = 0; + msg_grid_scroll_discount = 0; } /// Increment "msg_scrolled". @@ -2708,9 +2728,9 @@ static void msg_puts_printf(const char *str, const ptrdiff_t maxlen) memcpy(p, s, (size_t)len); *(p + len) = '\0'; if (info_message) { - mch_msg(buf); + os_msg(buf); } else { - mch_errmsg(buf); + os_errmsg(buf); } } @@ -2985,31 +3005,36 @@ static int do_more_prompt(int typed_char) } #if defined(MSWIN) -void mch_errmsg(char *str) +/// Headless (no UI) error message handler. +static void do_msg(const char *str, bool errmsg) { + static bool did_err = false; assert(str != NULL); wchar_t *utf16str; int r = utf8_to_utf16(str, -1, &utf16str); - if (r != 0) { + if (r != 0 && !did_err) { + did_err = true; fprintf(stderr, "utf8_to_utf16 failed: %d", r); - } else { - fwprintf(stderr, L"%ls", utf16str); + ELOG("utf8_to_utf16 failed: %d", r); + } else if (r == 0) { + if (errmsg) { + fwprintf(stderr, L"%ls", utf16str); + } else { + wprintf(L"%ls", utf16str); + } xfree(utf16str); } } -/// Give a message. To be used when the UI is not initialized yet. -void mch_msg(char *str) +void os_errmsg(const char *str) { - assert(str != NULL); - wchar_t *utf16str; - int r = utf8_to_utf16(str, -1, &utf16str); - if (r != 0) { - fprintf(stderr, "utf8_to_utf16 failed: %d", r); - } else { - wprintf(L"%ls", utf16str); - xfree(utf16str); - } + do_msg(str, true); +} + +/// Headless (no UI) message handler. +void os_msg(const char *str) +{ + do_msg(str, false); } #endif // MSWIN @@ -3144,6 +3169,7 @@ int msg_end(void) void msg_ext_ui_flush(void) { if (!ui_has(kUIMessages)) { + msg_ext_kind = NULL; return; } @@ -3423,8 +3449,8 @@ void give_warning(char *message, bool hl) void give_warning2(char *const message, char *const a1, bool hl) { - vim_snprintf((char *)IObuff, IOSIZE, message, a1); - give_warning((char *)IObuff, hl); + vim_snprintf(IObuff, IOSIZE, message, a1); + give_warning(IObuff, hl); } /// Advance msg cursor to column "col". diff --git a/src/nvim/message.h b/src/nvim/message.h index 31cd54f18c..191d3b8da7 100644 --- a/src/nvim/message.h +++ b/src/nvim/message.h @@ -66,6 +66,8 @@ EXTERN ScreenGrid msg_grid_adj INIT(= SCREEN_GRID_INIT); // value of msg_scrolled at latest msg_scroll_flush. EXTERN int msg_scrolled_at_flush INIT(= 0); +EXTERN int msg_grid_scroll_discount INIT(= 0); + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "message.h.generated.h" #endif diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index 7b267d6ce4..950b025e53 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -1,24 +1,47 @@ // 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 <assert.h> #include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> #include "nvim/ascii.h" +#include "nvim/buffer.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" -#include "nvim/diff.h" #include "nvim/drawscreen.h" +#include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_docmd.h" #include "nvim/fold.h" +#include "nvim/getchar.h" +#include "nvim/globals.h" #include "nvim/grid.h" +#include "nvim/keycodes.h" +#include "nvim/macros.h" +#include "nvim/mark.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" +#include "nvim/menu.h" +#include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" -#include "nvim/os_unix.h" +#include "nvim/normal.h" +#include "nvim/ops.h" +#include "nvim/option.h" #include "nvim/plines.h" +#include "nvim/pos.h" +#include "nvim/screen.h" +#include "nvim/search.h" #include "nvim/state.h" +#include "nvim/statusline.h" #include "nvim/strings.h" #include "nvim/syntax.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" #include "nvim/vim.h" @@ -31,8 +54,156 @@ static linenr_T orig_topline = 0; static int orig_topfill = 0; -/// Translate window coordinates to buffer position without any side effects -int get_fpos_of_mouse(pos_T *mpos) +/// Get class of a character for selection: same class means same word. +/// 0: blank +/// 1: punctuation groups +/// 2: normal word character +/// >2: multi-byte word character. +static int get_mouse_class(char *p) +{ + if (MB_BYTE2LEN((uint8_t)p[0]) > 1) { + return mb_get_class(p); + } + + const int c = (uint8_t)(*p); + if (c == ' ' || c == '\t') { + return 0; + } + if (vim_iswordc(c)) { + return 2; + } + + // There are a few special cases where we want certain combinations of + // characters to be considered as a single word. These are things like + // "->", "/ *", "*=", "+=", "&=", "<=", ">=", "!=" etc. Otherwise, each + // character is in its own class. + if (c != NUL && vim_strchr("-+*/%<>&|^!=", c) != NULL) { + return 1; + } + return c; +} + +/// Move "pos" back to the start of the word it's in. +static void find_start_of_word(pos_T *pos) +{ + char *line; + int cclass; + int col; + + line = ml_get(pos->lnum); + cclass = get_mouse_class(line + pos->col); + + while (pos->col > 0) { + col = pos->col - 1; + col -= utf_head_off(line, line + col); + if (get_mouse_class(line + col) != cclass) { + break; + } + pos->col = col; + } +} + +/// Move "pos" forward to the end of the word it's in. +/// When 'selection' is "exclusive", the position is just after the word. +static void find_end_of_word(pos_T *pos) +{ + char *line; + int cclass; + int col; + + line = ml_get(pos->lnum); + if (*p_sel == 'e' && pos->col > 0) { + pos->col--; + pos->col -= utf_head_off(line, line + pos->col); + } + cclass = get_mouse_class(line + pos->col); + while (line[pos->col] != NUL) { + col = pos->col + utfc_ptr2len(line + pos->col); + if (get_mouse_class(line + col) != cclass) { + if (*p_sel == 'e') { + pos->col = col; + } + break; + } + pos->col = col; + } +} + +/// Move the current tab to tab in same column as mouse or to end of the +/// tabline if there is no tab there. +static void move_tab_to_mouse(void) +{ + int tabnr = tab_page_click_defs[mouse_col].tabnr; + if (tabnr <= 0) { + tabpage_move(9999); + } else if (tabnr < tabpage_index(curtab)) { + tabpage_move(tabnr - 1); + } else { + tabpage_move(tabnr); + } +} + +/// Call click definition function for column "col" in the "click_defs" array for button +/// "which_button". +static void call_click_def_func(StlClickDefinition *click_defs, int col, int which_button) +{ + typval_T argv[] = { + { + .v_lock = VAR_FIXED, + .v_type = VAR_NUMBER, + .vval = { + .v_number = (varnumber_T)click_defs[col].tabnr + }, + }, + { + .v_lock = VAR_FIXED, + .v_type = VAR_NUMBER, + .vval = { + .v_number = ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_4CLICK + ? 4 + : ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_3CLICK + ? 3 + : ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK + ? 2 + : 1))) + }, + }, + { + .v_lock = VAR_FIXED, + .v_type = VAR_STRING, + .vval = { + .v_string = (which_button == MOUSE_LEFT + ? "l" + : (which_button == MOUSE_RIGHT + ? "r" + : (which_button == MOUSE_MIDDLE + ? "m" + : "?"))) + }, + }, + { + .v_lock = VAR_FIXED, + .v_type = VAR_STRING, + .vval = { + .v_string = (char[]) { + (char)(mod_mask & MOD_MASK_SHIFT ? 's' : ' '), + (char)(mod_mask & MOD_MASK_CTRL ? 'c' : ' '), + (char)(mod_mask & MOD_MASK_ALT ? 'a' : ' '), + (char)(mod_mask & MOD_MASK_META ? 'm' : ' '), + NUL + } + }, + } + }; + typval_T rettv; + (void)call_vim_function(click_defs[col].func, ARRAY_SIZE(argv), argv, &rettv); + tv_clear(&rettv); +} + +/// Translate window coordinates to buffer position without any side effects. +/// Returns IN_BUFFER and sets "mpos->col" to the column when in buffer text. +/// The column is one for the first column. +static int get_fpos_of_mouse(pos_T *mpos) { int grid = mouse_grid; int row = mouse_row; @@ -67,13 +238,724 @@ int get_fpos_of_mouse(pos_T *mpos) mpos->col = vcol2col(wp, mpos->lnum, col); - if (mpos->col > 0) { - mpos->col--; - } mpos->coladd = 0; return IN_BUFFER; } +/// Do the appropriate action for the current mouse click in the current mode. +/// Not used for Command-line mode. +/// +/// Normal and Visual Mode: +/// event modi- position visual change action +/// fier cursor window +/// left press - yes end yes +/// left press C yes end yes "^]" (2) +/// left press S yes end (popup: extend) yes "*" (2) +/// left drag - yes start if moved no +/// left relse - yes start if moved no +/// middle press - yes if not active no put register +/// middle press - yes if active no yank and put +/// right press - yes start or extend yes +/// right press S yes no change yes "#" (2) +/// right drag - yes extend no +/// right relse - yes extend no +/// +/// Insert or Replace Mode: +/// event modi- position visual change action +/// fier cursor window +/// left press - yes (cannot be active) yes +/// left press C yes (cannot be active) yes "CTRL-O^]" (2) +/// left press S yes (cannot be active) yes "CTRL-O*" (2) +/// left drag - yes start or extend (1) no CTRL-O (1) +/// left relse - yes start or extend (1) no CTRL-O (1) +/// middle press - no (cannot be active) no put register +/// right press - yes start or extend yes CTRL-O +/// right press S yes (cannot be active) yes "CTRL-O#" (2) +/// +/// (1) only if mouse pointer moved since press +/// (2) only if click is in same buffer +/// +/// @param oap operator argument, can be NULL +/// @param c K_LEFTMOUSE, etc +/// @param dir Direction to 'put' if necessary +/// @param fixindent PUT_FIXINDENT if fixing indent necessary +/// +/// @return true if start_arrow() should be called for edit mode. +bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) +{ + static bool got_click = false; // got a click some time back + + int which_button; // MOUSE_LEFT, _MIDDLE or _RIGHT + bool is_click; // If false it's a drag or release event + bool is_drag; // If true it's a drag event + int jump_flags = 0; // flags for jump_to_mouse() + pos_T start_visual; + bool moved; // Has cursor moved? + bool in_winbar; // mouse in window bar + bool in_statuscol; // mouse in status column + bool in_status_line; // mouse in status line + static bool in_tab_line = false; // mouse clicked in tab line + bool in_sep_line; // mouse in vertical separator line + int c1, c2; + pos_T save_cursor; + win_T *old_curwin = curwin; + static pos_T orig_cursor; + colnr_T leftcol, rightcol; + pos_T end_visual; + long diff; + int old_active = VIsual_active; + int old_mode = VIsual_mode; + int regname; + + save_cursor = curwin->w_cursor; + + for (;;) { + which_button = get_mouse_button(KEY2TERMCAP1(c), &is_click, &is_drag); + if (is_drag) { + // If the next character is the same mouse event then use that + // one. Speeds up dragging the status line. + // Note: Since characters added to the stuff buffer in the code + // below need to come before the next character, do not do this + // when the current character was stuffed. + if (!KeyStuffed && vpeekc() != NUL) { + int nc; + int save_mouse_grid = mouse_grid; + int save_mouse_row = mouse_row; + int save_mouse_col = mouse_col; + + // Need to get the character, peeking doesn't get the actual one. + nc = safe_vgetc(); + if (c == nc) { + continue; + } + vungetc(nc); + mouse_grid = save_mouse_grid; + mouse_row = save_mouse_row; + mouse_col = save_mouse_col; + } + } + break; + } + + if (c == K_MOUSEMOVE) { + // Mouse moved without a button pressed. + return false; + } + + // Ignore drag and release events if we didn't get a click. + if (is_click) { + got_click = true; + } else { + if (!got_click) { // didn't get click, ignore + return false; + } + if (!is_drag) { // release, reset got_click + got_click = false; + if (in_tab_line) { + in_tab_line = false; + return false; + } + } + } + + // CTRL right mouse button does CTRL-T + if (is_click && (mod_mask & MOD_MASK_CTRL) && which_button == MOUSE_RIGHT) { + if (State & MODE_INSERT) { + stuffcharReadbuff(Ctrl_O); + } + if (count > 1) { + stuffnumReadbuff(count); + } + stuffcharReadbuff(Ctrl_T); + got_click = false; // ignore drag&release now + return false; + } + + // CTRL only works with left mouse button + if ((mod_mask & MOD_MASK_CTRL) && which_button != MOUSE_LEFT) { + return false; + } + + // When a modifier is down, ignore drag and release events, as well as + // multiple clicks and the middle mouse button. + // Accept shift-leftmouse drags when 'mousemodel' is "popup.*". + if ((mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT + | MOD_MASK_META)) + && (!is_click + || (mod_mask & MOD_MASK_MULTI_CLICK) + || which_button == MOUSE_MIDDLE) + && !((mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT)) + && mouse_model_popup() + && which_button == MOUSE_LEFT) + && !((mod_mask & MOD_MASK_ALT) + && !mouse_model_popup() + && which_button == MOUSE_RIGHT)) { + return false; + } + + // If the button press was used as the movement command for an operator (eg + // "d<MOUSE>"), or it is the middle button that is held down, ignore + // drag/release events. + if (!is_click && which_button == MOUSE_MIDDLE) { + return false; + } + + if (oap != NULL) { + regname = oap->regname; + } else { + regname = 0; + } + + // Middle mouse button does a 'put' of the selected text + if (which_button == MOUSE_MIDDLE) { + if (State == MODE_NORMAL) { + // If an operator was pending, we don't know what the user wanted to do. + // Go back to normal mode: Clear the operator and beep(). + if (oap != NULL && oap->op_type != OP_NOP) { + clearopbeep(oap); + return false; + } + + // If visual was active, yank the highlighted text and put it + // before the mouse pointer position. + // In Select mode replace the highlighted text with the clipboard. + if (VIsual_active) { + if (VIsual_select) { + stuffcharReadbuff(Ctrl_G); + stuffReadbuff("\"+p"); + } else { + stuffcharReadbuff('y'); + stuffcharReadbuff(K_MIDDLEMOUSE); + } + return false; + } + // The rest is below jump_to_mouse() + } else if ((State & MODE_INSERT) == 0) { + return false; + } + + // Middle click in insert mode doesn't move the mouse, just insert the + // contents of a register. '.' register is special, can't insert that + // with do_put(). + // Also paste at the cursor if the current mode isn't in 'mouse' (only + // happens for the GUI). + if ((State & MODE_INSERT)) { + if (regname == '.') { + insert_reg(regname, true); + } else { + if (regname == 0 && eval_has_provider("clipboard")) { + regname = '*'; + } + if ((State & REPLACE_FLAG) && !yank_register_mline(regname)) { + insert_reg(regname, true); + } else { + do_put(regname, NULL, BACKWARD, 1L, + (fixindent ? PUT_FIXINDENT : 0) | PUT_CURSEND); + + // Repeat it with CTRL-R CTRL-O r or CTRL-R CTRL-P r + AppendCharToRedobuff(Ctrl_R); + AppendCharToRedobuff(fixindent ? Ctrl_P : Ctrl_O); + AppendCharToRedobuff(regname == 0 ? '"' : regname); + } + } + return false; + } + } + + // When dragging or button-up stay in the same window. + if (!is_click) { + jump_flags |= MOUSE_FOCUS | MOUSE_DID_MOVE; + } + + start_visual.lnum = 0; + + if (tab_page_click_defs != NULL) { // only when initialized + // Check for clicking in the tab page line. + if (mouse_grid <= 1 && mouse_row == 0 && firstwin->w_winrow > 0) { + if (is_drag) { + if (in_tab_line) { + move_tab_to_mouse(); + } + return false; + } + + // click in a tab selects that tab page + if (is_click && cmdwin_type == 0 && mouse_col < Columns) { + in_tab_line = true; + c1 = tab_page_click_defs[mouse_col].tabnr; + switch (tab_page_click_defs[mouse_col].type) { + case kStlClickDisabled: + break; + case kStlClickTabClose: { + tabpage_T *tp; + + // Close the current or specified tab page. + if (c1 == 999) { + tp = curtab; + } else { + tp = find_tabpage(c1); + } + if (tp == curtab) { + if (first_tabpage->tp_next != NULL) { + tabpage_close(false); + } + } else if (tp != NULL) { + tabpage_close_other(tp, false); + } + break; + } + case kStlClickTabSwitch: + if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { + // double click opens new page + end_visual_mode(); + tabpage_new(); + tabpage_move(c1 == 0 ? 9999 : c1 - 1); + } else { + // Go to specified tab page, or next one if not clicking + // on a label. + goto_tabpage(c1); + + // It's like clicking on the status line of a window. + if (curwin != old_curwin) { + end_visual_mode(); + } + } + break; + case kStlClickFuncRun: + call_click_def_func(tab_page_click_defs, mouse_col, which_button); + break; + } + } + return true; + } else if (is_drag && in_tab_line) { + move_tab_to_mouse(); + return false; + } + } + + // When 'mousemodel' is "popup" or "popup_setpos", translate mouse events: + // right button up -> pop-up menu + // shift-left button -> right button + // alt-left button -> alt-right button + if (mouse_model_popup()) { + if (which_button == MOUSE_RIGHT + && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) { + if (!is_click) { + // Ignore right button release events, only shows the popup + // menu on the button down event. + return false; + } + jump_flags = 0; + if (strcmp(p_mousem, "popup_setpos") == 0) { + // First set the cursor position before showing the popup + // menu. + if (VIsual_active) { + pos_T m_pos; + // set MOUSE_MAY_STOP_VIS if we are outside the + // selection or the current window (might have false + // negative here) + if (mouse_row < curwin->w_winrow + || mouse_row > (curwin->w_winrow + curwin->w_height)) { + jump_flags = MOUSE_MAY_STOP_VIS; + } else if (get_fpos_of_mouse(&m_pos) != IN_BUFFER) { + jump_flags = MOUSE_MAY_STOP_VIS; + } else { + if (VIsual_mode == 'V') { + if ((curwin->w_cursor.lnum <= VIsual.lnum + && (m_pos.lnum < curwin->w_cursor.lnum || VIsual.lnum < m_pos.lnum)) + || (VIsual.lnum < curwin->w_cursor.lnum + && (m_pos.lnum < VIsual.lnum || curwin->w_cursor.lnum < m_pos.lnum))) { + jump_flags = MOUSE_MAY_STOP_VIS; + } + } else if ((ltoreq(curwin->w_cursor, VIsual) + && (lt(m_pos, curwin->w_cursor) || lt(VIsual, m_pos))) + || (lt(VIsual, curwin->w_cursor) + && (lt(m_pos, VIsual) || lt(curwin->w_cursor, m_pos)))) { + jump_flags = MOUSE_MAY_STOP_VIS; + } else if (VIsual_mode == Ctrl_V) { + getvcols(curwin, &curwin->w_cursor, &VIsual, &leftcol, &rightcol); + getvcol(curwin, &m_pos, NULL, &m_pos.col, NULL); + if (m_pos.col < leftcol || m_pos.col > rightcol) { + jump_flags = MOUSE_MAY_STOP_VIS; + } + } + } + } else { + jump_flags = MOUSE_MAY_STOP_VIS; + } + } + if (jump_flags) { + jump_flags = jump_to_mouse(jump_flags, NULL, which_button); + redraw_curbuf_later(VIsual_active ? UPD_INVERTED : UPD_VALID); + update_screen(); + setcursor(); + ui_flush(); // Update before showing popup menu + } + show_popupmenu(); + got_click = false; // ignore release events + return (jump_flags & CURSOR_MOVED) != 0; + } + if (which_button == MOUSE_LEFT + && (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT))) { + which_button = MOUSE_RIGHT; + mod_mask &= ~MOD_MASK_SHIFT; + } + } + + if ((State & (MODE_NORMAL | MODE_INSERT)) + && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) { + if (which_button == MOUSE_LEFT) { + if (is_click) { + // stop Visual mode for a left click in a window, but not when on a status line + if (VIsual_active) { + jump_flags |= MOUSE_MAY_STOP_VIS; + } + } else { + jump_flags |= MOUSE_MAY_VIS; + } + } else if (which_button == MOUSE_RIGHT) { + if (is_click && VIsual_active) { + // Remember the start and end of visual before moving the cursor. + if (lt(curwin->w_cursor, VIsual)) { + start_visual = curwin->w_cursor; + end_visual = VIsual; + } else { + start_visual = VIsual; + end_visual = curwin->w_cursor; + } + } + jump_flags |= MOUSE_FOCUS; + jump_flags |= MOUSE_MAY_VIS; + } + } + + // If an operator is pending, ignore all drags and releases until the next mouse click. + if (!is_drag && oap != NULL && oap->op_type != OP_NOP) { + got_click = false; + oap->motion_type = kMTCharWise; + } + + // When releasing the button let jump_to_mouse() know. + if (!is_click && !is_drag) { + jump_flags |= MOUSE_RELEASED; + } + + // JUMP! + jump_flags = jump_to_mouse(jump_flags, + oap == NULL ? NULL : &(oap->inclusive), + which_button); + + moved = (jump_flags & CURSOR_MOVED); + in_winbar = (jump_flags & MOUSE_WINBAR); + in_statuscol = (jump_flags & MOUSE_STATUSCOL); + in_status_line = (jump_flags & IN_STATUS_LINE); + in_sep_line = (jump_flags & IN_SEP_LINE); + + if ((in_winbar || in_status_line || in_statuscol) && is_click) { + // Handle click event on window bar or status lin + int click_grid = mouse_grid; + int click_row = mouse_row; + int click_col = mouse_col; + win_T *wp = mouse_find_win(&click_grid, &click_row, &click_col); + if (wp == NULL) { + return false; + } + + StlClickDefinition *click_defs = in_status_line ? wp->w_status_click_defs + : in_winbar ? wp->w_winbar_click_defs + : wp->w_statuscol_click_defs; + + if (in_status_line && global_stl_height() > 0) { + // global statusline is displayed for the current window, + // and spans the whole screen. + click_defs = curwin->w_status_click_defs; + click_col = mouse_col; + } + + if (click_defs != NULL) { + switch (click_defs[click_col].type) { + case kStlClickDisabled: + break; + case kStlClickFuncRun: + call_click_def_func(click_defs, click_col, which_button); + break; + default: + assert(false && "winbar and statusline only support %@ for clicks"); + break; + } + } + + return false; + } else if (in_winbar || in_statuscol) { + // A drag or release event in the window bar and status column has no side effects. + return false; + } + + // When jumping to another window, clear a pending operator. That's a bit + // friendlier than beeping and not jumping to that window. + if (curwin != old_curwin && oap != NULL && oap->op_type != OP_NOP) { + clearop(oap); + } + + if (mod_mask == 0 + && !is_drag + && (jump_flags & (MOUSE_FOLD_CLOSE | MOUSE_FOLD_OPEN)) + && which_button == MOUSE_LEFT) { + // open or close a fold at this line + if (jump_flags & MOUSE_FOLD_OPEN) { + openFold(curwin->w_cursor, 1L); + } else { + closeFold(curwin->w_cursor, 1L); + } + // don't move the cursor if still in the same window + if (curwin == old_curwin) { + curwin->w_cursor = save_cursor; + } + } + + // Set global flag that we are extending the Visual area with mouse dragging; + // temporarily minimize 'scrolloff'. + if (VIsual_active && is_drag && get_scrolloff_value(curwin)) { + // In the very first line, allow scrolling one line + if (mouse_row == 0) { + mouse_dragging = 2; + } else { + mouse_dragging = 1; + } + } + + // When dragging the mouse above the window, scroll down. + if (is_drag && mouse_row < 0 && !in_status_line) { + scroll_redraw(false, 1L); + mouse_row = 0; + } + + if (start_visual.lnum) { // right click in visual mode + // When ALT is pressed make Visual mode blockwise. + if (mod_mask & MOD_MASK_ALT) { + VIsual_mode = Ctrl_V; + } + + // In Visual-block mode, divide the area in four, pick up the corner + // that is in the quarter that the cursor is in. + if (VIsual_mode == Ctrl_V) { + getvcols(curwin, &start_visual, &end_visual, &leftcol, &rightcol); + if (curwin->w_curswant > (leftcol + rightcol) / 2) { + end_visual.col = leftcol; + } else { + end_visual.col = rightcol; + } + if (curwin->w_cursor.lnum >= + (start_visual.lnum + end_visual.lnum) / 2) { + end_visual.lnum = start_visual.lnum; + } + + // move VIsual to the right column + start_visual = curwin->w_cursor; // save the cursor pos + curwin->w_cursor = end_visual; + coladvance(end_visual.col); + VIsual = curwin->w_cursor; + curwin->w_cursor = start_visual; // restore the cursor + } else { + // If the click is before the start of visual, change the start. + // If the click is after the end of visual, change the end. If + // the click is inside the visual, change the closest side. + if (lt(curwin->w_cursor, start_visual)) { + VIsual = end_visual; + } else if (lt(end_visual, curwin->w_cursor)) { + VIsual = start_visual; + } else { + // In the same line, compare column number + if (end_visual.lnum == start_visual.lnum) { + if (curwin->w_cursor.col - start_visual.col > + end_visual.col - curwin->w_cursor.col) { + VIsual = start_visual; + } else { + VIsual = end_visual; + } + } else { + // In different lines, compare line number + diff = (curwin->w_cursor.lnum - start_visual.lnum) - + (end_visual.lnum - curwin->w_cursor.lnum); + + if (diff > 0) { // closest to end + VIsual = start_visual; + } else if (diff < 0) { // closest to start + VIsual = end_visual; + } else { // in the middle line + if (curwin->w_cursor.col < + (start_visual.col + end_visual.col) / 2) { + VIsual = end_visual; + } else { + VIsual = start_visual; + } + } + } + } + } + } else if ((State & MODE_INSERT) && VIsual_active) { + // If Visual mode started in insert mode, execute "CTRL-O" + stuffcharReadbuff(Ctrl_O); + } + + // Middle mouse click: Put text before cursor. + if (which_button == MOUSE_MIDDLE) { + if (regname == 0 && eval_has_provider("clipboard")) { + regname = '*'; + } + if (yank_register_mline(regname)) { + if (mouse_past_bottom) { + dir = FORWARD; + } + } else if (mouse_past_eol) { + dir = FORWARD; + } + + if (fixindent) { + c1 = (dir == BACKWARD) ? '[' : ']'; + c2 = 'p'; + } else { + c1 = (dir == FORWARD) ? 'p' : 'P'; + c2 = NUL; + } + prep_redo(regname, count, NUL, c1, NUL, c2, NUL); + + // Remember where the paste started, so in edit() Insstart can be set to this position + if (restart_edit != 0) { + where_paste_started = curwin->w_cursor; + } + do_put(regname, NULL, dir, count, + (fixindent ? PUT_FIXINDENT : 0)| PUT_CURSEND); + } else if (((mod_mask & MOD_MASK_CTRL) || (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) + && bt_quickfix(curbuf)) { + // Ctrl-Mouse click or double click in a quickfix window jumps to the + // error under the mouse pointer. + if (curwin->w_llist_ref == NULL) { // quickfix window + do_cmdline_cmd(".cc"); + } else { // location list window + do_cmdline_cmd(".ll"); + } + got_click = false; // ignore drag&release now + } else if ((mod_mask & MOD_MASK_CTRL) + || (curbuf->b_help && (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK)) { + // Ctrl-Mouse click (or double click in a help window) jumps to the tag + // under the mouse pointer. + if (State & MODE_INSERT) { + stuffcharReadbuff(Ctrl_O); + } + stuffcharReadbuff(Ctrl_RSB); + got_click = false; // ignore drag&release now + } else if ((mod_mask & MOD_MASK_SHIFT)) { + // Shift-Mouse click searches for the next occurrence of the word under + // the mouse pointer + if (State & MODE_INSERT || (VIsual_active && VIsual_select)) { + stuffcharReadbuff(Ctrl_O); + } + if (which_button == MOUSE_LEFT) { + stuffcharReadbuff('*'); + } else { // MOUSE_RIGHT + stuffcharReadbuff('#'); + } + } else if (in_status_line || in_sep_line) { + // Do nothing if on status line or vertical separator + // Handle double clicks otherwise + } else if ((mod_mask & MOD_MASK_MULTI_CLICK) && (State & (MODE_NORMAL | MODE_INSERT))) { + if (is_click || !VIsual_active) { + if (VIsual_active) { + orig_cursor = VIsual; + } else { + VIsual = curwin->w_cursor; + orig_cursor = VIsual; + VIsual_active = true; + VIsual_reselect = true; + // start Select mode if 'selectmode' contains "mouse" + may_start_select('o'); + setmouse(); + } + if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { + // Double click with ALT pressed makes it blockwise. + if (mod_mask & MOD_MASK_ALT) { + VIsual_mode = Ctrl_V; + } else { + VIsual_mode = 'v'; + } + } else if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_3CLICK) { + VIsual_mode = 'V'; + } else if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_4CLICK) { + VIsual_mode = Ctrl_V; + } + } + // A double click selects a word or a block. + if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { + pos_T *pos = NULL; + int gc; + + if (is_click) { + // If the character under the cursor (skipping white space) is + // not a word character, try finding a match and select a (), + // {}, [], #if/#endif, etc. block. + end_visual = curwin->w_cursor; + while (gc = gchar_pos(&end_visual), ascii_iswhite(gc)) { + inc(&end_visual); + } + if (oap != NULL) { + oap->motion_type = kMTCharWise; + } + if (oap != NULL + && VIsual_mode == 'v' + && !vim_iswordc(gchar_pos(&end_visual)) + && equalpos(curwin->w_cursor, VIsual) + && (pos = findmatch(oap, NUL)) != NULL) { + curwin->w_cursor = *pos; + if (oap->motion_type == kMTLineWise) { + VIsual_mode = 'V'; + } else if (*p_sel == 'e') { + if (lt(curwin->w_cursor, VIsual)) { + VIsual.col++; + } else { + curwin->w_cursor.col++; + } + } + } + } + + if (pos == NULL && (is_click || is_drag)) { + // When not found a match or when dragging: extend to include a word. + if (lt(curwin->w_cursor, orig_cursor)) { + find_start_of_word(&curwin->w_cursor); + find_end_of_word(&VIsual); + } else { + find_start_of_word(&VIsual); + if (*p_sel == 'e' && *get_cursor_pos_ptr() != NUL) { + curwin->w_cursor.col += + utfc_ptr2len(get_cursor_pos_ptr()); + } + find_end_of_word(&curwin->w_cursor); + } + } + curwin->w_set_curswant = true; + } + if (is_click) { + redraw_curbuf_later(UPD_INVERTED); // update the inversion + } + } else if (VIsual_active && !old_active) { + if (mod_mask & MOD_MASK_ALT) { + VIsual_mode = Ctrl_V; + } else { + VIsual_mode = 'v'; + } + } + + // If Visual mode changed show it later. + if ((!VIsual_active && old_active && mode_displayed) + || (VIsual_active && p_smd && msg_silent == 0 + && (!old_active || VIsual_mode != old_mode))) { + redraw_cmdline = true; + } + + return moved; +} + /// Return true if "c" is a mouse key. bool is_mouse_key(int c) { @@ -100,6 +982,21 @@ bool is_mouse_key(int c) || c == K_X2DRAG || c == K_X2RELEASE; } + +/// @return true when 'mousemodel' is set to "popup" or "popup_setpos". +static bool mouse_model_popup(void) +{ + return p_mousem[0] == 'p'; +} + +static win_T *dragwin = NULL; ///< window being dragged + +/// Reset the window being dragged. To be called when switching tab page. +void reset_dragwin(void) +{ + dragwin = NULL; +} + /// Move the cursor to the specified row and column on the screen. /// Change current window if necessary. Returns an integer with the /// CURSOR_MOVED bit set if the cursor has moved or unset otherwise. @@ -134,9 +1031,9 @@ int jump_to_mouse(int flags, bool *inclusive, int which_button) static bool on_status_line = false; static bool on_sep_line = false; static bool on_winbar = false; + static bool on_statuscol = false; static int prev_row = -1; static int prev_col = -1; - static win_T *dragwin = NULL; // window being dragged static int did_drag = false; // drag was noticed win_T *wp, *old_curwin; @@ -177,6 +1074,9 @@ retnomove: if (on_winbar) { return IN_OTHER_WIN | MOUSE_WINBAR; } + if (on_statuscol) { + return IN_OTHER_WIN | MOUSE_STATUSCOL; + } if (flags & MOUSE_MAY_STOP_VIS) { end_visual_mode(); redraw_curbuf_later(UPD_INVERTED); // delete the inversion @@ -211,6 +1111,10 @@ retnomove: ? wp->w_winbar_height != 0 : false; + on_statuscol = !on_status_line && !on_winbar && col < win_col_off(wp) + ? *wp->w_p_stc != NUL + : false; + on_sep_line = grid == DEFAULT_GRID_HANDLE && col >= wp->w_width ? col - wp->w_width + 1 == 1 : false; @@ -238,6 +1142,10 @@ retnomove: return IN_OTHER_WIN | MOUSE_WINBAR; } + if (on_statuscol) { + return IN_OTHER_WIN | MOUSE_STATUSCOL; + } + fdc = win_fdccol_count(wp); dragwin = NULL; @@ -303,17 +1211,15 @@ retnomove: // Don't use start_arrow() if we're in the same window if (curwin == old_curwin) { return IN_STATUS_LINE; - } else { - return IN_STATUS_LINE | CURSOR_MOVED; } + return IN_STATUS_LINE | CURSOR_MOVED; } if (sep_line_offset) { // In (or below) status line // Don't use start_arrow() if we're in the same window if (curwin == old_curwin) { return IN_SEP_LINE; - } else { - return IN_SEP_LINE | CURSOR_MOVED; } + return IN_SEP_LINE | CURSOR_MOVED; } curwin->w_cursor.lnum = curwin->w_topline; @@ -340,6 +1246,9 @@ retnomove: } else if (on_winbar && which_button == MOUSE_RIGHT) { // After a click on the window bar don't start Visual mode. return IN_OTHER_WIN | MOUSE_WINBAR; + } else if (on_statuscol && which_button == MOUSE_RIGHT) { + // After a click on the status column don't start Visual mode. + return IN_OTHER_WIN | MOUSE_STATUSCOL; } else { // keep_window_focus must be true // before moving the cursor for a left click, stop Visual mode @@ -631,16 +1540,16 @@ 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 *line = (char_u *)ml_get_buf(wp->w_buffer, lnum, false); + char *line = ml_get_buf(wp->w_buffer, lnum, false); chartabsize_T cts; - init_chartabsize_arg(&cts, wp, lnum, 0, (char *)line, (char *)line); + init_chartabsize_arg(&cts, wp, lnum, 0, line, line); while (cts.cts_vcol < vcol && *cts.cts_ptr != NUL) { cts.cts_vcol += win_lbr_chartabsize(&cts, NULL); MB_PTR_ADV(cts.cts_ptr); } clear_chartabsize_arg(&cts); - return (colnr_T)((char_u *)cts.cts_ptr - line); + return (colnr_T)(cts.cts_ptr - line); } /// Set UI mouse depending on current mode and 'mouse'. @@ -654,7 +1563,7 @@ void setmouse(void) // Set orig_topline. Used when jumping to another window, so that a double // click still works. -void set_mouse_topline(win_T *wp) +static void set_mouse_topline(win_T *wp) { orig_topline = wp->w_topline; orig_topfill = wp->w_topfill; @@ -666,10 +1575,10 @@ void set_mouse_topline(win_T *wp) static colnr_T scroll_line_len(linenr_T lnum) { colnr_T col = 0; - char_u *line = (char_u *)ml_get(lnum); + char *line = ml_get(lnum); if (*line != NUL) { for (;;) { - int numchar = win_chartabsize(curwin, (char *)line, col); + int numchar = win_chartabsize(curwin, line, col); MB_PTR_ADV(line); if (*line == NUL) { // don't count the last character break; @@ -771,10 +1680,10 @@ static int mouse_adjust_click(win_T *wp, int row, int col) // highlighting the second byte, not the ninth. linenr_T lnum = wp->w_cursor.lnum; - char_u *line = (char_u *)ml_get(lnum); - char_u *ptr = line; - char_u *ptr_end; - char_u *ptr_row_offset = line; // Where we begin adjusting `ptr_end` + char *line = ml_get(lnum); + char *ptr = line; + char *ptr_end; + char *ptr_row_offset = line; // Where we begin adjusting `ptr_end` // Find the offset where scanning should begin. int offset = wp->w_leftcol; @@ -792,8 +1701,8 @@ static int mouse_adjust_click(win_T *wp, int row, int col) // checked for concealed characters. vcol = 0; while (vcol < offset && *ptr != NUL) { - vcol += win_chartabsize(curwin, (char *)ptr, vcol); - ptr += utfc_ptr2len((char *)ptr); + vcol += win_chartabsize(curwin, ptr, vcol); + ptr += utfc_ptr2len(ptr); } ptr_row_offset = ptr; @@ -803,8 +1712,8 @@ static int mouse_adjust_click(win_T *wp, int row, int col) vcol = offset; ptr_end = ptr_row_offset; while (vcol < col && *ptr_end != NUL) { - vcol += win_chartabsize(curwin, (char *)ptr_end, vcol); - ptr_end += utfc_ptr2len((char *)ptr_end); + vcol += win_chartabsize(curwin, ptr_end, vcol); + ptr_end += utfc_ptr2len(ptr_end); } int matchid; @@ -818,7 +1727,7 @@ static int mouse_adjust_click(win_T *wp, int row, int col) #define DECR() nudge--; ptr_end -= utfc_ptr2len((char *)ptr_end) while (ptr < ptr_end && *ptr != NUL) { - cwidth = win_chartabsize(curwin, (char *)ptr, vcol); + cwidth = win_chartabsize(curwin, ptr, vcol); vcol += cwidth; if (cwidth > 1 && *ptr == '\t' && nudge > 0) { // A tab will "absorb" any previous adjustments. @@ -846,7 +1755,7 @@ static int mouse_adjust_click(win_T *wp, int row, int col) while (prev_matchid == matchid && *ptr != NUL) { INCR(); - ptr += utfc_ptr2len((char *)ptr); + ptr += utfc_ptr2len(ptr); matchid = syn_get_concealed_id(wp, lnum, (colnr_T)(ptr - line)); } @@ -854,7 +1763,7 @@ static int mouse_adjust_click(win_T *wp, int row, int col) } } - ptr += utfc_ptr2len((char *)ptr); + ptr += utfc_ptr2len(ptr); } return col + nudge; diff --git a/src/nvim/mouse.h b/src/nvim/mouse.h index 21ff56bbbc..8b2d7e0acd 100644 --- a/src/nvim/mouse.h +++ b/src/nvim/mouse.h @@ -4,42 +4,54 @@ #include <stdbool.h> #include "nvim/buffer_defs.h" +#include "nvim/normal.h" #include "nvim/vim.h" - -// jump_to_mouse() returns one of first four these values, possibly with -// some of the other three added. -#define IN_UNKNOWN 0 -#define IN_BUFFER 1 -#define IN_STATUS_LINE 2 // on status or command line -#define IN_SEP_LINE 4 // on vertical separator line -#define IN_OTHER_WIN 8 // in other window but can't go there -#define CURSOR_MOVED 0x100 -#define MOUSE_FOLD_CLOSE 0x200 // clicked on '-' in fold column -#define MOUSE_FOLD_OPEN 0x400 // clicked on '+' in fold column -#define MOUSE_WINBAR 0x800 // in window toolbar - -// flags for jump_to_mouse() -#define MOUSE_FOCUS 0x01 // need to stay in this window -#define MOUSE_MAY_VIS 0x02 // may start Visual mode -#define MOUSE_DID_MOVE 0x04 // only act when mouse has moved -#define MOUSE_SETPOS 0x08 // only set current mouse position -#define MOUSE_MAY_STOP_VIS 0x10 // may stop Visual mode -#define MOUSE_RELEASED 0x20 // button was released - -// Codes for mouse button events in lower three bits: -#define MOUSE_LEFT 0x00 -#define MOUSE_MIDDLE 0x01 -#define MOUSE_RIGHT 0x02 -#define MOUSE_RELEASE 0x03 - -#define MOUSE_X1 0x300 // Mouse-button X1 (6th) -#define MOUSE_X2 0x400 // Mouse-button X2 - -// Direction for nv_mousescroll() and ins_mousescroll() -#define MSCR_DOWN 0 // DOWN must be false -#define MSCR_UP 1 -#define MSCR_LEFT (-1) -#define MSCR_RIGHT (-2) +#include "nvim/window.h" + +/// jump_to_mouse() returns one of first five these values, possibly with +/// some of the other five added. +enum { + IN_UNKNOWN = 0, + IN_BUFFER = 1, + IN_STATUS_LINE = 2, ///< on status or command line + IN_SEP_LINE = 4, ///< on vertical separator line + IN_OTHER_WIN = 8, ///< in other window but can't go there + CURSOR_MOVED = 0x100, + MOUSE_FOLD_CLOSE = 0x200, ///< clicked on '-' in fold column + MOUSE_FOLD_OPEN = 0x400, ///< clicked on '+' in fold column + MOUSE_WINBAR = 0x800, ///< in window toolbar + MOUSE_STATUSCOL = 0x1000, ///< in 'statuscolumn' +}; + +/// flags for jump_to_mouse() +enum { + MOUSE_FOCUS = 0x01, ///< need to stay in this window + MOUSE_MAY_VIS = 0x02, ///< may start Visual mode + MOUSE_DID_MOVE = 0x04, ///< only act when mouse has moved + MOUSE_SETPOS = 0x08, ///< only set current mouse position + MOUSE_MAY_STOP_VIS = 0x10, ///< may stop Visual mode + MOUSE_RELEASED = 0x20, ///< button was released +}; + +enum { + // Codes for mouse button events in lower three bits: + MOUSE_LEFT = 0x00, + MOUSE_MIDDLE = 0x01, + MOUSE_RIGHT = 0x02, + MOUSE_RELEASE = 0x03, + + // mouse buttons that are handled like a key press + MOUSE_X1 = 0x300, ///< Mouse-button X1 (6th) + MOUSE_X2 = 0x400, ///< Mouse-button X2 +}; + +/// Direction for nv_mousescroll() and ins_mousescroll() +enum { + MSCR_DOWN = 0, ///< DOWN must be false + MSCR_UP = 1, + MSCR_LEFT = -1, + MSCR_RIGHT = -2, +}; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mouse.h.generated.h" diff --git a/src/nvim/move.c b/src/nvim/move.c index 9b7755a828..3af26b910e 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -11,8 +11,9 @@ // The 'scrolloff' option makes this a bit complicated. #include <assert.h> -#include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stddef.h> #include "nvim/ascii.h" #include "nvim/buffer.h" @@ -21,21 +22,30 @@ #include "nvim/diff.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" -#include "nvim/eval.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/eval/window.h" #include "nvim/fold.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" +#include "nvim/macros.h" #include "nvim/mbyte.h" -#include "nvim/memline.h" +#include "nvim/memline_defs.h" +#include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/strings.h" +#include "nvim/types.h" +#include "nvim/vim.h" #include "nvim/window.h" typedef struct { @@ -150,7 +160,6 @@ void update_topline(win_T *wp) if (!default_grid.chars || wp->w_height_inner == 0) { wp->w_topline = wp->w_cursor.lnum; wp->w_botline = wp->w_topline; - wp->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; wp->w_viewport_invalid = true; wp->w_scbind_pos = 1; return; @@ -333,15 +342,6 @@ void update_topline(win_T *wp) *so_ptr = save_so; } -// Update win->w_topline to move the cursor onto the screen. -void update_topline_win(win_T *win) -{ - switchwin_T switchwin; - switch_win(&switchwin, win, NULL, true); - update_topline(curwin); - restore_win(&switchwin, true); -} - // Return the scrolljump value to use for the current window. // When 'scrolljump' is positive use it as-is. // When 'scrolljump' is negative use it as a percentage of the window height. @@ -381,12 +381,19 @@ static bool check_top_offset(void) return false; } +/// Update w_curswant. +void update_curswant_force(void) +{ + validate_virtcol(); + curwin->w_curswant = curwin->w_virtcol; + curwin->w_set_curswant = false; +} + +/// Update w_curswant if w_set_curswant is set. void update_curswant(void) { if (curwin->w_set_curswant) { - validate_virtcol(); - curwin->w_curswant = curwin->w_virtcol; - curwin->w_set_curswant = false; + update_curswant_force(); } } @@ -601,57 +608,67 @@ void validate_virtcol(void) 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_valid & VALID_VIRTCOL) { + return; } + + getvvcol(wp, &wp->w_cursor, NULL, &(wp->w_virtcol), NULL); + redraw_for_cursorcolumn(wp); + wp->w_valid |= VALID_VIRTCOL; } // Validate curwin->w_cline_height only. void validate_cheight(void) { check_cursor_moved(curwin); - if (!(curwin->w_valid & VALID_CHEIGHT)) { - curwin->w_cline_height = plines_win_full(curwin, curwin->w_cursor.lnum, - NULL, &curwin->w_cline_folded, - true); - curwin->w_valid |= VALID_CHEIGHT; + + if (curwin->w_valid & VALID_CHEIGHT) { + return; } + + curwin->w_cline_height = plines_win_full(curwin, curwin->w_cursor.lnum, + NULL, &curwin->w_cline_folded, + true); + curwin->w_valid |= VALID_CHEIGHT; } // Validate w_wcol and w_virtcol only. void validate_cursor_col(void) { validate_virtcol(); - if (!(curwin->w_valid & VALID_WCOL)) { - colnr_T col = curwin->w_virtcol; - colnr_T off = curwin_col_off(); - col += off; - int width = curwin->w_width_inner - off + curwin_col_off2(); - - // long line wrapping, adjust curwin->w_wrow - if (curwin->w_p_wrap && col >= (colnr_T)curwin->w_width_inner - && width > 0) { - // use same formula as what is used in curs_columns() - col -= ((col - curwin->w_width_inner) / width + 1) * width; - } - if (col > (int)curwin->w_leftcol) { - col -= curwin->w_leftcol; - } else { - col = 0; - } - curwin->w_wcol = col; - curwin->w_valid |= VALID_WCOL; + if (curwin->w_valid & VALID_WCOL) { + return; } + + colnr_T col = curwin->w_virtcol; + colnr_T off = curwin_col_off(); + col += off; + int width = curwin->w_width_inner - off + curwin_col_off2(); + + // long line wrapping, adjust curwin->w_wrow + if (curwin->w_p_wrap && col >= (colnr_T)curwin->w_width_inner + && width > 0) { + // use same formula as what is used in curs_columns() + col -= ((col - curwin->w_width_inner) / width + 1) * width; + } + if (col > (int)curwin->w_leftcol) { + col -= curwin->w_leftcol; + } else { + col = 0; + } + curwin->w_wcol = col; + + curwin->w_valid |= VALID_WCOL; } // Compute offset of a window, occupied by absolute or relative line number, // fold column and sign column (these don't move when scrolling horizontally). int win_col_off(win_T *wp) { - return ((wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) + 1 : 0) + return ((wp->w_p_nu || wp->w_p_rnu || (*wp->w_p_stc != NUL)) ? + (number_width(wp) + (*wp->w_p_stc == NUL)) : 0) + (cmdwin_type == 0 || wp != curwin ? 0 : 1) + win_fdccol_count(wp) + (win_signcol_count(wp) * win_signcol_width(wp)); @@ -743,7 +760,7 @@ void curs_columns(win_T *wp, int may_scroll) // When cursor wraps to first char of next line in Insert // mode, the 'showbreak' string isn't shown, backup to first // column - char *const sbr = (char *)get_showbreak_value(wp); + char *const sbr = get_showbreak_value(wp); if (*sbr && *get_cursor_pos_ptr() == NUL && wp->w_wcol == vim_strsize(sbr)) { wp->w_wcol = 0; @@ -813,8 +830,7 @@ void curs_columns(win_T *wp, int may_scroll) if ((wp->w_wrow >= wp->w_height_inner || ((prev_skipcol > 0 || wp->w_wrow + so >= wp->w_height_inner) - && (plines = - plines_win_nofill(wp, wp->w_cursor.lnum, false)) - 1 + && (plines = plines_win_nofill(wp, wp->w_cursor.lnum, false)) - 1 >= wp->w_height_inner)) && wp->w_height_inner != 0 && wp->w_cursor.lnum == wp->w_topline @@ -922,11 +938,16 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, int *ccolp, int rowoff = 0; colnr_T coloff = 0; bool visible_row = false; - - if (pos->lnum >= wp->w_topline && pos->lnum < wp->w_botline) { - row = plines_m_win(wp, wp->w_topline, pos->lnum - 1) + 1; + bool is_folded = false; + + if (pos->lnum >= wp->w_topline && pos->lnum <= wp->w_botline) { + linenr_T lnum = pos->lnum; + is_folded = hasFoldingWin(wp, lnum, &lnum, NULL, true, NULL); + row = plines_m_win(wp, wp->w_topline, lnum - 1) + 1; + // Add filler lines above this buffer line. + row += win_get_fill(wp, lnum); visible_row = true; - } else if (pos->lnum < wp->w_topline) { + } else if (!local || pos->lnum < wp->w_topline) { row = 0; } else { row = wp->w_height_inner; @@ -935,41 +956,43 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, int *ccolp, bool existing_row = (pos->lnum > 0 && pos->lnum <= wp->w_buffer->b_ml.ml_line_count); - if ((local && existing_row) || visible_row) { - colnr_T off; - colnr_T col; - int width; - - getvcol(wp, pos, &scol, &ccol, &ecol); - - // similar to what is done in validate_cursor_col() - col = scol; - off = win_col_off(wp); - col += off; - width = wp->w_width - off + win_col_off2(wp); - - // long line wrapping, adjust row - if (wp->w_p_wrap && col >= (colnr_T)wp->w_width && width > 0) { - // use same formula as what is used in curs_columns() - rowoff = visible_row ? ((col - wp->w_width) / width + 1) : 0; - col -= rowoff * width; - } + if ((local || visible_row) && existing_row) { + const colnr_T off = win_col_off(wp); + if (is_folded) { + row += local ? 0 : wp->w_winrow + wp->w_winrow_off; + coloff = (local ? 0 : wp->w_wincol + wp->w_wincol_off) + 1 + off; + } else { + getvcol(wp, pos, &scol, &ccol, &ecol); + + // similar to what is done in validate_cursor_col() + colnr_T col = scol; + col += off; + int width = wp->w_width - off + win_col_off2(wp); + + // long line wrapping, adjust row + if (wp->w_p_wrap && col >= (colnr_T)wp->w_width && width > 0) { + // use same formula as what is used in curs_columns() + rowoff = visible_row ? ((col - wp->w_width) / width + 1) : 0; + col -= rowoff * width; + } - col -= wp->w_leftcol; + col -= wp->w_leftcol; - if (col >= 0 && col < wp->w_width) { - coloff = col - scol + (local ? 0 : wp->w_wincol + wp->w_wincol_off) + 1; - } else { - scol = ccol = ecol = 0; - // character is left or right of the window - if (local) { - coloff = col < 0 ? -1 : wp->w_width_inner + 1; + if (col >= 0 && col < wp->w_width && row + rowoff <= wp->w_height) { + coloff = col - scol + (local ? 0 : wp->w_wincol + wp->w_wincol_off) + 1; + row += local ? 0 : wp->w_winrow + wp->w_winrow_off; } else { - row = 0; + // character is left, right or below of the window + scol = ccol = ecol = 0; + if (local) { + coloff = col < 0 ? -1 : wp->w_width_inner + 1; + } else { + row = rowoff = 0; + } } } } - *rowp = (local ? 0 : wp->w_winrow + wp->w_winrow_off) + row + rowoff; + *rowp = row + rowoff; *scolp = scol + coloff; *ccolp = ccol + coloff; *ecolp = ecol + coloff; @@ -991,6 +1014,10 @@ void f_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) .col = (colnr_T)tv_get_number(&argvars[2]) - 1, .coladd = 0 }; + if (pos.lnum > wp->w_buffer->b_ml.ml_line_count) { + semsg(_(e_invalid_line_number_nr), pos.lnum); + return; + } int row = 0; int scol = 0, ccol = 0, ecol = 0; textpos2screenpos(wp, &pos, &row, &scol, &ccol, &ecol, false); diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 71ed5ccf81..d60e18590f 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -1,23 +1,28 @@ // 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 <assert.h> #include <inttypes.h> -#include <msgpack.h> +#include <msgpack/object.h> +#include <msgpack/pack.h> +#include <msgpack/sbuffer.h> +#include <msgpack/unpack.h> #include <stdbool.h> -#include <string.h> +#include <stdio.h> +#include <stdlib.h> #include <uv.h> #include "klib/kvec.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" #include "nvim/api/ui.h" -#include "nvim/api/vim.h" -#include "nvim/ascii.h" #include "nvim/channel.h" -#include "nvim/eval.h" -#include "nvim/event/libuv_process.h" +#include "nvim/event/defs.h" #include "nvim/event/loop.h" +#include "nvim/event/process.h" #include "nvim/event/rstream.h" -#include "nvim/event/socket.h" +#include "nvim/event/stream.h" #include "nvim/event/wstream.h" #include "nvim/log.h" #include "nvim/main.h" @@ -25,12 +30,14 @@ #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/msgpack_rpc/unpacker.h" #include "nvim/os/input.h" -#include "nvim/os_unix.h" +#include "nvim/rbuffer.h" +#include "nvim/types.h" #include "nvim/ui.h" -#include "nvim/vim.h" +#include "nvim/ui_client.h" #if MIN_LOG_LEVEL > LOGLVL_DBG # define log_client_msg(...) @@ -94,7 +101,6 @@ bool rpc_send_event(uint64_t id, const char *name, Array args) Channel *channel = NULL; if (id && (!(channel = find_rpc_channel(id)))) { - api_free_array(args); return false; } @@ -130,6 +136,7 @@ Object rpc_send_call(uint64_t id, const char *method_name, Array args, ArenaMem uint32_t request_id = rpc->next_request_id++; // Send the msgpack-rpc request send_request(channel, request_id, method_name, args); + api_free_array(args); // Push the frame ChannelCallFrame frame = { request_id, false, false, NIL, NULL }; @@ -244,8 +251,8 @@ static void parse_msgpack(Channel *channel) ui_client_event_raw_line(p->grid_line_event); } else if (p->ui_handler.fn != NULL && p->result.type == kObjectTypeArray) { p->ui_handler.fn(p->result.data.array); - arena_mem_free(arena_finish(&p->arena)); } + arena_mem_free(arena_finish(&p->arena)); } else if (p->type == kMessageTypeResponse) { ChannelCallFrame *frame = kv_last(channel->rpc.call_stack); if (p->request_id != frame->request_id) { @@ -293,7 +300,7 @@ static void handle_request(Channel *channel, Unpacker *p, Array args) assert(p->type == kMessageTypeRequest || p->type == kMessageTypeNotification); if (!p->handler.fn) { - send_error(channel, p->type, p->request_id, p->unpack_error.msg); + send_error(channel, p->handler, p->type, p->request_id, p->unpack_error.msg); api_clear_error(&p->unpack_error); arena_mem_free(arena_finish(&p->arena)); return; @@ -352,6 +359,7 @@ static void request_event(void **argv) msgpack_packer response; msgpack_packer_init(&response, &out_buffer, msgpack_sbuffer_write); channel_write(channel, serialize_response(channel->id, + e->handler, e->type, e->request_id, &error, @@ -434,11 +442,13 @@ static void internal_read_event(void **argv) wstream_release_wbuffer(buffer); } -static void send_error(Channel *chan, MessageType type, uint32_t id, char *err) +static void send_error(Channel *chan, MsgpackRpcRequestHandler handler, MessageType type, + uint32_t id, char *err) { Error e = ERROR_INIT; api_set_error(&e, kErrorTypeException, "%s", err); channel_write(chan, serialize_response(chan->id, + handler, type, id, &e, @@ -482,7 +492,6 @@ static void broadcast_event(const char *name, Array args) }); if (!kv_size(subscribed)) { - api_free_array(args); goto end; } @@ -537,26 +546,8 @@ void rpc_close(Channel *channel) channel_decref(channel); if (channel->streamtype == kChannelStreamStdio - || channel->id == ui_client_channel_id) { - multiqueue_put(main_loop.fast_events, exit_event, 0); - } -} - -static void exit_delay_cb(uv_timer_t *handle) -{ - uv_timer_stop(&main_loop.exit_delay_timer); - multiqueue_put(main_loop.fast_events, exit_event, 0); -} - -static void exit_event(void **argv) -{ - if (exit_need_delay) { - uv_timer_start(&main_loop.exit_delay_timer, exit_delay_cb, 0, 0); - return; - } - - if (!exiting) { - os_exit(0); + || (channel->id == ui_client_channel_id && channel->streamtype != kChannelStreamProc)) { + exit_from_channel(0); } } @@ -602,22 +593,30 @@ static WBuffer *serialize_request(uint64_t channel_id, uint32_t request_id, cons refcount, xfree); msgpack_sbuffer_clear(sbuffer); - api_free_array(args); return rv; } -static WBuffer *serialize_response(uint64_t channel_id, MessageType type, uint32_t response_id, - Error *err, Object arg, msgpack_sbuffer *sbuffer) +static WBuffer *serialize_response(uint64_t channel_id, MsgpackRpcRequestHandler handler, + MessageType type, uint32_t response_id, Error *err, Object arg, + msgpack_sbuffer *sbuffer) { msgpack_packer pac; msgpack_packer_init(&pac, sbuffer, msgpack_sbuffer_write); if (ERROR_SET(err) && type == kMessageTypeNotification) { - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(err->type)); - ADD(args, STRING_OBJ(cstr_to_string(err->msg))); - msgpack_rpc_serialize_request(0, cstr_as_string("nvim_error_event"), - args, &pac); - api_free_array(args); + if (handler.fn == handle_nvim_paste) { + // TODO(bfredl): this is pretty much ad-hoc. maybe TUI and UI:s should be + // allowed to ask nvim to just scream directly in the users face + // instead of sending nvim_error_event, in general. + semsg("paste: %s", err->msg); + api_clear_error(err); + } else { + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(err->type)); + ADD(args, STRING_OBJ(cstr_to_string(err->msg))); + msgpack_rpc_serialize_request(0, cstr_as_string("nvim_error_event"), + args, &pac); + api_free_array(args); + } } else { msgpack_rpc_serialize_response(response_id, err, arg, &pac); } diff --git a/src/nvim/msgpack_rpc/channel.h b/src/nvim/msgpack_rpc/channel.h index ac7911bb2c..ce5806930c 100644 --- a/src/nvim/msgpack_rpc/channel.h +++ b/src/nvim/msgpack_rpc/channel.h @@ -6,8 +6,10 @@ #include "nvim/api/private/defs.h" #include "nvim/channel.h" +#include "nvim/event/multiqueue.h" #include "nvim/event/process.h" #include "nvim/event/socket.h" +#include "nvim/macros.h" #include "nvim/vim.h" #define METHOD_MAXLEN 512 diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c index ddca9afad0..5f0f03dd69 100644 --- a/src/nvim/msgpack_rpc/helpers.c +++ b/src/nvim/msgpack_rpc/helpers.c @@ -1,21 +1,25 @@ // 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 <inttypes.h> -#include <msgpack.h> +#include <msgpack/object.h> +#include <msgpack/sbuffer.h> +#include <msgpack/unpack.h> +#include <msgpack/zone.h> #include <stdbool.h> +#include <stddef.h> +#include <stdint.h> #include "klib/kvec.h" -#include "nvim/api/private/dispatch.h" +#include "msgpack/pack.h" #include "nvim/api/private/helpers.h" #include "nvim/assert.h" -#include "nvim/log.h" +#include "nvim/event/wstream.h" #include "nvim/memory.h" #include "nvim/msgpack_rpc/helpers.h" -#include "nvim/vim.h" +#include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "keysets.generated.h" +# include "keysets.generated.h" // IWYU pragma: export # include "msgpack_rpc/helpers.c.generated.h" #endif @@ -35,6 +39,8 @@ typedef struct { size_t idx; } MPToAPIObjectStackItem; +// uncrustify:off + /// Convert type used by msgpack parser to Nvim API type. /// /// @param[in] obj Msgpack value to convert. @@ -95,9 +101,8 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg) 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)), \ - })); \ + ? xmemdupz("", 0) \ + : xmemdupz(obj->via.attr.ptr, obj->via.attr.size)), })); \ break; \ } STR_CASE(MSGPACK_OBJECT_STR, str, cur.mobj, *cur.aobj, STRING_OBJ) @@ -115,7 +120,7 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg) .mobj = &cur.mobj->via.array.ptr[idx], .aobj = &cur.aobj->data.array.items[idx], .container = false, - })); + })); } } else { *cur.aobj = ARRAY_OBJ(((Array) { @@ -124,7 +129,7 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg) .items = (size > 0 ? xcalloc(size, sizeof(*cur.aobj->data.array.items)) : NULL), - })); + })); cur.container = true; kv_last(stack) = cur; } @@ -168,7 +173,7 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg) .mobj = &cur.mobj->via.map.ptr[idx].val, .aobj = &cur.aobj->data.dictionary.items[idx].value, .container = false, - })); + })); } } } else { @@ -178,7 +183,7 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg) .items = (size > 0 ? xcalloc(size, sizeof(*cur.aobj->data.dictionary.items)) : NULL), - })); + })); cur.container = true; kv_last(stack) = cur; } @@ -368,7 +373,7 @@ void msgpack_rpc_from_object(const Object result, msgpack_packer *const res) kvi_push(stack, ((APIToMPObjectStackItem) { .aobj = &cur.aobj->data.array.items[idx], .container = false, - })); + })); } } else { msgpack_pack_array(res, size); @@ -386,12 +391,11 @@ void msgpack_rpc_from_object(const Object result, msgpack_packer *const res) const size_t idx = cur.idx; cur.idx++; kv_last(stack) = cur; - msgpack_rpc_from_string(cur.aobj->data.dictionary.items[idx].key, - res); + msgpack_rpc_from_string(cur.aobj->data.dictionary.items[idx].key, res); kvi_push(stack, ((APIToMPObjectStackItem) { .aobj = &cur.aobj->data.dictionary.items[idx].value, .container = false, - })); + })); } } else { msgpack_pack_map(res, size); @@ -408,6 +412,8 @@ void msgpack_rpc_from_object(const Object result, msgpack_packer *const res) kvi_destroy(stack); } +// uncrustify:on + void msgpack_rpc_from_array(Array result, msgpack_packer *res) FUNC_ATTR_NONNULL_ARG(2) { diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c index 81b58764d7..1d75c208be 100644 --- a/src/nvim/msgpack_rpc/server.c +++ b/src/nvim/msgpack_rpc/server.c @@ -1,25 +1,22 @@ // 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 <assert.h> #include <inttypes.h> -#include <stdlib.h> +#include <stdbool.h> +#include <stdio.h> #include <string.h> +#include <uv.h> -#include "nvim/ascii.h" +#include "nvim/channel.h" #include "nvim/eval.h" #include "nvim/event/socket.h" -#include "nvim/fileio.h" #include "nvim/garray.h" #include "nvim/log.h" #include "nvim/main.h" #include "nvim/memory.h" -#include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/server.h" #include "nvim/os/os.h" -#include "nvim/path.h" -#include "nvim/strings.h" -#include "nvim/vim.h" +#include "nvim/os/stdpaths_defs.h" #define MAX_CONNECTIONS 32 #define ENV_LISTEN "NVIM_LISTEN_ADDRESS" // deprecated @@ -104,7 +101,7 @@ char *server_address_new(const char *name) xfree(dir); #endif if ((size_t)r >= sizeof(fmt)) { - ELOG("truncated server address"); + ELOG("truncated server address: %.40s...", fmt); } return xstrdup(fmt); } diff --git a/src/nvim/msgpack_rpc/unpacker.c b/src/nvim/msgpack_rpc/unpacker.c index 24480835a1..44a16beb48 100644 --- a/src/nvim/msgpack_rpc/unpacker.c +++ b/src/nvim/msgpack_rpc/unpacker.c @@ -1,9 +1,17 @@ // 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 <assert.h> +#include <stdbool.h> +#include <stdlib.h> + +#include "klib/kvec.h" +#include "mpack/conv.h" #include "nvim/api/private/helpers.h" -#include "nvim/log.h" +#include "nvim/ascii.h" +#include "nvim/macros.h" #include "nvim/memory.h" +#include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/msgpack_rpc/unpacker.h" #include "nvim/ui_client.h" @@ -178,7 +186,7 @@ void unpacker_init(Unpacker *p) mpack_parser_init(&p->parser, 0); p->parser.data.p = p; mpack_tokbuf_init(&p->reader); - p->unpack_error = (Error)ERROR_INIT; + p->unpack_error = ERROR_INIT; p->arena = (Arena)ARENA_EMPTY; } @@ -290,7 +298,7 @@ error: // // When method is "grid_line", we furthermore decode a cell at a time like: // -// <0>[2, "redraw", <10>[{11}["grid_line", <13>[g, r, c, [<14>[cell], <14>[cell], ...]], ...], <11>[...], ...]] +// <0>[2, "redraw", <10>[{11}["grid_line", <14>[g, r, c, [<15>[cell], <15>[cell], ...]], ...], <11>[...], ...]] // // where [cell] is [char, repeat, attr], where 'repeat' and 'attr' is optional @@ -310,17 +318,19 @@ bool unpacker_advance(Unpacker *p) } } - if (p->state >= 10 && p->state != 12) { + if (p->state >= 10 && p->state != 13) { if (!unpacker_parse_redraw(p)) { return false; } - if (p->state == 14) { + if (p->state == 15) { // grid_line event already unpacked goto done; } else { + assert(p->state == 12); // unpack other ui events using mpack_parse() p->arena = (Arena)ARENA_EMPTY; + p->state = 13; } } @@ -347,11 +357,11 @@ done: case 2: p->state = 0; return true; - case 12: - case 14: + case 13: + case 15: p->ncalls--; if (p->ncalls > 0) { - p->state = (p->state == 14) ? 13 : 12; + p->state = (p->state == 15) ? 14 : 12; } else if (p->nevents > 0) { p->state = 11; } else { @@ -372,6 +382,7 @@ bool unpacker_parse_redraw(Unpacker *p) size_t size = p->read_size; GridLineEvent *g = p->grid_line_event; +// -V:NEXT_TYPE:501 #define NEXT_TYPE(tok, typ) \ result = mpack_rtoken(&data, &size, &tok); \ if (result == MPACK_EOF) { \ @@ -419,14 +430,14 @@ redo: } return true; } else { - p->state = 13; + p->state = 14; p->arena = (Arena)ARENA_EMPTY; p->grid_line_event = arena_alloc(&p->arena, sizeof *p->grid_line_event, true); g = p->grid_line_event; } FALLTHROUGH; - case 13: + case 14: NEXT_TYPE(tok, MPACK_TOKEN_ARRAY); int eventarrsize = (int)tok.length; if (eventarrsize != 4) { @@ -447,10 +458,10 @@ redo: p->read_ptr = data; p->read_size = size; - p->state = 14; + p->state = 15; FALLTHROUGH; - case 14: + case 15: assert(g->icell < g->ncells); NEXT_TYPE(tok, MPACK_TOKEN_ARRAY); @@ -504,6 +515,9 @@ redo: } goto redo; + case 12: + return true; + default: abort(); } diff --git a/src/nvim/msgpack_rpc/unpacker.h b/src/nvim/msgpack_rpc/unpacker.h index 35048fb877..b8b2d38d3b 100644 --- a/src/nvim/msgpack_rpc/unpacker.h +++ b/src/nvim/msgpack_rpc/unpacker.h @@ -7,10 +7,14 @@ #include "mpack/mpack_core.h" #include "mpack/object.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" +#include "nvim/grid_defs.h" #include "nvim/memory.h" #include "nvim/msgpack_rpc/channel_defs.h" +#include "nvim/types.h" +#include "nvim/ui_client.h" struct Unpacker { mpack_parser_t parser; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index e058be9135..58a18ca5a8 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -8,14 +8,21 @@ // #include <assert.h> +#include <ctype.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> +#include <time.h> +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cmdhist.h" @@ -25,8 +32,6 @@ #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" -#include "nvim/eval/userfunc.h" -#include "nvim/event/loop.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" @@ -34,18 +39,18 @@ #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/help.h" -#include "nvim/indent.h" +#include "nvim/highlight_defs.h" #include "nvim/keycodes.h" -#include "nvim/log.h" -#include "nvim/main.h" +#include "nvim/macros.h" #include "nvim/mapping.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/mouse.h" #include "nvim/move.h" @@ -57,16 +62,19 @@ #include "nvim/plines.h" #include "nvim/profile.h" #include "nvim/quickfix.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/spell.h" #include "nvim/spellfile.h" #include "nvim/spellsuggest.h" #include "nvim/state.h" +#include "nvim/statusline.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/tag.h" #include "nvim/textformat.h" #include "nvim/textobject.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -145,8 +153,7 @@ static const struct nv_cmd { nv_func_T cmd_func; ///< function for this command uint16_t cmd_flags; ///< NV_ flags int16_t cmd_arg; ///< value for ca.arg -} nv_cmds[] = -{ +} nv_cmds[] = { { NUL, nv_error, 0, 0 }, { Ctrl_A, nv_addsub, 0, 0 }, { Ctrl_B, nv_page, NV_STS, BACKWARD }, @@ -444,12 +451,34 @@ static int find_command(int cmdchar) /// message, return true. static bool check_text_locked(oparg_T *oap) { - if (text_locked()) { + if (!text_locked()) { + return false; + } + + if (oap != NULL) { clearopbeep(oap); - text_locked_msg(); + } + text_locked_msg(); + return true; +} + +/// If text is locked, "curbuf->b_ro_locked" or "allbuf_lock" is set: +/// Give an error message, possibly beep and return true. +/// "oap" may be NULL. +static bool check_text_or_curbuf_locked(oparg_T *oap) +{ + if (check_text_locked(oap)) { return true; } - return false; + + if (!curbuf_locked()) { + return false; + } + + if (oap != NULL) { + clearop(oap); + } + return true; } /// Normal state entry point. This is called on: @@ -613,6 +642,7 @@ static bool normal_need_redraw_mode_message(NormalState *s) && stuff_empty() && typebuf_typed() && emsg_silent == 0 + && !in_assert_fails && !did_wait_return && s->oa.op_type == OP_NOP); } @@ -1099,8 +1129,7 @@ static int normal_execute(VimState *state, int key) goto finish; } - if ((nv_cmds[s->idx].cmd_flags & NV_NCW) - && (check_text_locked(&s->oa) || curbuf_locked())) { + if ((nv_cmds[s->idx].cmd_flags & NV_NCW) && check_text_or_curbuf_locked(&s->oa)) { // this command is not allowed now s->command_finished = true; goto finish; @@ -1132,6 +1161,7 @@ static int normal_execute(VimState *state, int key) if (s->need_flushbuf) { ui_flush(); } + if (s->ca.cmdchar != K_IGNORE && s->ca.cmdchar != K_EVENT) { did_cursorhold = false; } @@ -1222,8 +1252,7 @@ static void normal_check_interrupt(NormalState *s) static void normal_check_window_scrolled(NormalState *s) { if (!finish_op) { - // Trigger Scroll if the viewport changed. - may_trigger_winscrolled(); + may_trigger_win_scrolled_resized(); } } @@ -1389,6 +1418,9 @@ static int normal_check(VimState *state) fclose(time_fd); time_fd = NULL; } + // After the first screen update may start triggering WinScrolled + // autocmd events. Store all the scroll positions and sizes now. + may_make_initial_scroll_size_snapshot(); } // May perform garbage collection when waiting for a character, but @@ -1433,854 +1465,6 @@ static void set_vcount_ca(cmdarg_T *cap, bool *set_prevcount) *set_prevcount = false; // only set v:prevcount once } -/// Move the current tab to tab in same column as mouse or to end of the -/// tabline if there is no tab there. -static void move_tab_to_mouse(void) -{ - int tabnr = tab_page_click_defs[mouse_col].tabnr; - if (tabnr <= 0) { - tabpage_move(9999); - } else if (tabnr < tabpage_index(curtab)) { - tabpage_move(tabnr - 1); - } else { - tabpage_move(tabnr); - } -} - -/// Call click definition function for column "col" in the "click_defs" array for button -/// "which_button". -static void call_click_def_func(StlClickDefinition *click_defs, int col, int which_button) -{ - typval_T argv[] = { - { - .v_lock = VAR_FIXED, - .v_type = VAR_NUMBER, - .vval = { - .v_number = (varnumber_T)click_defs[col].tabnr - }, - }, - { - .v_lock = VAR_FIXED, - .v_type = VAR_NUMBER, - .vval = { - .v_number = ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_4CLICK - ? 4 - : ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_3CLICK - ? 3 - : ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK - ? 2 - : 1))) - }, - }, - { - .v_lock = VAR_FIXED, - .v_type = VAR_STRING, - .vval = { - .v_string = (which_button == MOUSE_LEFT - ? "l" - : (which_button == MOUSE_RIGHT - ? "r" - : (which_button == MOUSE_MIDDLE - ? "m" - : "?"))) - }, - }, - { - .v_lock = VAR_FIXED, - .v_type = VAR_STRING, - .vval = { - .v_string = (char[]) { - (char)(mod_mask & MOD_MASK_SHIFT ? 's' : ' '), - (char)(mod_mask & MOD_MASK_CTRL ? 'c' : ' '), - (char)(mod_mask & MOD_MASK_ALT ? 'a' : ' '), - (char)(mod_mask & MOD_MASK_META ? 'm' : ' '), - NUL - } - }, - } - }; - typval_T rettv; - (void)call_vim_function(click_defs[col].func, ARRAY_SIZE(argv), argv, &rettv); - tv_clear(&rettv); -} - -/// Do the appropriate action for the current mouse click in the current mode. -/// Not used for Command-line mode. -/// -/// Normal and Visual Mode: -/// event modi- position visual change action -/// fier cursor window -/// left press - yes end yes -/// left press C yes end yes "^]" (2) -/// left press S yes end (popup: extend) yes "*" (2) -/// left drag - yes start if moved no -/// left relse - yes start if moved no -/// middle press - yes if not active no put register -/// middle press - yes if active no yank and put -/// right press - yes start or extend yes -/// right press S yes no change yes "#" (2) -/// right drag - yes extend no -/// right relse - yes extend no -/// -/// Insert or Replace Mode: -/// event modi- position visual change action -/// fier cursor window -/// left press - yes (cannot be active) yes -/// left press C yes (cannot be active) yes "CTRL-O^]" (2) -/// left press S yes (cannot be active) yes "CTRL-O*" (2) -/// left drag - yes start or extend (1) no CTRL-O (1) -/// left relse - yes start or extend (1) no CTRL-O (1) -/// middle press - no (cannot be active) no put register -/// right press - yes start or extend yes CTRL-O -/// right press S yes (cannot be active) yes "CTRL-O#" (2) -/// -/// (1) only if mouse pointer moved since press -/// (2) only if click is in same buffer -/// -/// @param oap operator argument, can be NULL -/// @param c K_LEFTMOUSE, etc -/// @param dir Direction to 'put' if necessary -/// @param fixindent PUT_FIXINDENT if fixing indent necessary -/// -/// @return true if start_arrow() should be called for edit mode. -bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) -{ - static bool got_click = false; // got a click some time back - - int which_button; // MOUSE_LEFT, _MIDDLE or _RIGHT - bool is_click; // If false it's a drag or release event - bool is_drag; // If true it's a drag event - int jump_flags = 0; // flags for jump_to_mouse() - pos_T start_visual; - bool moved; // Has cursor moved? - bool in_winbar; // mouse in window bar - bool in_status_line; // mouse in status line - static bool in_tab_line = false; // mouse clicked in tab line - bool in_sep_line; // mouse in vertical separator line - int c1, c2; - pos_T save_cursor; - win_T *old_curwin = curwin; - static pos_T orig_cursor; - colnr_T leftcol, rightcol; - pos_T end_visual; - long diff; - int old_active = VIsual_active; - int old_mode = VIsual_mode; - int regname; - - save_cursor = curwin->w_cursor; - - for (;;) { - which_button = get_mouse_button(KEY2TERMCAP1(c), &is_click, &is_drag); - if (is_drag) { - // If the next character is the same mouse event then use that - // one. Speeds up dragging the status line. - // Note: Since characters added to the stuff buffer in the code - // below need to come before the next character, do not do this - // when the current character was stuffed. - if (!KeyStuffed && vpeekc() != NUL) { - int nc; - int save_mouse_grid = mouse_grid; - int save_mouse_row = mouse_row; - int save_mouse_col = mouse_col; - - // Need to get the character, peeking doesn't get the actual one. - nc = safe_vgetc(); - if (c == nc) { - continue; - } - vungetc(nc); - mouse_grid = save_mouse_grid; - mouse_row = save_mouse_row; - mouse_col = save_mouse_col; - } - } - break; - } - - if (c == K_MOUSEMOVE) { - // Mouse moved without a button pressed. - return false; - } - - // Ignore drag and release events if we didn't get a click. - if (is_click) { - got_click = true; - } else { - if (!got_click) { // didn't get click, ignore - return false; - } - if (!is_drag) { // release, reset got_click - got_click = false; - if (in_tab_line) { - in_tab_line = false; - return false; - } - } - } - - // CTRL right mouse button does CTRL-T - if (is_click && (mod_mask & MOD_MASK_CTRL) && which_button == MOUSE_RIGHT) { - if (State & MODE_INSERT) { - stuffcharReadbuff(Ctrl_O); - } - if (count > 1) { - stuffnumReadbuff(count); - } - stuffcharReadbuff(Ctrl_T); - got_click = false; // ignore drag&release now - return false; - } - - // CTRL only works with left mouse button - if ((mod_mask & MOD_MASK_CTRL) && which_button != MOUSE_LEFT) { - return false; - } - - // When a modifier is down, ignore drag and release events, as well as - // multiple clicks and the middle mouse button. - // Accept shift-leftmouse drags when 'mousemodel' is "popup.*". - if ((mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT - | MOD_MASK_META)) - && (!is_click - || (mod_mask & MOD_MASK_MULTI_CLICK) - || which_button == MOUSE_MIDDLE) - && !((mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT)) - && mouse_model_popup() - && which_button == MOUSE_LEFT) - && !((mod_mask & MOD_MASK_ALT) - && !mouse_model_popup() - && which_button == MOUSE_RIGHT)) { - return false; - } - - // If the button press was used as the movement command for an operator (eg - // "d<MOUSE>"), or it is the middle button that is held down, ignore - // drag/release events. - if (!is_click && which_button == MOUSE_MIDDLE) { - return false; - } - - if (oap != NULL) { - regname = oap->regname; - } else { - regname = 0; - } - - // Middle mouse button does a 'put' of the selected text - if (which_button == MOUSE_MIDDLE) { - if (State == MODE_NORMAL) { - // If an operator was pending, we don't know what the user wanted to do. - // Go back to normal mode: Clear the operator and beep(). - if (oap != NULL && oap->op_type != OP_NOP) { - clearopbeep(oap); - return false; - } - - // If visual was active, yank the highlighted text and put it - // before the mouse pointer position. - // In Select mode replace the highlighted text with the clipboard. - if (VIsual_active) { - if (VIsual_select) { - stuffcharReadbuff(Ctrl_G); - stuffReadbuff("\"+p"); - } else { - stuffcharReadbuff('y'); - stuffcharReadbuff(K_MIDDLEMOUSE); - } - return false; - } - // The rest is below jump_to_mouse() - } else if ((State & MODE_INSERT) == 0) { - return false; - } - - // Middle click in insert mode doesn't move the mouse, just insert the - // contents of a register. '.' register is special, can't insert that - // with do_put(). - // Also paste at the cursor if the current mode isn't in 'mouse' (only - // happens for the GUI). - if ((State & MODE_INSERT)) { - if (regname == '.') { - insert_reg(regname, true); - } else { - if (regname == 0 && eval_has_provider("clipboard")) { - regname = '*'; - } - if ((State & REPLACE_FLAG) && !yank_register_mline(regname)) { - insert_reg(regname, true); - } else { - do_put(regname, NULL, BACKWARD, 1L, - (fixindent ? PUT_FIXINDENT : 0) | PUT_CURSEND); - - // Repeat it with CTRL-R CTRL-O r or CTRL-R CTRL-P r - AppendCharToRedobuff(Ctrl_R); - AppendCharToRedobuff(fixindent ? Ctrl_P : Ctrl_O); - AppendCharToRedobuff(regname == 0 ? '"' : regname); - } - } - return false; - } - } - - // When dragging or button-up stay in the same window. - if (!is_click) { - jump_flags |= MOUSE_FOCUS | MOUSE_DID_MOVE; - } - - start_visual.lnum = 0; - - // Check for clicking in the tab page line. - if (mouse_grid <= 1 && mouse_row == 0 && firstwin->w_winrow > 0) { - if (is_drag) { - if (in_tab_line) { - move_tab_to_mouse(); - } - return false; - } - - // click in a tab selects that tab page - if (is_click && cmdwin_type == 0 && mouse_col < Columns) { - in_tab_line = true; - c1 = tab_page_click_defs[mouse_col].tabnr; - switch (tab_page_click_defs[mouse_col].type) { - case kStlClickDisabled: - break; - case kStlClickTabClose: { - tabpage_T *tp; - - // Close the current or specified tab page. - if (c1 == 999) { - tp = curtab; - } else { - tp = find_tabpage(c1); - } - if (tp == curtab) { - if (first_tabpage->tp_next != NULL) { - tabpage_close(false); - } - } else if (tp != NULL) { - tabpage_close_other(tp, false); - } - break; - } - case kStlClickTabSwitch: - if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { - // double click opens new page - end_visual_mode(); - tabpage_new(); - tabpage_move(c1 == 0 ? 9999 : c1 - 1); - } else { - // Go to specified tab page, or next one if not clicking - // on a label. - goto_tabpage(c1); - - // It's like clicking on the status line of a window. - if (curwin != old_curwin) { - end_visual_mode(); - } - } - break; - case kStlClickFuncRun: - call_click_def_func(tab_page_click_defs, mouse_col, which_button); - break; - } - } - return true; - } else if (is_drag && in_tab_line) { - move_tab_to_mouse(); - return false; - } - - // When 'mousemodel' is "popup" or "popup_setpos", translate mouse events: - // right button up -> pop-up menu - // shift-left button -> right button - // alt-left button -> alt-right button - if (mouse_model_popup()) { - if (which_button == MOUSE_RIGHT - && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) { - if (!is_click) { - // Ignore right button release events, only shows the popup - // menu on the button down event. - return false; - } - jump_flags = 0; - if (strcmp(p_mousem, "popup_setpos") == 0) { - // First set the cursor position before showing the popup - // menu. - if (VIsual_active) { - pos_T m_pos; - // set MOUSE_MAY_STOP_VIS if we are outside the - // selection or the current window (might have false - // negative here) - if (mouse_row < curwin->w_winrow - || mouse_row > (curwin->w_winrow + curwin->w_height)) { - jump_flags = MOUSE_MAY_STOP_VIS; - } else if (get_fpos_of_mouse(&m_pos) != IN_BUFFER) { - jump_flags = MOUSE_MAY_STOP_VIS; - } else { - if ((lt(curwin->w_cursor, VIsual) - && (lt(m_pos, curwin->w_cursor) || lt(VIsual, m_pos))) - || (lt(VIsual, curwin->w_cursor) - && (lt(m_pos, VIsual) || lt(curwin->w_cursor, m_pos)))) { - jump_flags = MOUSE_MAY_STOP_VIS; - } else if (VIsual_mode == Ctrl_V) { - getvcols(curwin, &curwin->w_cursor, &VIsual, &leftcol, &rightcol); - getvcol(curwin, &m_pos, NULL, &m_pos.col, NULL); - if (m_pos.col < leftcol || m_pos.col > rightcol) { - jump_flags = MOUSE_MAY_STOP_VIS; - } - } - } - } else { - jump_flags = MOUSE_MAY_STOP_VIS; - } - } - if (jump_flags) { - jump_flags = jump_to_mouse(jump_flags, NULL, which_button); - redraw_curbuf_later(VIsual_active ? UPD_INVERTED : UPD_VALID); - update_screen(); - setcursor(); - ui_flush(); // Update before showing popup menu - } - show_popupmenu(); - got_click = false; // ignore release events - return (jump_flags & CURSOR_MOVED) != 0; - } - if (which_button == MOUSE_LEFT - && (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT))) { - which_button = MOUSE_RIGHT; - mod_mask &= ~MOD_MASK_SHIFT; - } - } - - if ((State & (MODE_NORMAL | MODE_INSERT)) - && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) { - if (which_button == MOUSE_LEFT) { - if (is_click) { - // stop Visual mode for a left click in a window, but not when on a status line - if (VIsual_active) { - jump_flags |= MOUSE_MAY_STOP_VIS; - } - } else { - jump_flags |= MOUSE_MAY_VIS; - } - } else if (which_button == MOUSE_RIGHT) { - if (is_click && VIsual_active) { - // Remember the start and end of visual before moving the cursor. - if (lt(curwin->w_cursor, VIsual)) { - start_visual = curwin->w_cursor; - end_visual = VIsual; - } else { - start_visual = VIsual; - end_visual = curwin->w_cursor; - } - } - jump_flags |= MOUSE_FOCUS; - jump_flags |= MOUSE_MAY_VIS; - } - } - - // If an operator is pending, ignore all drags and releases until the next mouse click. - if (!is_drag && oap != NULL && oap->op_type != OP_NOP) { - got_click = false; - oap->motion_type = kMTCharWise; - } - - // When releasing the button let jump_to_mouse() know. - if (!is_click && !is_drag) { - jump_flags |= MOUSE_RELEASED; - } - - // JUMP! - jump_flags = jump_to_mouse(jump_flags, - oap == NULL ? NULL : &(oap->inclusive), - which_button); - - moved = (jump_flags & CURSOR_MOVED); - in_winbar = (jump_flags & MOUSE_WINBAR); - in_status_line = (jump_flags & IN_STATUS_LINE); - in_sep_line = (jump_flags & IN_SEP_LINE); - - if ((in_winbar || in_status_line) && is_click) { - // Handle click event on window bar or status lin - int click_grid = mouse_grid; - int click_row = mouse_row; - int click_col = mouse_col; - win_T *wp = mouse_find_win(&click_grid, &click_row, &click_col); - if (wp == NULL) { - return false; - } - - StlClickDefinition *click_defs = in_status_line ? wp->w_status_click_defs - : wp->w_winbar_click_defs; - - if (in_status_line && global_stl_height() > 0) { - // global statusline is displayed for the current window, - // and spans the whole screen. - click_defs = curwin->w_status_click_defs; - click_col = mouse_col; - } - - if (click_defs != NULL) { - switch (click_defs[click_col].type) { - case kStlClickDisabled: - break; - case kStlClickFuncRun: - call_click_def_func(click_defs, click_col, which_button); - break; - default: - assert(false && "winbar and statusline only support %@ for clicks"); - break; - } - } - - return false; - } else if (in_winbar) { - // A drag or release event in the window bar has no side effects. - return false; - } - - // When jumping to another window, clear a pending operator. That's a bit - // friendlier than beeping and not jumping to that window. - if (curwin != old_curwin && oap != NULL && oap->op_type != OP_NOP) { - clearop(oap); - } - - if (mod_mask == 0 - && !is_drag - && (jump_flags & (MOUSE_FOLD_CLOSE | MOUSE_FOLD_OPEN)) - && which_button == MOUSE_LEFT) { - // open or close a fold at this line - if (jump_flags & MOUSE_FOLD_OPEN) { - openFold(curwin->w_cursor, 1L); - } else { - closeFold(curwin->w_cursor, 1L); - } - // don't move the cursor if still in the same window - if (curwin == old_curwin) { - curwin->w_cursor = save_cursor; - } - } - - // Set global flag that we are extending the Visual area with mouse dragging; - // temporarily minimize 'scrolloff'. - if (VIsual_active && is_drag && get_scrolloff_value(curwin)) { - // In the very first line, allow scrolling one line - if (mouse_row == 0) { - mouse_dragging = 2; - } else { - mouse_dragging = 1; - } - } - - // When dragging the mouse above the window, scroll down. - if (is_drag && mouse_row < 0 && !in_status_line) { - scroll_redraw(false, 1L); - mouse_row = 0; - } - - if (start_visual.lnum) { // right click in visual mode - // When ALT is pressed make Visual mode blockwise. - if (mod_mask & MOD_MASK_ALT) { - VIsual_mode = Ctrl_V; - } - - // In Visual-block mode, divide the area in four, pick up the corner - // that is in the quarter that the cursor is in. - if (VIsual_mode == Ctrl_V) { - getvcols(curwin, &start_visual, &end_visual, &leftcol, &rightcol); - if (curwin->w_curswant > (leftcol + rightcol) / 2) { - end_visual.col = leftcol; - } else { - end_visual.col = rightcol; - } - if (curwin->w_cursor.lnum >= - (start_visual.lnum + end_visual.lnum) / 2) { - end_visual.lnum = start_visual.lnum; - } - - // move VIsual to the right column - start_visual = curwin->w_cursor; // save the cursor pos - curwin->w_cursor = end_visual; - coladvance(end_visual.col); - VIsual = curwin->w_cursor; - curwin->w_cursor = start_visual; // restore the cursor - } else { - // If the click is before the start of visual, change the start. - // If the click is after the end of visual, change the end. If - // the click is inside the visual, change the closest side. - if (lt(curwin->w_cursor, start_visual)) { - VIsual = end_visual; - } else if (lt(end_visual, curwin->w_cursor)) { - VIsual = start_visual; - } else { - // In the same line, compare column number - if (end_visual.lnum == start_visual.lnum) { - if (curwin->w_cursor.col - start_visual.col > - end_visual.col - curwin->w_cursor.col) { - VIsual = start_visual; - } else { - VIsual = end_visual; - } - } else { - // In different lines, compare line number - diff = (curwin->w_cursor.lnum - start_visual.lnum) - - (end_visual.lnum - curwin->w_cursor.lnum); - - if (diff > 0) { // closest to end - VIsual = start_visual; - } else if (diff < 0) { // closest to start - VIsual = end_visual; - } else { // in the middle line - if (curwin->w_cursor.col < - (start_visual.col + end_visual.col) / 2) { - VIsual = end_visual; - } else { - VIsual = start_visual; - } - } - } - } - } - } else if ((State & MODE_INSERT) && VIsual_active) { - // If Visual mode started in insert mode, execute "CTRL-O" - stuffcharReadbuff(Ctrl_O); - } - - // Middle mouse click: Put text before cursor. - if (which_button == MOUSE_MIDDLE) { - if (regname == 0 && eval_has_provider("clipboard")) { - regname = '*'; - } - if (yank_register_mline(regname)) { - if (mouse_past_bottom) { - dir = FORWARD; - } - } else if (mouse_past_eol) { - dir = FORWARD; - } - - if (fixindent) { - c1 = (dir == BACKWARD) ? '[' : ']'; - c2 = 'p'; - } else { - c1 = (dir == FORWARD) ? 'p' : 'P'; - c2 = NUL; - } - prep_redo(regname, count, NUL, c1, NUL, c2, NUL); - - // Remember where the paste started, so in edit() Insstart can be set to this position - if (restart_edit != 0) { - where_paste_started = curwin->w_cursor; - } - do_put(regname, NULL, dir, count, - (fixindent ? PUT_FIXINDENT : 0)| PUT_CURSEND); - } else if (((mod_mask & MOD_MASK_CTRL) || (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) - && bt_quickfix(curbuf)) { - // Ctrl-Mouse click or double click in a quickfix window jumps to the - // error under the mouse pointer. - if (curwin->w_llist_ref == NULL) { // quickfix window - do_cmdline_cmd(".cc"); - } else { // location list window - do_cmdline_cmd(".ll"); - } - got_click = false; // ignore drag&release now - } else if ((mod_mask & MOD_MASK_CTRL) - || (curbuf->b_help && (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK)) { - // Ctrl-Mouse click (or double click in a help window) jumps to the tag - // under the mouse pointer. - if (State & MODE_INSERT) { - stuffcharReadbuff(Ctrl_O); - } - stuffcharReadbuff(Ctrl_RSB); - got_click = false; // ignore drag&release now - } else if ((mod_mask & MOD_MASK_SHIFT)) { - // Shift-Mouse click searches for the next occurrence of the word under - // the mouse pointer - if (State & MODE_INSERT || (VIsual_active && VIsual_select)) { - stuffcharReadbuff(Ctrl_O); - } - if (which_button == MOUSE_LEFT) { - stuffcharReadbuff('*'); - } else { // MOUSE_RIGHT - stuffcharReadbuff('#'); - } - } else if (in_status_line || in_sep_line) { - // Do nothing if on status line or vertical separator - // Handle double clicks otherwise - } else if ((mod_mask & MOD_MASK_MULTI_CLICK) && (State & (MODE_NORMAL | MODE_INSERT))) { - if (is_click || !VIsual_active) { - if (VIsual_active) { - orig_cursor = VIsual; - } else { - VIsual = curwin->w_cursor; - orig_cursor = VIsual; - VIsual_active = true; - VIsual_reselect = true; - // start Select mode if 'selectmode' contains "mouse" - may_start_select('o'); - setmouse(); - } - if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { - // Double click with ALT pressed makes it blockwise. - if (mod_mask & MOD_MASK_ALT) { - VIsual_mode = Ctrl_V; - } else { - VIsual_mode = 'v'; - } - } else if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_3CLICK) { - VIsual_mode = 'V'; - } else if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_4CLICK) { - VIsual_mode = Ctrl_V; - } - } - // A double click selects a word or a block. - if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { - pos_T *pos = NULL; - int gc; - - if (is_click) { - // If the character under the cursor (skipping white space) is - // not a word character, try finding a match and select a (), - // {}, [], #if/#endif, etc. block. - end_visual = curwin->w_cursor; - while (gc = gchar_pos(&end_visual), ascii_iswhite(gc)) { - inc(&end_visual); - } - if (oap != NULL) { - oap->motion_type = kMTCharWise; - } - if (oap != NULL - && VIsual_mode == 'v' - && !vim_iswordc(gchar_pos(&end_visual)) - && equalpos(curwin->w_cursor, VIsual) - && (pos = findmatch(oap, NUL)) != NULL) { - curwin->w_cursor = *pos; - if (oap->motion_type == kMTLineWise) { - VIsual_mode = 'V'; - } else if (*p_sel == 'e') { - if (lt(curwin->w_cursor, VIsual)) { - VIsual.col++; - } else { - curwin->w_cursor.col++; - } - } - } - } - - if (pos == NULL && (is_click || is_drag)) { - // When not found a match or when dragging: extend to include a word. - if (lt(curwin->w_cursor, orig_cursor)) { - find_start_of_word(&curwin->w_cursor); - find_end_of_word(&VIsual); - } else { - find_start_of_word(&VIsual); - if (*p_sel == 'e' && *get_cursor_pos_ptr() != NUL) { - curwin->w_cursor.col += - utfc_ptr2len(get_cursor_pos_ptr()); - } - find_end_of_word(&curwin->w_cursor); - } - } - curwin->w_set_curswant = true; - } - if (is_click) { - redraw_curbuf_later(UPD_INVERTED); // update the inversion - } - } else if (VIsual_active && !old_active) { - if (mod_mask & MOD_MASK_ALT) { - VIsual_mode = Ctrl_V; - } else { - VIsual_mode = 'v'; - } - } - - // If Visual mode changed show it later. - if ((!VIsual_active && old_active && mode_displayed) - || (VIsual_active && p_smd && msg_silent == 0 - && (!old_active || VIsual_mode != old_mode))) { - redraw_cmdline = true; - } - - return moved; -} - -/// Move "pos" back to the start of the word it's in. -static void find_start_of_word(pos_T *pos) -{ - char_u *line; - int cclass; - int col; - - line = (char_u *)ml_get(pos->lnum); - cclass = get_mouse_class(line + pos->col); - - while (pos->col > 0) { - col = pos->col - 1; - col -= utf_head_off((char *)line, (char *)line + col); - if (get_mouse_class(line + col) != cclass) { - break; - } - pos->col = col; - } -} - -/// Move "pos" forward to the end of the word it's in. -/// When 'selection' is "exclusive", the position is just after the word. -static void find_end_of_word(pos_T *pos) -{ - char_u *line; - int cclass; - int col; - - line = (char_u *)ml_get(pos->lnum); - if (*p_sel == 'e' && pos->col > 0) { - pos->col--; - pos->col -= utf_head_off((char *)line, (char *)line + pos->col); - } - cclass = get_mouse_class(line + pos->col); - while (line[pos->col] != NUL) { - col = pos->col + utfc_ptr2len((char *)line + pos->col); - if (get_mouse_class(line + col) != cclass) { - if (*p_sel == 'e') { - pos->col = col; - } - break; - } - pos->col = col; - } -} - -/// Get class of a character for selection: same class means same word. -/// 0: blank -/// 1: punctuation groups -/// 2: normal word character -/// >2: multi-byte word character. -static int get_mouse_class(char_u *p) -{ - if (MB_BYTE2LEN(p[0]) > 1) { - return mb_get_class(p); - } - - const int c = *p; - if (c == ' ' || c == '\t') { - return 0; - } - if (vim_iswordc(c)) { - return 2; - } - - // There are a few special cases where we want certain combinations of - // characters to be considered as a single word. These are things like - // "->", "/ *", "*=", "+=", "&=", "<=", ">=", "!=" etc. Otherwise, each - // character is in its own class. - if (c != NUL && vim_strchr("-+*/%<>&|^!=", c) != NULL) { - return 1; - } - return c; -} - /// End Visual mode. /// This function should ALWAYS be called to end Visual mode, except from /// do_pending_operator(). @@ -2342,8 +1526,7 @@ void restore_visual_mode(void) /// @param dir the direction of searching, is either FORWARD or BACKWARD /// @param *colp is in/decremented if "ptr[-dir]" should also be included. /// @param bnp points to a counter for square brackets. -static bool find_is_eval_item(const char_u *const ptr, int *const colp, int *const bnp, - const int dir) +static bool find_is_eval_item(const char *const ptr, int *const colp, int *const bnp, const int dir) { // Accept everything inside []. if ((*ptr == ']' && dir == BACKWARD) || (*ptr == '[' && dir == FORWARD)) { @@ -2414,7 +1597,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char **text // if i == 0: try to find an identifier // if i == 1: try to find any non-white text - char_u *ptr = (char_u *)ml_get_buf(wp->w_buffer, lnum, false); + char *ptr = ml_get_buf(wp->w_buffer, lnum, false); for (i = (find_type & FIND_IDENT) ? 0 : 1; i < 2; i++) { // 1. skip to start of identifier/text col = startcol; @@ -2427,7 +1610,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char **text if (this_class != 0 && (i == 1 || this_class != 1)) { break; } - col += utfc_ptr2len((char *)ptr + col); + col += utfc_ptr2len(ptr + col); } // When starting on a ']' count it, so that we include the '['. @@ -2438,12 +1621,12 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char **text // // Remember class of character under cursor. if ((find_type & FIND_EVAL) && ptr[col] == ']') { - this_class = mb_get_class((char_u *)"a"); + this_class = mb_get_class("a"); } else { this_class = mb_get_class(ptr + col); } while (col > 0 && this_class != 0) { - prevcol = col - 1 - utf_head_off((char *)ptr, (char *)ptr + col - 1); + prevcol = col - 1 - utf_head_off(ptr, ptr + col - 1); prev_class = mb_get_class(ptr + prevcol); if (this_class != prev_class && (i == 0 @@ -2477,7 +1660,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char **text return 0; } ptr += col; - *text = (char *)ptr; + *text = ptr; if (textcol != NULL) { *textcol = col; } @@ -2495,7 +1678,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char **text || ((find_type & FIND_EVAL) && col <= (int)startcol && find_is_eval_item(ptr + col, &col, &bn, FORWARD)))) { - col += utfc_ptr2len((char *)ptr + col); + col += utfc_ptr2len(ptr + col); } assert(col >= 0); @@ -2620,9 +1803,7 @@ void may_clear_cmdline(void) } // Routines for displaying a partly typed command -#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 char old_showcmd_buf[SHOWCMD_BUFLEN]; // For push_showcmd() static bool showcmd_is_clear = true; static bool showcmd_visual = false; @@ -2661,25 +1842,25 @@ void clear_showcmd(void) getvcols(curwin, &curwin->w_cursor, &VIsual, &leftcol, &rightcol); p_sbr = saved_sbr; curwin->w_p_sbr = saved_w_sbr; - snprintf((char *)showcmd_buf, SHOWCMD_BUFLEN, "%" PRId64 "x%" PRId64, + snprintf(showcmd_buf, SHOWCMD_BUFLEN, "%" PRId64 "x%" PRId64, (int64_t)lines, (int64_t)rightcol - leftcol + 1); } else if (VIsual_mode == 'V' || VIsual.lnum != curwin->w_cursor.lnum) { - snprintf((char *)showcmd_buf, SHOWCMD_BUFLEN, "%" PRId64, (int64_t)lines); + snprintf(showcmd_buf, SHOWCMD_BUFLEN, "%" PRId64, (int64_t)lines); } else { - char_u *s, *e; + char *s, *e; int l; int bytes = 0; int chars = 0; if (cursor_bot) { s = ml_get_pos(&VIsual); - e = (char_u *)get_cursor_pos_ptr(); + e = get_cursor_pos_ptr(); } else { - s = (char_u *)get_cursor_pos_ptr(); + s = get_cursor_pos_ptr(); e = ml_get_pos(&VIsual); } while ((*p_sel != 'e') ? s <= e : s < e) { - l = utfc_ptr2len((char *)s); + l = utfc_ptr2len(s); if (l == 0) { bytes++; chars++; @@ -2690,9 +1871,9 @@ void clear_showcmd(void) s += l; } if (bytes == chars) { - sprintf((char *)showcmd_buf, "%d", chars); + snprintf(showcmd_buf, SHOWCMD_BUFLEN, "%d", chars); } else { - sprintf((char *)showcmd_buf, "%d-%d", chars, bytes); + snprintf(showcmd_buf, SHOWCMD_BUFLEN, "%d-%d", chars, bytes); } } int limit = ui_has(kUIMessages) ? SHOWCMD_BUFLEN - 1 : SHOWCMD_COLS; @@ -2745,11 +1926,11 @@ bool add_to_showcmd(int c) } } - char *p = (char *)transchar(c); + char *p = transchar(c); if (*p == ' ') { STRCPY(p, "<20>"); } - size_t old_len = STRLEN(showcmd_buf); + size_t old_len = strlen(showcmd_buf); size_t extra_len = strlen(p); size_t limit = ui_has(kUIMessages) ? SHOWCMD_BUFLEN - 1 : SHOWCMD_COLS; if (old_len + extra_len > limit) { @@ -2782,7 +1963,7 @@ static void del_from_showcmd(int len) return; } - old_len = (int)STRLEN(showcmd_buf); + old_len = (int)strlen(showcmd_buf); if (len > old_len) { len = old_len; } @@ -2815,22 +1996,31 @@ void pop_showcmd(void) static void display_showcmd(void) { + int len = (int)strlen(showcmd_buf); + showcmd_is_clear = (len == 0); + + if (*p_sloc == 's') { + win_redr_status(curwin); + setcursor(); // put cursor back where it belongs + return; + } + if (*p_sloc == 't') { + draw_tabline(); + setcursor(); // put cursor back where it belongs + return; + } + // 'showcmdloc' is "last" or empty if (p_ch == 0 && !ui_has(kUIMessages)) { - // TODO(bfredl): would be nice to show in global statusline, perhaps return; } - int len; - len = (int)STRLEN(showcmd_buf); - showcmd_is_clear = (len == 0); - if (ui_has(kUIMessages)) { MAXSIZE_TEMP_ARRAY(content, 1); MAXSIZE_TEMP_ARRAY(chunk, 2); if (len > 0) { // placeholder for future highlight support ADD_C(chunk, INTEGER_OBJ(0)); - ADD_C(chunk, STRING_OBJ(cstr_as_string((char *)showcmd_buf))); + ADD_C(chunk, STRING_OBJ(cstr_as_string(showcmd_buf))); ADD_C(content, ARRAY_OBJ(chunk)); } ui_call_msg_showcmd(content); @@ -2842,7 +2032,7 @@ static void display_showcmd(void) grid_puts_line_start(&msg_grid_adj, showcmd_row); if (!showcmd_is_clear) { - grid_puts(&msg_grid_adj, (char *)showcmd_buf, showcmd_row, sc_col, + grid_puts(&msg_grid_adj, showcmd_buf, showcmd_row, sc_col, HL_ATTR(HLF_MSG)); } @@ -3021,17 +2211,19 @@ static void nv_addsub(cmdarg_T *cap) /// CTRL-F, CTRL-B, etc: Scroll page up or down. static void nv_page(cmdarg_T *cap) { - if (!checkclearop(cap->oap)) { - if (mod_mask & MOD_MASK_CTRL) { - // <C-PageUp>: tab page back; <C-PageDown>: tab page forward - if (cap->arg == BACKWARD) { - goto_tabpage(-(int)cap->count1); - } else { - goto_tabpage((int)cap->count0); - } + if (checkclearop(cap->oap)) { + return; + } + + if (mod_mask & MOD_MASK_CTRL) { + // <C-PageUp>: tab page back; <C-PageDown>: tab page forward + if (cap->arg == BACKWARD) { + goto_tabpage(-(int)cap->count1); } else { - (void)onepage(cap->arg, cap->count1); + goto_tabpage((int)cap->count0); } + } else { + (void)onepage(cap->arg, cap->count1); } } @@ -3043,22 +2235,23 @@ static void nv_gd(oparg_T *oap, int nchar, int thisblock) size_t len; char *ptr; if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0 - || !find_decl((char_u *)ptr, len, nchar == 'd', thisblock, SEARCH_START)) { + || !find_decl(ptr, len, nchar == 'd', thisblock, SEARCH_START)) { clearopbeep(oap); - } 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; - } + return; + } + + 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; } } /// @return true if line[offset] is not inside a C-style comment or string, /// false otherwise. -static bool is_ident(char_u *line, int offset) +static bool is_ident(const char *line, int offset) { bool incomment = false; int instring = 0; @@ -3066,11 +2259,11 @@ static bool is_ident(char_u *line, int offset) for (int i = 0; i < offset && line[i] != NUL; i++) { if (instring != 0) { - if (prev != '\\' && line[i] == instring) { + if (prev != '\\' && (uint8_t)line[i] == instring) { instring = 0; } } else if ((line[i] == '"' || line[i] == '\'') && !incomment) { - instring = line[i]; + instring = (uint8_t)line[i]; } else { if (incomment) { if (prev == '*' && line[i] == '/') { @@ -3083,7 +2276,7 @@ static bool is_ident(char_u *line, int offset) } } - prev = line[i]; + prev = (uint8_t)line[i]; } return incomment == false && instring == 0; @@ -3097,9 +2290,9 @@ static bool is_ident(char_u *line, int offset) /// @param flags_arg flags passed to searchit() /// /// @return fail when not found. -bool find_decl(char_u *ptr, size_t len, bool locally, bool thisblock, int flags_arg) +bool find_decl(char *ptr, size_t len, bool locally, bool thisblock, int flags_arg) { - char_u *pat; + char *pat; pos_T old_pos; pos_T par_pos; pos_T found_pos; @@ -3115,7 +2308,7 @@ bool find_decl(char_u *ptr, size_t len, bool locally, bool thisblock, int flags_ // Put "\V" before the pattern to avoid that the special meaning of "." // and "~" causes trouble. assert(len <= INT_MAX); - sprintf((char *)pat, vim_iswordp(ptr) ? "\\V\\<%.*s\\>" : "\\V%.*s", + sprintf(pat, vim_iswordp(ptr) ? "\\V\\<%.*s\\>" : "\\V%.*s", // NOLINT(runtime/printf) (int)len, ptr); old_pos = curwin->w_cursor; save_p_ws = p_ws; @@ -3176,7 +2369,7 @@ bool find_decl(char_u *ptr, size_t len, bool locally, bool thisblock, int flags_ curwin->w_cursor.col = 0; continue; } - bool valid = is_ident((char_u *)get_cursor_line_ptr(), curwin->w_cursor.col); + bool valid = is_ident(get_cursor_line_ptr(), curwin->w_cursor.col); // If the current position is not a valid identifier and a previous match is // present, favor that one instead. @@ -3231,7 +2424,7 @@ bool find_decl(char_u *ptr, size_t len, bool locally, bool thisblock, int flags_ /// @return true if able to move cursor, false otherwise. static bool nv_screengo(oparg_T *oap, int dir, long dist) { - int linelen = linetabsize((char_u *)get_cursor_line_ptr()); + int linelen = linetabsize(get_cursor_line_ptr()); bool retval = true; bool atend = false; int n; @@ -3302,7 +2495,7 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) } curwin->w_cursor.lnum--; - linelen = linetabsize((char_u *)get_cursor_line_ptr()); + linelen = linetabsize(get_cursor_line_ptr()); if (linelen > width1) { int w = (((linelen - width1 - 1) / width2) + 1) * width2; assert(curwin->w_curswant <= INT_MAX - w); @@ -3338,7 +2531,7 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) if (curwin->w_curswant >= width1) { curwin->w_curswant -= width2; } - linelen = linetabsize((char_u *)get_cursor_line_ptr()); + linelen = linetabsize(get_cursor_line_ptr()); } } } @@ -3357,7 +2550,7 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) validate_virtcol(); colnr_T virtcol = curwin->w_virtcol; if (virtcol > (colnr_T)width1 && *get_showbreak_value(curwin) != NUL) { - virtcol -= vim_strsize((char *)get_showbreak_value(curwin)); + virtcol -= vim_strsize(get_showbreak_value(curwin)); } int c = utf_ptr2char(get_cursor_pos_ptr()); @@ -3409,7 +2602,7 @@ static void nv_mousescroll(cmdarg_T *cap) if (cap->arg == MSCR_UP || cap->arg == MSCR_DOWN) { if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { (void)onepage(cap->arg ? FORWARD : BACKWARD, 1L); - } else { + } else if (p_mousescroll_vert > 0) { cap->count1 = p_mousescroll_vert; cap->count0 = p_mousescroll_vert; nv_scroll_line(cap); @@ -3513,6 +2706,7 @@ static bool nv_z_get_count(cmdarg_T *cap, int *nchar_arg) no_mapping--; allow_keys--; (void)add_to_showcmd(nchar); + if (nchar == K_DEL || nchar == K_KDEL) { n /= 10; } else if (ascii_isdigit(nchar)) { @@ -3553,6 +2747,7 @@ static int nv_zg_zw(cmdarg_T *cap, int nchar) no_mapping--; allow_keys--; (void)add_to_showcmd(nchar); + if (vim_strchr("gGwW", nchar) == NULL) { clearopbeep(cap->oap); return OK; @@ -3578,7 +2773,7 @@ static int nv_zg_zw(cmdarg_T *cap, int nchar) len = spell_move_to(curwin, FORWARD, true, true, NULL); emsg_off--; if (len != 0 && curwin->w_cursor.col <= pos.col) { - ptr = (char *)ml_get_pos(&curwin->w_cursor); + ptr = ml_get_pos(&curwin->w_cursor); } curwin->w_cursor = pos; } @@ -3587,7 +2782,7 @@ static int nv_zg_zw(cmdarg_T *cap, int nchar) return FAIL; } assert(len <= INT_MAX); - spell_add_word((char_u *)ptr, (int)len, + spell_add_word(ptr, (int)len, nchar == 'w' || nchar == 'W' ? SPELL_ADD_BAD : SPELL_ADD_GOOD, (nchar == 'G' || nchar == 'W') ? 0 : (int)cap->count1, undo); @@ -3604,7 +2799,7 @@ static void nv_zet(cmdarg_T *cap) long old_fdl = curwin->w_p_fdl; int old_fen = curwin->w_p_fen; - int l_p_siso = (int)get_sidescrolloff_value(curwin); + int siso = (int)get_sidescrolloff_value(curwin); if (ascii_isdigit(nchar) && !nv_z_get_count(cap, &nchar)) { return; @@ -3734,8 +2929,8 @@ static void nv_zet(cmdarg_T *cap) } else { getvcol(curwin, &curwin->w_cursor, &col, NULL, NULL); } - if (col > l_p_siso) { - col -= l_p_siso; + if (col > siso) { + col -= siso; } else { col = 0; } @@ -3755,10 +2950,10 @@ static void nv_zet(cmdarg_T *cap) getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col); } n = curwin->w_width_inner - curwin_col_off(); - if (col + l_p_siso < n) { + if (col + siso < n) { col = 0; } else { - col = col + l_p_siso - n + 1; + col = col + siso - n + 1; } if (curwin->w_leftcol != col) { curwin->w_leftcol = col; @@ -4023,44 +3218,45 @@ static void nv_colon(cmdarg_T *cap) 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 && !is_lua) { - // translate "count:" into ":.,.+(count - 1)" - stuffcharReadbuff('.'); - if (cap->count0 > 1) { - stuffReadbuff(",.+"); - stuffnumReadbuff(cap->count0 - 1L); - } - } + return; + } - // When typing, don't type below an old message - if (KeyTyped) { - compute_cmdrow(); + 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 && !is_lua) { + // translate "count:" into ":.,.+(count - 1)" + stuffcharReadbuff('.'); + if (cap->count0 > 1) { + stuffReadbuff(",.+"); + stuffnumReadbuff(cap->count0 - 1L); } + } - 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); - } + // When typing, don't type below an old message + if (KeyTyped) { + compute_cmdrow(); + } - if (cmd_result == false) { - // The Ex command failed, do not execute the operator. - clearop(cap->oap); - } else if (cap->oap->op_type != OP_NOP - && (cap->oap->start.lnum > curbuf->b_ml.ml_line_count - || cap->oap->start.col > - (colnr_T)strlen(ml_get(cap->oap->start.lnum)) - || did_emsg)) { - // The start of the operator has become invalid by the Ex command. - clearopbeep(cap->oap); - } + 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 (cmd_result == false) { + // The Ex command failed, do not execute the operator. + clearop(cap->oap); + } else if (cap->oap->op_type != OP_NOP + && (cap->oap->start.lnum > curbuf->b_ml.ml_line_count + || cap->oap->start.col > + (colnr_T)strlen(ml_get(cap->oap->start.lnum)) + || did_emsg)) { + // The start of the operator has become invalid by the Ex command. + clearopbeep(cap->oap); } } @@ -4091,14 +3287,16 @@ static void nv_ctrlh(cmdarg_T *cap) /// CTRL-L: clear screen and redraw. static void nv_clear(cmdarg_T *cap) { - if (!checkclearop(cap->oap)) { - // Clear all syntax states to force resyncing. - syn_stack_free_all(curwin->w_s); - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - wp->w_s->b_syn_slow = false; - } - redraw_later(curwin, UPD_CLEAR); + if (checkclearop(cap->oap)) { + return; } + + // Clear all syntax states to force resyncing. + syn_stack_free_all(curwin->w_s); + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + wp->w_s->b_syn_slow = false; + } + redraw_later(curwin, UPD_CLEAR); } /// CTRL-O: In Select mode: switch to Visual mode for one command. @@ -4129,21 +3327,23 @@ static void nv_hat(cmdarg_T *cap) /// "Z" commands. static void nv_Zet(cmdarg_T *cap) { - if (!checkclearopq(cap->oap)) { - switch (cap->nchar) { - // "ZZ": equivalent to ":x". - case 'Z': - do_cmdline_cmd("x"); - break; + if (checkclearopq(cap->oap)) { + return; + } - // "ZQ": equivalent to ":q!" (Elvis compatible). - case 'Q': - do_cmdline_cmd("q!"); - break; + switch (cap->nchar) { + // "ZZ": equivalent to ":x". + case 'Z': + do_cmdline_cmd("x"); + break; - default: - clearopbeep(cap->oap); - } + // "ZQ": equivalent to ":q!" (Elvis compatible). + case 'Q': + do_cmdline_cmd("q!"); + break; + + default: + clearopbeep(cap->oap); } } @@ -4274,7 +3474,7 @@ static void nv_ident(cmdarg_T *cap) // Allocate buffer to put the command in. Inserting backslashes can // double the length of the word. p_kp / curbuf->b_p_kp could be added // and some numbers. - char *kp = *curbuf->b_p_kp == NUL ? (char *)p_kp : curbuf->b_p_kp; // 'keywordprg' + char *kp = *curbuf->b_p_kp == NUL ? p_kp : curbuf->b_p_kp; // 'keywordprg' bool kp_help = (*kp == NUL || strcmp(kp, ":he") == 0 || strcmp(kp, ":help") == 0); if (kp_help && *skipwhite(ptr) == NUL) { emsg(_(e_noident)); // found white space only @@ -4295,7 +3495,7 @@ static void nv_ident(cmdarg_T *cap) setpcmark(); curwin->w_cursor.col = (colnr_T)(ptr - get_cursor_line_ptr()); - if (!g_cmd && vim_iswordp((char_u *)ptr)) { + if (!g_cmd && vim_iswordp(ptr)) { STRCPY(buf, "\\<"); } no_smartcase = true; // don't use 'smartcase' now @@ -4310,11 +3510,7 @@ static void nv_ident(cmdarg_T *cap) case ']': tag_cmd = true; - if (p_cst) { - STRCPY(buf, "cstag "); - } else { - STRCPY(buf, "ts "); - } + STRCPY(buf, "ts "); break; default: @@ -4338,7 +3534,7 @@ static void nv_ident(cmdarg_T *cap) p = vim_strsave_fnameescape((const char *)ptr, VSE_NONE); } else { // Escape the argument properly for a shell command - p = (char *)vim_strsave_shellescape((char_u *)ptr, true, true); + p = vim_strsave_shellescape(ptr, true, true); } xfree(ptr); char *newbuf = xrealloc(buf, strlen(buf) + strlen(p) + 1); @@ -4347,9 +3543,9 @@ static void nv_ident(cmdarg_T *cap) xfree(p); } else { if (cmdchar == '*') { - aux_ptr = (p_magic ? "/.*~[^$\\" : "/^$\\"); + aux_ptr = (magic_isset() ? "/.*~[^$\\" : "/^$\\"); } else if (cmdchar == '#') { - aux_ptr = (p_magic ? "/?.*~[^$\\" : "/?^$\\"); + aux_ptr = (magic_isset() ? "/?.*~[^$\\" : "/?^$\\"); } else if (tag_cmd) { if (curbuf->b_help) { // ":help" handles unescaped argument @@ -4361,10 +3557,10 @@ static void nv_ident(cmdarg_T *cap) aux_ptr = "\\|\"\n*?["; } - p = buf + STRLEN(buf); + p = buf + strlen(buf); while (n-- > 0) { // put a backslash before \ and some others - if (vim_strchr(aux_ptr, *ptr) != NULL) { + if (vim_strchr(aux_ptr, (uint8_t)(*ptr)) != NULL) { *p++ = '\\'; } // When current byte is a part of multibyte character, copy all @@ -4381,7 +3577,7 @@ static void nv_ident(cmdarg_T *cap) // Execute the command. if (cmdchar == '*' || cmdchar == '#') { if (!g_cmd - && vim_iswordp(mb_prevptr((char_u *)get_cursor_line_ptr(), (char_u *)ptr))) { + && vim_iswordp(mb_prevptr(get_cursor_line_ptr(), ptr))) { STRCAT(buf, "\\>"); } @@ -4428,10 +3624,10 @@ bool get_visual_text(cmdarg_T *cap, char **pp, size_t *lenp) *lenp = strlen(*pp); } else { if (lt(curwin->w_cursor, VIsual)) { - *pp = (char *)ml_get_pos(&curwin->w_cursor); + *pp = ml_get_pos(&curwin->w_cursor); *lenp = (size_t)VIsual.col - (size_t)curwin->w_cursor.col + 1; } else { - *pp = (char *)ml_get_pos(&VIsual); + *pp = ml_get_pos(&VIsual); *lenp = (size_t)curwin->w_cursor.col - (size_t)VIsual.col + 1; } if (**pp == NUL) { @@ -4477,7 +3673,9 @@ static void nv_scroll(cmdarg_T *cap) && curwin->w_cursor.lnum > curwin->w_topline; n--) { (void)hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL); - curwin->w_cursor.lnum--; + if (curwin->w_cursor.lnum > curwin->w_topline) { + curwin->w_cursor.lnum--; + } } } else { curwin->w_cursor.lnum -= (linenr_T)cap->count1 - 1; @@ -4647,10 +3845,10 @@ static void nv_left(cmdarg_T *cap) // Don't adjust op_end now, otherwise it won't work. if ((cap->oap->op_type == OP_DELETE || cap->oap->op_type == OP_CHANGE) && !LINEEMPTY(curwin->w_cursor.lnum)) { - char_u *cp = (char_u *)get_cursor_pos_ptr(); + char *cp = get_cursor_pos_ptr(); if (*cp != NUL) { - curwin->w_cursor.col += utfc_ptr2len((char *)cp); + curwin->w_cursor.col += utfc_ptr2len(cp); } cap->retval |= CA_NO_ADJ_OP_END; } @@ -4676,13 +3874,14 @@ static void nv_up(cmdarg_T *cap) // <S-Up> is page up cap->arg = BACKWARD; nv_page(cap); - } else { - cap->oap->motion_type = kMTLineWise; - if (cursor_up(cap->count1, cap->oap->op_type == OP_NOP) == false) { - clearopbeep(cap->oap); - } else if (cap->arg) { - beginline(BL_WHITE | BL_FIX); - } + return; + } + + cap->oap->motion_type = kMTLineWise; + if (cursor_up(cap->count1, cap->oap->op_type == OP_NOP) == false) { + clearopbeep(cap->oap); + } else if (cap->arg) { + beginline(BL_WHITE | BL_FIX); } } @@ -4722,14 +3921,10 @@ static void nv_down(cmdarg_T *cap) /// Grab the file name under the cursor and edit it. static void nv_gotofile(cmdarg_T *cap) { - char_u *ptr; + char *ptr; linenr_T lnum = -1; - if (check_text_locked(cap->oap)) { - return; - } - if (curbuf_locked()) { - clearop(cap->oap); + if (check_text_or_curbuf_locked(cap->oap)) { return; } @@ -4741,7 +3936,7 @@ static void nv_gotofile(cmdarg_T *cap) (void)autowrite(curbuf, false); } setpcmark(); - if (do_ecmd(0, (char *)ptr, NULL, NULL, ECMD_LAST, + if (do_ecmd(0, ptr, NULL, NULL, ECMD_LAST, buf_hide(curbuf) ? ECMD_HIDE : 0, curwin) == OK && cap->nchar == 'F' && lnum >= 0) { curwin->w_cursor.lnum = lnum; @@ -4802,7 +3997,7 @@ static void nv_search(cmdarg_T *cap) // When using 'incsearch' the cursor may be moved to set a different search // start position. - cap->searchbuf = (char *)getcmdline(cap->cmdchar, cap->count1, 0, true); + cap->searchbuf = getcmdline(cap->cmdchar, cap->count1, 0, true); if (cap->searchbuf == NULL) { clearop(oap); @@ -4848,7 +4043,7 @@ static int normal_search(cmdarg_T *cap, int dir, char *pat, int opt, int *wrappe curwin->w_set_curswant = true; CLEAR_FIELD(sia); - int i = do_search(cap->oap, dir, dir, (char_u *)pat, cap->count1, + int i = do_search(cap->oap, dir, dir, pat, cap->count1, opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, &sia); if (wrapped != NULL) { *wrapped = sia.sa_wrapped; @@ -4888,22 +4083,23 @@ static void nv_csearch(cmdarg_T *cap) cap->oap->motion_type = kMTCharWise; if (IS_SPECIAL(cap->nchar) || searchc(cap, t_cmd) == false) { clearopbeep(cap->oap); - } else { - curwin->w_set_curswant = true; - // Include a Tab for "tx" and for "dfx". - if (gchar_cursor() == TAB && virtual_active() && cap->arg == FORWARD - && (t_cmd || cap->oap->op_type != OP_NOP)) { - colnr_T scol, ecol; + return; + } - getvcol(curwin, &curwin->w_cursor, &scol, NULL, &ecol); - curwin->w_cursor.coladd = ecol - scol; - } else { - curwin->w_cursor.coladd = 0; - } - adjust_for_sel(cap); - if ((fdo_flags & FDO_HOR) && KeyTyped && cap->oap->op_type == OP_NOP) { - foldOpenCursor(); - } + curwin->w_set_curswant = true; + // Include a Tab for "tx" and for "dfx". + if (gchar_cursor() == TAB && virtual_active() && cap->arg == FORWARD + && (t_cmd || cap->oap->op_type != OP_NOP)) { + colnr_T scol, ecol; + + getvcol(curwin, &curwin->w_cursor, &scol, NULL, &ecol); + curwin->w_cursor.coladd = ecol - scol; + } else { + curwin->w_cursor.coladd = 0; + } + adjust_for_sel(cap); + if ((fdo_flags & FDO_HOR) && KeyTyped && cap->oap->op_type == OP_NOP) { + foldOpenCursor(); } } @@ -5057,7 +4253,7 @@ static void nv_brackets(cmdarg_T *cap) } else { // Make a copy, if the line was changed it will be freed. ptr = xstrnsave(ptr, len); - find_pattern_in_path((char_u *)ptr, 0, len, true, + find_pattern_in_path(ptr, 0, len, true, cap->count0 == 0 ? !isupper(cap->nchar) : false, (((cap->nchar & 0xf) == ('d' & 0xf)) ? FIND_DEFINE @@ -5150,9 +4346,8 @@ static void nv_brackets(cmdarg_T *cap) cap->nchar == 's', false, NULL) == 0) { clearopbeep(cap->oap); break; - } else { - curwin->w_set_curswant = true; } + curwin->w_set_curswant = true; } if (cap->oap->op_type == OP_NOP && (fdo_flags & FDO_SEARCH) && KeyTyped) { foldOpenCursor(); @@ -5226,25 +4421,28 @@ static void nv_brace(cmdarg_T *cap) cap->oap->inclusive = false; curwin->w_set_curswant = true; - if (findsent(cap->arg, cap->count1) == false) { + if (findsent(cap->arg, cap->count1) == FAIL) { clearopbeep(cap->oap); - } else { - // Don't leave the cursor on the NUL past end of line. - adjust_cursor(cap->oap); - curwin->w_cursor.coladd = 0; - if ((fdo_flags & FDO_BLOCK) && KeyTyped && cap->oap->op_type == OP_NOP) { - foldOpenCursor(); - } + return; + } + + // Don't leave the cursor on the NUL past end of line. + adjust_cursor(cap->oap); + curwin->w_cursor.coladd = 0; + if ((fdo_flags & FDO_BLOCK) && KeyTyped && cap->oap->op_type == OP_NOP) { + foldOpenCursor(); } } /// "m" command: Mark a position. static void nv_mark(cmdarg_T *cap) { - if (!checkclearop(cap->oap)) { - if (setmark(cap->nchar) == false) { - clearopbeep(cap->oap); - } + if (checkclearop(cap->oap)) { + return; + } + + if (setmark(cap->nchar) == false) { + clearopbeep(cap->oap); } } @@ -5258,11 +4456,12 @@ static void nv_findpar(cmdarg_T *cap) curwin->w_set_curswant = true; if (!findpar(&cap->oap->inclusive, cap->arg, cap->count1, NUL, false)) { clearopbeep(cap->oap); - } else { - curwin->w_cursor.coladd = 0; - if ((fdo_flags & FDO_BLOCK) && KeyTyped && cap->oap->op_type == OP_NOP) { - foldOpenCursor(); - } + return; + } + + curwin->w_cursor.coladd = 0; + if ((fdo_flags & FDO_BLOCK) && KeyTyped && cap->oap->op_type == OP_NOP) { + foldOpenCursor(); } } @@ -5283,20 +4482,22 @@ static void nv_undo(cmdarg_T *cap) /// <Undo> command. static void nv_kundo(cmdarg_T *cap) { - if (!checkclearopq(cap->oap)) { - if (bt_prompt(curbuf)) { - clearopbeep(cap->oap); - return; - } - u_undo((int)cap->count1); - curwin->w_set_curswant = true; + if (checkclearopq(cap->oap)) { + return; + } + + if (bt_prompt(curbuf)) { + clearopbeep(cap->oap); + return; } + u_undo((int)cap->count1); + curwin->w_set_curswant = true; } /// Handle the "r" command. static void nv_replace(cmdarg_T *cap) { - char_u *ptr; + char *ptr; int had_ctrl_v; if (checkclearop(cap->oap)) { @@ -5359,8 +4560,8 @@ static void nv_replace(cmdarg_T *cap) } // Abort if not enough characters to replace. - ptr = (char_u *)get_cursor_pos_ptr(); - if (STRLEN(ptr) < (unsigned)cap->count1 + ptr = get_cursor_pos_ptr(); + if (strlen(ptr) < (unsigned)cap->count1 || (mb_charlen(ptr) < cap->count1)) { clearopbeep(cap->oap); return; @@ -5501,15 +4702,20 @@ static void nv_Replace(cmdarg_T *cap) VIsual_mode_orig = VIsual_mode; // remember original area for gv VIsual_mode = 'V'; nv_operator(cap); - } else if (!checkclearopq(cap->oap)) { - if (!MODIFIABLE(curbuf)) { - emsg(_(e_modifiable)); - } else { - if (virtual_active()) { - coladvance(getviscol()); - } - invoke_edit(cap, false, cap->arg ? 'V' : 'R', false); + return; + } + + if (checkclearopq(cap->oap)) { + return; + } + + if (!MODIFIABLE(curbuf)) { + emsg(_(e_modifiable)); + } else { + if (virtual_active()) { + coladvance(getviscol()); } + invoke_edit(cap, false, cap->arg ? 'V' : 'R', false); } } @@ -5520,20 +4726,25 @@ static void nv_vreplace(cmdarg_T *cap) cap->cmdchar = 'r'; cap->nchar = cap->extra_char; nv_replace(cap); // Do same as "r" in Visual mode for now - } else if (!checkclearopq(cap->oap)) { - if (!MODIFIABLE(curbuf)) { - emsg(_(e_modifiable)); - } else { - if (cap->extra_char == Ctrl_V) { // get another character - cap->extra_char = get_literal(false); - } - stuffcharReadbuff(cap->extra_char); - stuffcharReadbuff(ESC); - if (virtual_active()) { - coladvance(getviscol()); - } - invoke_edit(cap, true, 'v', false); + return; + } + + if (checkclearopq(cap->oap)) { + return; + } + + if (!MODIFIABLE(curbuf)) { + emsg(_(e_modifiable)); + } else { + if (cap->extra_char == Ctrl_V) { // get another character + cap->extra_char = get_literal(false); } + stuffcharReadbuff(cap->extra_char); + stuffcharReadbuff(ESC); + if (virtual_active()) { + coladvance(getviscol()); + } + invoke_edit(cap, true, 'v', false); } } @@ -5691,6 +4902,10 @@ static void nv_gomark(cmdarg_T *cap) { int name; MarkMove flags = jop_flags & JOP_VIEW ? kMarkSetView : 0; // flags for moving to the mark + if (cap->oap->op_type != OP_NOP) { + // When there is a pending operator, do not restore the view as this is usually unexpected. + flags = 0; + } MarkMoveRes move_res = 0; // Result from moving to the mark const bool old_KeyTyped = KeyTyped; // getting file may reset it @@ -5730,42 +4945,44 @@ static void nv_pcmark(cmdarg_T *cap) MarkMoveRes move_res = 0; // Result from moving to the mark const bool old_KeyTyped = KeyTyped; // getting file may reset it. - if (!checkclearopq(cap->oap)) { - if (cap->cmdchar == TAB && mod_mask == MOD_MASK_CTRL) { - if (!goto_tabpage_lastused()) { - clearopbeep(cap->oap); - } - return; - } + if (checkclearopq(cap->oap)) { + return; + } - if (cap->cmdchar == 'g') { - fm = get_changelist(curbuf, curwin, (int)cap->count1); - } else { - fm = get_jumplist(curwin, (int)cap->count1); - flags |= KMarkNoContext | kMarkJumpList; - } - // Changelist and jumplist have their own error messages. Therefore avoid - // calling nv_mark_move_to() when not found to avoid incorrect error - // messages. - if (fm != NULL) { - move_res = nv_mark_move_to(cap, flags, fm); - } else if (cap->cmdchar == 'g') { - if (curbuf->b_changelistlen == 0) { - emsg(_("E664: changelist is empty")); - } else if (cap->count1 < 0) { - emsg(_("E662: At start of changelist")); - } else { - emsg(_("E663: At end of changelist")); - } - } else { + if (cap->cmdchar == TAB && mod_mask == MOD_MASK_CTRL) { + if (!goto_tabpage_lastused()) { clearopbeep(cap->oap); } - if (cap->oap->op_type == OP_NOP - && (move_res & kMarkSwitchedBuf || move_res & kMarkChangedLine) - && (fdo_flags & FDO_MARK) - && old_KeyTyped) { - foldOpenCursor(); + return; + } + + if (cap->cmdchar == 'g') { + fm = get_changelist(curbuf, curwin, (int)cap->count1); + } else { + fm = get_jumplist(curwin, (int)cap->count1); + flags |= KMarkNoContext | kMarkJumpList; + } + // Changelist and jumplist have their own error messages. Therefore avoid + // calling nv_mark_move_to() when not found to avoid incorrect error + // messages. + if (fm != NULL) { + move_res = nv_mark_move_to(cap, flags, fm); + } else if (cap->cmdchar == 'g') { + if (curbuf->b_changelistlen == 0) { + emsg(_("E664: changelist is empty")); + } else if (cap->count1 < 0) { + emsg(_("E662: At start of changelist")); + } else { + emsg(_("E663: At end of changelist")); } + } else { + clearopbeep(cap->oap); + } + if (cap->oap->op_type == OP_NOP + && (move_res & kMarkSwitchedBuf || move_res & kMarkChangedLine) + && (fdo_flags & FDO_MARK) + && old_KeyTyped) { + foldOpenCursor(); } } @@ -5840,10 +5057,12 @@ static void nv_visual(cmdarg_T *cap) VIsual_mode = resel_VIsual_mode; if (VIsual_mode == 'v') { if (resel_VIsual_line_count <= 1) { - validate_virtcol(); + update_curswant_force(); assert(cap->count0 >= INT_MIN && cap->count0 <= INT_MAX); - curwin->w_curswant = (curwin->w_virtcol - + resel_VIsual_vcol * (int)cap->count0 - 1); + curwin->w_curswant += resel_VIsual_vcol * (int)cap->count0; + if (*p_sel != 'e') { + curwin->w_curswant--; + } } else { curwin->w_curswant = resel_VIsual_vcol; } @@ -5853,10 +5072,9 @@ static void nv_visual(cmdarg_T *cap) curwin->w_curswant = MAXCOL; coladvance(MAXCOL); } else if (VIsual_mode == Ctrl_V) { - validate_virtcol(); + update_curswant_force(); assert(cap->count0 >= INT_MIN && cap->count0 <= INT_MAX); - curwin->w_curswant = (curwin->w_virtcol - + resel_VIsual_vcol * (int)cap->count0 - 1); + curwin->w_curswant += resel_VIsual_vcol * (int)cap->count0 - 1; coladvance(curwin->w_curswant); } else { curwin->w_set_curswant = true; @@ -6067,7 +5285,7 @@ static void nv_g_underscore_cmd(cmdarg_T *cap) return; } - char_u *ptr = (char_u *)get_cursor_line_ptr(); + char *ptr = get_cursor_line_ptr(); // In Visual mode we may end up after the line. if (curwin->w_cursor.col > 0 && ptr[curwin->w_cursor.col] == NUL) { @@ -6105,9 +5323,7 @@ static void nv_g_dollar_cmd(cmdarg_T *cap) coladvance((colnr_T)i); // Make sure we stick in this column. - validate_virtcol(); - curwin->w_curswant = curwin->w_virtcol; - curwin->w_set_curswant = false; + update_curswant_force(); if (curwin->w_cursor.col > 0 && curwin->w_p_wrap) { // Check for landing on a character that got split at // the end of the line. We do not want to advance to @@ -6138,9 +5354,7 @@ static void nv_g_dollar_cmd(cmdarg_T *cap) } // Make sure we stick in this column. - validate_virtcol(); - curwin->w_curswant = curwin->w_virtcol; - curwin->w_set_curswant = false; + update_curswant_force(); } } @@ -6278,7 +5492,7 @@ static void nv_g_cmd(cmdarg_T *cap) case 'M': oap->motion_type = kMTCharWise; oap->inclusive = false; - i = linetabsize((char_u *)get_cursor_line_ptr()); + i = linetabsize(get_cursor_line_ptr()); if (cap->count0 > 0 && cap->count0 <= 100) { coladvance((colnr_T)(i * cap->count0 / 100)); } else { @@ -6499,43 +5713,46 @@ static void nv_g_cmd(cmdarg_T *cap) /// Handle "o" and "O" commands. static void n_opencmd(cmdarg_T *cap) { - if (!checkclearopq(cap->oap)) { - if (cap->cmdchar == 'O') { - // Open above the first line of a folded sequence of lines - (void)hasFolding(curwin->w_cursor.lnum, - &curwin->w_cursor.lnum, NULL); - } else { - // Open below the last line of a folded sequence of lines - (void)hasFolding(curwin->w_cursor.lnum, - NULL, &curwin->w_cursor.lnum); - } - if (u_save((linenr_T)(curwin->w_cursor.lnum - - (cap->cmdchar == 'O' ? 1 : 0)), - (linenr_T)(curwin->w_cursor.lnum + - (cap->cmdchar == 'o' ? 1 : 0)) - ) - && open_line(cap->cmdchar == 'O' ? BACKWARD : FORWARD, - 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; - } - invoke_edit(cap, false, cap->cmdchar, true); + if (checkclearopq(cap->oap)) { + return; + } + + if (cap->cmdchar == 'O') { + // Open above the first line of a folded sequence of lines + (void)hasFolding(curwin->w_cursor.lnum, + &curwin->w_cursor.lnum, NULL); + } else { + // Open below the last line of a folded sequence of lines + (void)hasFolding(curwin->w_cursor.lnum, + NULL, &curwin->w_cursor.lnum); + } + if (u_save((linenr_T)(curwin->w_cursor.lnum - + (cap->cmdchar == 'O' ? 1 : 0)), + (linenr_T)(curwin->w_cursor.lnum + + (cap->cmdchar == 'o' ? 1 : 0))) + && open_line(cap->cmdchar == 'O' ? BACKWARD : FORWARD, + 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; } + invoke_edit(cap, false, cap->cmdchar, true); } } /// "." command: redo last change. static void nv_dot(cmdarg_T *cap) { - if (!checkclearopq(cap->oap)) { - // If "restart_edit" is true, the last but one command is repeated - // instead of the last command (inserting text). This is used for - // CTRL-O <.> in insert mode. - if (start_redo(cap->count0, restart_edit != 0 && !arrow_used) == false) { - clearopbeep(cap->oap); - } + if (checkclearopq(cap->oap)) { + return; + } + + // If "restart_edit" is true, the last but one command is repeated + // instead of the last command (inserting text). This is used for + // CTRL-O <.> in insert mode. + if (start_redo(cap->count0, restart_edit != 0 && !arrow_used) == false) { + clearopbeep(cap->oap); } } @@ -6559,10 +5776,12 @@ static void nv_redo_or_register(cmdarg_T *cap) return; } - if (!checkclearopq(cap->oap)) { - u_redo((int)cap->count1); - curwin->w_set_curswant = true; + if (checkclearopq(cap->oap)) { + return; } + + u_redo((int)cap->count1); + curwin->w_set_curswant = true; } /// Handle "U" command. @@ -6574,10 +5793,15 @@ static void nv_Undo(cmdarg_T *cap) cap->cmdchar = 'g'; cap->nchar = 'U'; nv_operator(cap); - } else if (!checkclearopq(cap->oap)) { - u_undoline(); - curwin->w_set_curswant = true; + return; + } + + if (checkclearopq(cap->oap)) { + return; } + + u_undoline(); + curwin->w_set_curswant = true; } /// '~' command: If tilde is not an operator and Visual is off: swap case of a @@ -7060,7 +6284,7 @@ static void nv_object(cmdarg_T *cap) { bool flag; bool include; - char_u *mps_save; + char *mps_save; if (cap->cmdchar == 'i') { include = false; // "ix" = inner object: exclude white space @@ -7068,7 +6292,7 @@ static void nv_object(cmdarg_T *cap) include = true; // "ax" = an object: include white space } // Make sure (), [], {} and <> are in 'matchpairs' - mps_save = (char_u *)curbuf->b_p_mps; + mps_save = curbuf->b_p_mps; curbuf->b_p_mps = "(:),{:},[:],<:>"; switch (cap->nchar) { @@ -7123,7 +6347,7 @@ static void nv_object(cmdarg_T *cap) break; } - curbuf->b_p_mps = (char *)mps_save; + curbuf->b_p_mps = mps_save; if (!flag) { clearopbeep(cap->oap); } @@ -7140,16 +6364,21 @@ static void nv_record(cmdarg_T *cap) cap->cmdchar = 'g'; cap->nchar = 'q'; nv_operator(cap); - } else if (!checkclearop(cap->oap)) { - if (cap->nchar == ':' || cap->nchar == '/' || cap->nchar == '?') { - stuffcharReadbuff(cap->nchar); - stuffcharReadbuff(K_CMDWIN); - } else { - // (stop) recording into a named register, unless executing a - // register. - if (reg_executing == 0 && do_record(cap->nchar) == FAIL) { - clearopbeep(cap->oap); - } + return; + } + + if (checkclearop(cap->oap)) { + return; + } + + if (cap->nchar == ':' || cap->nchar == '/' || cap->nchar == '?') { + stuffcharReadbuff(cap->nchar); + stuffcharReadbuff(K_CMDWIN); + } else { + // (stop) recording into a named register, unless executing a + // register. + if (reg_executing == 0 && do_record(cap->nchar) == FAIL) { + clearopbeep(cap->oap); } } } @@ -7191,24 +6420,29 @@ static void nv_join(cmdarg_T *cap) { if (VIsual_active) { // join the visual lines nv_operator(cap); - } else if (!checkclearop(cap->oap)) { - if (cap->count0 <= 1) { - cap->count0 = 2; // default for join is two lines! - } - if (curwin->w_cursor.lnum + cap->count0 - 1 > - curbuf->b_ml.ml_line_count) { - // can't join when on the last line - if (cap->count0 <= 2) { - clearopbeep(cap->oap); - return; - } - cap->count0 = curbuf->b_ml.ml_line_count - curwin->w_cursor.lnum + 1; - } + return; + } - prep_redo(cap->oap->regname, cap->count0, - NUL, cap->cmdchar, NUL, NUL, cap->nchar); - do_join((size_t)cap->count0, cap->nchar == NUL, true, true, true); + if (checkclearop(cap->oap)) { + return; + } + + if (cap->count0 <= 1) { + cap->count0 = 2; // default for join is two lines! } + if (curwin->w_cursor.lnum + cap->count0 - 1 > + curbuf->b_ml.ml_line_count) { + // can't join when on the last line + if (cap->count0 <= 2) { + clearopbeep(cap->oap); + return; + } + cap->count0 = curbuf->b_ml.ml_line_count - curwin->w_cursor.lnum + 1; + } + + prep_redo(cap->oap->regname, cap->count0, + NUL, cap->cmdchar, NUL, NUL, cap->nchar); + do_join((size_t)cap->count0, cap->nchar == NUL, true, true, true); } /// "P", "gP", "p" and "gp" commands. @@ -7238,117 +6472,121 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent) } else { clearopbeep(cap->oap); } - } else if (bt_prompt(curbuf) && !prompt_curpos_editable()) { - clearopbeep(cap->oap); - } else { - if (fix_indent) { - dir = (cap->cmdchar == ']' && cap->nchar == 'p') - ? FORWARD : BACKWARD; - flags |= PUT_FIXINDENT; - } else { - dir = (cap->cmdchar == 'P' - || ((cap->cmdchar == 'g' || cap->cmdchar == 'z') - && cap->nchar == 'P')) ? BACKWARD : FORWARD; - } - prep_redo_cmd(cap); - if (cap->cmdchar == 'g') { - flags |= PUT_CURSEND; - } else if (cap->cmdchar == 'z') { - flags |= PUT_BLOCK_INNER; - } - - if (VIsual_active) { - // Putting in Visual mode: The put text replaces the selected - // text. First delete the selected text, then put the new text. - // Need to save and restore the registers that the delete - // overwrites if the old contents is being put. - was_visual = true; - regname = cap->oap->regname; - bool keep_registers = cap->cmdchar == 'P'; - // '+' and '*' could be the same selection - 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 - savereg = copy_register(regname); - } + return; + } - // To place the cursor correctly after a blockwise put, and to leave the - // text in the correct position when putting over a selection with - // 'virtualedit' and past the end of the line, we use the 'c' operator in - // 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. - cap->cmdchar = 'd'; - cap->nchar = NUL; - cap->oap->regname = keep_registers ? '_' : NUL; - msg_silent++; - nv_operator(cap); - do_pending_operator(cap, 0, false); - empty = (curbuf->b_ml.ml_flags & ML_EMPTY); - msg_silent--; + if (bt_prompt(curbuf) && !prompt_curpos_editable()) { + clearopbeep(cap->oap); + return; + } - // delete PUT_LINE_BACKWARD; - cap->oap->regname = regname; - } + if (fix_indent) { + dir = (cap->cmdchar == ']' && cap->nchar == 'p') + ? FORWARD : BACKWARD; + flags |= PUT_FIXINDENT; + } else { + dir = (cap->cmdchar == 'P' + || ((cap->cmdchar == 'g' || cap->cmdchar == 'z') + && cap->nchar == 'P')) ? BACKWARD : FORWARD; + } + prep_redo_cmd(cap); + if (cap->cmdchar == 'g') { + flags |= PUT_CURSEND; + } else if (cap->cmdchar == 'z') { + flags |= PUT_BLOCK_INNER; + } - // When deleted a linewise Visual area, put the register as - // lines to avoid it joined with the next line. When deletion was - // charwise, split a line when putting lines. - if (VIsual_mode == 'V') { - flags |= PUT_LINE; - } else if (VIsual_mode == 'v') { - flags |= PUT_LINE_SPLIT; - } - if (VIsual_mode == Ctrl_V && dir == FORWARD) { - flags |= PUT_LINE_FORWARD; - } - dir = BACKWARD; - if ((VIsual_mode != 'V' - && curwin->w_cursor.col < curbuf->b_op_start.col) - || (VIsual_mode == 'V' - && curwin->w_cursor.lnum < curbuf->b_op_start.lnum)) { - // cursor is at the end of the line or end of file, put - // forward. - dir = FORWARD; - } - // May have been reset in do_put(). - VIsual_active = true; + if (VIsual_active) { + // Putting in Visual mode: The put text replaces the selected + // text. First delete the selected text, then put the new text. + // Need to save and restore the registers that the delete + // overwrites if the old contents is being put. + was_visual = true; + regname = cap->oap->regname; + bool keep_registers = cap->cmdchar == 'P'; + // '+' and '*' could be the same selection + 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 + savereg = copy_register(regname); + } + + // To place the cursor correctly after a blockwise put, and to leave the + // text in the correct position when putting over a selection with + // 'virtualedit' and past the end of the line, we use the 'c' operator in + // 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. + cap->cmdchar = 'd'; + cap->nchar = NUL; + cap->oap->regname = keep_registers ? '_' : NUL; + msg_silent++; + nv_operator(cap); + do_pending_operator(cap, 0, false); + empty = (curbuf->b_ml.ml_flags & ML_EMPTY); + msg_silent--; + + // delete PUT_LINE_BACKWARD; + cap->oap->regname = regname; + } + + // When deleted a linewise Visual area, put the register as + // lines to avoid it joined with the next line. When deletion was + // charwise, split a line when putting lines. + if (VIsual_mode == 'V') { + flags |= PUT_LINE; + } else if (VIsual_mode == 'v') { + flags |= PUT_LINE_SPLIT; + } + if (VIsual_mode == Ctrl_V && dir == FORWARD) { + flags |= PUT_LINE_FORWARD; + } + dir = BACKWARD; + if ((VIsual_mode != 'V' + && curwin->w_cursor.col < curbuf->b_op_start.col) + || (VIsual_mode == 'V' + && curwin->w_cursor.lnum < curbuf->b_op_start.lnum)) { + // cursor is at the end of the line or end of file, put + // forward. + dir = FORWARD; } - do_put(cap->oap->regname, savereg, dir, cap->count1, flags); + // May have been reset in do_put(). + VIsual_active = true; + } + do_put(cap->oap->regname, savereg, dir, cap->count1, flags); - // If a register was saved, free it - if (savereg != NULL) { - free_register(savereg); - xfree(savereg); - } + // If a register was saved, free it + if (savereg != NULL) { + free_register(savereg); + xfree(savereg); + } - // What to reselect with "gv"? Selecting the just put text seems to - // be the most useful, since the original text was removed. - if (was_visual) { - curbuf->b_visual.vi_start = curbuf->b_op_start; - curbuf->b_visual.vi_end = curbuf->b_op_end; - // need to adjust cursor position - if (*p_sel == 'e') { - inc(&curbuf->b_visual.vi_end); - } + // What to reselect with "gv"? Selecting the just put text seems to + // be the most useful, since the original text was removed. + if (was_visual) { + curbuf->b_visual.vi_start = curbuf->b_op_start; + curbuf->b_visual.vi_end = curbuf->b_op_end; + // need to adjust cursor position + if (*p_sel == 'e') { + inc(&curbuf->b_visual.vi_end); } + } - // When all lines were selected and deleted do_put() leaves an empty - // line that needs to be deleted now. - if (empty && *ml_get(curbuf->b_ml.ml_line_count) == NUL) { - ml_delete(curbuf->b_ml.ml_line_count, true); - deleted_lines(curbuf->b_ml.ml_line_count + 1, 1); + // When all lines were selected and deleted do_put() leaves an empty + // line that needs to be deleted now. + if (empty && *ml_get(curbuf->b_ml.ml_line_count) == NUL) { + ml_delete(curbuf->b_ml.ml_line_count, true); + deleted_lines(curbuf->b_ml.ml_line_count + 1, 1); - // If the cursor was in that line, move it to the end of the last - // line. - if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { - curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - coladvance(MAXCOL); - } + // If the cursor was in that line, move it to the end of the last + // line. + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + coladvance(MAXCOL); } - auto_format(false, true); } + auto_format(false, true); } /// "o" and "O" commands. @@ -7392,12 +6630,6 @@ static void nv_event(cmdarg_T *cap) } } -/// @return true when 'mousemodel' is set to "popup" or "popup_setpos". -static bool mouse_model_popup(void) -{ - return p_mousem[0] == 'p'; -} - void normal_cmd(oparg_T *oap, bool toplevel) { NormalState s; diff --git a/src/nvim/normal.h b/src/nvim/normal.h index 0317080f4f..bed1a40b97 100644 --- a/src/nvim/normal.h +++ b/src/nvim/normal.h @@ -3,7 +3,8 @@ #include <stdbool.h> -#include "nvim/buffer_defs.h" // for win_T +#include "nvim/buffer_defs.h" +#include "nvim/macros.h" #include "nvim/pos.h" // Values for find_ident_under_cursor() @@ -72,6 +73,12 @@ typedef struct cmdarg_S { #define CA_COMMAND_BUSY 1 // skip restarting edit() once #define CA_NO_ADJ_OP_END 2 // don't adjust operator end +// columns needed by shown command +#define SHOWCMD_COLS 10 +// 'showcmd' buffer shared between normal.c and statusline.c +#define SHOWCMD_BUFLEN (SHOWCMD_COLS + 1 + 30) +EXTERN char showcmd_buf[SHOWCMD_BUFLEN]; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "normal.h.generated.h" #endif diff --git a/src/nvim/ops.c b/src/nvim/ops.c index d3a47d77a2..435ca106ab 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -5,13 +5,18 @@ // op_change, op_yank, do_put, do_join #include <assert.h> +#include <ctype.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> -#include "klib/kvec.h" +#include "nvim/api/private/defs.h" #include "nvim/ascii.h" #include "nvim/assert.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" @@ -20,16 +25,19 @@ #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" -#include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" #include "nvim/fold.h" +#include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/globals.h" +#include "nvim/highlight_defs.h" #include "nvim/indent.h" #include "nvim/indent_c.h" -#include "nvim/log.h" +#include "nvim/keycodes.h" #include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -43,13 +51,14 @@ #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/os/time.h" -#include "nvim/path.h" #include "nvim/plines.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/terminal.h" #include "nvim/textformat.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -71,7 +80,7 @@ struct block_def { int startspaces; // 'extra' cols before first char int endspaces; // 'extra' cols after last char int textlen; // chars in block - char_u *textstart; // pointer to 1st char (partially) in block + char *textstart; // pointer to 1st char (partially) in block colnr_T textcol; // index of chars (partially) in block colnr_T start_vcol; // start col of 1st char wholly inside block colnr_T end_vcol; // start col of 1st char wholly after block @@ -81,7 +90,7 @@ struct block_def { int pre_whitesp; // screen cols of ws before block int pre_whitesp_c; // chars of ws before block colnr_T end_char_vcols; // number of vcols of post-block char - colnr_T start_char_vcols; // number of vcols of pre-block char + colnr_T start_char_vcols; // number of vcols of pre-block char }; #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -95,8 +104,7 @@ struct block_def { // The names of operators. // IMPORTANT: Index must correspond with defines in vim.h!!! // The third field indicates whether the operator always works on lines. -static char opchars[][3] = -{ +static char opchars[][3] = { { NUL, NUL, 0 }, // OP_NOP { 'd', NUL, OPF_CHANGE }, // OP_DELETE { 'y', NUL, 0 }, // OP_YANK @@ -256,10 +264,10 @@ void op_shift(oparg_T *oap, int curs_top, int amount) "%" PRId64 " line %sed %d times", amount); char *msg_line_plural = NGETTEXT("%" PRId64 " lines %sed %d time", "%" PRId64 " lines %sed %d times", amount); - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, NGETTEXT(msg_line_single, msg_line_plural, oap->line_count), (int64_t)oap->line_count, op, amount); - msg_attr_keep((char *)IObuff, 0, true, false); + msg_attr_keep(IObuff, 0, true, false); } if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { @@ -327,7 +335,7 @@ static void shift_block(oparg_T *oap, int amount) { const bool left = (oap->op_type == OP_LSHIFT); const int oldstate = State; - char_u *newp; + char *newp; const int oldcol = curwin->w_cursor.col; const int sw_val = (int)get_sw_value_indent(curbuf); const int ts_val = (int)curbuf->b_p_ts; @@ -350,7 +358,7 @@ static void shift_block(oparg_T *oap, int amount) return; // multiplication overflow } - char_u *const oldp = (char_u *)get_cursor_line_ptr(); + char *const oldp = get_cursor_line_ptr(); int startcol, oldlen, newlen; @@ -361,9 +369,9 @@ static void shift_block(oparg_T *oap, int amount) // 4. Construct new string total += bd.pre_whitesp; // all virtual WS up to & incl a split TAB colnr_T ws_vcol = bd.start_vcol - bd.pre_whitesp; - char_u *old_textstart = bd.textstart; + char *old_textstart = bd.textstart; if (bd.startspaces) { - if (utfc_ptr2len((char *)bd.textstart) == 1) { + if (utfc_ptr2len(bd.textstart) == 1) { bd.textstart++; } else { ws_vcol = 0; @@ -374,13 +382,13 @@ static void shift_block(oparg_T *oap, int amount) // TODO(vim): is passing bd.textstart for start of the line OK? chartabsize_T cts; init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, - bd.start_vcol, (char *)bd.textstart, (char *)bd.textstart); + bd.start_vcol, bd.textstart, bd.textstart); while (ascii_iswhite(*cts.cts_ptr)) { incr = lbr_chartabsize_adv(&cts); total += incr; cts.cts_vcol += incr; } - bd.textstart = (char_u *)cts.cts_ptr; + bd.textstart = cts.cts_ptr; bd.start_vcol = cts.cts_vcol; clear_chartabsize_arg(&cts); @@ -395,10 +403,10 @@ static void shift_block(oparg_T *oap, int amount) // if we're splitting a TAB, allow for it int col_pre = bd.pre_whitesp_c - (bd.startspaces != 0); bd.textcol -= col_pre; - const int len = (int)STRLEN(bd.textstart) + 1; + const int len = (int)strlen(bd.textstart) + 1; int col = bd.textcol + i + j + len; assert(col >= 0); - newp = (char_u *)xmalloc((size_t)col); + newp = xmalloc((size_t)col); memset(newp, NUL, (size_t)col); memmove(newp, oldp, (size_t)bd.textcol); startcol = bd.textcol; @@ -411,14 +419,14 @@ static void shift_block(oparg_T *oap, int amount) } else { // left colnr_T destination_col; // column to which text in block will // be shifted - char_u *verbatim_copy_end; // end of the part of the line which is + char *verbatim_copy_end; // end of the part of the line which is // copied verbatim colnr_T verbatim_copy_width; // the (displayed) width of this part // of line size_t fill; // nr of spaces that replace a TAB size_t new_line_len; // the length of the line after the // block shift - char_u *non_white = bd.textstart; + char *non_white = bd.textstart; // Firstly, let's find the first non-whitespace character that is // displayed after the block's start column and the character's column @@ -438,13 +446,13 @@ static void shift_block(oparg_T *oap, int amount) chartabsize_T cts; init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, - non_white_col, (char *)bd.textstart, (char *)non_white); + non_white_col, bd.textstart, non_white); while (ascii_iswhite(*cts.cts_ptr)) { incr = lbr_chartabsize_adv(&cts); cts.cts_vcol += incr; } non_white_col = cts.cts_vcol; - non_white = (char_u *)cts.cts_ptr; + non_white = cts.cts_ptr; clear_chartabsize_arg(&cts); const colnr_T block_space_width = non_white_col - oap->start_vcol; @@ -467,7 +475,7 @@ static void shift_block(oparg_T *oap, int amount) verbatim_copy_width -= bd.start_char_vcols; } init_chartabsize_arg(&cts, curwin, 0, verbatim_copy_width, - (char *)bd.textstart, (char *)verbatim_copy_end); + bd.textstart, verbatim_copy_end); while (cts.cts_vcol < destination_col) { incr = lbr_chartabsize(&cts); if (cts.cts_vcol + incr > destination_col) { @@ -477,7 +485,7 @@ static void shift_block(oparg_T *oap, int amount) MB_PTR_ADV(cts.cts_ptr); } verbatim_copy_width = cts.cts_vcol; - verbatim_copy_end = (char_u *)cts.cts_ptr; + verbatim_copy_end = cts.cts_ptr; clear_chartabsize_arg(&cts); // If "destination_col" is different from the width of the initial @@ -492,9 +500,9 @@ static void shift_block(oparg_T *oap, int amount) // - the beginning of the original line up to "verbatim_copy_end", // - "fill" number of spaces, // - the rest of the line, pointed to by non_white. - new_line_len = verbatim_diff + fill + STRLEN(non_white) + 1; + new_line_len = verbatim_diff + fill + strlen(non_white) + 1; - newp = (char_u *)xmalloc(new_line_len); + newp = xmalloc(new_line_len); startcol = (int)verbatim_diff; oldlen = bd.textcol + (int)(non_white - bd.textstart) - (int)verbatim_diff; newlen = (int)fill; @@ -503,7 +511,7 @@ static void shift_block(oparg_T *oap, int amount) STRMOVE(newp + verbatim_diff + fill, non_white); } // replace the line - ml_replace(curwin->w_cursor.lnum, (char *)newp, false); + ml_replace(curwin->w_cursor.lnum, newp, false); changed_bytes(curwin->w_cursor.lnum, bd.textcol); extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum - 1, startcol, oldlen, newlen, @@ -515,14 +523,14 @@ static void shift_block(oparg_T *oap, int amount) /// Insert string "s" (b_insert ? before : after) block :AKelly /// Caller must prepare for undo. -static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def *bdp) +static void block_insert(oparg_T *oap, char *s, int b_insert, struct block_def *bdp) { int ts_val; int count = 0; // extra spaces to replace a cut TAB int spaces = 0; // non-zero if cutting a TAB colnr_T offset; // pointer along new line - size_t s_len = STRLEN(s); - char_u *newp, *oldp; // new, old lines + size_t s_len = strlen(s); + char *newp, *oldp; // new, old lines linenr_T lnum; // loop var int oldstate = State; State = MODE_INSERT; // don't want MODE_REPLACE for State @@ -533,7 +541,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def continue; // OP_INSERT, line ends before block start } - oldp = (char_u *)ml_get(lnum); + oldp = ml_get(lnum); if (b_insert) { ts_val = bdp->start_char_vcols; @@ -562,7 +570,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def if (spaces > 0) { // avoid copying part of a multi-byte character - offset -= utf_head_off((char *)oldp, (char *)oldp + offset); + offset -= utf_head_off(oldp, oldp + offset); } if (spaces < 0) { // can happen when the cursor was moved spaces = 0; @@ -570,7 +578,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def assert(count >= 0); // Make sure the allocated size matches what is actually copied below. - newp = xmalloc(STRLEN(oldp) + (size_t)spaces + s_len + newp = xmalloc(strlen(oldp) + (size_t)spaces + s_len + (spaces > 0 && !bdp->is_short ? (size_t)ts_val - (size_t)spaces : 0) + (size_t)count + 1); @@ -607,7 +615,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def } STRMOVE(newp + offset, oldp); - ml_replace(lnum, (char *)newp, false); + ml_replace(lnum, newp, false); extmark_splice_cols(curbuf, (int)lnum - 1, startcol, skipped, offset - startcol, kExtmarkUndo); @@ -628,7 +636,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def void op_reindent(oparg_T *oap, Indenter how) { long i = 0; - char_u *l; + char *l; int amount; linenr_T first_changed = 0; linenr_T last_changed = 0; @@ -658,7 +666,7 @@ void op_reindent(oparg_T *oap, Indenter how) // indented, unless there is only one line. if (i != oap->line_count - 1 || oap->line_count == 1 || how != get_lisp_indent) { - l = (char_u *)skipwhite(get_cursor_line_ptr()); + l = skipwhite(get_cursor_line_ptr()); if (*l == NUL) { // empty or blank line amount = 0; } else { @@ -706,14 +714,14 @@ void op_reindent(oparg_T *oap, Indenter how) } // Keep the last expression line here, for repeating. -static char_u *expr_line = NULL; +static char *expr_line = NULL; /// Get an expression for the "\"=expr1" or "CTRL-R =expr1" /// /// @return '=' when OK, NUL otherwise. int get_expr_register(void) { - char_u *new_line; + char *new_line; new_line = getcmdline('=', 0L, 0, true); if (new_line == NULL) { @@ -722,7 +730,7 @@ int get_expr_register(void) if (*new_line == NUL) { // use previous line xfree(new_line); } else { - set_expr_line((char *)new_line); + set_expr_line(new_line); } return '='; } @@ -732,7 +740,7 @@ int get_expr_register(void) void set_expr_line(char *new_line) { xfree(expr_line); - expr_line = (char_u *)new_line; + expr_line = new_line; } /// Get the result of the '=' register expression. @@ -750,7 +758,7 @@ char *get_expr_line(void) // Make a copy of the expression, because evaluating it may cause it to be // changed. - expr_copy = xstrdup((char *)expr_line); + expr_copy = xstrdup(expr_line); // When we are invoked recursively limit the evaluation to 10 levels. // Then return the string as-is. @@ -771,7 +779,7 @@ char *get_expr_line_src(void) if (expr_line == NULL) { return NULL; } - return xstrdup((char *)expr_line); + return xstrdup(expr_line); } /// @return whether `regname` is a valid name of a yank register. @@ -894,7 +902,7 @@ bool yank_register_mline(int regname) /// @return FAIL for failure, OK otherwise. int do_record(int c) { - char_u *p; + char *p; static int regname; yankreg_T *old_y_previous; int retval; @@ -919,10 +927,10 @@ int do_record(int c) dict_T *dict = get_v_event(&save_v_event); // The recorded text contents. - p = get_recorded(); + p = (char *)get_recorded(); if (p != NULL) { // Remove escaping for K_SPECIAL in multi-byte chars. - vim_unescape_ks(p); + vim_unescape_ks((char_u *)p); (void)tv_dict_add_str(dict, S_LEN("regcontents"), (const char *)p); } @@ -974,7 +982,7 @@ static void set_yreg_additional_data(yankreg_T *reg, dict_T *additional_data) /// uppercase). "p" must have been allocated. /// /// @return FAIL for failure, OK otherwise -static int stuff_yank(int regname, char_u *p) +static int stuff_yank(int regname, char *p) { // check for read-only register if (regname != 0 && !valid_yank_reg(regname, true)) { @@ -988,18 +996,18 @@ static int stuff_yank(int regname, char_u *p) yankreg_T *reg = get_yank_register(regname, YREG_YANK); if (is_append_register(regname) && reg->y_array != NULL) { char **pp = &(reg->y_array[reg->y_size - 1]); - char_u *lp = xmalloc(strlen(*pp) + STRLEN(p) + 1); + char *lp = xmalloc(strlen(*pp) + strlen(p) + 1); STRCPY(lp, *pp); // TODO(philix): use xstpcpy() in stuff_yank() STRCAT(lp, p); xfree(p); xfree(*pp); - *pp = (char *)lp; + *pp = lp; } else { free_register(reg); set_yreg_additional_data(reg, NULL); - reg->y_array = xmalloc(sizeof(char_u *)); - reg->y_array[0] = (char *)p; + reg->y_array = xmalloc(sizeof(char *)); + reg->y_array[0] = p; reg->y_size = 1; reg->y_type = kMTCharWise; } @@ -1020,22 +1028,22 @@ static int execreg_lastc = NUL; /// 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 **lines, size_t *idx) +static char *execreg_line_continuation(char **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); + ga_init(&ga, (int)sizeof(char), 400); - char_u *p; + char *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 = (char_u *)skipwhite(lines[i]); + p = skipwhite(lines[i]); if (*p != '\\' && (p[0] != '"' || p[1] != '\\' || p[2] != ' ')) { break; } @@ -1045,14 +1053,14 @@ static char_u *execreg_line_continuation(char **lines, size_t *idx) // join all the lines ga_concat(&ga, lines[cmd_start]); for (size_t j = cmd_start + 1; j <= cmd_end; j++) { - p = (char_u *)skipwhite(lines[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_concat(&ga, p + 1); } } ga_append(&ga, NUL); @@ -1060,7 +1068,7 @@ static char_u *execreg_line_continuation(char **lines, size_t *idx) ga_clear(&ga); *idx = i; - return (char_u *)str; + return str; } /// Execute a yank register: copy it into the stuff buffer @@ -1072,7 +1080,7 @@ static char_u *execreg_line_continuation(char **lines, size_t *idx) /// @return FAIL for failure, OK otherwise int do_execreg(int regname, int colon, int addcr, int silent) { - char_u *p; + char *p; int retval = OK; if (regname == '@') { // repeat previous one @@ -1101,22 +1109,22 @@ int do_execreg(int regname, int colon, int addcr, int silent) // don't keep the cmdline containing @: XFREE_CLEAR(new_last_cmdline); // Escape all control characters with a CTRL-V - p = vim_strsave_escaped_ext((char_u *)last_cmdline, - (char_u *)"\001\002\003\004\005\006\007" + p = vim_strsave_escaped_ext(last_cmdline, + "\001\002\003\004\005\006\007" "\010\011\012\013\014\015\016\017" "\020\021\022\023\024\025\026\027" "\030\031\032\033\034\035\036\037", Ctrl_V, false); // When in Visual mode "'<,'>" will be prepended to the command. // Remove it when it's already there. - if (VIsual_active && STRNCMP(p, "'<,'>", 5) == 0) { + if (VIsual_active && strncmp(p, "'<,'>", 5) == 0) { retval = put_in_typebuf(p + 5, true, true, silent); } else { retval = put_in_typebuf(p, true, true, silent); } xfree(p); } else if (regname == '=') { - p = (char_u *)get_expr_line(); + p = get_expr_line(); if (p == NULL) { return FAIL; } @@ -1151,16 +1159,16 @@ int do_execreg(int regname, int colon, int addcr, int silent) } // Handle line-continuation for :@<register> - char_u *str = (char_u *)reg->y_array[i]; + char *str = reg->y_array[i]; bool free_str = false; if (colon && i > 0) { - p = (char_u *)skipwhite((char *)str); + 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((char *)str); + escaped = vim_strsave_escape_ks(str); if (free_str) { xfree(str); } @@ -1185,18 +1193,20 @@ static void put_reedit_in_typebuf(int silent) { char_u buf[3]; - if (restart_edit != NUL) { - if (restart_edit == 'V') { - buf[0] = 'g'; - buf[1] = 'R'; - buf[2] = NUL; - } else { - buf[0] = (char_u)(restart_edit == 'I' ? 'i' : restart_edit); - buf[1] = NUL; - } - if (ins_typebuf((char *)buf, REMAP_NONE, 0, true, silent) == OK) { - restart_edit = NUL; - } + if (restart_edit == NUL) { + return; + } + + if (restart_edit == 'V') { + buf[0] = 'g'; + buf[1] = 'R'; + buf[2] = NUL; + } else { + buf[0] = (char_u)(restart_edit == 'I' ? 'i' : restart_edit); + buf[1] = NUL; + } + if (ins_typebuf((char *)buf, REMAP_NONE, 0, true, silent) == OK) { + restart_edit = NUL; } } @@ -1206,7 +1216,7 @@ static void put_reedit_in_typebuf(int silent) /// @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) +static int put_in_typebuf(char *s, bool esc, bool colon, int silent) { int retval = OK; @@ -1218,9 +1228,9 @@ static int put_in_typebuf(char_u *s, bool esc, bool colon, int silent) char *p; if (esc) { - p = vim_strsave_escape_ks((char *)s); + p = vim_strsave_escape_ks(s); } else { - p = (char *)s; + p = s; } if (p == NULL) { retval = FAIL; @@ -1339,11 +1349,11 @@ bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg) if (last_search_pat() == NULL && errmsg) { emsg(_(e_noprevre)); } - *argp = (char *)last_search_pat(); + *argp = last_search_pat(); return true; case '.': // last inserted text - *argp = (char *)get_last_insert_save(); + *argp = get_last_insert_save(); *allocated = true; if (*argp == NULL && errmsg) { emsg(_(e_noinstext)); @@ -1355,9 +1365,8 @@ bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg) if (!errmsg) { return false; } - *argp - = (char *)file_name_at_cursor(FNAME_MESS | FNAME_HYP | (regname == Ctrl_P ? FNAME_EXP : 0), - 1L, NULL); + *argp = file_name_at_cursor(FNAME_MESS | FNAME_HYP | (regname == Ctrl_P ? FNAME_EXP : 0), + 1L, NULL); *allocated = true; return true; @@ -1410,11 +1419,11 @@ bool cmdline_paste_reg(int regname, bool literally_arg, bool remcr) } for (size_t i = 0; i < reg->y_size; i++) { - cmdline_paste_str((char_u *)reg->y_array[i], literally); + cmdline_paste_str(reg->y_array[i], literally); // Insert ^M between lines, unless `remcr` is true. if (i < reg->y_size - 1 && !remcr) { - cmdline_paste_str((char_u *)"\r", literally); + cmdline_paste_str("\r", literally); } // Check for CTRL-C, in case someone tries to paste a few thousand @@ -1447,8 +1456,8 @@ int op_delete(oparg_T *oap) { int n; linenr_T lnum; - char_u *ptr; - char_u *newp, *oldp; + char *ptr; + char *newp, *oldp; struct block_def bd = { 0 }; linenr_T old_lcount = curbuf->b_ml.ml_line_count; @@ -1481,11 +1490,11 @@ int op_delete(oparg_T *oap) && oap->line_count > 1 && oap->motion_force == NUL && oap->op_type == OP_DELETE) { - ptr = (char_u *)ml_get(oap->end.lnum) + oap->end.col; + ptr = ml_get(oap->end.lnum) + oap->end.col; if (*ptr != NUL) { ptr += oap->inclusive; } - ptr = (char_u *)skipwhite((char *)ptr); + ptr = skipwhite(ptr); if (*ptr == NUL && inindent(0)) { oap->motion_type = kMTLineWise; } @@ -1579,8 +1588,8 @@ int op_delete(oparg_T *oap) // If we delete a TAB, it may be replaced by several characters. // Thus the number of characters may increase! n = bd.textlen - bd.startspaces - bd.endspaces; - oldp = (char_u *)ml_get(lnum); - newp = (char_u *)xmalloc(STRLEN(oldp) - (size_t)n + 1); + oldp = ml_get(lnum); + newp = xmalloc(strlen(oldp) - (size_t)n + 1); // copy up to deleted part memmove(newp, oldp, (size_t)bd.textcol); // insert spaces @@ -1590,7 +1599,7 @@ int op_delete(oparg_T *oap) oldp += bd.textcol + bd.textlen; STRMOVE(newp + bd.textcol + bd.startspaces + bd.endspaces, oldp); // replace the line - ml_replace(lnum, (char *)newp, false); + ml_replace(lnum, newp, false); extmark_splice_cols(curbuf, (int)lnum - 1, bd.textcol, bd.textlen, bd.startspaces + bd.endspaces, @@ -1697,8 +1706,8 @@ int op_delete(oparg_T *oap) if (virtual_op) { // fix up things for virtualedit-delete: // break the tabs which are going to get in our way - char_u *curline = (char_u *)get_cursor_line_ptr(); - int len = (int)STRLEN(curline); + char *curline = get_cursor_line_ptr(); + int len = (int)strlen(curline); if (oap->end.coladd != 0 && (int)oap->end.col >= len - 1 @@ -1777,10 +1786,12 @@ setmarks: /// Used for deletion. static void mb_adjust_opend(oparg_T *oap) { - if (oap->inclusive) { - char *p = ml_get(oap->end.lnum); - oap->end.col += utf_cp_tail_off(p, p + oap->end.col); + if (!oap->inclusive) { + return; } + + char *p = ml_get(oap->end.lnum); + oap->end.col += utf_cp_tail_off(p, p + oap->end.col); } /// Put character 'c' at position 'lp' @@ -1811,10 +1822,10 @@ static int op_replace(oparg_T *oap, int c) { int n, numc; int num_chars; - char_u *newp, *oldp; + char *newp, *oldp; colnr_T oldlen; struct block_def bd; - char_u *after_p = NULL; + char *after_p = NULL; int had_ctrl_v_cr = false; if ((curbuf->b_ml.ml_flags & ML_EMPTY) || oap->empty) { @@ -1886,8 +1897,8 @@ static int op_replace(oparg_T *oap, int c) num_chars = numc; numc *= utf_char2len(c); - oldp = (char_u *)get_cursor_line_ptr(); - oldlen = (int)STRLEN(oldp); + oldp = get_cursor_line_ptr(); + oldlen = (int)strlen(oldp); size_t newp_size = (size_t)bd.textcol + (size_t)bd.startspaces; if (had_ctrl_v_cr || (c != '\r' && c != '\n')) { @@ -1913,7 +1924,7 @@ static int op_replace(oparg_T *oap, int c) // strlen(newp) at this point int newp_len = bd.textcol + bd.startspaces; while (--num_chars >= 0) { - newp_len += utf_char2bytes(c, (char *)newp + newp_len); + newp_len += utf_char2bytes(c, newp + newp_len); } if (!bd.is_short) { // insert post-spaces @@ -1926,16 +1937,16 @@ static int op_replace(oparg_T *oap, int c) } else { // Replacing with \r or \n means splitting the line. after_p_len = (size_t)col; - after_p = (char_u *)xmalloc(after_p_len); + after_p = xmalloc(after_p_len); memmove(after_p, oldp, after_p_len); newrows = 1; } // replace the line - ml_replace(curwin->w_cursor.lnum, (char *)newp, false); + ml_replace(curwin->w_cursor.lnum, newp, false); curbuf_splice_pending++; linenr_T baselnum = curwin->w_cursor.lnum; if (after_p != NULL) { - ml_append(curwin->w_cursor.lnum++, (char *)after_p, (int)after_p_len, false); + ml_append(curwin->w_cursor.lnum++, after_p, (int)after_p_len, false); appended_lines_mark(curwin->w_cursor.lnum, 1L); oap->end.lnum++; xfree(after_p); @@ -2087,7 +2098,7 @@ void op_tilde(oparg_T *oap) for (;;) { did_change |= swapchars(oap->op_type, &pos, pos.lnum == oap->end.lnum ? oap->end.col + 1 : - (int)STRLEN(ml_get_pos(&pos))); + (int)strlen(ml_get_pos(&pos))); if (ltoreq(oap->end, pos) || inc(&pos) == -1) { break; } @@ -2130,7 +2141,7 @@ static int swapchars(int op_type, pos_T *pos, int length) int did_change = 0; for (int todo = length; todo > 0; todo--) { - const int len = utfc_ptr2len((char *)ml_get_pos(pos)); + const int len = utfc_ptr2len(ml_get_pos(pos)); // we're counting bytes, not characters if (len > 0) { @@ -2239,7 +2250,7 @@ void op_insert(oparg_T *oap, long count1) } curwin->w_ve_flags = VE_ALL; coladvance_force(oap->op_type == OP_APPEND - ? oap->end_vcol + 1 : getviscol()); + ? oap->end_vcol + 1 : getviscol()); if (oap->op_type == OP_APPEND) { curwin->w_cursor.col--; } @@ -2408,7 +2419,7 @@ void op_insert(oparg_T *oap, long count1) ins_text = xstrnsave(firstline, (size_t)ins_len); // block handled here if (u_save(oap->start.lnum, (linenr_T)(oap->end.lnum + 1)) == OK) { - block_insert(oap, (char_u *)ins_text, (oap->op_type == OP_INSERT), &bd); + block_insert(oap, ins_text, (oap->op_type == OP_INSERT), &bd); } curwin->w_cursor.col = oap->start.col; @@ -2430,10 +2441,10 @@ int op_change(oparg_T *oap) long ins_len; long pre_textlen = 0; long pre_indent = 0; - char_u *newp; - char_u *firstline; - char_u *ins_text; - char_u *oldp; + char *newp; + char *firstline; + char *ins_text; + char *oldp; struct block_def bd; l = oap->start.col; @@ -2465,9 +2476,9 @@ int op_change(oparg_T *oap) || gchar_cursor() == NUL)) { coladvance_force(getviscol()); } - firstline = (char_u *)ml_get(oap->start.lnum); - pre_textlen = (long)STRLEN(firstline); - pre_indent = (long)getwhitecols((char *)firstline); + firstline = ml_get(oap->start.lnum); + pre_textlen = (long)strlen(firstline); + pre_indent = (long)getwhitecols(firstline); bd.textcol = curwin->w_cursor.col; } @@ -2475,8 +2486,14 @@ int op_change(oparg_T *oap) fix_indent(); } + // Reset finish_op now, don't want it set inside edit(). + const bool save_finish_op = finish_op; + finish_op = false; + retval = edit(NUL, false, (linenr_T)1); + finish_op = save_finish_op; + // In Visual block mode, handle copying the new text to all lines of the // block. // Don't repeat the insert when Insert mode ended with CTRL-C. @@ -2484,20 +2501,20 @@ int op_change(oparg_T *oap) && oap->start.lnum != oap->end.lnum && !got_int) { // Auto-indenting may have changed the indent. If the cursor was past // the indent, exclude that indent change from the inserted text. - firstline = (char_u *)ml_get(oap->start.lnum); + firstline = ml_get(oap->start.lnum); if (bd.textcol > (colnr_T)pre_indent) { - long new_indent = (long)getwhitecols((char *)firstline); + long new_indent = (long)getwhitecols(firstline); pre_textlen += new_indent - pre_indent; bd.textcol += (colnr_T)(new_indent - pre_indent); } - ins_len = (long)STRLEN(firstline) - pre_textlen; + ins_len = (long)strlen(firstline) - pre_textlen; if (ins_len > 0) { // Subsequent calls to ml_get() flush the firstline data - take a // copy of the inserted text. - ins_text = (char_u *)xmalloc((size_t)(ins_len + 1)); - STRLCPY(ins_text, firstline + bd.textcol, ins_len + 1); + ins_text = xmalloc((size_t)(ins_len + 1)); + xstrlcpy(ins_text, firstline + bd.textcol, (size_t)ins_len + 1); for (linenr = oap->start.lnum + 1; linenr <= oap->end.lnum; linenr++) { block_prep(oap, &bd, linenr, true); @@ -2512,8 +2529,8 @@ int op_change(oparg_T *oap) } else { vpos.coladd = 0; } - oldp = (char_u *)ml_get(linenr); - newp = xmalloc(STRLEN(oldp) + (size_t)vpos.coladd + oldp = ml_get(linenr); + newp = xmalloc(strlen(oldp) + (size_t)vpos.coladd + (size_t)ins_len + 1); // copy up to block start memmove(newp, oldp, (size_t)bd.textcol); @@ -2524,7 +2541,7 @@ int op_change(oparg_T *oap) offset += ins_len; oldp += bd.textcol; STRMOVE(newp + offset, oldp); - ml_replace(linenr, (char *)newp, false); + ml_replace(linenr, newp, false); extmark_splice_cols(curbuf, (int)linenr - 1, bd.textcol, 0, vpos.coladd + (int)ins_len, kExtmarkUndo); } @@ -2559,12 +2576,14 @@ void free_register(yankreg_T *reg) FUNC_ATTR_NONNULL_ALL { set_yreg_additional_data(reg, NULL); - if (reg->y_array != NULL) { - for (size_t i = reg->y_size; i-- > 0;) { // from y_size - 1 to 0 included - xfree(reg->y_array[i]); - } - XFREE_CLEAR(reg->y_array); + if (reg->y_array == NULL) { + return; + } + + for (size_t i = reg->y_size; i-- > 0;) { // from y_size - 1 to 0 included + xfree(reg->y_array[i]); } + XFREE_CLEAR(reg->y_array); } /// Yanks the text between "oap->start" and "oap->end" into a yank register. @@ -2604,8 +2623,8 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) MotionType yank_type = oap->motion_type; size_t yanklines = (size_t)oap->line_count; linenr_T yankendlnum = oap->end.lnum; - char_u *p; - char_u *pnew; + char *p; + char *pnew; struct block_def bd; yankreg_T *curr = reg; // copy of current register @@ -2663,7 +2682,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) colnr_T startcol = 0, endcol = MAXCOL; int is_oneChar = false; colnr_T cs, ce; - p = (char_u *)ml_get(lnum); + p = ml_get(lnum); bd.startspaces = 0; bd.endspaces = 0; @@ -2688,7 +2707,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) // Don't add space for double-wide // char; endcol will be on last byte // of multi-byte char. - && utf_head_off((char *)p, (char *)p + endcol) == 0)) { + && utf_head_off(p, p + endcol) == 0)) { if (oap->start.lnum == oap->end.lnum && oap->start.col == oap->end.col) { // Special case: inside a single char @@ -2705,7 +2724,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) } } if (endcol == MAXCOL) { - endcol = (colnr_T)STRLEN(p); + endcol = (colnr_T)strlen(p); } if (startcol > endcol || is_oneChar) { @@ -2724,7 +2743,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) } if (curr != reg) { // append the new block to the old block - new_ptr = xmalloc(sizeof(char_u *) * (curr->y_size + reg->y_size)); + new_ptr = xmalloc(sizeof(char *) * (curr->y_size + reg->y_size)); for (j = 0; j < curr->y_size; j++) { new_ptr[j] = curr->y_array[j]; } @@ -2746,7 +2765,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) STRCAT(pnew, reg->y_array[0]); xfree(curr->y_array[j]); xfree(reg->y_array[0]); - curr->y_array[j++] = (char *)pnew; + curr->y_array[j++] = pnew; y_idx = 1; } else { y_idx = 0; @@ -2812,8 +2831,8 @@ static void yank_copy_line(yankreg_T *reg, struct block_def *bd, size_t y_idx, } int size = bd->startspaces + bd->endspaces + bd->textlen; assert(size >= 0); - char_u *pnew = xmallocz((size_t)size); - reg->y_array[y_idx] = (char *)pnew; + char *pnew = xmallocz((size_t)size); + reg->y_array[y_idx] = pnew; memset(pnew, ' ', (size_t)bd->startspaces); pnew += bd->startspaces; memmove(pnew, bd->textstart, (size_t)bd->textlen); @@ -2824,7 +2843,7 @@ static void yank_copy_line(yankreg_T *reg, struct block_def *bd, size_t y_idx, int s = bd->textlen + bd->endspaces; while (s > 0 && ascii_iswhite(*(bd->textstart + s - 1))) { - s = s - utf_head_off((char *)bd->textstart, (char *)bd->textstart + s - 1) - 1; + s = s - utf_head_off(bd->textstart, bd->textstart + s - 1) - 1; pnew--; } } @@ -2993,11 +3012,11 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) // strlen(ml_get(curwin->w_cursor.lnum)). With 'virtualedit' and the // cursor past the end of the line, curwin->w_cursor.coladd is // incremented instead of curwin->w_cursor.col. - char_u *cursor_pos = (char_u *)get_cursor_pos_ptr(); + char *cursor_pos = get_cursor_pos_ptr(); bool one_past_line = (*cursor_pos == NUL); bool eol = false; if (!one_past_line) { - eol = (*(cursor_pos + utfc_ptr2len((char *)cursor_pos)) == NUL); + eol = (*(cursor_pos + utfc_ptr2len(cursor_pos)) == NUL); } bool ve_allows = (cur_ve_flags == VE_ALL || cur_ve_flags == VE_ONEMORE); @@ -3067,7 +3086,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (y_array != NULL) { break; } - y_array = xmalloc(y_size * sizeof(char_u *)); + y_array = xmalloc(y_size * sizeof(char *)); } } else { y_size = 1; // use fake one-line yank register @@ -3132,7 +3151,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (y_size == 0 || y_array == NULL) { semsg(_("E353: Nothing in register %s"), - regname == 0 ? (char_u *)"\"" : transchar(regname)); + regname == 0 ? "\"" : transchar(regname)); goto end; } @@ -3273,7 +3292,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) shortline = (vcol < col) || (vcol == col && !*ptr); - if (vcol < col) { // line too short, padd with spaces + if (vcol < col) { // line too short, pad with spaces bd.startspaces = col - vcol; } else if (vcol > col) { bd.endspaces = vcol - col; @@ -3370,6 +3389,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) // adjust '] mark curbuf->b_op_end.lnum = curwin->w_cursor.lnum - 1; curbuf->b_op_end.col = bd.textcol + (colnr_T)totlen - 1; + if (curbuf->b_op_end.col < 0) { + curbuf->b_op_end.col = 0; + } curbuf->b_op_end.coladd = 0; if (flags & PUT_CURSEND) { colnr_T len; @@ -3694,20 +3716,23 @@ void adjust_cursor_eol(void) { unsigned int cur_ve_flags = get_ve_flags(); - if (curwin->w_cursor.col > 0 - && gchar_cursor() == NUL - && (cur_ve_flags & VE_ONEMORE) == 0 - && !(restart_edit || (State & MODE_INSERT))) { - // Put the cursor on the last character in the line. - dec_cursor(); + const bool adj_cursor = (curwin->w_cursor.col > 0 + && gchar_cursor() == NUL + && (cur_ve_flags & VE_ONEMORE) == 0 + && !(restart_edit || (State & MODE_INSERT))); + if (!adj_cursor) { + return; + } + + // Put the cursor on the last character in the line. + dec_cursor(); - if (cur_ve_flags == VE_ALL) { - colnr_T scol, ecol; + if (cur_ve_flags == VE_ALL) { + colnr_T scol, ecol; - // Coladd is set to the width of the last character. - getvcol(curwin, &curwin->w_cursor, &scol, NULL, &ecol); - curwin->w_cursor.coladd = ecol - scol + 1; - } + // Coladd is set to the width of the last character. + getvcol(curwin, &curwin->w_cursor, &scol, NULL, &ecol); + curwin->w_cursor.coladd = ecol - scol + 1; } } @@ -3746,10 +3771,10 @@ int get_unname_register(void) /// ":dis" and ":registers": Display the contents of the yank registers. void ex_display(exarg_T *eap) { - char_u *p; + char *p; yankreg_T *yb; int name; - char_u *arg = (char_u *)eap->arg; + char *arg = eap->arg; int clen; int type; @@ -3771,7 +3796,7 @@ void ex_display(exarg_T *eap) type = 'b'; break; } - if (arg != NULL && vim_strchr((char *)arg, name) == NULL) { + if (arg != NULL && vim_strchr(arg, name) == NULL) { continue; // did not ask for this register } @@ -3815,10 +3840,10 @@ void ex_display(exarg_T *eap) msg_puts_attr("^J", attr); n -= 2; } - for (p = (char_u *)yb->y_array[j]; - *p != NUL && (n -= ptr2cells((char *)p)) >= 0; p++) { // -V1019 - clen = utfc_ptr2len((char *)p); - msg_outtrans_len((char *)p, clen); + 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; } } @@ -3831,15 +3856,15 @@ void ex_display(exarg_T *eap) } // display last inserted text - if ((p = get_last_insert()) != NULL - && (arg == NULL || vim_strchr((char *)arg, '.') != NULL) && !got_int - && !message_filtered((char *)p)) { + if ((p = (char *)get_last_insert()) != NULL + && (arg == NULL || vim_strchr(arg, '.') != NULL) && !got_int + && !message_filtered(p)) { msg_puts("\n c \". "); - dis_msg((char *)p, true); + dis_msg(p, true); } // display last command line - if (last_cmdline != NULL && (arg == NULL || vim_strchr((char *)arg, ':') != NULL) + if (last_cmdline != NULL && (arg == NULL || vim_strchr(arg, ':') != NULL) && !got_int && !message_filtered(last_cmdline)) { msg_puts("\n c \": "); dis_msg(last_cmdline, false); @@ -3847,14 +3872,14 @@ void ex_display(exarg_T *eap) // display current file name if (curbuf->b_fname != NULL - && (arg == NULL || vim_strchr((char *)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 - if ((arg == NULL || vim_strchr((char *)arg, '%') != NULL) && !got_int) { + if ((arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int) { char *fname; linenr_T dummy; @@ -3866,17 +3891,17 @@ void ex_display(exarg_T *eap) // display last search pattern if (last_search_pat() != NULL - && (arg == NULL || vim_strchr((char *)arg, '/') != NULL) && !got_int - && !message_filtered((char *)last_search_pat())) { + && (arg == NULL || vim_strchr(arg, '/') != NULL) && !got_int + && !message_filtered(last_search_pat())) { msg_puts("\n c \"/ "); - dis_msg((char *)last_search_pat(), false); + dis_msg(last_search_pat(), false); } // display last used expression - if (expr_line != NULL && (arg == NULL || vim_strchr((char *)arg, '=') != NULL) - && !got_int && !message_filtered((char *)expr_line)) { + if (expr_line != NULL && (arg == NULL || vim_strchr(arg, '=') != NULL) + && !got_int && !message_filtered(expr_line)) { msg_puts("\n c \"= "); - dis_msg((char *)expr_line, false); + dis_msg(expr_line, false); } } @@ -4188,11 +4213,13 @@ static bool reset_lbr(void) /// Restore 'linebreak' and take care of side effects. static void restore_lbr(bool lbr_saved) { - if (!curwin->w_p_lbr && lbr_saved) { - // changing 'linebreak' may require w_virtcol to be updated - curwin->w_p_lbr = true; - curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL); + if (curwin->w_p_lbr || !lbr_saved) { + return; } + + // changing 'linebreak' may require w_virtcol to be updated + curwin->w_p_lbr = true; + curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL); } /// prepare a few things for block mode yank/delete/tilde @@ -4329,7 +4356,7 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool bdp->textlen = (int)(pend - pstart); } bdp->textcol = (colnr_T)(pstart - line); - bdp->textstart = (char_u *)pstart; + bdp->textstart = pstart; restore_lbr(lbr_saved); } @@ -4451,13 +4478,13 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd) int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) { int col; - char_u *buf1 = NULL; - char_u buf2[NUMBUFLEN]; + char *buf1 = NULL; + char buf2[NUMBUFLEN]; int pre; // 'X' or 'x': hex; '0': octal; 'B' or 'b': bin static bool hexupper = false; // 0xABC uvarnumber_T n; uvarnumber_T oldn; - char_u *ptr; + char *ptr; int c; int todel; int firstdigit; @@ -4484,10 +4511,10 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) } curwin->w_cursor = *pos; - ptr = (char_u *)ml_get(pos->lnum); + ptr = ml_get(pos->lnum); col = pos->col; - if (*ptr == NUL || col + !!save_coladd >= (int)STRLEN(ptr)) { + if (*ptr == NUL || col + !!save_coladd >= (int)strlen(ptr)) { goto theend; } @@ -4496,14 +4523,14 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) if (do_bin) { while (col > 0 && ascii_isbdigit(ptr[col])) { col--; - col -= utf_head_off((char *)ptr, (char *)ptr + col); + col -= utf_head_off(ptr, ptr + col); } } if (do_hex) { while (col > 0 && ascii_isxdigit(ptr[col])) { col--; - col -= utf_head_off((char *)ptr, (char *)ptr + col); + col -= utf_head_off(ptr, ptr + col); } } if (do_bin @@ -4511,7 +4538,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) && !((col > 0 && (ptr[col] == 'X' || ptr[col] == 'x') && ptr[col - 1] == '0' - && !utf_head_off((char *)ptr, (char *)ptr + col - 1) + && !utf_head_off(ptr, ptr + col - 1) && ascii_isxdigit(ptr[col + 1])))) { // In case of binary/hexadecimal pattern overlap match, rescan @@ -4519,7 +4546,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) while (col > 0 && ascii_isdigit(ptr[col])) { col--; - col -= utf_head_off((char *)ptr, (char *)ptr + col); + col -= utf_head_off(ptr, ptr + col); } } @@ -4527,17 +4554,17 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) && col > 0 && (ptr[col] == 'X' || ptr[col] == 'x') && ptr[col - 1] == '0' - && !utf_head_off((char *)ptr, (char *)ptr + col - 1) + && !utf_head_off(ptr, ptr + col - 1) && ascii_isxdigit(ptr[col + 1])) || (do_bin && col > 0 && (ptr[col] == 'B' || ptr[col] == 'b') && ptr[col - 1] == '0' - && !utf_head_off((char *)ptr, (char *)ptr + col - 1) + && !utf_head_off(ptr, ptr + col - 1) && ascii_isbdigit(ptr[col + 1]))) { // Found hexadecimal or binary number, move to its start. col--; - col -= utf_head_off((char *)ptr, (char *)ptr + col); + col -= utf_head_off(ptr, ptr + col); } else { // Search forward and then backward to find the start of number. col = pos->col; @@ -4559,7 +4586,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) if (visual) { while (ptr[col] != NUL && length > 0 && !ascii_isdigit(ptr[col]) && !(do_alpha && ASCII_ISALPHA(ptr[col]))) { - int mb_len = utfc_ptr2len((char *)ptr + col); + int mb_len = utfc_ptr2len(ptr + col); col += mb_len; length -= mb_len; @@ -4570,7 +4597,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) } if (col > pos->col && ptr[col - 1] == '-' - && !utf_head_off((char *)ptr, (char *)ptr + col - 1) + && !utf_head_off(ptr, ptr + col - 1) && !do_unsigned) { negative = true; was_positive = false; @@ -4578,7 +4605,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) } // If a number was found, and saving for undo works, replace the number. - firstdigit = ptr[col]; + firstdigit = (uint8_t)ptr[col]; if (!ascii_isdigit(firstdigit) && !(do_alpha && ASCII_ISALPHA(firstdigit))) { beep_flush(); goto theend; @@ -4616,7 +4643,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) curwin->w_cursor.col = col; } else { if (col > 0 && ptr[col - 1] == '-' - && !utf_head_off((char *)ptr, (char *)ptr + col - 1) + && !utf_head_off(ptr, ptr + col - 1) && !visual && !do_unsigned) { // negative number @@ -4627,11 +4654,11 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) // get the number value (unsigned) if (visual && VIsual_mode != 'V') { maxlen = (curbuf->b_visual.vi_curswant == MAXCOL - ? (int)STRLEN(ptr) - col + ? (int)strlen(ptr) - col : length); } - vim_str2nr((char *)ptr + col, &pre, &length, + vim_str2nr(ptr + col, &pre, &length, 0 + (do_bin ? STR2NR_BIN : 0) + (do_oct ? STR2NR_OCT : 0) + (do_hex ? STR2NR_HEX : 0), @@ -4732,7 +4759,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) length--; } if (pre == 'b' || pre == 'B' || pre == 'x' || pre == 'X') { - *ptr++ = (char_u)pre; + *ptr++ = (char)pre; length--; } @@ -4754,15 +4781,15 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) buf2[i] = '\0'; } else if (pre == 0) { - vim_snprintf((char *)buf2, ARRAY_SIZE(buf2), "%" PRIu64, (uint64_t)n); + vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIu64, (uint64_t)n); } else if (pre == '0') { - vim_snprintf((char *)buf2, ARRAY_SIZE(buf2), "%" PRIo64, (uint64_t)n); + vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIo64, (uint64_t)n); } else if (hexupper) { - vim_snprintf((char *)buf2, ARRAY_SIZE(buf2), "%" PRIX64, (uint64_t)n); + vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIX64, (uint64_t)n); } else { - vim_snprintf((char *)buf2, ARRAY_SIZE(buf2), "%" PRIx64, (uint64_t)n); + vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIx64, (uint64_t)n); } - length -= (int)STRLEN(buf2); + length -= (int)strlen(buf2); // Adjust number of zeros to the new number of digits, so the // total length of the number remains the same. @@ -4775,7 +4802,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) } *ptr = NUL; STRCAT(buf1, buf2); - ins_str((char *)buf1); // insert the new number + ins_str(buf1); // insert the new number endpos = curwin->w_cursor; if (curwin->w_cursor.col) { curwin->w_cursor.col--; @@ -5032,7 +5059,7 @@ void write_reg_contents_lst(int name, char **strings, bool must_append, MotionTy return; } - str_to_reg(reg, yank_type, (char *)strings, STRLEN(strings), + str_to_reg(reg, yank_type, (char *)strings, strlen((char *)strings), block_len, true); finish_write_reg(name, reg, old_y_previous); } @@ -5064,7 +5091,7 @@ void write_reg_contents_ex(int name, const char *str, ssize_t len, bool must_app // Special case: '/' search pattern if (name == '/') { - set_last_search_pat((char_u *)str, RE_SEARCH, true, true); + set_last_search_pat(str, RE_SEARCH, true, true); return; } @@ -5096,7 +5123,7 @@ void write_reg_contents_ex(int name, const char *str, ssize_t len, bool must_app if (must_append && expr_line) { // append has been specified and expr_line already exists, so we'll // append the new string to expr_line. - size_t exprlen = STRLEN(expr_line); + size_t exprlen = strlen(expr_line); totlen += exprlen; offset = exprlen; @@ -5154,7 +5181,7 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char *str, // Count the number of lines within the string if (str_list) { - for (char_u **ss = (char_u **)str; *ss != NULL; ss++) { + for (char **ss = (char **)str; *ss != NULL; ss++) { newlines++; } } else { @@ -5176,7 +5203,7 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char *str, } // Grow the register array to hold the pointers to the new lines. - char **pp = xrealloc(y_ptr->y_array, (y_ptr->y_size + newlines) * sizeof(char_u *)); + char **pp = xrealloc(y_ptr->y_array, (y_ptr->y_size + newlines) * sizeof(char *)); y_ptr->y_array = pp; size_t lnum = y_ptr->y_size; // The current line number. @@ -5186,8 +5213,8 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char *str, // Find the end of each line and save it into the array. if (str_list) { - for (char_u **ss = (char_u **)str; *ss != NULL; ss++, lnum++) { - size_t ss_len = STRLEN(*ss); + for (char **ss = (char **)str; *ss != NULL; ss++, lnum++) { + size_t ss_len = strlen(*ss); pp[lnum] = xmemdupz(*ss, ss_len); if (ss_len > maxlen) { maxlen = ss_len; @@ -5195,12 +5222,11 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char *str, } } else { size_t line_len; - for (const char_u *start = (char_u *)str, *end = (char_u *)str + len; + for (const char *start = str, *end = str + len; start < end + extraline; start += line_len + 1, lnum++) { assert(end - start >= 0); - line_len = (size_t)((char_u *)xmemscan(start, '\n', - (size_t)(end - start)) - start); + line_len = (size_t)((char *)xmemscan(start, '\n', (size_t)(end - start)) - start); if (line_len > maxlen) { maxlen = line_len; } @@ -5252,8 +5278,8 @@ void clear_oparg(oparg_T *oap) /// line, stopping if it encounters an end-of-line (NUL byte). In that /// case, eol_size will be added to the character count to account for /// the size of the EOL character. -static varnumber_T line_count_info(char_u *line, varnumber_T *wc, varnumber_T *cc, - varnumber_T limit, int eol_size) +static varnumber_T line_count_info(char *line, varnumber_T *wc, varnumber_T *cc, varnumber_T limit, + int eol_size) { varnumber_T i; varnumber_T words = 0; @@ -5270,7 +5296,7 @@ static varnumber_T line_count_info(char_u *line, varnumber_T *wc, varnumber_T *c is_word = 1; } chars++; - i += utfc_ptr2len((char *)line + i); + i += utfc_ptr2len(line + i); } if (is_word) { @@ -5294,9 +5320,9 @@ static varnumber_T line_count_info(char_u *line, varnumber_T *wc, varnumber_T *c /// @param dict when not NULL, store the info there instead of showing it. void cursor_pos_info(dict_T *dict) { - char_u *p; - char_u buf1[50]; - char_u buf2[40]; + char *p; + char buf1[50]; + char buf2[40]; linenr_T lnum; varnumber_T byte_count = 0; varnumber_T bom_count = 0; @@ -5378,7 +5404,7 @@ void cursor_pos_info(dict_T *dict) // Do extra processing for VIsual mode. if (l_VIsual_active && lnum >= min_pos.lnum && lnum <= max_pos.lnum) { - char_u *s = NULL; + char *s = NULL; long len = 0L; switch (l_VIsual_mode) { @@ -5390,7 +5416,7 @@ void cursor_pos_info(dict_T *dict) len = (long)bd.textlen; break; case 'V': - s = (char_u *)ml_get(lnum); + s = ml_get(lnum); len = MAXCOL; break; case 'v': { @@ -5399,7 +5425,7 @@ void cursor_pos_info(dict_T *dict) colnr_T end_col = (lnum == max_pos.lnum) ? max_pos.col - start_col + 1 : MAXCOL; - s = (char_u *)ml_get(lnum) + start_col; + s = ml_get(lnum) + start_col; len = end_col; } break; @@ -5410,7 +5436,7 @@ void cursor_pos_info(dict_T *dict) if (lnum == curbuf->b_ml.ml_line_count && !curbuf->b_p_eol && (curbuf->b_p_bin || !curbuf->b_p_fixeol) - && (long)STRLEN(s) < len) { + && (long)strlen(s) < len) { byte_count_cursor -= eol_size; } } @@ -5420,14 +5446,14 @@ void cursor_pos_info(dict_T *dict) word_count_cursor += word_count; char_count_cursor += char_count; byte_count_cursor = byte_count - + line_count_info((char_u *)ml_get(lnum), &word_count_cursor, + + line_count_info(ml_get(lnum), &word_count_cursor, &char_count_cursor, (varnumber_T)curwin->w_cursor.col + 1, eol_size); } } // Add to the running totals - byte_count += line_count_info((char_u *)ml_get(lnum), &word_count, &char_count, + byte_count += line_count_info(ml_get(lnum), &word_count, &char_count, (varnumber_T)MAXCOL, eol_size); } @@ -5442,7 +5468,7 @@ void cursor_pos_info(dict_T *dict) getvcols(curwin, &min_pos, &max_pos, &min_pos.col, &max_pos.col); int64_t cols; STRICT_SUB(oparg.end_vcol + 1, oparg.start_vcol, &cols, int64_t); - vim_snprintf((char *)buf1, sizeof(buf1), _("%" PRId64 " Cols; "), + vim_snprintf(buf1, sizeof(buf1), _("%" PRId64 " Cols; "), cols); } else { buf1[0] = NUL; @@ -5450,7 +5476,7 @@ void cursor_pos_info(dict_T *dict) if (char_count_cursor == byte_count_cursor && char_count == byte_count) { - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Selected %s%" PRId64 " of %" PRId64 " Lines;" " %" PRId64 " of %" PRId64 " Words;" " %" PRId64 " of %" PRId64 " Bytes"), @@ -5459,7 +5485,7 @@ void cursor_pos_info(dict_T *dict) (int64_t)word_count_cursor, (int64_t)word_count, (int64_t)byte_count_cursor, (int64_t)byte_count); } else { - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Selected %s%" PRId64 " of %" PRId64 " Lines;" " %" PRId64 " of %" PRId64 " Words;" " %" PRId64 " of %" PRId64 " Chars;" @@ -5471,30 +5497,30 @@ void cursor_pos_info(dict_T *dict) (int64_t)byte_count_cursor, (int64_t)byte_count); } } else { - p = (char_u *)get_cursor_line_ptr(); + p = get_cursor_line_ptr(); validate_virtcol(); - col_print((char *)buf1, sizeof(buf1), (int)curwin->w_cursor.col + 1, + col_print(buf1, sizeof(buf1), (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1); - col_print((char *)buf2, sizeof(buf2), (int)STRLEN(p), linetabsize(p)); + col_print((char *)buf2, sizeof(buf2), (int)strlen(p), linetabsize(p)); if (char_count_cursor == byte_count_cursor && char_count == byte_count) { - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Col %s of %s; Line %" PRId64 " of %" PRId64 ";" " Word %" PRId64 " of %" PRId64 ";" " Byte %" PRId64 " of %" PRId64 ""), - (char *)buf1, (char *)buf2, + buf1, buf2, (int64_t)curwin->w_cursor.lnum, (int64_t)curbuf->b_ml.ml_line_count, (int64_t)word_count_cursor, (int64_t)word_count, (int64_t)byte_count_cursor, (int64_t)byte_count); } else { - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Col %s of %s; Line %" PRId64 " of %" PRId64 ";" " Word %" PRId64 " of %" PRId64 ";" " Char %" PRId64 " of %" PRId64 ";" " Byte %" PRId64 " of %" PRId64 ""), - (char *)buf1, (char *)buf2, + buf1, buf2, (int64_t)curwin->w_cursor.lnum, (int64_t)curbuf->b_ml.ml_line_count, (int64_t)word_count_cursor, (int64_t)word_count, @@ -5507,19 +5533,19 @@ void cursor_pos_info(dict_T *dict) bom_count = bomb_size(); if (dict == NULL && bom_count > 0) { const size_t len = strlen(IObuff); - vim_snprintf((char *)IObuff + len, IOSIZE - len, + vim_snprintf(IObuff + len, IOSIZE - len, _("(+%" PRId64 " for BOM)"), (int64_t)bom_count); } if (dict == NULL) { // Don't shorten this message, the user asked for it. - p = (char_u *)p_shm; + p = p_shm; p_shm = ""; if (p_ch < 1) { msg_start(); msg_scroll = true; } - msg((char *)IObuff); - p_shm = (char *)p; + msg(IObuff); + p_shm = p; } } @@ -5553,13 +5579,22 @@ static void op_colon(oparg_T *oap) } else { stuffnumReadbuff((long)oap->start.lnum); } - if (oap->end.lnum != oap->start.lnum) { + + // When using !! on a closed fold the range ".!" works best to operate + // on, it will be made the whole closed fold later. + linenr_T endOfStartFold = oap->start.lnum; + (void)hasFolding(oap->start.lnum, NULL, &endOfStartFold); + if (oap->end.lnum != oap->start.lnum && oap->end.lnum != endOfStartFold) { + // Make it a range with the end line. 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) { + } else if (oap->start.lnum == curwin->w_cursor.lnum + // do not use ".+number" for a closed fold, it would count + // folded lines twice + && !hasFolding(oap->end.lnum, NULL, NULL)) { stuffReadbuff(".+"); stuffnumReadbuff(oap->line_count - 1); } else { @@ -5591,10 +5626,11 @@ static void op_colon(oparg_T *oap) static Callback opfunc_cb; /// Process the 'operatorfunc' option value. -/// @return OK or FAIL -int set_operatorfunc_option(void) +void set_operatorfunc_option(char **errmsg) { - return option_set_callback_func(p_opfunc, &opfunc_cb); + if (option_set_callback_func(p_opfunc, &opfunc_cb) == FAIL) { + *errmsg = e_invarg; + } } #if defined(EXITFREE) @@ -5604,12 +5640,17 @@ void free_operatorfunc_option(void) } #endif +/// Mark the global 'operatorfunc' callback with "copyID" so that it is not +/// garbage collected. +bool set_ref_in_opfunc(int copyID) +{ + return set_ref_in_callback(&opfunc_cb, copyID, NULL, NULL); +} + /// 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; @@ -5636,9 +5677,11 @@ static void op_function(const oparg_T *oap) // Reset virtual_op so that 'virtualedit' can be changed in the // function. + const TriState save_virtual_op = virtual_op; virtual_op = kNone; // Reset finish_op so that mode() returns the right value. + const bool save_finish_op = finish_op; finish_op = false; typval_T rettv; @@ -6052,7 +6095,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) // Include the trailing byte of a multi-byte char. if (oap->inclusive) { - const int l = utfc_ptr2len((char *)ml_get_pos(&oap->end)); + const int l = utfc_ptr2len(ml_get_pos(&oap->end)); if (l > 1) { oap->end.col += l - 1; } @@ -6178,8 +6221,6 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) // Restore linebreak, so that when the user edits it looks as before. restore_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; } @@ -6203,7 +6244,11 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) // 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); + if (use_indentexpr_for_lisp()) { + op_reindent(oap, get_expr_indent); + } else { + op_reindent(oap, get_lisp_indent); + } break; } op_reindent(oap, @@ -6398,7 +6443,7 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing) } if (!eval_has_provider("clipboard")) { - if (batch_change_count == 1 && !quiet + if (batch_change_count <= 1 && !quiet && (!clipboard_didwarn || (explicit_cb_reg && !redirecting()))) { clipboard_didwarn = true; // Do NOT error (emsg()) here--if it interrupts :redir we get into @@ -6554,8 +6599,8 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) if (TV_LIST_ITEM_TV(tv_list_last(res))->v_type != VAR_STRING) { goto err; } - char_u *regtype = (char_u *)TV_LIST_ITEM_TV(tv_list_last(res))->vval.v_string; - if (regtype == NULL || STRLEN(regtype) > 1) { + char *regtype = TV_LIST_ITEM_TV(tv_list_last(res))->vval.v_string; + if (regtype == NULL || strlen(regtype) > 1) { goto err; } switch (regtype[0]) { diff --git a/src/nvim/ops.h b/src/nvim/ops.h index 840e33a48c..75ea1853a0 100644 --- a/src/nvim/ops.h +++ b/src/nvim/ops.h @@ -2,14 +2,17 @@ #define NVIM_OPS_H #include <stdbool.h> +#include <stddef.h> #include "nvim/ascii.h" #include "nvim/eval/typval.h" -#include "nvim/ex_cmds_defs.h" // for exarg_T +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/extmark.h" #include "nvim/macros.h" -#include "nvim/normal.h" // for MotionType and oparg_T +#include "nvim/normal.h" #include "nvim/os/time.h" +#include "nvim/pos.h" #include "nvim/types.h" typedef int (*Indenter)(void); diff --git a/src/nvim/option.c b/src/nvim/option.c index 208112561a..01a5c7677f 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -21,38 +21,46 @@ #define IN_OPTION_C #include <assert.h> +#include <ctype.h> #include <inttypes.h> #include <limits.h> #include <stdbool.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> -#include "nvim/arglist.h" +#include "auto/config.h" +#include "nvim/api/private/defs.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" +#include "nvim/cmdexpand.h" #include "nvim/cursor_shape.h" #include "nvim/decoration_provider.h" #include "nvim/diff.h" #include "nvim/drawscreen.h" -#include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/ex_session.h" -#include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/garray.h" -#include "nvim/getchar.h" -#include "nvim/hardcopy.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/grid_defs.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/indent_c.h" +#include "nvim/insexpand.h" #include "nvim/keycodes.h" #include "nvim/locale.h" +#include "nvim/log.h" #include "nvim/macros.h" #include "nvim/mapping.h" #include "nvim/mbyte.h" @@ -65,21 +73,25 @@ #include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/option_defs.h" #include "nvim/optionstr.h" #include "nvim/os/os.h" -#include "nvim/os_unix.h" #include "nvim/path.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" #include "nvim/regexp.h" +#include "nvim/runtime.h" #include "nvim/screen.h" #include "nvim/search.h" +#include "nvim/sign_defs.h" #include "nvim/spell.h" #include "nvim/spellfile.h" #include "nvim/spellsuggest.h" #include "nvim/strings.h" -#include "nvim/syntax.h" +#include "nvim/tag.h" +#include "nvim/terminal.h" +#include "nvim/types.h" #include "nvim/ui.h" -#include "nvim/ui_compositor.h" #include "nvim/undo.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -88,7 +100,6 @@ #endif #include "nvim/api/extmark.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" #include "nvim/lua/executor.h" #include "nvim/os/input.h" #include "nvim/os/lang.h" @@ -164,8 +175,6 @@ void set_init_tablocal(void) /// editor state initialized here. Do logging in set_init_2 or later. void set_init_1(bool clean_arg) { - int opt_idx; - langmap_init(); // Find default value for 'shell' option. @@ -193,7 +202,7 @@ void set_init_1(bool clean_arg) static char *(names[3]) = { "TMPDIR", "TEMP", "TMP" }; #endif garray_T ga; - opt_idx = findoption("backupskip"); + int opt_idx = findoption("backupskip"); ga_init(&ga, 1, 100); for (size_t n = 0; n < ARRAY_SIZE(names); n++) { @@ -207,7 +216,7 @@ void set_init_1(bool clean_arg) p = "/tmp"; # endif mustfree = false; - } else + } else // NOLINT(readability/braces) #endif { p = vim_getenv(names[n]); @@ -219,7 +228,7 @@ void set_init_1(bool clean_arg) xstrlcpy(item, p, len); add_pathsep(item); xstrlcat(item, "*", len); - if (find_dup_item(ga.ga_data, (char_u *)item, options[opt_idx].flags) + if (find_dup_item(ga.ga_data, item, options[opt_idx].flags) == NULL) { ga_grow(&ga, (int)len); if (!GA_EMPTY(&ga)) { @@ -242,19 +251,14 @@ void set_init_1(bool clean_arg) } { - char_u *cdpath; - char_u *buf; - int i; - int j; - // Initialize the 'cdpath' option's default value. - cdpath = (char_u *)vim_getenv("CDPATH"); + char *cdpath = vim_getenv("CDPATH"); if (cdpath != NULL) { - buf = xmalloc(2 * STRLEN(cdpath) + 2); + char *buf = xmalloc(2 * strlen(cdpath) + 2); { buf[0] = ','; // start with ",", current dir first - j = 1; - for (i = 0; cdpath[i] != NUL; i++) { + int j = 1; + for (int i = 0; cdpath[i] != NUL; i++) { if (vim_ispathlistsep(cdpath[i])) { buf[j++] = ','; } else { @@ -265,9 +269,9 @@ void set_init_1(bool clean_arg) } } buf[j] = NUL; - opt_idx = findoption("cdpath"); + int opt_idx = findoption("cdpath"); if (opt_idx >= 0) { - options[opt_idx].def_val = (char *)buf; + options[opt_idx].def_val = buf; options[opt_idx].flags |= P_DEF_ALLOCED; } else { xfree(buf); // cannot happen @@ -277,28 +281,6 @@ void set_init_1(bool clean_arg) } } -#if defined(MSWIN) || defined(MAC) - // Set print encoding on platforms that don't default to latin1 - set_string_default("printencoding", "hp-roman8", false); -#endif - - // 'printexpr' must be allocated to be able to evaluate it. - set_string_default("printexpr", -#ifdef UNIX - "system(['lpr'] " - "+ (empty(&printdevice)?[]:['-P', &printdevice]) " - "+ [v:fname_in])" - ". delete(v:fname_in)" - "+ v:shell_error", -#elif defined(MSWIN) - "system(['copy', v:fname_in, " - "empty(&printdevice)?'LPT1':&printdevice])" - ". delete(v:fname_in)", -#else - "", -#endif - false); - char *backupdir = stdpaths_user_state_subpath("backup", 2, true); const size_t backupdir_len = strlen(backupdir); backupdir = xrealloc(backupdir, backupdir_len + 3); @@ -352,25 +334,25 @@ void set_init_1(bool clean_arg) // them. // Don't set the P_ALLOCED flag, because we don't want to free the // default. - for (opt_idx = 0; options[opt_idx].fullname; opt_idx++) { - if (options[opt_idx].flags & P_NO_DEF_EXP) { + for (int opt_idx = 0; options[opt_idx].fullname; opt_idx++) { + vimoption_T *opt = &options[opt_idx]; + if (opt->flags & P_NO_DEF_EXP) { continue; } char *p; - if ((options[opt_idx].flags & P_GETTEXT) - && options[opt_idx].var != NULL) { - p = _(*(char **)options[opt_idx].var); + if ((opt->flags & P_GETTEXT) && opt->var != NULL) { + p = _(*(char **)opt->var); } else { p = option_expand(opt_idx, NULL); } if (p != NULL) { p = xstrdup(p); - *(char **)options[opt_idx].var = p; - if (options[opt_idx].flags & P_DEF_ALLOCED) { - xfree(options[opt_idx].def_val); + *(char **)opt->var = p; + if (opt->flags & P_DEF_ALLOCED) { + xfree(opt->def_val); } - options[opt_idx].def_val = p; - options[opt_idx].flags |= P_DEF_ALLOCED; + opt->def_val = p; + opt->flags |= P_DEF_ALLOCED; } } @@ -392,12 +374,12 @@ void set_init_1(bool clean_arg) // enc_locale() will try to find the encoding of the current locale. // This will be used when 'default' is used as encoding specifier // in 'fileencodings' - char_u *p = enc_locale(); + char *p = enc_locale(); if (p == NULL) { // use utf-8 as 'default' if locale encoding can't be detected. - p = (char_u *)xmemdupz(S_LEN("utf-8")); + p = xmemdupz(S_LEN("utf-8")); } - fenc_default = (char *)p; + fenc_default = p; #ifdef HAVE_WORKING_LIBINTL // GNU gettext 0.10.37 supports this feature: set the codeset used for @@ -413,32 +395,32 @@ void set_init_1(bool clean_arg) /// This does not take care of side effects! /// /// @param opt_flags OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL -static void set_option_default(int opt_idx, int opt_flags) +static void set_option_default(const int opt_idx, int opt_flags) { - char_u *varp; // pointer to variable for current option int both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0; - varp = (char_u *)get_varp_scope(&(options[opt_idx]), both ? OPT_LOCAL : opt_flags); - uint32_t flags = options[opt_idx].flags; + // pointer to variable for current option + vimoption_T *opt = &options[opt_idx]; + char_u *varp = (char_u *)get_varp_scope(opt, both ? OPT_LOCAL : opt_flags); + uint32_t flags = opt->flags; if (varp != NULL) { // skip hidden option, nothing to do for it if (flags & P_STRING) { // Use set_string_option_direct() for local options to handle // freeing and allocating the value. - if (options[opt_idx].indir != PV_NONE) { - set_string_option_direct(NULL, opt_idx, - options[opt_idx].def_val, opt_flags, 0); + if (opt->indir != PV_NONE) { + set_string_option_direct(NULL, opt_idx, opt->def_val, opt_flags, 0); } else { if ((opt_flags & OPT_FREE) && (flags & P_ALLOCED)) { free_string_option(*(char **)(varp)); } - *(char **)varp = options[opt_idx].def_val; - options[opt_idx].flags &= ~P_ALLOCED; + *(char **)varp = opt->def_val; + opt->flags &= ~P_ALLOCED; } } else if (flags & P_NUM) { - if (options[opt_idx].indir == PV_SCROLL) { + if (opt->indir == PV_SCROLL) { win_comp_scroll(curwin); } else { - long def_val = (long)options[opt_idx].def_val; + long def_val = (long)opt->def_val; if ((long *)varp == &curwin->w_p_so || (long *)varp == &curwin->w_p_siso) { // 'scrolloff' and 'sidescrolloff' local values have a @@ -449,21 +431,21 @@ static void set_option_default(int opt_idx, int opt_flags) } // May also set global value for local option. if (both) { - *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = + *(long *)get_varp_scope(opt, OPT_GLOBAL) = def_val; } } } else { // P_BOOL - *(int *)varp = (int)(intptr_t)options[opt_idx].def_val; + *(int *)varp = (int)(intptr_t)opt->def_val; #ifdef UNIX // 'modeline' defaults to off for root - if (options[opt_idx].indir == PV_ML && getuid() == ROOT_UID) { + if (opt->indir == PV_ML && getuid() == ROOT_UID) { *(int *)varp = false; } #endif // May also set global value for local option. if (both) { - *(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = + *(int *)get_varp_scope(opt, OPT_GLOBAL) = *(int *)varp; } } @@ -506,30 +488,31 @@ static void set_string_default(const char *name, char *val, bool allocated) { int opt_idx = findoption(name); if (opt_idx >= 0) { - if (options[opt_idx].flags & P_DEF_ALLOCED) { - xfree(options[opt_idx].def_val); + vimoption_T *opt = &options[opt_idx]; + if (opt->flags & P_DEF_ALLOCED) { + xfree(opt->def_val); } - options[opt_idx].def_val = allocated ? val : xstrdup(val); - options[opt_idx].flags |= P_DEF_ALLOCED; + opt->def_val = allocated ? val : xstrdup(val); + opt->flags |= P_DEF_ALLOCED; } } -// For an option value that contains comma separated items, find "newval" in -// "origval". Return NULL if not found. -static char_u *find_dup_item(char_u *origval, const char_u *newval, uint32_t flags) +/// For an option value that contains comma separated items, find "newval" in +/// "origval". Return NULL if not found. +static char *find_dup_item(char *origval, const char *newval, uint32_t flags) FUNC_ATTR_NONNULL_ARG(2) { - int bs = 0; - if (origval == NULL) { return NULL; } - const size_t newlen = STRLEN(newval); - for (char_u *s = origval; *s != NUL; s++) { + int bs = 0; + + const size_t newlen = strlen(newval); + for (char *s = origval; *s != NUL; s++) { if ((!(flags & P_COMMA) || s == origval || (s[-1] == ',' && !(bs & 1))) - && STRNCMP(s, newval, newlen) == 0 + && strncmp(s, newval, newlen) == 0 && (!(flags & P_COMMA) || s[newlen] == ',' || s[newlen] == NUL)) { return s; } @@ -550,9 +533,7 @@ static char_u *find_dup_item(char_u *origval, const char_u *newval, uint32_t fla /// Used for 'lines' and 'columns'. void set_number_default(char *name, long val) { - int opt_idx; - - opt_idx = findoption(name); + int opt_idx = findoption(name); if (opt_idx >= 0) { options[opt_idx].def_val = (char *)(intptr_t)val; } @@ -577,6 +558,7 @@ void free_all_options(void) } } free_operatorfunc_option(); + free_tagfunc_option(); } #endif @@ -586,11 +568,9 @@ void set_init_2(bool headless) // set in set_init_1 but logging is not allowed there ILOG("startup runtimepath/packpath value: %s", p_rtp); - int idx; - // 'scroll' defaults to half the window height. The stored default is zero, // which results in the actual value computed from the window height. - idx = findoption("scroll"); + int idx = findoption("scroll"); if (idx >= 0 && !(options[idx].flags & P_WAS_SET)) { set_option_default(idx, OPT_LOCAL); } @@ -602,7 +582,6 @@ void set_init_2(bool headless) p_window = Rows - 1; } set_number_default("window", Rows - 1); - (void)parse_printoptions(); // parse 'printoptions' default value } /// Initialize the options, part three: After reading the .vimrc @@ -623,7 +602,7 @@ void set_init_3(void) : !(options[idx_sp].flags & P_WAS_SET); size_t len = 0; - char *p = (char *)invocation_path_tail((char_u *)p_sh, &len); + char *p = (char *)invocation_path_tail(p_sh, &len); p = xstrnsave(p, len); { @@ -689,23 +668,25 @@ void set_helplang_default(const char *lang) return; } int idx = findoption("hlg"); - if (idx >= 0 && !(options[idx].flags & P_WAS_SET)) { - if (options[idx].flags & P_ALLOCED) { - free_string_option((char *)p_hlg); - } - p_hlg = (char_u *)xmemdupz(lang, lang_len); - // zh_CN becomes "cn", zh_TW becomes "tw". - if (STRNICMP(p_hlg, "zh_", 3) == 0 && STRLEN(p_hlg) >= 5) { - p_hlg[0] = (char_u)TOLOWER_ASC(p_hlg[3]); - p_hlg[1] = (char_u)TOLOWER_ASC(p_hlg[4]); - } else if (STRLEN(p_hlg) >= 1 && *p_hlg == 'C') { - // any C like setting, such as C.UTF-8, becomes "en" - p_hlg[0] = 'e'; - p_hlg[1] = 'n'; - } - p_hlg[2] = NUL; - options[idx].flags |= P_ALLOCED; + if (idx < 0 || (options[idx].flags & P_WAS_SET)) { + return; + } + + if (options[idx].flags & P_ALLOCED) { + free_string_option(p_hlg); } + p_hlg = xmemdupz(lang, lang_len); + // zh_CN becomes "cn", zh_TW becomes "tw". + if (STRNICMP(p_hlg, "zh_", 3) == 0 && strlen(p_hlg) >= 5) { + p_hlg[0] = (char)TOLOWER_ASC(p_hlg[3]); + p_hlg[1] = (char)TOLOWER_ASC(p_hlg[4]); + } else if (strlen(p_hlg) >= 1 && *p_hlg == 'C') { + // any C like setting, such as C.UTF-8, becomes "en" + p_hlg[0] = 'e'; + p_hlg[1] = 'n'; + } + p_hlg[2] = NUL; + options[idx].flags |= P_ALLOCED; } /// 'title' and 'icon' only default to true if they have not been set or reset @@ -715,12 +696,10 @@ void set_helplang_default(const char *lang) /// machine. void set_title_defaults(void) { - int idx1; - // If GUI is (going to be) used, we can always set the window title and // icon name. Saves a bit of time, because the X11 display server does // not need to be contacted. - idx1 = findoption("title"); + int idx1 = findoption("title"); if (idx1 >= 0 && !(options[idx1].flags & P_WAS_SET)) { options[idx1].def_val = 0; p_title = 0; @@ -758,17 +737,8 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, char *varp = varp_arg; char *save_arg = NULL; char *s = NULL; - char_u *oldval = NULL; // previous value if *varp - char *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; char whichwrap[80]; // When using ":set opt=val" for a global option @@ -780,7 +750,7 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, } // The old value is kept until we are sure that the new value is valid. - oldval = *(char_u **)varp; + char *oldval = *(char **)varp; if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { origval_l = *(char_u **)get_varp_scope(&(options[opt_idx]), OPT_LOCAL); @@ -793,14 +763,16 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, } } + char *origval; // 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)) { - origval = *(char_u **)get_varp(&options[opt_idx]); + origval = *(char **)get_varp(&options[opt_idx]); } else { origval = oldval; } + char *newval; if (nextchar == '&') { // set to default val newval = options[opt_idx].def_val; // expand environment variables and ~ since the default value was @@ -847,15 +819,15 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, } xfree(oldval); if (origval == oldval) { - origval = *(char_u **)varp; + origval = *(char **)varp; } - if (origval_l == oldval) { + if (origval_l == (char_u *)oldval) { origval_l = *(char_u **)varp; } - if (origval_g == oldval) { + if (origval_g == (char_u *)oldval) { origval_g = *(char_u **)varp; } - oldval = *(char_u **)varp; + oldval = *(char **)varp; } else if (varp == (char *)&p_ww && ascii_isdigit(*arg)) { // Convert 'whichwrap' number to string, for backwards compatibility // with Vim 3.0. @@ -892,9 +864,9 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, // backslashes. // get a bit too much - newlen = (unsigned)strlen(arg) + 1; + size_t newlen = strlen(arg) + 1; if (op != OP_NONE) { - newlen += (unsigned)STRLEN(origval) + 1; + newlen += strlen(origval) + 1; } newval = xmalloc(newlen); s = newval; @@ -908,7 +880,7 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, if (*arg == '\\' && arg[1] != NUL #ifdef BACKSLASH_IN_FILENAME && !((flags & P_EXPAND) - && vim_isfilec(arg[1]) + && vim_isfilec((uint8_t)arg[1]) && !ascii_iswhite(arg[1]) && (arg[1] != '\\' || (s == newval && arg[2] != '\\'))) @@ -936,7 +908,7 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, xfree(newval); newlen = (unsigned)strlen(s) + 1; if (op != OP_NONE) { - newlen += (unsigned)STRLEN(origval) + 1; + newlen += (unsigned)strlen(origval) + 1; } newval = xmalloc(newlen); STRCPY(newval, s); @@ -947,8 +919,8 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, // and when adding to avoid duplicates int len = 0; if (op == OP_REMOVING || (flags & P_NODUP)) { - len = (int)STRLEN(newval); - s = (char *)find_dup_item(origval, (char_u *)newval, flags); + len = (int)strlen(newval); + s = find_dup_item(origval, newval, flags); // do not add if already there if ((op == OP_ADDING || op == OP_PREPENDING) && s != NULL) { @@ -958,15 +930,15 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, // if no duplicate, move pointer to end of original value if (s == NULL) { - s = (char *)origval + (int)STRLEN(origval); + s = origval + (int)strlen(origval); } } // concatenate the two strings; add a ',' if needed if (op == OP_ADDING || op == OP_PREPENDING) { - comma = ((flags & P_COMMA) && *origval != NUL && *newval != NUL); + int comma = ((flags & P_COMMA) && *origval != NUL && *newval != NUL); if (op == OP_ADDING) { - len = (int)STRLEN(origval); + len = (int)strlen(origval); // Strip a trailing comma, would get 2. if (comma && len > 1 && (flags & P_ONECOMMA) == P_ONECOMMA @@ -992,7 +964,7 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, if (*s) { // may need to remove a comma if (flags & P_COMMA) { - if (s == (char *)origval) { + if (s == origval) { // include comma after string if (s[len] == ',') { len++; @@ -1003,7 +975,7 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, len++; } } - STRMOVE(newval + (s - (char *)origval), s + len); + STRMOVE(newval + (s - origval), s + len); } } @@ -1014,14 +986,14 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, // 'whichwrap' if (flags & P_ONECOMMA) { if (*s != ',' && *(s + 1) == ',' - && vim_strchr(s + 2, *s) != NULL) { + && vim_strchr(s + 2, (uint8_t)(*s)) != NULL) { // Remove the duplicated value and the next comma. STRMOVE(s, s + 2); continue; } } else { if ((!(flags & P_COMMA) || *s != ',') - && vim_strchr(s + 1, *s) != NULL) { + && vim_strchr(s + 1, (uint8_t)(*s)) != NULL) { STRMOVE(s, s + 1); continue; } @@ -1039,13 +1011,13 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, *(char_u **)(varp) = (char_u *)newval; // origval may be freed by did_set_string_option(), make a copy. - saved_origval = (origval != NULL) ? xstrdup((char *)origval) : NULL; - saved_origval_l = (origval_l != NULL) ? xstrdup((char *)origval_l) : NULL; - saved_origval_g = (origval_g != NULL) ? xstrdup((char *)origval_g) : NULL; + char *saved_origval = (origval != NULL) ? xstrdup(origval) : NULL; + char *saved_origval_l = (origval_l != NULL) ? xstrdup((char *)origval_l) : NULL; + char *saved_origval_g = (origval_g != NULL) ? xstrdup((char *)origval_g) : NULL; // newval (and varp) may become invalid if the buffer is closed by // autocommands. - saved_newval = (newval != NULL) ? xstrdup(newval) : NULL; + char *saved_newval = (newval != NULL) ? xstrdup(newval) : NULL; { uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags); @@ -1064,7 +1036,7 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, // Handle side effects, and set the global value for ":set" on local // options. Note: when setting 'syntax' or 'filetype' autocommands may // be triggered that can cause havoc. - *errmsg = did_set_string_option(opt_idx, (char **)varp, (char *)oldval, + *errmsg = did_set_string_option(opt_idx, (char **)varp, oldval, errbuf, errbuflen, opt_flags, value_checked); @@ -1073,8 +1045,8 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, if (*errmsg == NULL) { if (!starting) { - trigger_optionsset_string(opt_idx, opt_flags, saved_origval, saved_origval_l, - saved_origval_g, saved_newval); + trigger_optionset_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), @@ -1107,21 +1079,7 @@ static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, /// @return FAIL if an error is detected, OK otherwise int do_set(char *arg, int opt_flags) { - int opt_idx; - char *errmsg; - char errbuf[80]; - char *startarg; - int prefix; // 1: nothing, 0: "no", 2: "inv" in front of name - char_u nextchar; // next non-white char after option name - int afterchar; // character just after option name - int len; - int i; - varnumber_T value; - int key; - uint32_t flags; // flags for current option - char *varp = NULL; // pointer to variable for current option int did_show = false; // already showed one value - set_op_T op = 0; if (*arg == NUL) { showoptions(0, opt_flags); @@ -1129,11 +1087,13 @@ int do_set(char *arg, int opt_flags) goto theend; } + char errbuf[80]; + while (*arg != NUL) { // loop to process all options - errmsg = NULL; - startarg = arg; // remember for error message + char *errmsg = NULL; + char *startarg = arg; // remember for error message - if (STRNCMP(arg, "all", 3) == 0 && !isalpha(arg[3]) + if (strncmp(arg, "all", 3) == 0 && !ASCII_ISALPHA(arg[3]) && !(opt_flags & OPT_MODELINE)) { // ":set all" show all options. // ":set all&" set all options to their default value. @@ -1151,17 +1111,19 @@ int do_set(char *arg, int opt_flags) did_show = true; } } else { - prefix = 1; - if (STRNCMP(arg, "no", 2) == 0) { + int prefix = 1; // 1: nothing, 0: "no", 2: "inv" in front of name + if (strncmp(arg, "no", 2) == 0) { prefix = 0; arg += 2; - } else if (STRNCMP(arg, "inv", 3) == 0) { + } else if (strncmp(arg, "inv", 3) == 0) { prefix = 2; arg += 3; } // find end of name - key = 0; + int key = 0; + int len; + int opt_idx; if (*arg == '<') { opt_idx = -1; // look out for <t_>;> @@ -1182,7 +1144,7 @@ int do_set(char *arg, int opt_flags) } len++; if (opt_idx == -1) { - key = find_key_option((char_u *)arg + 1, true); + key = find_key_option(arg + 1, true); } } else { len = 0; @@ -1196,19 +1158,19 @@ int do_set(char *arg, int opt_flags) } opt_idx = findoption_len((const char *)arg, (size_t)len); if (opt_idx == -1) { - key = find_key_option((char_u *)arg, false); + key = find_key_option(arg, false); } } // remember character after option name - afterchar = (uint8_t)arg[len]; + int afterchar = (uint8_t)arg[len]; // skip white space, allow ":set ai ?" while (ascii_iswhite(arg[len])) { len++; } - op = OP_NONE; + set_op_T op = OP_NONE; if (arg[len] != NUL && arg[len + 1] == '=') { if (arg[len] == '+') { op = OP_ADDING; // "+=" @@ -1221,18 +1183,21 @@ int do_set(char *arg, int opt_flags) len++; } } - nextchar = (uint8_t)arg[len]; + char_u nextchar = (uint8_t)arg[len]; // next non-white char after option name if (opt_idx == -1 && key == 0) { // found a mismatch: skip errmsg = e_unknown_option; goto skip; } + uint32_t flags; // flags for current option + char *varp = NULL; // pointer to variable for current option + if (opt_idx >= 0) { if (options[opt_idx].var == NULL) { // hidden option: skip // Only give an error message when requesting the value of // a hidden option, ignore setting it. - if (vim_strchr("=:!&<", nextchar) == NULL + if (vim_strchr("=:!&<", (uint8_t)nextchar) == NULL && (!(options[opt_idx].flags & P_BOOL) || nextchar == '?')) { errmsg = e_unsupportedoption; @@ -1286,7 +1251,7 @@ int do_set(char *arg, int opt_flags) goto skip; } - if (vim_strchr("?=:!&<", nextchar) != NULL) { + if (vim_strchr("?=:!&<", (uint8_t)nextchar) != NULL) { arg += len; if (nextchar == '&' && arg[1] == 'v' && arg[2] == 'i') { if (arg[3] == 'm') { // "opt&vim": set to Vim default @@ -1295,7 +1260,7 @@ int do_set(char *arg, int opt_flags) arg += 2; } } - if (vim_strchr("?!&<", nextchar) != NULL + if (vim_strchr("?!&<", (uint8_t)nextchar) != NULL && arg[1] != NUL && !ascii_iswhite(arg[1])) { errmsg = e_trailing; goto skip; @@ -1308,7 +1273,7 @@ int do_set(char *arg, int opt_flags) // if (nextchar == '?' || (prefix == 1 - && vim_strchr("=:&<", nextchar) == NULL + && vim_strchr("=:&<", (uint8_t)nextchar) == NULL && !(flags & P_BOOL))) { // print value if (did_show) { @@ -1341,6 +1306,7 @@ int do_set(char *arg, int opt_flags) } } else { int value_checked = false; + varnumber_T value; if (flags & P_BOOL) { // boolean if (nextchar == '=' || nextchar == ':') { @@ -1380,7 +1346,7 @@ int do_set(char *arg, int opt_flags) errmsg = set_bool_option(opt_idx, (char_u *)varp, (int)value, opt_flags); } else { // Numeric or string. - if (vim_strchr("=:&<", nextchar) == NULL + if (vim_strchr("=:&<", (uint8_t)nextchar) == NULL || prefix != 1) { errmsg = e_invarg; goto skip; @@ -1411,12 +1377,13 @@ int do_set(char *arg, int opt_flags) || *arg == '^' || (*arg != NUL && (!arg[1] || ascii_iswhite(arg[1])) && !ascii_isdigit(*arg)))) { - value = string_to_key((char_u *)arg); + value = string_to_key(arg); if (value == 0 && (long *)varp != &p_wcm) { errmsg = e_invarg; goto skip; } } else if (*arg == '-' || ascii_isdigit(*arg)) { + int i; // Allow negative, octal and hex numbers. vim_str2nr(arg, NULL, &i, STR2NR_ALL, &value, NULL, 0, true); if (i == 0 || (arg[i] != NUL && !ascii_iswhite(arg[i]))) { @@ -1465,7 +1432,7 @@ skip: // - skip until a blank found, taking care of backslashes // - skip blanks // - skip one "=val" argument (for hidden options ":set gfn =xx") - for (i = 0; i < 2; i++) { + for (int i = 0; i < 2; i++) { while (*arg != NUL && !ascii_iswhite(*arg)) { if (*arg++ == '\\' && *arg != NUL) { arg++; @@ -1479,8 +1446,8 @@ skip: } if (errmsg != NULL) { - STRLCPY(IObuff, _(errmsg), IOSIZE); - i = (int)strlen(IObuff) + 2; + xstrlcpy(IObuff, _(errmsg), IOSIZE); + int i = (int)strlen(IObuff) + 2; if (i + (arg - startarg) < IOSIZE) { // append the argument with the error STRCAT(IObuff, ": "); @@ -1489,10 +1456,10 @@ skip: IObuff[i + (arg - startarg)] = NUL; } // make sure all characters are printable - trans_characters((char *)IObuff, IOSIZE); + trans_characters(IObuff, IOSIZE); no_wait_return++; // wait_return() done later - emsg((char *)IObuff); // show error highlighted + emsg(IObuff); // show error highlighted no_wait_return--; return FAIL; @@ -1505,11 +1472,11 @@ theend: if (silent_mode && did_show) { // After displaying option values in silent mode. silent_mode = false; - info_message = true; // use mch_msg(), not mch_errmsg() + info_message = true; // use os_msg(), not os_errmsg() msg_putchar('\n'); ui_flush(); silent_mode = true; - info_message = false; // use mch_msg(), not mch_errmsg() + info_message = false; // use os_msg(), not os_errmsg() } return OK; @@ -1540,15 +1507,15 @@ void did_set_option(int opt_idx, int opt_flags, int new_value, int value_checked /// Convert a key name or string into a key value. /// Used for 'wildchar' and 'cedit' options. -int string_to_key(char_u *arg) +int string_to_key(char *arg) { if (*arg == '<') { return find_key_option(arg + 1, true); } if (*arg == '^') { - return CTRL_CHR(arg[1]); + return CTRL_CHR((uint8_t)arg[1]); } - return *arg; + return (uint8_t)(*arg); } // When changing 'title', 'titlestring', 'icon' or 'iconstring', call @@ -1621,11 +1588,9 @@ void set_options_bin(int oldval, int newval, int opt_flags) /// number, return -1. int get_shada_parameter(int type) { - char_u *p; - - p = find_shada_parameter(type); + char *p = find_shada_parameter(type); if (p != NULL && ascii_isdigit(*p)) { - return atoi((char *)p); + return atoi(p); } return -1; } @@ -1633,11 +1598,11 @@ int get_shada_parameter(int type) /// Find the parameter represented by the given character (eg ''', ':', '"', or /// '/') in the 'shada' option and return a pointer to the string after it. /// Return NULL if the parameter is not specified in the string. -char_u *find_shada_parameter(int type) +char *find_shada_parameter(int type) { for (char *p = p_shada; *p; p++) { if (*p == type) { - return (char_u *)p + 1; + return p + 1; } if (*p == 'n') { // 'n' is always the last one break; @@ -1675,9 +1640,9 @@ static char *option_expand(int opt_idx, char *val) // Escape spaces when expanding 'tags', they are used to separate file // names. // For 'spellsuggest' expand after "file:". - expand_env_esc((char_u *)val, (char_u *)NameBuff, MAXPATHL, - (char_u **)options[opt_idx].var == &p_tags, false, - (char_u **)options[opt_idx].var == (char_u **)&p_sps ? (char_u *)"file:" : + expand_env_esc(val, NameBuff, MAXPATHL, + (char **)options[opt_idx].var == &p_tags, false, + (char_u **)options[opt_idx].var == (char_u **)&p_sps ? "file:" : NULL); if (strcmp(NameBuff, val) == 0) { // they are the same return NULL; @@ -1729,9 +1694,7 @@ static void didset_options2(void) /// Check for string options that are NULL (normally only termcap options). void check_options(void) { - int opt_idx; - - for (opt_idx = 0; options[opt_idx].fullname != NULL; opt_idx++) { + for (int opt_idx = 0; options[opt_idx].fullname != NULL; opt_idx++) { if ((options[opt_idx].flags & P_STRING) && options[opt_idx].var != NULL) { check_string_option((char **)get_varp(&(options[opt_idx]))); } @@ -1795,9 +1758,9 @@ void redraw_titles(void) bool valid_name(const char *val, const char *allowed) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - for (const char_u *s = (char_u *)val; *s != NUL; s++) { + for (const char *s = val; *s != NUL; s++) { if (!ASCII_ISALNUM(*s) - && vim_strchr(allowed, *s) == NULL) { + && vim_strchr(allowed, (uint8_t)(*s)) == NULL) { return false; } } @@ -1888,6 +1851,46 @@ void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx) } } +/// Apply the OptionSet autocommand. +static void apply_optionset_autocmd(int opt_idx, long opt_flags, long oldval, long oldval_g, + long newval, const char *errmsg) +{ + // Don't do this while starting up, failure or recursively. + if (starting || errmsg != NULL || *get_vim_var_str(VV_OPTION_TYPE) != NUL) { + return; + } + + char buf_old[12], buf_old_global[12], buf_new[12], buf_type[12]; + + vim_snprintf(buf_old, sizeof(buf_old), "%ld", oldval); + vim_snprintf(buf_old_global, sizeof(buf_old_global), "%ld", oldval_g); + vim_snprintf(buf_new, sizeof(buf_new), "%ld", newval); + vim_snprintf(buf_type, sizeof(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); + 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, options[opt_idx].fullname, NULL, false, NULL); + reset_v_option_vars(); +} + /// Set the value of a boolean option, taking care of side effects /// /// @param[in] opt_idx Option index in options[] table. @@ -1956,7 +1959,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va || (opt_flags & OPT_GLOBAL) || opt_flags == 0) && !bufIsChanged(bp) && bp->b_ml.ml_mfp != NULL) { u_compute_hash(bp, hash); - u_read_undo(NULL, hash, (char_u *)bp->b_fname); + u_read_undo(NULL, hash, bp->b_fname); } } } @@ -1975,14 +1978,12 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va } else if ((int *)varp == &curbuf->b_p_ma) { // when 'modifiable' is changed, redraw the window title redraw_titles(); - } else if ((int *)varp == &curbuf->b_p_eol) { - // when 'endofline' is changed, redraw the window title - redraw_titles(); - } else if ((int *)varp == &curbuf->b_p_fixeol) { - // when 'fixeol' is changed, redraw the window title - redraw_titles(); - } else if ((int *)varp == &curbuf->b_p_bomb) { - // when 'bomb' is changed, redraw the window title and tab page text + } else if ((int *)varp == &curbuf->b_p_eof + || (int *)varp == &curbuf->b_p_eol + || (int *)varp == &curbuf->b_p_fixeol + || (int *)varp == &curbuf->b_p_bomb) { + // redraw the window title and tab page text when 'endoffile', 'endofline', + // 'fixeol' or 'bomb' is changed redraw_titles(); } else if ((int *)varp == &curbuf->b_p_bin) { // when 'bin' is set also set some other options @@ -2042,10 +2043,9 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va } redraw_titles(); modified_was_set = value; - } #ifdef BACKSLASH_IN_FILENAME - else if ((int *)varp == &p_ssl) { + } else if ((int *)varp == &p_ssl) { if (p_ssl) { psepc = '/'; psepcN = '\\'; @@ -2060,9 +2060,8 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va buflist_slash_adjust(); alist_slash_adjust(); scriptnames_slash_adjust(); - } #endif - else if ((int *)varp == &curwin->w_p_wrap) { + } else if ((int *)varp == &curwin->w_p_wrap) { // If 'wrap' is set, set w_leftcol to zero. if (curwin->w_p_wrap) { curwin->w_leftcol = 0; @@ -2084,6 +2083,9 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va if (curwin->w_p_spell) { errmsg = did_set_spelllang(curwin); } + } else if (((int *)varp == &curwin->w_p_nu || (int *)varp == &curwin->w_p_rnu) + && *curwin->w_p_stc != NUL) { // '(relative)number' + 'statuscolumn' + curwin->w_nrwidth_line_count = 0; } if ((int *)varp == &curwin->w_p_arab) { @@ -2146,40 +2148,10 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va options[opt_idx].flags |= P_WAS_SET; - // 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_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); - 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, options[opt_idx].fullname, NULL, false, NULL); - reset_v_option_vars(); - } + apply_optionset_autocmd(opt_idx, opt_flags, + (long)(old_value ? true : false), + (long)(old_global_value ? true : false), + (long)(value ? true : false), NULL); if (options[opt_idx].flags & P_UI_OPTION) { ui_call_option_set(cstr_as_string(options[opt_idx].fullname), @@ -2276,6 +2248,8 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, int minval = 0; if (value < minval) { errmsg = e_positive; + } else { + p_ch_was_zero = value == 0; } } else if (pp == &p_tm) { if (value < 0) { @@ -2334,7 +2308,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, } else if (pp == &curwin->w_p_nuw || pp == &curwin->w_allbuf_opt.wo_nuw) { if (value < 1) { errmsg = e_positive; - } else if (value > 20) { + } else if (value > MAX_NUMBERWIDTH) { errmsg = e_invarg; } } else if (pp == &curbuf->b_p_iminsert || pp == &p_iminsert) { @@ -2589,41 +2563,8 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, options[opt_idx].flags |= P_WAS_SET; - // 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); - 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, options[opt_idx].fullname, NULL, false, NULL); - reset_v_option_vars(); - } + apply_optionset_autocmd(opt_idx, opt_flags, old_value, old_global_value, + value, errmsg); if (errmsg == NULL && options[opt_idx].flags & P_UI_OPTION) { ui_call_option_set(cstr_as_string(options[opt_idx].fullname), @@ -2641,7 +2582,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, } /// Called after an option changed: check if something needs to be redrawn. -void check_redraw(uint32_t flags) +void check_redraw_for(buf_T *buf, win_T *win, uint32_t flags) { // Careful: P_RALL is a combination of other P_ flags bool all = (flags & P_RALL) == P_RALL; @@ -2650,20 +2591,29 @@ void check_redraw(uint32_t flags) status_redraw_all(); } + if ((flags & P_RTABL) || all) { // mark tablines dirty + redraw_tabline = true; + } + if ((flags & P_RBUF) || (flags & P_RWIN) || all) { - changed_window_setting(); + changed_window_setting_win(win); } if (flags & P_RBUF) { - redraw_curbuf_later(UPD_NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); } if (flags & P_RWINONLY) { - redraw_later(curwin, UPD_NOT_VALID); + redraw_later(win, UPD_NOT_VALID); } if (all) { redraw_all_later(UPD_NOT_VALID); } } +void check_redraw(uint32_t flags) +{ + check_redraw_for(curbuf, curwin, flags); +} + /// Find index for named option /// /// @param[in] arg Option to find index for. @@ -2673,15 +2623,14 @@ void check_redraw(uint32_t flags) int findoption_len(const char *const arg, const size_t len) { const char *s; - const char *p; static int quick_tab[27] = { 0, 0 }; // quick access table // For first call: Initialize the quick-access table. // It contains the index for the first option that starts with a certain // letter. There are 26 letters, plus the first "t_" option. if (quick_tab[1] == 0) { - p = options[0].fullname; - for (short int i = 1; (s = options[i].fullname) != NULL; i++) { + const char *p = options[0].fullname; + for (uint16_t i = 1; (s = options[i].fullname) != NULL; i++) { if (s[0] != p[0]) { if (s[0] == 't' && s[1] == '_') { quick_tab[26] = i; @@ -2726,7 +2675,7 @@ int findoption_len(const char *const arg, const size_t len) opt_idx = -1; } else { // Nvim: handle option aliases. - if (STRNCMP(options[opt_idx].fullname, "viminfo", 7) == 0) { + if (strncmp(options[opt_idx].fullname, "viminfo", 7) == 0) { if (strlen(options[opt_idx].fullname) == 7) { return findoption_len("shada", 5); } @@ -2839,6 +2788,7 @@ int findoption(const char *const arg) /// Gets the value for an option. /// /// @param stringval NULL when only checking existence +/// @param flagsp set to the option flags (P_xxxx) (if not NULL) /// /// @returns: /// Number option: gov_number, *numval gets value. @@ -2848,7 +2798,8 @@ int findoption(const char *const arg) /// Hidden Toggle option: gov_hidden_bool. /// Hidden String option: gov_hidden_string. /// Unknown option: gov_unknown. -getoption_T get_option_value(const char *name, long *numval, char **stringval, int opt_flags) +getoption_T get_option_value(const char *name, long *numval, char **stringval, uint32_t *flagsp, + int scope) { if (get_tty_option(name, stringval)) { return gov_string; @@ -2859,7 +2810,12 @@ getoption_T get_option_value(const char *name, long *numval, char **stringval, i return gov_unknown; } - char_u *varp = (char_u *)get_varp_scope(&(options[opt_idx]), opt_flags); + char_u *varp = (char_u *)get_varp_scope(&(options[opt_idx]), scope); + + if (flagsp != NULL) { + // Return the P_xxxx option flags. + *flagsp = options[opt_idx].flags; + } if (options[opt_idx].flags & P_STRING) { if (varp == NULL) { // hidden option @@ -2914,7 +2870,6 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o return SOPT_STRING | SOPT_GLOBAL; } - char_u *varp = NULL; int rv = 0; int opt_idx = findoption(name); if (opt_idx < 0) { @@ -2950,15 +2905,13 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o if (p->indir & PV_WIN) { if (opt_type == SREQ_BUF) { return 0; // Requested buffer-local, not window-local option - } else { - rv |= SOPT_WIN; } + rv |= SOPT_WIN; } else if (p->indir & PV_BUF) { if (opt_type == SREQ_WIN) { return 0; // Requested window-local, not buffer-local option - } else { - rv |= SOPT_BUF; } + rv |= SOPT_BUF; } } @@ -2966,12 +2919,13 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o return rv; } + char_u *varp = NULL; + if (opt_type == SREQ_GLOBAL) { if (p->var == VAR_WIN) { return 0; - } else { - varp = p->var; } + varp = p->var; } else { if (opt_type == SREQ_BUF) { // Special case: 'modified' is b_changed, but we also want to @@ -3005,7 +2959,7 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o if (varp != NULL) { if (p->flags & P_STRING) { - *stringval = xstrdup(*(char **)(varp)); + *stringval = *(char **)(varp); } else if (p->flags & P_NUM) { *numval = *(long *)varp; } else { @@ -3041,65 +2995,65 @@ char *set_option_value(const char *const name, const long number, const char *co return NULL; // Fail silently; many old vimrcs set t_xx options. } - int opt_idx; - char_u *varp; - - opt_idx = findoption(name); + int opt_idx = findoption(name); if (opt_idx < 0) { semsg(_("E355: Unknown option: %s"), name); - } else { - uint32_t flags = options[opt_idx].flags; - // Disallow changing some options in the sandbox - if (sandbox > 0 && (flags & P_SECURE)) { - emsg(_(e_sandbox)); - return NULL; + return NULL; + } + + uint32_t flags = options[opt_idx].flags; + // Disallow changing some options in the sandbox + if (sandbox > 0 && (flags & P_SECURE)) { + emsg(_(e_sandbox)); + return NULL; + } + + if (flags & P_STRING) { + const char *s = string; + if (s == NULL || opt_flags & OPT_CLEAR) { + s = ""; } - if (flags & P_STRING) { - const char *s = string; - if (s == NULL || opt_flags & OPT_CLEAR) { - s = ""; - } - return set_string_option(opt_idx, s, opt_flags); - } - - varp = (char_u *)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 - } - } - 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 { - char *s = NULL; - (void)get_option_value(name, &numval, &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 set_string_option(opt_idx, s, opt_flags); + } + + char_u *varp = (char_u *)get_varp_scope(&(options[opt_idx]), opt_flags); + if (varp == NULL) { + // hidden option is not changed + return NULL; + } + + 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 + } + } + 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 { + char *s = NULL; + (void)get_option_value(name, &numval, &s, NULL, OPT_GLOBAL); } } - return NULL; + if (flags & P_NUM) { + return set_num_option(opt_idx, varp, numval, NULL, 0, opt_flags); + } + return set_bool_option(opt_idx, varp, (int)numval, opt_flags); } /// Call set_option_value() and when an error is returned report it. @@ -3114,6 +3068,12 @@ void set_option_value_give_err(const char *name, long number, const char *string } } +bool is_option_allocated(const char *name) +{ + int idx = findoption(name); + return idx >= 0 && (options[idx].flags & P_ALLOCED); +} + /// Return true if "name" is a string option. /// Returns false if option "name" does not exist. bool is_string_option(const char *name) @@ -3125,19 +3085,18 @@ bool is_string_option(const char *name) // Translate a string like "t_xx", "<t_xx>" or "<S-Tab>" to a key number. // When "has_lt" is true there is a '<' before "*arg_arg". // Returns 0 when the key is not recognized. -int find_key_option_len(const char_u *arg_arg, size_t len, bool has_lt) +int find_key_option_len(const char *arg_arg, size_t len, bool has_lt) { int key = 0; - int modifiers; - const char_u *arg = arg_arg; + const char *arg = arg_arg; // Don't use get_special_key_code() for t_xx, we don't want it to call // add_termcap_entry(). if (len >= 4 && arg[0] == 't' && arg[1] == '_') { - key = TERMCAP2KEY(arg[2], arg[3]); + key = TERMCAP2KEY((uint8_t)arg[2], (uint8_t)arg[3]); } else if (has_lt) { arg--; // put arg at the '<' - modifiers = 0; + int modifiers = 0; key = find_special_key(&arg, len + 1, &modifiers, FSK_KEYCODE | FSK_KEEP_X_KEY | FSK_SIMPLIFY, NULL); if (modifiers) { // can't handle modifiers here @@ -3147,9 +3106,9 @@ int find_key_option_len(const char_u *arg_arg, size_t len, bool has_lt) return key; } -static int find_key_option(const char_u *arg, bool has_lt) +static int find_key_option(const char *arg, bool has_lt) { - return find_key_option_len(arg, STRLEN(arg), has_lt); + return find_key_option_len(arg, strlen(arg), has_lt); } /// if 'all' == 0: show changed options @@ -3158,16 +3117,6 @@ static int find_key_option(const char_u *arg, bool has_lt) /// @param opt_flags OPT_LOCAL and/or OPT_GLOBAL static void showoptions(int all, int opt_flags) { - vimoption_T *p; - int col; - char_u *varp; - int item_count; - int run; - int row, rows; - int cols; - int i; - int len; - #define INC 20 #define GAP 3 @@ -3186,16 +3135,16 @@ static void showoptions(int all, int opt_flags) // 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++) { + for (int run = 1; run <= 2 && !got_int; run++) { // collect the items in items[] - item_count = 0; - for (p = &options[0]; p->fullname != NULL; p++) { + int item_count = 0; + for (vimoption_T *p = &options[0]; p->fullname != NULL; p++) { // apply :filter /pat/ if (message_filtered(p->fullname)) { continue; } - varp = NULL; + char_u *varp = NULL; if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) != 0) { if (p->indir != PV_NONE) { varp = (char_u *)get_varp_scope(p, opt_flags); @@ -3205,13 +3154,14 @@ static void showoptions(int all, int opt_flags) } if (varp != NULL && (all == 1 || (all == 0 && !optval_default(p, varp)))) { + int len; 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((char *)NameBuff) + 1; + len = (int)strlen(p->fullname) + vim_strsize(NameBuff) + 1; } if ((len <= INC - GAP && run == 1) || (len > INC - GAP && run == 2)) { @@ -3220,13 +3170,15 @@ static void showoptions(int all, int opt_flags) } } + int rows; + // display the items if (run == 1) { assert(Columns <= INT_MAX - GAP && Columns + GAP >= INT_MIN + 3 && (Columns + GAP - 3) / INC >= INT_MIN && (Columns + GAP - 3) / INC <= INT_MAX); - cols = (Columns + GAP - 3) / INC; + int cols = (Columns + GAP - 3) / INC; if (cols == 0) { cols = 1; } @@ -3234,13 +3186,13 @@ static void showoptions(int all, int opt_flags) } else { // run == 2 rows = item_count; } - for (row = 0; row < rows && !got_int; row++) { + for (int row = 0; row < rows && !got_int; row++) { msg_putchar('\n'); // go to next line if (got_int) { // 'q' typed in more break; } - col = 0; - for (i = row; i < item_count; i += rows) { + int col = 0; + for (int i = row; i < item_count; i += rows) { msg_col = col; // make columns showoneopt(items[i], opt_flags); col += INC; @@ -3252,7 +3204,7 @@ static void showoptions(int all, int opt_flags) } /// Return true if option "p" has its default value. -static int optval_default(vimoption_T *p, char_u *varp) +static int optval_default(vimoption_T *p, const char_u *varp) { if (varp == NULL) { return true; // hidden option is always at default @@ -3302,7 +3254,7 @@ static void showoneopt(vimoption_T *p, int opt_flags) int save_silent = silent_mode; silent_mode = false; - info_message = true; // use mch_msg(), not mch_errmsg() + info_message = true; // use os_msg(), not os_errmsg() char_u *varp = (char_u *)get_varp_scope(p, opt_flags); @@ -3320,7 +3272,7 @@ static void showoneopt(vimoption_T *p, int opt_flags) msg_putchar('='); // put value string in NameBuff option_value2string(p, opt_flags); - msg_outtrans((char *)NameBuff); + msg_outtrans(NameBuff); } silent_mode = save_silent; @@ -3349,14 +3301,6 @@ static void showoneopt(vimoption_T *p, int opt_flags) /// Return FAIL on error, OK otherwise. int makeset(FILE *fd, int opt_flags, int local_only) { - vimoption_T *p; - char *varp; // currently used value - char_u *varp_fresh; // local value - char_u *varp_local = NULL; // fresh value - char *cmd; - int round; - int pri; - // Some options are never written: // - Options that don't have a default (terminal name, columns, lines). // - Terminal options. @@ -3364,8 +3308,8 @@ int makeset(FILE *fd, int opt_flags, int local_only) // // Do the loop over "options[]" twice: once for options with the // P_PRI_MKRC flag and once without. - for (pri = 1; pri >= 0; pri--) { - for (p = &options[0]; p->fullname; p++) { + for (int pri = 1; pri >= 0; pri--) { + for (vimoption_T *p = &options[0]; p->fullname; p++) { if (!(p->flags & P_NO_MKRC) && ((pri == 1) == ((p->flags & P_PRI_MKRC) != 0))) { // skip global option when only doing locals @@ -3379,7 +3323,7 @@ int makeset(FILE *fd, int opt_flags, int local_only) continue; } - varp = get_varp_scope(p, opt_flags); + char *varp = get_varp_scope(p, opt_flags); // currently used value // Hidden options are never written. if (!varp) { continue; @@ -3394,7 +3338,8 @@ int makeset(FILE *fd, int opt_flags, int local_only) continue; } - round = 2; + int round = 2; + char_u *varp_local = NULL; // fresh value if (p->indir != PV_NONE) { if (p->var == VAR_WIN) { // skip window-local option when only doing globals @@ -3404,7 +3349,7 @@ int makeset(FILE *fd, int opt_flags, int local_only) // When fresh value of window-local option is not at the // default, need to write it too. if (!(opt_flags & OPT_GLOBAL) && !local_only) { - varp_fresh = (char_u *)get_varp_scope(p, OPT_GLOBAL); + char_u *varp_fresh = (char_u *)get_varp_scope(p, OPT_GLOBAL); // local value if (!optval_default(p, varp_fresh)) { round = 1; varp_local = (char_u *)varp; @@ -3417,6 +3362,7 @@ int makeset(FILE *fd, int opt_flags, int local_only) // Round 1: fresh value for window-local options. // Round 2: other values for (; round <= 2; varp = (char *)varp_local, round++) { + char *cmd; if (round == 1 || (opt_flags & OPT_GLOBAL)) { cmd = "set"; } else { @@ -3480,22 +3426,21 @@ int makefoldset(FILE *fd) static int put_setstring(FILE *fd, char *cmd, char *name, char **valuep, uint64_t flags) { - char_u *s; - char_u *buf = NULL; - char_u *part = NULL; - char *p; - if (fprintf(fd, "%s %s=", cmd, name) < 0) { return FAIL; } + + char *buf = NULL; + char_u *part = NULL; + if (*valuep != NULL) { // Output 'pastetoggle' as key names. For other // options some characters have to be escaped with // CTRL-V or backslash if (valuep == &p_pt) { - s = (char_u *)(*valuep); + char_u *s = (char_u *)(*valuep); while (*s != NUL) { - if (put_escstr(fd, (char_u *)str2special((const char **)&s, false, false), 2) == FAIL) { + if (put_escstr(fd, (char *)str2special((const char **)&s, false, false), 2) == FAIL) { return FAIL; } } @@ -3504,7 +3449,7 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char **valuep, uint64_ // replace home directory in the whole option value into "buf" buf = xmalloc(size); - home_replace(NULL, *valuep, (char *)buf, size, false); + home_replace(NULL, *valuep, buf, size, false); // If the option value is longer than MAXPATHL, we need to append // each comma separated part of the option separately, so that it @@ -3517,7 +3462,7 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char **valuep, uint64_ if (put_eol(fd) == FAIL) { goto fail; } - p = (char *)buf; + char *p = buf; while (*p != NUL) { // for each comma separated option part, append value to // the option, :set rtp+=value @@ -3525,7 +3470,7 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char **valuep, uint64_ goto fail; } (void)copy_option_part(&p, (char *)part, size, ","); - if (put_escstr(fd, part, 2) == FAIL || put_eol(fd) == FAIL) { + if (put_escstr(fd, (char *)part, 2) == FAIL || put_eol(fd) == FAIL) { goto fail; } } @@ -3538,7 +3483,7 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char **valuep, uint64_ return FAIL; } xfree(buf); - } else if (put_escstr(fd, (char_u *)(*valuep), 2) == FAIL) { + } else if (put_escstr(fd, *valuep, 2) == FAIL) { return FAIL; } } @@ -3554,11 +3499,10 @@ fail: static int put_setnum(FILE *fd, char *cmd, char *name, long *valuep) { - long wc; - if (fprintf(fd, "%s %s=", cmd, name) < 0) { return FAIL; } + long wc; if (wc_use_keyname((char_u *)valuep, &wc)) { // print 'wildchar' and 'wildcharm' as a key name if (fputs((char *)get_special_key_name((int)wc, 0), fd) < 0) { @@ -3598,8 +3542,7 @@ void unset_global_local_option(char *name, void *from) } p = &(options[opt_idx]); - switch ((int)p->indir) - { + switch ((int)p->indir) { // global option with local value: use local value if it's been set case PV_EP: clear_string_option(&buf->b_p_ep); @@ -3689,84 +3632,93 @@ void unset_global_local_option(char *name, void *from) clear_string_option(&((win_T *)from)->w_p_ve); ((win_T *)from)->w_ve_flags = 0; break; + case PV_STC: + clear_string_option(&((win_T *)from)->w_p_stc); + break; } } -/// Get pointer to option variable, depending on local or global scope. -char *get_varp_scope(vimoption_T *p, int opt_flags) +char *get_varp_scope_from(vimoption_T *p, int scope, buf_T *buf, win_T *win) { - if ((opt_flags & OPT_GLOBAL) && p->indir != PV_NONE) { + if ((scope & OPT_GLOBAL) && p->indir != PV_NONE) { if (p->var == VAR_WIN) { - return GLOBAL_WO(get_varp(p)); + return GLOBAL_WO(get_varp_from(p, buf, win)); } return (char *)p->var; } - if ((opt_flags & OPT_LOCAL) && ((int)p->indir & PV_BOTH)) { + if ((scope & OPT_LOCAL) && ((int)p->indir & PV_BOTH)) { switch ((int)p->indir) { case PV_FP: - return (char *)&(curbuf->b_p_fp); + return (char *)&(buf->b_p_fp); case PV_EFM: - return (char *)&(curbuf->b_p_efm); + return (char *)&(buf->b_p_efm); case PV_GP: - return (char *)&(curbuf->b_p_gp); + return (char *)&(buf->b_p_gp); case PV_MP: - return (char *)&(curbuf->b_p_mp); + return (char *)&(buf->b_p_mp); case PV_EP: - return (char *)&(curbuf->b_p_ep); + return (char *)&(buf->b_p_ep); case PV_KP: - return (char *)&(curbuf->b_p_kp); + return (char *)&(buf->b_p_kp); case PV_PATH: - return (char *)&(curbuf->b_p_path); + return (char *)&(buf->b_p_path); case PV_AR: - return (char *)&(curbuf->b_p_ar); + return (char *)&(buf->b_p_ar); case PV_TAGS: - return (char *)&(curbuf->b_p_tags); + return (char *)&(buf->b_p_tags); case PV_TC: - return (char *)&(curbuf->b_p_tc); + return (char *)&(buf->b_p_tc); case PV_SISO: - return (char *)&(curwin->w_p_siso); + return (char *)&(win->w_p_siso); case PV_SO: - return (char *)&(curwin->w_p_so); + return (char *)&(win->w_p_so); case PV_DEF: - return (char *)&(curbuf->b_p_def); + return (char *)&(buf->b_p_def); case PV_INC: - return (char *)&(curbuf->b_p_inc); + return (char *)&(buf->b_p_inc); case PV_DICT: - return (char *)&(curbuf->b_p_dict); + return (char *)&(buf->b_p_dict); case PV_TSR: - return (char *)&(curbuf->b_p_tsr); + return (char *)&(buf->b_p_tsr); case PV_TSRFU: - return (char *)&(curbuf->b_p_tsrfu); + return (char *)&(buf->b_p_tsrfu); case PV_TFU: - return (char *)&(curbuf->b_p_tfu); + return (char *)&(buf->b_p_tfu); case PV_SBR: - return (char *)&(curwin->w_p_sbr); + return (char *)&(win->w_p_sbr); case PV_STL: - return (char *)&(curwin->w_p_stl); + return (char *)&(win->w_p_stl); case PV_WBR: - return (char *)&(curwin->w_p_wbr); + return (char *)&(win->w_p_wbr); case PV_UL: - return (char *)&(curbuf->b_p_ul); + return (char *)&(buf->b_p_ul); case PV_LW: - return (char *)&(curbuf->b_p_lw); + return (char *)&(buf->b_p_lw); case PV_BKC: - return (char *)&(curbuf->b_p_bkc); + return (char *)&(buf->b_p_bkc); case PV_MENC: - return (char *)&(curbuf->b_p_menc); + return (char *)&(buf->b_p_menc); case PV_FCS: - return (char *)&(curwin->w_p_fcs); + return (char *)&(win->w_p_fcs); case PV_LCS: - return (char *)&(curwin->w_p_lcs); + return (char *)&(win->w_p_lcs); case PV_VE: - return (char *)&(curwin->w_p_ve); + return (char *)&(win->w_p_ve); } return NULL; // "cannot happen" } - return (char *)get_varp(p); + return (char *)get_varp_from(p, buf, win); } -/// Get pointer to option variable. -static char_u *get_varp(vimoption_T *p) +/// Get pointer to option variable, depending on local or global scope. +/// +/// @param scope can be OPT_LOCAL, OPT_GLOBAL or a combination. +char *get_varp_scope(vimoption_T *p, int scope) +{ + return get_varp_scope_from(p, scope, curbuf, curwin); +} + +static char_u *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win) { // hidden option, always return NULL if (p->var == NULL) { @@ -3779,308 +3731,320 @@ static char_u *get_varp(vimoption_T *p) // global option with local value: use local value if it's been set case PV_EP: - return *curbuf->b_p_ep != NUL - ? (char_u *)&curbuf->b_p_ep : p->var; + return *buf->b_p_ep != NUL + ? (char_u *)&buf->b_p_ep : p->var; case PV_KP: - return *curbuf->b_p_kp != NUL - ? (char_u *)&curbuf->b_p_kp : p->var; + return *buf->b_p_kp != NUL + ? (char_u *)&buf->b_p_kp : p->var; case PV_PATH: - return *curbuf->b_p_path != NUL - ? (char_u *)&(curbuf->b_p_path) : p->var; + return *buf->b_p_path != NUL + ? (char_u *)&(buf->b_p_path) : p->var; case PV_AR: - return curbuf->b_p_ar >= 0 - ? (char_u *)&(curbuf->b_p_ar) : p->var; + return buf->b_p_ar >= 0 + ? (char_u *)&(buf->b_p_ar) : p->var; case PV_TAGS: - return *curbuf->b_p_tags != NUL - ? (char_u *)&(curbuf->b_p_tags) : p->var; + return *buf->b_p_tags != NUL + ? (char_u *)&(buf->b_p_tags) : p->var; case PV_TC: - return *curbuf->b_p_tc != NUL - ? (char_u *)&(curbuf->b_p_tc) : p->var; + return *buf->b_p_tc != NUL + ? (char_u *)&(buf->b_p_tc) : p->var; case PV_SISO: - return curwin->w_p_siso >= 0 - ? (char_u *)&(curwin->w_p_siso) : p->var; + return win->w_p_siso >= 0 + ? (char_u *)&(win->w_p_siso) : p->var; case PV_SO: - return curwin->w_p_so >= 0 - ? (char_u *)&(curwin->w_p_so) : p->var; + return win->w_p_so >= 0 + ? (char_u *)&(win->w_p_so) : p->var; case PV_BKC: - return *curbuf->b_p_bkc != NUL - ? (char_u *)&(curbuf->b_p_bkc) : p->var; + return *buf->b_p_bkc != NUL + ? (char_u *)&(buf->b_p_bkc) : p->var; case PV_DEF: - return *curbuf->b_p_def != NUL - ? (char_u *)&(curbuf->b_p_def) : p->var; + return *buf->b_p_def != NUL + ? (char_u *)&(buf->b_p_def) : p->var; case PV_INC: - return *curbuf->b_p_inc != NUL - ? (char_u *)&(curbuf->b_p_inc) : p->var; + return *buf->b_p_inc != NUL + ? (char_u *)&(buf->b_p_inc) : p->var; case PV_DICT: - return *curbuf->b_p_dict != NUL - ? (char_u *)&(curbuf->b_p_dict) : p->var; + return *buf->b_p_dict != NUL + ? (char_u *)&(buf->b_p_dict) : p->var; case PV_TSR: - return *curbuf->b_p_tsr != NUL - ? (char_u *)&(curbuf->b_p_tsr) : p->var; + return *buf->b_p_tsr != NUL + ? (char_u *)&(buf->b_p_tsr) : p->var; case PV_TSRFU: - return *curbuf->b_p_tsrfu != NUL - ? (char_u *)&(curbuf->b_p_tsrfu) : p->var; + return *buf->b_p_tsrfu != NUL + ? (char_u *)&(buf->b_p_tsrfu) : p->var; case PV_FP: - return *curbuf->b_p_fp != NUL - ? (char_u *)&(curbuf->b_p_fp) : p->var; + return *buf->b_p_fp != NUL + ? (char_u *)&(buf->b_p_fp) : p->var; case PV_EFM: - return *curbuf->b_p_efm != NUL - ? (char_u *)&(curbuf->b_p_efm) : p->var; + return *buf->b_p_efm != NUL + ? (char_u *)&(buf->b_p_efm) : p->var; case PV_GP: - return *curbuf->b_p_gp != NUL - ? (char_u *)&(curbuf->b_p_gp) : p->var; + return *buf->b_p_gp != NUL + ? (char_u *)&(buf->b_p_gp) : p->var; case PV_MP: - return *curbuf->b_p_mp != NUL - ? (char_u *)&(curbuf->b_p_mp) : p->var; + return *buf->b_p_mp != NUL + ? (char_u *)&(buf->b_p_mp) : p->var; case PV_SBR: - return *curwin->w_p_sbr != NUL - ? (char_u *)&(curwin->w_p_sbr) : p->var; + return *win->w_p_sbr != NUL + ? (char_u *)&(win->w_p_sbr) : p->var; case PV_STL: - return *curwin->w_p_stl != NUL - ? (char_u *)&(curwin->w_p_stl) : p->var; + return *win->w_p_stl != NUL + ? (char_u *)&(win->w_p_stl) : p->var; case PV_WBR: - return *curwin->w_p_wbr != NUL - ? (char_u *)&(curwin->w_p_wbr) : p->var; + return *win->w_p_wbr != NUL + ? (char_u *)&(win->w_p_wbr) : p->var; case PV_UL: - return curbuf->b_p_ul != NO_LOCAL_UNDOLEVEL - ? (char_u *)&(curbuf->b_p_ul) : p->var; + return buf->b_p_ul != NO_LOCAL_UNDOLEVEL + ? (char_u *)&(buf->b_p_ul) : p->var; case PV_LW: - return *curbuf->b_p_lw != NUL - ? (char_u *)&(curbuf->b_p_lw) : p->var; + return *buf->b_p_lw != NUL + ? (char_u *)&(buf->b_p_lw) : p->var; case PV_MENC: - return *curbuf->b_p_menc != NUL - ? (char_u *)&(curbuf->b_p_menc) : p->var; + return *buf->b_p_menc != NUL + ? (char_u *)&(buf->b_p_menc) : p->var; case PV_FCS: - return *curwin->w_p_fcs != NUL - ? (char_u *)&(curwin->w_p_fcs) : p->var; + return *win->w_p_fcs != NUL + ? (char_u *)&(win->w_p_fcs) : p->var; case PV_LCS: - return *curwin->w_p_lcs != NUL - ? (char_u *)&(curwin->w_p_lcs) : p->var; + return *win->w_p_lcs != NUL + ? (char_u *)&(win->w_p_lcs) : p->var; case PV_VE: - return *curwin->w_p_ve != NUL - ? (char_u *)&curwin->w_p_ve : p->var; + return *win->w_p_ve != NUL + ? (char_u *)&win->w_p_ve : p->var; case PV_ARAB: - return (char_u *)&(curwin->w_p_arab); + return (char_u *)&(win->w_p_arab); case PV_LIST: - return (char_u *)&(curwin->w_p_list); + return (char_u *)&(win->w_p_list); case PV_SPELL: - return (char_u *)&(curwin->w_p_spell); + return (char_u *)&(win->w_p_spell); case PV_CUC: - return (char_u *)&(curwin->w_p_cuc); + return (char_u *)&(win->w_p_cuc); case PV_CUL: - return (char_u *)&(curwin->w_p_cul); + return (char_u *)&(win->w_p_cul); case PV_CULOPT: - return (char_u *)&(curwin->w_p_culopt); + return (char_u *)&(win->w_p_culopt); case PV_CC: - return (char_u *)&(curwin->w_p_cc); + return (char_u *)&(win->w_p_cc); case PV_DIFF: - return (char_u *)&(curwin->w_p_diff); + return (char_u *)&(win->w_p_diff); case PV_FDC: - return (char_u *)&(curwin->w_p_fdc); + return (char_u *)&(win->w_p_fdc); case PV_FEN: - return (char_u *)&(curwin->w_p_fen); + return (char_u *)&(win->w_p_fen); case PV_FDI: - return (char_u *)&(curwin->w_p_fdi); + return (char_u *)&(win->w_p_fdi); case PV_FDL: - return (char_u *)&(curwin->w_p_fdl); + return (char_u *)&(win->w_p_fdl); case PV_FDM: - return (char_u *)&(curwin->w_p_fdm); + return (char_u *)&(win->w_p_fdm); case PV_FML: - return (char_u *)&(curwin->w_p_fml); + return (char_u *)&(win->w_p_fml); case PV_FDN: - return (char_u *)&(curwin->w_p_fdn); + return (char_u *)&(win->w_p_fdn); case PV_FDE: - return (char_u *)&(curwin->w_p_fde); + return (char_u *)&(win->w_p_fde); case PV_FDT: - return (char_u *)&(curwin->w_p_fdt); + return (char_u *)&(win->w_p_fdt); case PV_FMR: - return (char_u *)&(curwin->w_p_fmr); + return (char_u *)&(win->w_p_fmr); case PV_NU: - return (char_u *)&(curwin->w_p_nu); + return (char_u *)&(win->w_p_nu); case PV_RNU: - return (char_u *)&(curwin->w_p_rnu); + return (char_u *)&(win->w_p_rnu); case PV_NUW: - return (char_u *)&(curwin->w_p_nuw); + return (char_u *)&(win->w_p_nuw); case PV_WFH: - return (char_u *)&(curwin->w_p_wfh); + return (char_u *)&(win->w_p_wfh); case PV_WFW: - return (char_u *)&(curwin->w_p_wfw); + return (char_u *)&(win->w_p_wfw); case PV_PVW: - return (char_u *)&(curwin->w_p_pvw); + return (char_u *)&(win->w_p_pvw); case PV_RL: - return (char_u *)&(curwin->w_p_rl); + return (char_u *)&(win->w_p_rl); case PV_RLC: - return (char_u *)&(curwin->w_p_rlc); + return (char_u *)&(win->w_p_rlc); case PV_SCROLL: - return (char_u *)&(curwin->w_p_scr); + return (char_u *)&(win->w_p_scr); case PV_WRAP: - return (char_u *)&(curwin->w_p_wrap); + return (char_u *)&(win->w_p_wrap); case PV_LBR: - return (char_u *)&(curwin->w_p_lbr); + return (char_u *)&(win->w_p_lbr); case PV_BRI: - return (char_u *)&(curwin->w_p_bri); + return (char_u *)&(win->w_p_bri); case PV_BRIOPT: - return (char_u *)&(curwin->w_p_briopt); + return (char_u *)&(win->w_p_briopt); case PV_SCBIND: - return (char_u *)&(curwin->w_p_scb); + return (char_u *)&(win->w_p_scb); case PV_CRBIND: - return (char_u *)&(curwin->w_p_crb); + return (char_u *)&(win->w_p_crb); case PV_COCU: - return (char_u *)&(curwin->w_p_cocu); + return (char_u *)&(win->w_p_cocu); case PV_COLE: - return (char_u *)&(curwin->w_p_cole); + return (char_u *)&(win->w_p_cole); case PV_AI: - return (char_u *)&(curbuf->b_p_ai); + return (char_u *)&(buf->b_p_ai); case PV_BIN: - return (char_u *)&(curbuf->b_p_bin); + return (char_u *)&(buf->b_p_bin); case PV_BOMB: - return (char_u *)&(curbuf->b_p_bomb); + return (char_u *)&(buf->b_p_bomb); case PV_BH: - return (char_u *)&(curbuf->b_p_bh); + return (char_u *)&(buf->b_p_bh); case PV_BT: - return (char_u *)&(curbuf->b_p_bt); + return (char_u *)&(buf->b_p_bt); case PV_BL: - return (char_u *)&(curbuf->b_p_bl); + return (char_u *)&(buf->b_p_bl); case PV_CHANNEL: - return (char_u *)&(curbuf->b_p_channel); + return (char_u *)&(buf->b_p_channel); case PV_CI: - return (char_u *)&(curbuf->b_p_ci); + return (char_u *)&(buf->b_p_ci); case PV_CIN: - return (char_u *)&(curbuf->b_p_cin); + return (char_u *)&(buf->b_p_cin); case PV_CINK: - return (char_u *)&(curbuf->b_p_cink); + return (char_u *)&(buf->b_p_cink); case PV_CINO: - return (char_u *)&(curbuf->b_p_cino); + return (char_u *)&(buf->b_p_cino); case PV_CINSD: - return (char_u *)&(curbuf->b_p_cinsd); + return (char_u *)&(buf->b_p_cinsd); case PV_CINW: - return (char_u *)&(curbuf->b_p_cinw); + return (char_u *)&(buf->b_p_cinw); case PV_COM: - return (char_u *)&(curbuf->b_p_com); + return (char_u *)&(buf->b_p_com); case PV_CMS: - return (char_u *)&(curbuf->b_p_cms); + return (char_u *)&(buf->b_p_cms); case PV_CPT: - return (char_u *)&(curbuf->b_p_cpt); + return (char_u *)&(buf->b_p_cpt); #ifdef BACKSLASH_IN_FILENAME case PV_CSL: - return (char_u *)&(curbuf->b_p_csl); + return (char_u *)&(buf->b_p_csl); #endif case PV_CFU: - return (char_u *)&(curbuf->b_p_cfu); + return (char_u *)&(buf->b_p_cfu); case PV_OFU: - return (char_u *)&(curbuf->b_p_ofu); + return (char_u *)&(buf->b_p_ofu); + case PV_EOF: + return (char_u *)&(buf->b_p_eof); case PV_EOL: - return (char_u *)&(curbuf->b_p_eol); + return (char_u *)&(buf->b_p_eol); case PV_FIXEOL: - return (char_u *)&(curbuf->b_p_fixeol); + return (char_u *)&(buf->b_p_fixeol); case PV_ET: - return (char_u *)&(curbuf->b_p_et); + return (char_u *)&(buf->b_p_et); case PV_FENC: - return (char_u *)&(curbuf->b_p_fenc); + return (char_u *)&(buf->b_p_fenc); case PV_FF: - return (char_u *)&(curbuf->b_p_ff); + return (char_u *)&(buf->b_p_ff); case PV_FT: - return (char_u *)&(curbuf->b_p_ft); + return (char_u *)&(buf->b_p_ft); case PV_FO: - return (char_u *)&(curbuf->b_p_fo); + return (char_u *)&(buf->b_p_fo); case PV_FLP: - return (char_u *)&(curbuf->b_p_flp); + return (char_u *)&(buf->b_p_flp); case PV_IMI: - return (char_u *)&(curbuf->b_p_iminsert); + return (char_u *)&(buf->b_p_iminsert); case PV_IMS: - return (char_u *)&(curbuf->b_p_imsearch); + return (char_u *)&(buf->b_p_imsearch); case PV_INF: - return (char_u *)&(curbuf->b_p_inf); + return (char_u *)&(buf->b_p_inf); case PV_ISK: - return (char_u *)&(curbuf->b_p_isk); + return (char_u *)&(buf->b_p_isk); case PV_INEX: - return (char_u *)&(curbuf->b_p_inex); + return (char_u *)&(buf->b_p_inex); case PV_INDE: - return (char_u *)&(curbuf->b_p_inde); + return (char_u *)&(buf->b_p_inde); case PV_INDK: - return (char_u *)&(curbuf->b_p_indk); + return (char_u *)&(buf->b_p_indk); case PV_FEX: - return (char_u *)&(curbuf->b_p_fex); + return (char_u *)&(buf->b_p_fex); case PV_LISP: - return (char_u *)&(curbuf->b_p_lisp); + return (char_u *)&(buf->b_p_lisp); + case PV_LOP: + return (char_u *)&(buf->b_p_lop); case PV_ML: - return (char_u *)&(curbuf->b_p_ml); + return (char_u *)&(buf->b_p_ml); case PV_MPS: - return (char_u *)&(curbuf->b_p_mps); + return (char_u *)&(buf->b_p_mps); case PV_MA: - return (char_u *)&(curbuf->b_p_ma); + return (char_u *)&(buf->b_p_ma); case PV_MOD: - return (char_u *)&(curbuf->b_changed); + return (char_u *)&(buf->b_changed); case PV_NF: - return (char_u *)&(curbuf->b_p_nf); + return (char_u *)&(buf->b_p_nf); case PV_PI: - return (char_u *)&(curbuf->b_p_pi); + return (char_u *)&(buf->b_p_pi); case PV_QE: - return (char_u *)&(curbuf->b_p_qe); + return (char_u *)&(buf->b_p_qe); case PV_RO: - return (char_u *)&(curbuf->b_p_ro); + return (char_u *)&(buf->b_p_ro); case PV_SCBK: - return (char_u *)&(curbuf->b_p_scbk); + return (char_u *)&(buf->b_p_scbk); case PV_SI: - return (char_u *)&(curbuf->b_p_si); + return (char_u *)&(buf->b_p_si); case PV_STS: - return (char_u *)&(curbuf->b_p_sts); + return (char_u *)&(buf->b_p_sts); case PV_SUA: - return (char_u *)&(curbuf->b_p_sua); + return (char_u *)&(buf->b_p_sua); case PV_SWF: - return (char_u *)&(curbuf->b_p_swf); + return (char_u *)&(buf->b_p_swf); case PV_SMC: - return (char_u *)&(curbuf->b_p_smc); + return (char_u *)&(buf->b_p_smc); case PV_SYN: - return (char_u *)&(curbuf->b_p_syn); + return (char_u *)&(buf->b_p_syn); case PV_SPC: - return (char_u *)&(curwin->w_s->b_p_spc); + return (char_u *)&(win->w_s->b_p_spc); case PV_SPF: - return (char_u *)&(curwin->w_s->b_p_spf); + return (char_u *)&(win->w_s->b_p_spf); case PV_SPL: - return (char_u *)&(curwin->w_s->b_p_spl); + return (char_u *)&(win->w_s->b_p_spl); case PV_SPO: - return (char_u *)&(curwin->w_s->b_p_spo); + return (char_u *)&(win->w_s->b_p_spo); case PV_SW: - return (char_u *)&(curbuf->b_p_sw); + return (char_u *)&(buf->b_p_sw); case PV_TFU: - return (char_u *)&(curbuf->b_p_tfu); + return (char_u *)&(buf->b_p_tfu); case PV_TS: - return (char_u *)&(curbuf->b_p_ts); + return (char_u *)&(buf->b_p_ts); case PV_TW: - return (char_u *)&(curbuf->b_p_tw); + return (char_u *)&(buf->b_p_tw); case PV_UDF: - return (char_u *)&(curbuf->b_p_udf); + return (char_u *)&(buf->b_p_udf); case PV_WM: - return (char_u *)&(curbuf->b_p_wm); + return (char_u *)&(buf->b_p_wm); case PV_VSTS: - return (char_u *)&(curbuf->b_p_vsts); + return (char_u *)&(buf->b_p_vsts); case PV_VTS: - return (char_u *)&(curbuf->b_p_vts); + return (char_u *)&(buf->b_p_vts); case PV_KMAP: - return (char_u *)&(curbuf->b_p_keymap); + return (char_u *)&(buf->b_p_keymap); case PV_SCL: - return (char_u *)&(curwin->w_p_scl); + return (char_u *)&(win->w_p_scl); case PV_WINHL: - return (char_u *)&(curwin->w_p_winhl); + return (char_u *)&(win->w_p_winhl); case PV_WINBL: - return (char_u *)&(curwin->w_p_winbl); + return (char_u *)&(win->w_p_winbl); + case PV_STC: + return (char_u *)&(win->w_p_stc); default: iemsg(_("E356: get_varp ERROR")); } // always return a valid pointer to avoid a crash! - return (char_u *)&(curbuf->b_p_wm); + return (char_u *)&(buf->b_p_wm); +} + +/// Get pointer to option variable. +static inline char_u *get_varp(vimoption_T *p) +{ + return get_varp_from(p, curbuf, curwin); } /// Get the value of 'equalprg', either the buffer-local one or the global one. -char_u *get_equalprg(void) +char *get_equalprg(void) { if (*curbuf->b_p_ep == NUL) { return p_ep; } - return (char_u *)curbuf->b_p_ep; + return curbuf->b_p_ep; } /// Copy options from one window to another. @@ -4155,6 +4119,7 @@ void copy_winopt(winopt_T *from, winopt_T *to) to->wo_scl = copy_option_val(from->wo_scl); to->wo_winhl = copy_option_val(from->wo_winhl); to->wo_winbl = from->wo_winbl; + to->wo_stc = copy_option_val(from->wo_stc); // 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)); @@ -4192,6 +4157,7 @@ static void check_winopt(winopt_T *wop) check_string_option(&wop->wo_fcs); check_string_option(&wop->wo_ve); check_string_option(&wop->wo_wbr); + check_string_option(&wop->wo_stc); } /// Free the allocated memory inside a winopt_T. @@ -4218,6 +4184,7 @@ void clear_winopt(winopt_T *wop) clear_string_option(&wop->wo_fcs); clear_string_option(&wop->wo_ve); clear_string_option(&wop->wo_wbr); + clear_string_option(&wop->wo_stc); } void didset_window_options(win_T *wp, bool valid_cursor) @@ -4263,8 +4230,7 @@ static void init_buf_opt_idx(void) void buf_copy_options(buf_T *buf, int flags) { int should_copy = true; - char_u *save_p_isk = NULL; // init for GCC - int dont_do_help; + char *save_p_isk = NULL; // init for GCC int did_isk = false; // Skip this when the option defaults have not been set yet. Happens when @@ -4295,9 +4261,9 @@ void buf_copy_options(buf_T *buf, int flags) // 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; + bool 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 = (char_u *)buf->b_p_isk; + save_p_isk = buf->b_p_isk; buf->b_p_isk = NULL; } // Always free the allocated strings. If not already initialized, @@ -4370,10 +4336,13 @@ void buf_copy_options(buf_T *buf, int flags) #endif buf->b_p_cfu = xstrdup(p_cfu); COPY_OPT_SCTX(buf, BV_CFU); + set_buflocal_cfu_callback(buf); buf->b_p_ofu = xstrdup(p_ofu); COPY_OPT_SCTX(buf, BV_OFU); + set_buflocal_ofu_callback(buf); buf->b_p_tfu = xstrdup(p_tfu); COPY_OPT_SCTX(buf, BV_TFU); + set_buflocal_tfu_callback(buf); buf->b_p_sts = p_sts; COPY_OPT_SCTX(buf, BV_STS); buf->b_p_sts_nopaste = p_sts_nopaste; @@ -4411,6 +4380,8 @@ void buf_copy_options(buf_T *buf, int flags) COPY_OPT_SCTX(buf, BV_CINO); buf->b_p_cinsd = xstrdup(p_cinsd); COPY_OPT_SCTX(buf, BV_CINSD); + buf->b_p_lop = xstrdup(p_lop); + COPY_OPT_SCTX(buf, BV_LOP); // Don't copy 'filetype', it must be detected buf->b_p_ft = empty_option; @@ -4487,7 +4458,7 @@ void buf_copy_options(buf_T *buf, int flags) // Don't touch these at all when BCO_NOHELP is used and going from // or to a help buffer. if (dont_do_help) { - buf->b_p_isk = (char *)save_p_isk; + buf->b_p_isk = save_p_isk; if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) { (void)tabstop_set(p_vts, &buf->b_p_vts_array); } else { @@ -4542,15 +4513,15 @@ void reset_modifiable(void) } /// Set the global value for 'iminsert' to the local value. -void set_iminsert_global(void) +void set_iminsert_global(buf_T *buf) { - p_iminsert = curbuf->b_p_iminsert; + p_iminsert = buf->b_p_iminsert; } /// Set the global value for 'imsearch' to the local value. -void set_imsearch_global(void) +void set_imsearch_global(buf_T *buf) { - p_imsearch = curbuf->b_p_imsearch; + p_imsearch = buf->b_p_imsearch; } static int expand_option_idx = -1; @@ -4558,30 +4529,22 @@ static char_u expand_option_name[5] = { 't', '_', NUL, NUL, NUL }; static int expand_option_flags = 0; /// @param opt_flags OPT_GLOBAL and/or OPT_LOCAL -void set_context_in_set_cmd(expand_T *xp, char_u *arg, int opt_flags) +void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags) { - char_u nextchar; - uint32_t flags = 0; // init for GCC - int opt_idx = 0; // init for GCC - char_u *p; - char_u *s; - int is_term_option = false; - int key; - expand_option_flags = opt_flags; xp->xp_context = EXPAND_SETTINGS; if (*arg == NUL) { - xp->xp_pattern = (char *)arg; + xp->xp_pattern = arg; return; } - p = arg + STRLEN(arg) - 1; + char *p = arg + strlen(arg) - 1; if (*p == ' ' && *(p - 1) != '\\') { - xp->xp_pattern = (char *)p + 1; + xp->xp_pattern = p + 1; return; } while (p > arg) { - s = p; + char *s = p; // count number of backslashes before ' ' or ',' if (*p == ' ' || *p == ',') { while (s > arg && *(s - 1) == '\\') { @@ -4595,23 +4558,29 @@ void set_context_in_set_cmd(expand_T *xp, char_u *arg, int opt_flags) } p--; } - if (STRNCMP(p, "no", 2) == 0) { + if (strncmp(p, "no", 2) == 0) { xp->xp_context = EXPAND_BOOL_SETTINGS; p += 2; } - if (STRNCMP(p, "inv", 3) == 0) { + if (strncmp(p, "inv", 3) == 0) { xp->xp_context = EXPAND_BOOL_SETTINGS; p += 3; } - xp->xp_pattern = (char *)p; + xp->xp_pattern = p; arg = p; + + char nextchar; + uint32_t flags = 0; + int opt_idx = 0; + int is_term_option = false; + if (*arg == '<') { while (*p != '>') { if (*p++ == NUL) { // expand terminal option name return; } } - key = get_special_key_code(arg + 1); + int key = get_special_key_code((char_u *)arg + 1); if (key == 0) { // unknown name xp->xp_context = EXPAND_NOTHING; return; @@ -4631,8 +4600,8 @@ void set_context_in_set_cmd(expand_T *xp, char_u *arg, int opt_flags) } nextchar = *++p; is_term_option = true; - expand_option_name[2] = p[-2]; - expand_option_name[3] = p[-1]; + expand_option_name[2] = (char_u)p[-2]; + expand_option_name[3] = (char_u)p[-1]; } else { // Allow * wildcard. while (ASCII_ISALNUM(*p) || *p == '_' || *p == '*') { @@ -4671,7 +4640,7 @@ void set_context_in_set_cmd(expand_T *xp, char_u *arg, int opt_flags) } else { expand_option_idx = opt_idx; } - xp->xp_pattern = (char *)p + 1; + xp->xp_pattern = p + 1; return; } xp->xp_context = EXPAND_NOTHING; @@ -4679,30 +4648,29 @@ void set_context_in_set_cmd(expand_T *xp, char_u *arg, int opt_flags) return; } - xp->xp_pattern = (char *)p + 1; + xp->xp_pattern = p + 1; if (flags & P_EXPAND) { - p = options[opt_idx].var; - if (p == (char_u *)&p_bdir - || p == (char_u *)&p_dir - || p == (char_u *)&p_path - || p == (char_u *)&p_pp - || p == (char_u *)&p_rtp - || p == (char_u *)&p_cdpath - || p == (char_u *)&p_vdir) { + p = (char *)options[opt_idx].var; + if (p == (char *)&p_bdir + || p == (char *)&p_dir + || p == (char *)&p_path + || p == (char *)&p_pp + || p == (char *)&p_rtp + || p == (char *)&p_cdpath + || p == (char *)&p_vdir) { xp->xp_context = EXPAND_DIRECTORIES; - if (p == (char_u *)&p_path - || p == (char_u *)&p_cdpath) { + if (p == (char *)&p_path || p == (char *)&p_cdpath) { xp->xp_backslash = XP_BS_THREE; } else { xp->xp_backslash = XP_BS_ONE; } - } else if (p == (char_u *)&p_ft) { + } else if (p == (char *)&p_ft) { xp->xp_context = EXPAND_FILETYPE; } else { xp->xp_context = EXPAND_FILES; // for 'tags' need three backslashes for a space - if (p == (char_u *)&p_tags) { + if (p == (char *)&p_tags) { xp->xp_backslash = XP_BS_THREE; } else { xp->xp_backslash = XP_BS_ONE; @@ -4712,56 +4680,99 @@ void set_context_in_set_cmd(expand_T *xp, char_u *arg, int opt_flags) // For an option that is a list of file names, find the start of the // last file name. - for (p = arg + STRLEN(arg) - 1; p > (char_u *)xp->xp_pattern; p--) { + for (p = arg + strlen(arg) - 1; p > xp->xp_pattern; p--) { // count number of backslashes before ' ' or ',' if (*p == ' ' || *p == ',') { - s = p; - while (s > (char_u *)xp->xp_pattern && *(s - 1) == '\\') { + char *s = p; + while (s > xp->xp_pattern && *(s - 1) == '\\') { s--; } if ((*p == ' ' && (xp->xp_backslash == XP_BS_THREE && (p - s) < 3)) || (*p == ',' && (flags & P_COMMA) && ((p - s) & 1) == 0)) { - xp->xp_pattern = (char *)p + 1; + xp->xp_pattern = p + 1; break; } } // for 'spellsuggest' start at "file:" if (options[opt_idx].var == (char_u *)&p_sps - && STRNCMP(p, "file:", 5) == 0) { - xp->xp_pattern = (char *)p + 5; + && strncmp(p, "file:", 5) == 0) { + xp->xp_pattern = p + 5; break; } } } -int ExpandSettings(expand_T *xp, regmatch_T *regmatch, int *num_file, char ***file) +/// Returns true if "str" either matches "regmatch" or fuzzy matches "pat". +/// +/// If "test_only" is true and "fuzzy" is false and if "str" matches the regular +/// expression "regmatch", then returns true. Otherwise returns false. +/// +/// If "test_only" is false and "fuzzy" is false and if "str" matches the +/// regular expression "regmatch", then stores the match in matches[idx] and +/// returns true. +/// +/// If "test_only" is true and "fuzzy" is true and if "str" fuzzy matches +/// "fuzzystr", then returns true. Otherwise returns false. +/// +/// If "test_only" is false and "fuzzy" is true and if "str" fuzzy matches +/// "fuzzystr", then stores the match details in fuzmatch[idx] and returns true. +static bool match_str(char *const str, regmatch_T *const regmatch, char **const matches, + const int idx, const bool test_only, const bool fuzzy, + const char *const fuzzystr, fuzmatch_str_T *const fuzmatch) +{ + if (!fuzzy) { + if (vim_regexec(regmatch, str, (colnr_T)0)) { + if (!test_only) { + matches[idx] = xstrdup(str); + } + return true; + } + } else { + const int score = fuzzy_match_str(str, fuzzystr); + if (score != 0) { + if (!test_only) { + fuzmatch[idx].idx = idx; + fuzmatch[idx].str = xstrdup(str); + fuzmatch[idx].score = score; + } + return true; + } + } + return false; +} + +int ExpandSettings(expand_T *xp, regmatch_T *regmatch, char *fuzzystr, int *numMatches, + char ***matches, const bool can_fuzzy) { int num_normal = 0; // Nr of matching non-term-code settings - int match; int count = 0; - char *str; - int loop; static char *(names[]) = { "all" }; int ic = regmatch->rm_ic; // remember the ignore-case flag + fuzmatch_str_T *fuzmatch = NULL; + const bool fuzzy = can_fuzzy && cmdline_fuzzy_complete(fuzzystr); + // do this loop twice: // loop == 0: count the number of matching options // loop == 1: copy the matching options into allocated memory - for (loop = 0; loop <= 1; loop++) { + for (int loop = 0; loop <= 1; loop++) { + int match; regmatch->rm_ic = ic; if (xp->xp_context != EXPAND_BOOL_SETTINGS) { for (match = 0; match < (int)ARRAY_SIZE(names); match++) { - if (vim_regexec(regmatch, names[match], (colnr_T)0)) { + if (match_str(names[match], regmatch, *matches, + count, (loop == 0), fuzzy, fuzzystr, fuzmatch)) { if (loop == 0) { num_normal++; } else { - (*file)[count++] = xstrdup(names[match]); + count++; } } } } + char *str; for (size_t opt_idx = 0; (str = options[opt_idx].fullname) != NULL; opt_idx++) { if (options[opt_idx].var == NULL) { @@ -4771,39 +4782,51 @@ int ExpandSettings(expand_T *xp, regmatch_T *regmatch, int *num_file, char ***fi && !(options[opt_idx].flags & P_BOOL)) { continue; } - match = false; - if (vim_regexec(regmatch, str, (colnr_T)0) - || (options[opt_idx].shortname != NULL - && vim_regexec(regmatch, - options[opt_idx].shortname, - (colnr_T)0))) { - match = true; - } - if (match) { + if (match_str(str, regmatch, *matches, count, (loop == 0), + fuzzy, fuzzystr, fuzmatch)) { + if (loop == 0) { + num_normal++; + } else { + count++; + } + } else if (!fuzzy && options[opt_idx].shortname != NULL + && vim_regexec(regmatch, options[opt_idx].shortname, (colnr_T)0)) { + // Compare against the abbreviated option name (for regular + // expression match). Fuzzy matching (previous if) already + // matches against both the expanded and abbreviated names. if (loop == 0) { num_normal++; } else { - (*file)[count++] = xstrdup(str); + (*matches)[count++] = xstrdup(str); } } } if (loop == 0) { if (num_normal > 0) { - *num_file = num_normal; + *numMatches = num_normal; } else { return OK; } - *file = xmalloc((size_t)(*num_file) * sizeof(char *)); + if (!fuzzy) { + *matches = xmalloc((size_t)(*numMatches) * sizeof(char *)); + } else { + fuzmatch = xmalloc((size_t)(*numMatches) * sizeof(fuzmatch_str_T)); + } } } + + if (fuzzy) { + fuzzymatches_to_strmatches(fuzmatch, matches, count, false); + } + return OK; } void ExpandOldSetting(int *num_file, char ***file) { - char_u *var = NULL; + char *var = NULL; *num_file = 0; *file = xmalloc(sizeof(char_u *)); @@ -4816,14 +4839,14 @@ void ExpandOldSetting(int *num_file, char ***file) if (expand_option_idx >= 0) { // Put string of option value in NameBuff. option_value2string(&options[expand_option_idx], expand_option_flags); - var = (char_u *)NameBuff; + var = NameBuff; } else { - var = (char_u *)""; + var = ""; } // A backslash is required before some characters. This is the reverse of // what happens in do_set(). - char_u *buf = vim_strsave_escaped(var, escape_chars); + char *buf = vim_strsave_escaped(var, escape_chars); #ifdef BACKSLASH_IN_FILENAME // For MS-Windows et al. we don't double backslashes at the start and @@ -4832,14 +4855,14 @@ void ExpandOldSetting(int *num_file, char ***file) if (var[0] == '\\' && var[1] == '\\' && expand_option_idx >= 0 && (options[expand_option_idx].flags & P_EXPAND) - && vim_isfilec(var[2]) + && vim_isfilec((uint8_t)var[2]) && (var[2] != '\\' || (var == buf && var[4] != '\\'))) { STRMOVE(var, var + 1); } } #endif - *file[0] = (char *)buf; + *file[0] = buf; *num_file = 1; } @@ -4847,34 +4870,34 @@ void ExpandOldSetting(int *num_file, char ***file) /// NameBuff[]. Must not be called with a hidden option! /// /// @param opt_flags OPT_GLOBAL and/or OPT_LOCAL -static void option_value2string(vimoption_T *opp, int opt_flags) +static void option_value2string(vimoption_T *opp, int scope) { - char_u *varp = (char_u *)get_varp_scope(opp, opt_flags); + char *varp = get_varp_scope(opp, scope); if (opp->flags & P_NUM) { long wc = 0; - if (wc_use_keyname(varp, &wc)) { - STRLCPY(NameBuff, get_special_key_name((int)wc, 0), sizeof(NameBuff)); + if (wc_use_keyname((char_u *)varp, &wc)) { + xstrlcpy(NameBuff, (char *)get_special_key_name((int)wc, 0), sizeof(NameBuff)); } else if (wc != 0) { - STRLCPY(NameBuff, transchar((int)wc), sizeof(NameBuff)); + xstrlcpy(NameBuff, transchar((int)wc), sizeof(NameBuff)); } else { - snprintf((char *)NameBuff, + snprintf(NameBuff, sizeof(NameBuff), "%" PRId64, - (int64_t)*(long *)varp); + (int64_t)(*(long *)varp)); } } else { // P_STRING - varp = *(char_u **)(varp); + varp = *(char **)(varp); if (varp == NULL) { // Just in case. NameBuff[0] = NUL; } else if (opp->flags & P_EXPAND) { - home_replace(NULL, (char *)varp, (char *)NameBuff, MAXPATHL, false); + home_replace(NULL, varp, (char *)NameBuff, MAXPATHL, false); // Translate 'pastetoggle' into special key names. } else if ((char **)opp->var == &p_pt) { - str2specialbuf((const char *)p_pt, (char *)NameBuff, MAXPATHL); + str2specialbuf((const char *)p_pt, NameBuff, MAXPATHL); } else { - STRLCPY(NameBuff, varp, MAXPATHL); + xstrlcpy(NameBuff, varp, MAXPATHL); } } } @@ -4882,7 +4905,7 @@ static void option_value2string(vimoption_T *opp, int opt_flags) /// Return true if "varp" points to 'wildchar' or 'wildcharm' and it can be /// printed as a keyname. /// "*wcp" is set to the value of the option if it's 'wildchar' or 'wildcharm'. -static int wc_use_keyname(char_u *varp, long *wcp) +static int wc_use_keyname(const char_u *varp, long *wcp) { if (((long *)varp == &p_wc) || ((long *)varp == &p_wcm)) { *wcp = *(long *)varp; @@ -5075,24 +5098,22 @@ bool option_was_set(const char *name) void reset_option_was_set(const char *name) { const int idx = findoption(name); - - if (idx >= 0) { - options[idx].flags &= ~P_WAS_SET; + if (idx < 0) { + return; } + + options[idx].flags &= ~P_WAS_SET; } /// fill_breakat_flags() -- called when 'breakat' changes value. void fill_breakat_flags(void) { - char_u *p; - int i; - - for (i = 0; i < 256; i++) { + for (int i = 0; i < 256; i++) { breakat_flags[i] = false; } if (p_breakat != NULL) { - for (p = (char_u *)p_breakat; *p; p++) { + for (char_u *p = (char_u *)p_breakat; *p; p++) { breakat_flags[*p] = true; } } @@ -5110,16 +5131,16 @@ int fill_culopt_flags(char *val, win_T *wp) p = val; } while (*p != NUL) { - if (STRNCMP(p, "line", 4) == 0) { + if (strncmp(p, "line", 4) == 0) { p += 4; culopt_flags_new |= CULOPT_LINE; - } else if (STRNCMP(p, "both", 4) == 0) { + } else if (strncmp(p, "both", 4) == 0) { p += 4; culopt_flags_new |= CULOPT_LINE | CULOPT_NBR; - } else if (STRNCMP(p, "number", 6) == 0) { + } else if (strncmp(p, "number", 6) == 0) { p += 6; culopt_flags_new |= CULOPT_NBR; - } else if (STRNCMP(p, "screenline", 10) == 0) { + } else if (strncmp(p, "screenline", 10) == 0) { p += 10; culopt_flags_new |= CULOPT_SCRLINE; } @@ -5141,6 +5162,20 @@ int fill_culopt_flags(char *val, win_T *wp) return OK; } +/// Get the value of 'magic' taking "magic_overruled" into account. +bool magic_isset(void) +{ + switch (magic_overruled) { + case OPTION_MAGIC_ON: + return true; + case OPTION_MAGIC_OFF: + return false; + case OPTION_MAGIC_NOT_SET: + break; + } + return p_magic; +} + /// Set the callback function value for an option that accepts a function name, /// lambda, et al. (e.g. 'operatorfunc', 'tagfunc', etc.) /// @return OK if the option is successfully set to a function, otherwise FAIL @@ -5153,8 +5188,8 @@ int option_set_callback_func(char *optval, Callback *optcb) typval_T *tv; if (*optval == '{' - || (STRNCMP(optval, "function(", 9) == 0) - || (STRNCMP(optval, "funcref(", 8) == 0)) { + || (strncmp(optval, "function(", 9) == 0) + || (strncmp(optval, "funcref(", 8) == 0)) { // Lambda expression or a funcref tv = eval_expr(optval); if (tv == NULL) { @@ -5168,7 +5203,7 @@ int option_set_callback_func(char *optval, Callback *optcb) } Callback cb; - if (!callback_from_typval(&cb, tv)) { + if (!callback_from_typval(&cb, tv) || cb.type == kCallbackNone) { tv_free(tv); return FAIL; } @@ -5207,6 +5242,17 @@ unsigned int get_bkc_value(buf_T *buf) return buf->b_bkc_flags ? buf->b_bkc_flags : bkc_flags; } +/// Get the local or global value of 'formatlistpat'. +/// +/// @param buf The buffer. +char *get_flp_value(buf_T *buf) +{ + if (buf->b_p_flp == NULL || *buf->b_p_flp == NUL) { + return p_flp; + } + return buf->b_p_flp; +} + /// Get the local or global value of the 'virtualedit' flags. unsigned int get_ve_flags(void) { @@ -5217,16 +5263,16 @@ unsigned int get_ve_flags(void) /// /// @param win If not NULL, the window to get the local option from; global /// otherwise. -char_u *get_showbreak_value(win_T *const win) +char *get_showbreak_value(win_T *const win) FUNC_ATTR_WARN_UNUSED_RESULT { if (win->w_p_sbr == NULL || *win->w_p_sbr == NUL) { - return (char_u *)p_sbr; + return p_sbr; } if (strcmp(win->w_p_sbr, "NONE") == 0) { - return (char_u *)empty_option; + return empty_option; } - return (char_u *)win->w_p_sbr; + return win->w_p_sbr; } /// Return the current end-of-line type: EOL_DOS, EOL_UNIX or EOL_MAC. @@ -5345,9 +5391,9 @@ size_t copy_option_part(char **option, char *buf, size_t maxlen, char *sep_chars if (*p == '.') { buf[len++] = *p++; } - while (*p != NUL && vim_strchr(sep_chars, *p) == NULL) { + while (*p != NUL && vim_strchr(sep_chars, (uint8_t)(*p)) == NULL) { // Skip backslash before a separator character and space. - if (p[0] == '\\' && vim_strchr(sep_chars, p[1]) != NULL) { + if (p[0] == '\\' && vim_strchr(sep_chars, (uint8_t)p[1]) != NULL) { p++; } if (len < maxlen - 1) { diff --git a/src/nvim/option.h b/src/nvim/option.h index c65d2ee182..636257bfe8 100644 --- a/src/nvim/option.h +++ b/src/nvim/option.h @@ -1,7 +1,7 @@ #ifndef NVIM_OPTION_H #define NVIM_OPTION_H -#include "nvim/ex_cmds_defs.h" // for exarg_T +#include "nvim/ex_cmds_defs.h" /// Returned by get_option_value(). typedef enum { @@ -19,6 +19,8 @@ typedef enum { #define BCO_ALWAYS 2 // always copy the options #define BCO_NOHELP 4 // don't touch the help related options +#define MAX_NUMBERWIDTH 20 // used for 'numberwidth' and 'statuscolumn' + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "option.h.generated.h" #endif diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 25ef1fc091..d190fc5999 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -1,8 +1,8 @@ #ifndef NVIM_OPTION_DEFS_H #define NVIM_OPTION_DEFS_H -#include "eval/typval.h" // For scid_T -#include "nvim/macros.h" // For EXTERN +#include "nvim/eval/typval.h" +#include "nvim/macros.h" #include "nvim/types.h" // option_defs.h: definition of global variables for settable options @@ -24,6 +24,8 @@ #define P_NO_MKRC 0x200U ///< don't include in :mkvimrc output // when option changed, what to display: +#define P_UI_OPTION 0x400U ///< send option to remote UI +#define P_RTABL 0x800U ///< redraw tabline #define P_RSTAT 0x1000U ///< redraw status lines #define P_RWIN 0x2000U ///< redraw current window and recompute text #define P_RBUF 0x4000U ///< redraw current buffer and recompute text @@ -49,9 +51,9 @@ #define P_NDNAME 0x8000000U ///< only normal dir name chars allowed #define P_RWINONLY 0x10000000U ///< only redraw current window #define P_MLE 0x20000000U ///< under control of 'modelineexpr' +#define P_FUNC 0x40000000U ///< accept a function reference or a lambda -#define P_NO_DEF_EXP 0x40000000U ///< Do not expand default value. -#define P_UI_OPTION 0x80000000U ///< send option to remote ui +#define P_NO_DEF_EXP 0x80000000U ///< Do not expand default value. /// Flags for option-setting functions /// @@ -239,7 +241,7 @@ enum { SHM_MOD = 'm', ///< Modified. SHM_FILE = 'f', ///< (file 1 of 2) SHM_LAST = 'i', ///< Last line incomplete. - SHM_TEXT = 'x', ///< Tx instead of textmode. + SHM_TEXT = 'x', ///< tx instead of textmode. SHM_LINES = 'l', ///< "L" instead of "lines". SHM_NEW = 'n', ///< "[New]" instead of "[New file]". SHM_WRI = 'w', ///< "[w]" instead of "written". @@ -253,15 +255,15 @@ enum { SHM_ATTENTION = 'A', ///< No ATTENTION messages. SHM_INTRO = 'I', ///< Intro messages. SHM_COMPLETIONMENU = 'c', ///< Completion menu messages. + SHM_COMPLETIONSCAN = 'C', ///< Completion scanning messages. SHM_RECORDING = 'q', ///< Short recording message. SHM_FILEINFO = 'F', ///< No file info messages. - SHM_SEARCHCOUNT = 'S', ///< Search sats: '[1/10]' + SHM_SEARCHCOUNT = 'S', ///< Search stats: '[1/10]' }; /// Represented by 'a' flag. #define SHM_ALL_ABBREVIATIONS ((char[]) { \ SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW, SHM_WRI, \ - 0, \ - }) + 0 }) // characters for p_go: #define GO_ASEL 'a' // autoselect @@ -333,6 +335,9 @@ enum { STL_ALTPERCENT = 'P', ///< Percentage as TOP BOT ALL or NN%. STL_ARGLISTSTAT = 'a', ///< Argument list status as (x of y). STL_PAGENUM = 'N', ///< Page number (when printing). + STL_SHOWCMD = 'S', ///< 'showcmd' buffer + STL_FOLDCOL = 'C', ///< Fold column for 'statuscolumn' + STL_SIGNCOL = 's', ///< Sign column for 'statuscolumn' STL_VIM_EXPR = '{', ///< Start of expression to substitute. STL_SEPARATE = '=', ///< Separation between alignment sections. STL_TRUNCMARK = '<', ///< Truncation mark if line is too long. @@ -350,10 +355,10 @@ enum { STL_HELPFLAG, STL_HELPFLAG_ALT, STL_FILETYPE, STL_FILETYPE_ALT, \ STL_PREVIEWFLAG, STL_PREVIEWFLAG_ALT, STL_MODIFIED, STL_MODIFIED_ALT, \ STL_QUICKFIX, STL_PERCENTAGE, STL_ALTPERCENT, STL_ARGLISTSTAT, STL_PAGENUM, \ - STL_VIM_EXPR, STL_SEPARATE, STL_TRUNCMARK, STL_USER_HL, STL_HIGHLIGHT, \ - STL_TABPAGENR, STL_TABCLOSENR, STL_CLICK_FUNC, \ - 0, \ - }) + STL_SHOWCMD, STL_FOLDCOL, STL_SIGNCOL, STL_VIM_EXPR, STL_SEPARATE, \ + STL_TRUNCMARK, STL_USER_HL, STL_HIGHLIGHT, STL_TABPAGENR, STL_TABCLOSENR, \ + STL_CLICK_FUNC, STL_TABPAGENR, STL_TABCLOSENR, STL_CLICK_FUNC, \ + 0, }) // flags used for parsed 'wildmode' #define WIM_FULL 0x01 @@ -437,7 +442,7 @@ EXTERN unsigned bo_flags; #define BO_SPELL 0x20000 #define BO_WILD 0x40000 -EXTERN char_u *p_bsk; // 'backupskip' +EXTERN char *p_bsk; // 'backupskip' EXTERN char *p_breakat; // 'breakat' EXTERN char *p_bh; ///< 'bufhidden' EXTERN char *p_bt; ///< 'buftype' @@ -463,22 +468,13 @@ EXTERN long p_columns; // 'columns' EXTERN int p_confirm; // 'confirm' EXTERN char *p_cot; // 'completeopt' #ifdef BACKSLASH_IN_FILENAME -EXTERN char_u *p_csl; // 'completeslash' +EXTERN char *p_csl; // 'completeslash' #endif EXTERN long p_pb; // 'pumblend' EXTERN long p_ph; // 'pumheight' EXTERN long p_pw; // 'pumwidth' EXTERN char *p_com; ///< 'comments' EXTERN char *p_cpo; // 'cpoptions' -EXTERN char *p_csprg; // 'cscopeprg' -EXTERN int p_csre; // 'cscoperelative' -EXTERN char *p_csqf; // 'cscopequickfix' -#define CSQF_CMDS "sgdctefia" -#define CSQF_FLAGS "+-0" -EXTERN int p_cst; // 'cscopetag' -EXTERN long p_csto; // 'cscopetagorder' -EXTERN long p_cspc; // 'cscopepathcomp' -EXTERN int p_csverbose; // 'cscopeverbose' EXTERN char *p_debug; // 'debug' EXTERN char *p_def; // 'define' EXTERN char *p_inc; @@ -498,12 +494,13 @@ EXTERN int p_ed; // 'edcompatible' EXTERN char *p_ead; // 'eadirection' EXTERN int p_emoji; // 'emoji' EXTERN int p_ea; // 'equalalways' -EXTERN char_u *p_ep; // 'equalprg' +EXTERN char *p_ep; // 'equalprg' EXTERN int p_eb; // 'errorbells' -EXTERN char_u *p_ef; // 'errorfile' +EXTERN char *p_ef; // 'errorfile' EXTERN char *p_efm; // 'errorformat' EXTERN char *p_gefm; // 'grepformat' EXTERN char *p_gp; // 'grepprg' +EXTERN int p_eof; ///< 'endoffile' EXTERN int p_eol; ///< 'endofline' EXTERN char *p_ei; // 'eventignore' EXTERN int p_et; ///< 'expandtab' @@ -534,23 +531,15 @@ EXTERN unsigned fdo_flags; EXTERN char *p_fex; ///< 'formatexpr' EXTERN char *p_flp; ///< 'formatlistpat' EXTERN char *p_fo; ///< 'formatoptions' -EXTERN char_u *p_fp; // 'formatprg' +EXTERN char *p_fp; // 'formatprg' EXTERN int p_fs; // 'fsync' EXTERN int p_gd; // 'gdefault' -EXTERN char_u *p_pdev; // 'printdevice' -EXTERN char *p_penc; // 'printencoding' -EXTERN char *p_pexpr; // 'printexpr' -EXTERN char *p_pmfn; // 'printmbfont' -EXTERN char *p_pmcs; // 'printmbcharset' -EXTERN char *p_pfn; // 'printfont' -EXTERN char *p_popt; // 'printoptions' -EXTERN char_u *p_header; // 'printheader' EXTERN char *p_guicursor; // 'guicursor' -EXTERN char_u *p_guifont; // 'guifont' -EXTERN char_u *p_guifontwide; // 'guifontwide' +EXTERN char *p_guifont; // 'guifont' +EXTERN char *p_guifontwide; // 'guifontwide' EXTERN char *p_hf; // 'helpfile' EXTERN long p_hh; // 'helpheight' -EXTERN char_u *p_hlg; // 'helplang' +EXTERN char *p_hlg; // 'helplang' EXTERN int p_hid; // 'hidden' EXTERN char *p_hl; // 'highlight' EXTERN int p_hls; // 'hlsearch' @@ -579,16 +568,17 @@ EXTERN unsigned jop_flags; #define JOP_STACK 0x01 #define JOP_VIEW 0x02 EXTERN char *p_keymap; ///< 'keymap' -EXTERN char_u *p_kp; // 'keywordprg' +EXTERN char *p_kp; // 'keywordprg' EXTERN char *p_km; // 'keymodel' EXTERN char *p_langmap; // 'langmap' EXTERN int p_lnr; // 'langnoremap' EXTERN int p_lrm; // 'langremap' -EXTERN char_u *p_lm; // 'langmenu' +EXTERN char *p_lm; // 'langmenu' EXTERN long p_lines; // 'lines' EXTERN long p_linespace; // 'linespace' EXTERN int p_lisp; ///< 'lisp' -EXTERN char_u *p_lispwords; // 'lispwords' +EXTERN char *p_lop; ///< 'lispoptions' +EXTERN char *p_lispwords; // 'lispwords' EXTERN long p_ls; // 'laststatus' EXTERN long p_stal; // 'showtabline' EXTERN char *p_lcs; // 'listchars' @@ -598,10 +588,8 @@ EXTERN int p_lpl; // 'loadplugins' EXTERN int p_magic; // 'magic' EXTERN char *p_menc; // 'makeencoding' EXTERN char *p_mef; // 'makeef' -EXTERN char_u *p_mp; // 'makeprg' +EXTERN char *p_mp; // 'makeprg' EXTERN char *p_mps; ///< 'matchpairs' -EXTERN char_u *p_cc; // 'colorcolumn' -EXTERN int p_cc_cols[256]; // array for 'colorcolumn' columns EXTERN long p_mat; // 'matchtime' EXTERN long p_mco; // 'maxcombine' EXTERN long p_mfd; // 'maxfuncdepth' @@ -625,13 +613,13 @@ EXTERN long p_mouset; // 'mousetime' EXTERN int p_more; // 'more' EXTERN char *p_nf; ///< 'nrformats' EXTERN char *p_opfunc; // 'operatorfunc' -EXTERN char_u *p_para; // 'paragraphs' +EXTERN char *p_para; // 'paragraphs' EXTERN int p_paste; // 'paste' EXTERN char *p_pt; // 'pastetoggle' -EXTERN char_u *p_pex; // 'patchexpr' +EXTERN char *p_pex; // 'patchexpr' EXTERN char *p_pm; // 'patchmode' -EXTERN char_u *p_path; // 'path' -EXTERN char_u *p_cdpath; // 'cdpath' +EXTERN char *p_path; // 'path' +EXTERN char *p_cdpath; // 'cdpath' EXTERN int p_pi; ///< 'preserveindent' EXTERN long p_pyx; // 'pyxversion' EXTERN char *p_qe; ///< 'quoteescape' @@ -685,11 +673,11 @@ EXTERN unsigned ssop_flags; #define SSOP_SKIP_RTP 0x20000 EXTERN char *p_sh; // 'shell' -EXTERN char_u *p_shcf; // 'shellcmdflag' +EXTERN char *p_shcf; // 'shellcmdflag' EXTERN char *p_sp; // 'shellpipe' -EXTERN char_u *p_shq; // 'shellquote' +EXTERN char *p_shq; // 'shellquote' EXTERN char *p_sxq; // 'shellxquote' -EXTERN char_u *p_sxe; // 'shellxescape' +EXTERN char *p_sxe; // 'shellxescape' EXTERN char *p_srr; // 'shellredir' EXTERN int p_stmp; // 'shelltemp' #ifdef BACKSLASH_IN_FILENAME @@ -702,6 +690,7 @@ EXTERN long p_sw; ///< 'shiftwidth' EXTERN char *p_shm; // 'shortmess' EXTERN char *p_sbr; // 'showbreak' EXTERN int p_sc; // 'showcmd' +EXTERN char *p_sloc; // 'showcmdloc' EXTERN int p_sft; // 'showfulltag' EXTERN int p_sm; // 'showmatch' EXTERN int p_smd; // 'showmode' @@ -736,7 +725,7 @@ EXTERN unsigned int spo_flags; EXTERN char *p_sps; // 'spellsuggest' EXTERN int p_spr; // 'splitright' EXTERN int p_sol; // 'startofline' -EXTERN char_u *p_su; // 'suffixes' +EXTERN char *p_su; // 'suffixes' EXTERN char *p_swb; // 'switchbuf' EXTERN unsigned swb_flags; // Keep in sync with p_swb_values in optionstr.c @@ -758,7 +747,7 @@ EXTERN unsigned tc_flags; ///< flags from 'tagcase' #define TC_SMART 0x10 EXTERN long p_tl; ///< 'taglength' EXTERN int p_tr; ///< 'tagrelative' -EXTERN char_u *p_tags; ///< 'tags' +EXTERN char *p_tags; ///< 'tags' EXTERN int p_tgst; ///< 'tagstack' EXTERN int p_tbidi; ///< 'termbidi' EXTERN long p_tw; ///< 'textwidth' @@ -767,13 +756,13 @@ EXTERN int p_timeout; ///< 'timeout' EXTERN long p_tm; ///< 'timeoutlen' EXTERN int p_title; ///< 'title' EXTERN long p_titlelen; ///< 'titlelen' -EXTERN char_u *p_titleold; ///< 'titleold' +EXTERN char *p_titleold; ///< 'titleold' EXTERN char *p_titlestring; ///< 'titlestring' -EXTERN char_u *p_tsr; ///< 'thesaurus' +EXTERN char *p_tsr; ///< 'thesaurus' EXTERN int p_tgc; ///< 'termguicolors' EXTERN int p_ttimeout; ///< 'ttimeout' EXTERN long p_ttm; ///< 'ttimeoutlen' -EXTERN char_u *p_udir; ///< 'undodir' +EXTERN char *p_udir; ///< 'undodir' EXTERN int p_udf; ///< 'undofile' EXTERN long p_ul; ///< 'undolevels' EXTERN long p_ur; ///< 'undoreload' @@ -783,21 +772,21 @@ EXTERN char *p_shada; ///< 'shada' EXTERN char *p_shadafile; ///< 'shadafile' EXTERN char *p_vsts; ///< 'varsofttabstop' EXTERN char *p_vts; ///< 'vartabstop' -EXTERN char_u *p_vdir; ///< 'viewdir' +EXTERN char *p_vdir; ///< 'viewdir' EXTERN char *p_vop; ///< 'viewoptions' EXTERN unsigned vop_flags; ///< uses SSOP_ flags EXTERN int p_vb; ///< 'visualbell' EXTERN char *p_ve; ///< 'virtualedit' EXTERN unsigned ve_flags; -#define VE_BLOCK 5U // includes "all" -#define VE_INSERT 6U // includes "all" +#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" +#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 +char *p_vfile = ""; // used before options are initialized #else extern char *p_vfile; // 'verbosefile' #endif @@ -806,6 +795,7 @@ EXTERN char *p_wop; // 'wildoptions' EXTERN unsigned wop_flags; #define WOP_TAGFILE 0x01 #define WOP_PUM 0x02 +#define WOP_FUZZY 0x04 EXTERN long p_window; // 'window' EXTERN char *p_wak; // 'winaltkeys' EXTERN char *p_wig; // 'wildignore' @@ -864,6 +854,7 @@ enum { BV_CFU, BV_DEF, BV_INC, + BV_EOF, BV_EOL, BV_FIXEOL, BV_EP, @@ -886,6 +877,7 @@ enum { BV_KMAP, BV_KP, BV_LISP, + BV_LOP, BV_LW, BV_MENC, BV_MA, @@ -965,6 +957,7 @@ enum { WV_CULOPT, WV_CC, WV_SBR, + WV_STC, WV_STL, WV_WFH, WV_WFW, diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 149d0bace4..387ccd0888 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -10,6 +10,7 @@ -- secure=nil, gettext=nil, noglob=nil, normal_fname_chars=nil, -- pri_mkrc=nil, deny_in_modelines=nil, normal_dname_chars=nil, -- modelineexpr=nil, +-- func=nil, -- expand=nil, nodefault=nil, no_mkrc=nil, -- alloced=nil, -- save_pv_indir=nil, @@ -19,7 +20,7 @@ -- types: bool, number, string -- lists: (nil), comma, onecomma, flags, flagscomma -- scopes: global, buffer, window --- redraw options: statuslines, current_window, curent_window_only, +-- redraw options: statuslines, tabline, current_window, current_window_only, -- current_buffer, all_windows, curswant -- defaults: {condition=#if condition, if_true=default, if_false=default} -- #if condition: @@ -455,6 +456,7 @@ return { type='string', scope={'buffer'}, secure=true, alloced=true, + func=true, varname='p_cfu', defaults={if_true=""} }, @@ -496,58 +498,6 @@ return { defaults={if_true=macros('CPO_VIM')} }, { - full_name='cscopepathcomp', abbreviation='cspc', - short_desc=N_("how many components of the path to show"), - type='number', scope={'global'}, - varname='p_cspc', - defaults={if_true=0} - }, - { - full_name='cscopeprg', abbreviation='csprg', - short_desc=N_("command to execute cscope"), - type='string', scope={'global'}, - secure=true, - expand=true, - varname='p_csprg', - defaults={if_true="cscope"} - }, - { - full_name='cscopequickfix', abbreviation='csqf', - short_desc=N_("use quickfix window for cscope results"), - type='string', list='onecomma', scope={'global'}, - deny_duplicates=true, - varname='p_csqf', - defaults={if_true=""} - }, - { - full_name='cscoperelative', abbreviation='csre', - short_desc=N_("Use cscope.out path basename as prefix"), - type='bool', scope={'global'}, - varname='p_csre', - defaults={if_true=0} - }, - { - full_name='cscopetag', abbreviation='cst', - short_desc=N_("use cscope for tag commands"), - type='bool', scope={'global'}, - varname='p_cst', - defaults={if_true=0} - }, - { - full_name='cscopetagorder', abbreviation='csto', - short_desc=N_("determines \":cstag\" search order"), - type='number', scope={'global'}, - varname='p_csto', - defaults={if_true=0} - }, - { - full_name='cscopeverbose', abbreviation='csverb', - short_desc=N_("give messages when adding a cscope database"), - type='bool', scope={'global'}, - varname='p_csverbose', - defaults={if_true=1} - }, - { full_name='cursorbind', abbreviation='crb', short_desc=N_("move cursor in window as it moves in other windows"), type='bool', scope={'window'}, @@ -693,6 +643,15 @@ return { defaults={if_true=macros('ENC_DFLT')} }, { + full_name='endoffile', abbreviation='eof', + short_desc=N_("write CTRL-Z for last line in file"), + type='bool', scope={'buffer'}, + no_mkrc=true, + redraw={'statuslines'}, + varname='p_eof', + defaults={if_true=false} + }, + { full_name='endofline', abbreviation='eol', short_desc=N_("write <EOL> for last line in file"), type='bool', scope={'buffer'}, @@ -1416,6 +1375,14 @@ return { defaults={if_true=false} }, { + full_name='lispoptions', abbreviation='lop', + short_desc=N_("options for lisp indenting"), + type='string', list='onecomma', scope={'buffer'}, + deny_duplicates=true, + varname='p_lop', pv_name='p_lop', + defaults={if_true=''} + }, + { full_name='lispwords', abbreviation='lw', short_desc=N_("words that change how lisp indenting works"), type='string', list='onecomma', scope={'global', 'buffer'}, @@ -1673,6 +1640,7 @@ return { type='string', scope={'buffer'}, secure=true, alloced=true, + func=true, varname='p_ofu', defaults={if_true=""} }, @@ -1688,6 +1656,7 @@ return { short_desc=N_("function to be called for |g@| operator"), type='string', scope={'global'}, secure=true, + func=true, varname='p_opfunc', defaults={if_true=""} }, @@ -1771,65 +1740,6 @@ return { defaults={if_true=false} }, { - full_name='printdevice', abbreviation='pdev', - short_desc=N_("name of the printer to be used for :hardcopy"), - type='string', scope={'global'}, - secure=true, - varname='p_pdev', - defaults={if_true=""} - }, - { - full_name='printencoding', abbreviation='penc', - short_desc=N_("encoding to be used for printing"), - type='string', scope={'global'}, - varname='p_penc', - defaults={if_true=""} - }, - { - full_name='printexpr', abbreviation='pexpr', - short_desc=N_("expression used to print PostScript for :hardcopy"), - type='string', scope={'global'}, - secure=true, - varname='p_pexpr', - defaults={if_true=""} - }, - { - full_name='printfont', abbreviation='pfn', - short_desc=N_("name of the font to be used for :hardcopy"), - type='string', scope={'global'}, - varname='p_pfn', - defaults={if_true="courier"} - }, - { - full_name='printheader', abbreviation='pheader', - short_desc=N_("format of the header used for :hardcopy"), - type='string', scope={'global'}, - varname='p_header', - defaults={if_true="%<%f%h%m%=Page %N"} - }, - { - full_name='printmbcharset', abbreviation='pmbcs', - short_desc=N_("CJK character set to be used for :hardcopy"), - type='string', scope={'global'}, - varname='p_pmcs', - defaults={if_true=""} - }, - { - full_name='printmbfont', abbreviation='pmbfn', - short_desc=N_("font names to be used for CJK output of :hardcopy"), - type='string', scope={'global'}, - varname='p_pmfn', - defaults={if_true=""} - }, - { - full_name='printoptions', abbreviation='popt', - short_desc=N_("controls the format of :hardcopy output"), - type='string', list='onecomma', scope={'global'}, - deny_duplicates=true, - varname='p_popt', - defaults={if_true=""} - }, - { full_name='prompt', short_desc=N_("enable prompt in Ex mode"), type='bool', scope={'global'}, @@ -1870,6 +1780,8 @@ return { full_name='quickfixtextfunc', abbreviation='qftf', short_desc=N_("customize the quickfix window"), type='string', scope={'global'}, + secure=true, + func=true, varname='p_qftf', defaults={if_true=""} }, @@ -2036,7 +1948,7 @@ return { }, { full_name='secure', - short_desc=N_("mode for reading .vimrc in current dir"), + short_desc=N_("No description"), type='bool', scope={'global'}, secure=true, varname='p_secure', @@ -2213,6 +2125,13 @@ return { defaults={if_true=true} }, { + full_name='showcmdloc', abbreviation='sloc', + short_desc=N_("change location of partial command"), + type='string', scope={'global'}, + varname='p_sloc', + defaults={if_true="last"} + }, + { full_name='showfulltag', abbreviation='sft', short_desc=N_("show full tag pattern when completing tag"), type='bool', scope={'global'}, @@ -2379,6 +2298,15 @@ return { defaults={if_true=false} }, { + full_name='statuscolumn', abbreviation='stc', + short_desc=N_("custom format for the status column"), + type='string', scope={'window'}, + redraw={'current_window'}, + secure=true, + alloced=true, + defaults={if_true=""} + }, + { full_name='statusline', abbreviation='stl', short_desc=N_("custom format for the status line"), type='string', scope={'global', 'window'}, @@ -2443,6 +2371,8 @@ return { full_name='tagfunc', abbreviation='tfu', short_desc=N_("function used to perform tag searches"), type='string', scope={'buffer'}, + secure=true, + func=true, varname='p_tfu', defaults={if_true=""} }, @@ -2451,7 +2381,7 @@ return { short_desc=N_("custom format for the console tab pages line"), type='string', scope={'global'}, modelineexpr=true, - redraw={'statuslines'}, + redraw={'tabline'}, varname='p_tal', defaults={if_true=""} }, @@ -2573,6 +2503,7 @@ return { type='string', scope={'global', 'buffer'}, secure=true, alloced=true, + func=true, varname='p_tsrfu', defaults={if_true=""} }, @@ -2716,7 +2647,7 @@ return { full_name='verbose', abbreviation='vbs', short_desc=N_("give informative messages"), type='number', scope={'global'}, - varname='p_verbose', + varname='p_verbose', redraw={'ui_option'}, defaults={if_true=0} }, { diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 1da6fa15d7..a5a708600f 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -2,14 +2,14 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> -#include <inttypes.h> #include <stdbool.h> -#include <stdlib.h> +#include <stdint.h> #include <string.h> #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/cursor_shape.h" @@ -17,27 +17,40 @@ #include "nvim/digraph.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" #include "nvim/ex_getln.h" -#include "nvim/hardcopy.h" +#include "nvim/fold.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/insexpand.h" #include "nvim/keycodes.h" +#include "nvim/macros.h" #include "nvim/mapping.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/option_defs.h" #include "nvim/optionstr.h" +#include "nvim/os/os.h" +#include "nvim/pos.h" #include "nvim/quickfix.h" #include "nvim/runtime.h" +#include "nvim/screen.h" #include "nvim/spell.h" #include "nvim/spellfile.h" #include "nvim/spellsuggest.h" #include "nvim/strings.h" +#include "nvim/tag.h" #include "nvim/ui.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -78,7 +91,7 @@ static char *(p_swb_values[]) = { "useopen", "usetab", "split", "newtab", "vspli static char *(p_spk_values[]) = { "cursor", "screen", "topline", NULL }; static char *(p_tc_values[]) = { "followic", "ignore", "match", "followscs", "smart", NULL }; static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", "none", "NONE", NULL }; -static char *(p_wop_values[]) = { "tagfile", "pum", NULL }; +static char *(p_wop_values[]) = { "tagfile", "pum", "fuzzy", NULL }; static char *(p_wak_values[]) = { "yes", "menu", "no", NULL }; static char *(p_mousem_values[]) = { "extend", "popup", "popup_setpos", "mac", NULL }; static char *(p_sel_values[]) = { "inclusive", "exclusive", "old", NULL }; @@ -113,12 +126,14 @@ static char *(p_icm_values[]) = { "nosplit", "split", NULL }; static char *(p_jop_values[]) = { "stack", "view", NULL }; static char *(p_tpf_values[]) = { "BS", "HT", "FF", "ESC", "DEL", "C0", "C1", NULL }; static char *(p_rdb_values[]) = { "compositor", "nothrottle", "invalid", "nodelta", NULL }; +static char *(p_sloc_values[]) = { "last", "statusline", "tabline", NULL }; /// All possible flags for 'shm'. static char SHM_ALL[] = { SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW, SHM_WRI, SHM_ABBREVIATIONS, SHM_WRITE, SHM_TRUNC, SHM_TRUNCALL, SHM_OVER, SHM_OVERALL, SHM_SEARCH, SHM_ATTENTION, SHM_INTRO, - SHM_COMPLETIONMENU, SHM_RECORDING, SHM_FILEINFO, SHM_SEARCHCOUNT, 0, }; + SHM_COMPLETIONMENU, SHM_COMPLETIONSCAN, SHM_RECORDING, SHM_FILEINFO, + SHM_SEARCHCOUNT, 0, }; /// After setting various option values: recompute variables that depend on /// option values. @@ -148,40 +163,41 @@ void didset_string_options(void) /// "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 -void trigger_optionsset_string(int opt_idx, int opt_flags, char *oldval, char *oldval_l, - char *oldval_g, char *newval) +void trigger_optionset_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 - && newval != NULL - && *get_vim_var_str(VV_OPTION_TYPE) == NUL) { - char buf_type[7]; + if (oldval == NULL || newval == NULL + || *get_vim_var_str(VV_OPTION_TYPE) != NUL) { + return; + } - vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s", - (opt_flags & OPT_LOCAL) ? "local" : "global"); - 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); - 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, get_option(opt_idx)->fullname, NULL, false, NULL); - reset_v_option_vars(); + char buf_type[7]; + + vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s", + (opt_flags & OPT_LOCAL) ? "local" : "global"); + 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); + 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, get_option(opt_idx)->fullname, NULL, false, NULL); + reset_v_option_vars(); } static char *illegal_char(char *errbuf, size_t errbuflen, int c) @@ -190,7 +206,7 @@ static char *illegal_char(char *errbuf, size_t errbuflen, int c) return ""; } vim_snprintf(errbuf, errbuflen, _("E539: Illegal character <%s>"), - (char *)transchar(c)); + transchar(c)); return errbuf; } @@ -227,6 +243,7 @@ void check_buf_options(buf_T *buf) check_string_option(&buf->b_p_cink); check_string_option(&buf->b_p_cino); parse_cino(buf); + check_string_option(&buf->b_p_lop); check_string_option(&buf->b_p_ft); check_string_option(&buf->b_p_cinw); check_string_option(&buf->b_p_cinsd); @@ -434,8 +451,8 @@ char *set_string_option(const int opt_idx, const char *const value, const int op // call autocommand after handling side effects if (errmsg == NULL) { if (!starting) { - trigger_optionsset_string(opt_idx, opt_flags, saved_oldval, saved_oldval_l, saved_oldval_g, - saved_newval); + trigger_optionset_string(opt_idx, opt_flags, saved_oldval, saved_oldval_l, saved_oldval_g, + saved_newval); } if (opt->flags & P_UI_OPTION) { ui_call_option_set(cstr_as_string(opt->fullname), @@ -536,7 +553,7 @@ static int check_signcolumn(char *val) // check for 'auto:<NUMBER>-<NUMBER>' if (strlen(val) == 8 - && !STRNCMP(val, "auto:", 5) + && !strncmp(val, "auto:", 5) && ascii_isdigit(val[5]) && val[6] == '-' && ascii_isdigit(val[7])) { @@ -597,7 +614,7 @@ char *check_stl_option(char *s) groupdepth++; continue; } - if (vim_strchr(STL_ALL, *s) == NULL) { + if (vim_strchr(STL_ALL, (uint8_t)(*s)) == NULL) { return illegal_char(errbuf, sizeof(errbuf), *s); } if (*s == '{') { @@ -623,6 +640,952 @@ char *check_stl_option(char *s) static int shada_idx = -1; +static bool check_illegal_path_names(char *val, uint32_t flags) +{ + // Disallow a path separator (slash and/or backslash), wildcards and + // characters that are often illegal in a file name. Be more permissive + // if "secure" is off. + return (((flags & P_NFNAME) + && strpbrk(val, (secure ? "/\\*?[|;&<>\r\n" : "/\\*?[<>\r\n")) != NULL) + || ((flags & P_NDNAME) + && strpbrk(val, "*?[|;&<>\r\n") != NULL)); +} + +static void did_set_backupcopy(buf_T *buf, char *oldval, int opt_flags, char **errmsg) +{ + char *bkc = p_bkc; + unsigned int *flags = &bkc_flags; + + if (opt_flags & OPT_LOCAL) { + bkc = buf->b_p_bkc; + flags = &buf->b_bkc_flags; + } + + if ((opt_flags & OPT_LOCAL) && *bkc == NUL) { + // make the local value empty: use the global value + *flags = 0; + } else { + if (opt_strings_flags(bkc, p_bkc_values, flags, true) != OK) { + *errmsg = e_invarg; + } + + if (((*flags & BKC_AUTO) != 0) + + ((*flags & BKC_YES) != 0) + + ((*flags & BKC_NO) != 0) != 1) { + // Must have exactly one of "auto", "yes" and "no". + (void)opt_strings_flags(oldval, p_bkc_values, flags, true); + *errmsg = e_invarg; + } + } +} + +static void did_set_backupext_or_patchmode(char **errmsg) +{ + if (strcmp(*p_bex == '.' ? p_bex + 1 : p_bex, + *p_pm == '.' ? p_pm + 1 : p_pm) == 0) { + *errmsg = e_backupext_and_patchmode_are_equal; + } +} + +static void did_set_breakindentopt(win_T *win, char **errmsg) +{ + if (briopt_check(win) == FAIL) { + *errmsg = e_invarg; + } + // list setting requires a redraw + if (win == curwin && win->w_briopt_list) { + redraw_all_later(UPD_NOT_VALID); + } +} + +static void did_set_isopt(buf_T *buf, bool *did_chartab, char **errmsg) +{ + // 'isident', 'iskeyword', 'isprint or 'isfname' option: refill g_chartab[] + // If the new option is invalid, use old value. 'lisp' option: refill + // g_chartab[] for '-' char + if (buf_init_chartab(buf, true) == FAIL) { + *did_chartab = true; // need to restore it below + *errmsg = e_invarg; // error in value + } +} + +static void did_set_helpfile(void) +{ + // May compute new values for $VIM and $VIMRUNTIME + if (didset_vim) { + vim_unsetenv_ext("VIM"); + } + if (didset_vimruntime) { + vim_unsetenv_ext("VIMRUNTIME"); + } +} + +static void did_set_cursorlineopt(win_T *win, char **varp, char **errmsg) +{ + if (**varp == NUL || fill_culopt_flags(*varp, win) != OK) { + *errmsg = e_invarg; + } +} + +static void did_set_helplang(char **errmsg) +{ + // Check for "", "ab", "ab,cd", etc. + for (char *s = p_hlg; *s != NUL; s += 3) { + if (s[1] == NUL || ((s[2] != ',' || s[3] == NUL) && s[2] != NUL)) { + *errmsg = e_invarg; + break; + } + if (s[2] == NUL) { + break; + } + } +} + +static void did_set_highlight(char **varp, char **errmsg) +{ + if (strcmp(*varp, HIGHLIGHT_INIT) != 0) { + *errmsg = e_unsupportedoption; + } +} + +static void did_set_opt_flags(char *val, char **values, unsigned *flagp, bool list, char **errmsg) +{ + if (opt_strings_flags(val, values, flagp, list) != OK) { + *errmsg = e_invarg; + } +} + +static void did_set_opt_strings(char *val, char **values, bool list, char **errmsg) +{ + did_set_opt_flags(val, values, NULL, list, errmsg); +} + +static void did_set_sessionoptions(char *oldval, char **errmsg) +{ + if (opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true) != OK) { + *errmsg = e_invarg; + } + if ((ssop_flags & SSOP_CURDIR) && (ssop_flags & SSOP_SESDIR)) { + // Don't allow both "sesdir" and "curdir". + (void)opt_strings_flags(oldval, p_ssop_values, &ssop_flags, true); + *errmsg = e_invarg; + } +} + +static void did_set_ambiwidth(char **errmsg) +{ + if (check_opt_strings(p_ambw, p_ambw_values, false) != OK) { + *errmsg = e_invarg; + } else { + *errmsg = check_chars_options(); + } +} + +static void did_set_background(char **errmsg) +{ + if (check_opt_strings(p_bg, p_bg_values, false) != OK) { + *errmsg = e_invarg; + return; + } + + int dark = (*p_bg == 'd'); + + init_highlight(false, false); + + if (dark != (*p_bg == 'd') && get_var_value("g:colors_name") != NULL) { + // The color scheme must have set 'background' back to another + // value, that's not what we want here. Disable the color + // scheme and set the colors again. + do_unlet(S_LEN("g:colors_name"), true); + free_string_option(p_bg); + p_bg = xstrdup((dark ? "dark" : "light")); + check_string_option(&p_bg); + init_highlight(false, false); + } +} + +static void did_set_wildmode(char **errmsg) +{ + if (check_opt_wim() == FAIL) { + *errmsg = e_invarg; + } +} + +static void did_set_winaltkeys(char **errmsg) +{ + if (*p_wak == NUL || check_opt_strings(p_wak, p_wak_values, false) != OK) { + *errmsg = e_invarg; + } +} + +static void did_set_eventignore(char **errmsg) +{ + if (check_ei() == FAIL) { + *errmsg = e_invarg; + } +} + +// 'encoding', 'fileencoding' and 'makeencoding' +static void did_set_encoding(buf_T *buf, char **varp, char **gvarp, int opt_flags, char **errmsg) +{ + if (gvarp == &p_fenc) { + if (!MODIFIABLE(buf) && opt_flags != OPT_GLOBAL) { + *errmsg = e_modifiable; + return; + } + + if (vim_strchr(*varp, ',') != NULL) { + // No comma allowed in 'fileencoding'; catches confusing it + // with 'fileencodings'. + *errmsg = e_invarg; + return; + } + + // May show a "+" in the title now. + redraw_titles(); + // Add 'fileencoding' to the swap file. + ml_setflags(buf); + } + + // canonize the value, so that strcmp() can be used on it + char *p = enc_canonize(*varp); + xfree(*varp); + *varp = p; + if (varp == &p_enc) { + // only encoding=utf-8 allowed + if (strcmp(p_enc, "utf-8") != 0) { + *errmsg = e_unsupportedoption; + return; + } + spell_reload(); + } +} + +static void did_set_keymap(buf_T *buf, char **varp, int opt_flags, int *value_checked, + char **errmsg) +{ + if (!valid_filetype(*varp)) { + *errmsg = e_invarg; + return; + } + + int secure_save = secure; + + // Reset the secure flag, since the value of 'keymap' has + // been checked to be safe. + secure = 0; + + // load or unload key mapping tables + *errmsg = keymap_init(); + + secure = secure_save; + + // Since we check the value, there is no need to set P_INSECURE, + // even when the value comes from a modeline. + *value_checked = true; + + if (*errmsg == NULL) { + if (*buf->b_p_keymap != NUL) { + // Installed a new keymap, switch on using it. + buf->b_p_iminsert = B_IMODE_LMAP; + if (buf->b_p_imsearch != B_IMODE_USE_INSERT) { + buf->b_p_imsearch = B_IMODE_LMAP; + } + } else { + // Cleared the keymap, may reset 'iminsert' and 'imsearch'. + if (buf->b_p_iminsert == B_IMODE_LMAP) { + buf->b_p_iminsert = B_IMODE_NONE; + } + if (buf->b_p_imsearch == B_IMODE_LMAP) { + buf->b_p_imsearch = B_IMODE_USE_INSERT; + } + } + if ((opt_flags & OPT_LOCAL) == 0) { + set_iminsert_global(buf); + set_imsearch_global(buf); + } + status_redraw_buf(buf); + } +} + +static void did_set_fileformat(buf_T *buf, char **varp, const char *oldval, int opt_flags, + char **errmsg) +{ + if (!MODIFIABLE(buf) && !(opt_flags & OPT_GLOBAL)) { + *errmsg = e_modifiable; + } else if (check_opt_strings(*varp, p_ff_values, false) != OK) { + *errmsg = e_invarg; + } else { + redraw_titles(); + // update flag in swap file + ml_setflags(buf); + // Redraw needed when switching to/from "mac": a CR in the text + // will be displayed differently. + if (get_fileformat(buf) == EOL_MAC || *oldval == 'm') { + redraw_buf_later(buf, UPD_NOT_VALID); + } + } +} + +static void did_set_matchpairs(char **varp, char **errmsg) +{ + for (char *p = *varp; *p != NUL; p++) { + int x2 = -1; + int x3 = -1; + + p += utfc_ptr2len(p); + if (*p != NUL) { + x2 = (unsigned char)(*p++); + } + if (*p != NUL) { + x3 = utf_ptr2char(p); + p += utfc_ptr2len(p); + } + if (x2 != ':' || x3 == -1 || (*p != NUL && *p != ',')) { + *errmsg = e_invarg; + break; + } + if (*p == NUL) { + break; + } + } +} + +static void did_set_comments(char **varp, char *errbuf, size_t errbuflen, char **errmsg) +{ + for (char *s = *varp; *s;) { + while (*s && *s != ':') { + if (vim_strchr(COM_ALL, (uint8_t)(*s)) == NULL + && !ascii_isdigit(*s) && *s != '-') { + *errmsg = illegal_char(errbuf, errbuflen, *s); + break; + } + s++; + } + if (*s++ == NUL) { + *errmsg = N_("E524: Missing colon"); + } else if (*s == ',' || *s == NUL) { + *errmsg = N_("E525: Zero length string"); + } + if (*errmsg != NULL) { + break; + } + while (*s && *s != ',') { + if (*s == '\\' && s[1] != NUL) { + s++; + } + s++; + } + s = skip_to_option_part(s); + } +} + +static void did_set_global_listfillchars(win_T *win, char **varp, int opt_flags, char **errmsg) +{ + char **local_ptr = varp == &p_lcs ? &win->w_p_lcs : &win->w_p_fcs; + // only apply the global value to "win" when it does not have a local value + *errmsg = set_chars_option(win, varp, **local_ptr == NUL || !(opt_flags & OPT_GLOBAL)); + if (*errmsg == NULL) { + // If the current window is set to use the global + // 'listchars'/'fillchars' value, clear the window-local value. + if (!(opt_flags & OPT_GLOBAL)) { + clear_string_option(local_ptr); + } + FOR_ALL_TAB_WINDOWS(tp, wp) { + // If the current window has a local value need to apply it + // again, it was changed when setting the global value. + // If no error was returned above, we don't expect an error + // here, so ignore the return value. + local_ptr = varp == &p_lcs ? &wp->w_p_lcs : &wp->w_p_fcs; + if (**local_ptr == NUL) { + (void)set_chars_option(wp, local_ptr, true); + } + } + redraw_all_later(UPD_NOT_VALID); + } +} + +static void did_set_verbosefile(char **errmsg) +{ + verbose_stop(); + if (*p_vfile != NUL && verbose_open() == FAIL) { + *errmsg = e_invarg; + } +} + +static void did_set_shada(vimoption_T **opt, int *opt_idx, bool *free_oldval, char *errbuf, + size_t errbuflen, char **errmsg) +{ + // TODO(ZyX-I): Remove this code in the future, alongside with &viminfo + // option. + *opt_idx = (((*opt)->fullname[0] == 'v') + ? (shada_idx == -1 ? ((shada_idx = findoption("shada"))) : shada_idx) + : *opt_idx); + *opt = get_option(*opt_idx); + // Update free_oldval now that we have the opt_idx for 'shada', otherwise + // there would be a disconnect between the check for P_ALLOCED at the start + // of the function and the set of P_ALLOCED at the end of the function. + *free_oldval = ((*opt)->flags & P_ALLOCED); + for (char *s = p_shada; *s;) { + // Check it's a valid character + if (vim_strchr("!\"%'/:<@cfhnrs", (uint8_t)(*s)) == NULL) { + *errmsg = illegal_char(errbuf, errbuflen, *s); + break; + } + if (*s == 'n') { // name is always last one + break; + } else if (*s == 'r') { // skip until next ',' + while (*++s && *s != ',') {} + } else if (*s == '%') { + // optional number + while (ascii_isdigit(*++s)) {} + } else if (*s == '!' || *s == 'h' || *s == 'c') { + s++; // no extra chars + } else { // must have a number + while (ascii_isdigit(*++s)) {} + + if (!ascii_isdigit(*(s - 1))) { + if (errbuf != NULL) { + vim_snprintf(errbuf, errbuflen, + _("E526: Missing number after <%s>"), + transchar_byte((uint8_t)(*(s - 1)))); + *errmsg = errbuf; + } else { + *errmsg = ""; + } + break; + } + } + if (*s == ',') { + s++; + } else if (*s) { + if (errbuf != NULL) { + *errmsg = N_("E527: Missing comma"); + } else { + *errmsg = ""; + } + break; + } + } + if (*p_shada && *errmsg == NULL && get_shada_parameter('\'') < 0) { + *errmsg = N_("E528: Must specify a ' value"); + } +} + +static void did_set_showbreak(char **varp, char **errmsg) +{ + for (char *s = *varp; *s;) { + if (ptr2cells(s) != 1) { + *errmsg = e_showbreak_contains_unprintable_or_wide_character; + } + MB_PTR_ADV(s); + } +} + +static void did_set_titleiconstring(char **varp) +{ + // 'titlestring' and 'iconstring' + int flagval = (varp == &p_titlestring) ? STL_IN_TITLE : STL_IN_ICON; + + // NULL => statusline syntax + if (vim_strchr(*varp, '%') && check_stl_option(*varp) == NULL) { + stl_syntax |= flagval; + } else { + stl_syntax &= ~flagval; + } + did_set_title(); +} + +static void did_set_selection(char **errmsg) +{ + if (*p_sel == NUL || check_opt_strings(p_sel, p_sel_values, false) != OK) { + *errmsg = e_invarg; + } +} + +static void did_set_keymodel(char **errmsg) +{ + if (check_opt_strings(p_km, p_km_values, true) != OK) { + *errmsg = e_invarg; + return; + } + km_stopsel = (vim_strchr(p_km, 'o') != NULL); + km_startsel = (vim_strchr(p_km, 'a') != NULL); +} + +static void did_set_display(char **errmsg) +{ + if (opt_strings_flags(p_dy, p_dy_values, &dy_flags, true) != OK) { + *errmsg = e_invarg; + return; + } + (void)init_chartab(); + msg_grid_validate(); +} + +static void did_set_spellfile(char **varp, char **errmsg) +{ + // When there is a window for this buffer in which 'spell' + // is set load the wordlists. + + if ((!valid_spellfile(*varp))) { + *errmsg = e_invarg; + } else { + *errmsg = did_set_spell_option(true); + } +} + +static void did_set_spell(char **varp, char **errmsg) +{ + // When there is a window for this buffer in which 'spell' + // is set load the wordlists. + if (!valid_spelllang(*varp)) { + *errmsg = e_invarg; + } else { + *errmsg = did_set_spell_option(false); + } +} + +static void did_set_spellcapcheck(win_T *win, char **errmsg) +{ + // When 'spellcapcheck' is set compile the regexp program. + *errmsg = compile_cap_prog(win->w_s); +} + +static void did_set_spelloptions(win_T *win, char **errmsg) +{ + if (opt_strings_flags(win->w_s->b_p_spo, p_spo_values, &(win->w_s->b_p_spo_flags), + true) != OK) { + *errmsg = e_invarg; + } +} + +static void did_set_spellsuggest(char **errmsg) +{ + if (spell_check_sps() != OK) { + *errmsg = e_invarg; + } +} + +static void did_set_mkspellmem(char **errmsg) +{ + if (spell_check_msm() != OK) { + *errmsg = e_invarg; + } +} + +static void did_set_buftype(buf_T *buf, win_T *win, char **errmsg) +{ + // When 'buftype' is set, check for valid value. + if ((buf->terminal && buf->b_p_bt[0] != 't') + || (!buf->terminal && buf->b_p_bt[0] == 't') + || check_opt_strings(buf->b_p_bt, p_buftype_values, false) != OK) { + *errmsg = e_invarg; + } else { + if (win->w_status_height || global_stl_height()) { + win->w_redr_status = true; + redraw_later(win, UPD_VALID); + } + buf->b_help = (buf->b_p_bt[0] == 'h'); + redraw_titles(); + } +} + +// 'statusline', 'winbar', 'tabline', 'rulerformat' or 'statuscolumn' +static void did_set_statusline(win_T *win, char **varp, char **gvarp, char **errmsg) +{ + if (varp == &p_ruf) { // reset ru_wid first + ru_wid = 0; + } else if (varp == &win->w_p_stc) { + win->w_nrwidth_line_count = 0; + } + char *s = *varp; + if (varp == &p_ruf && *s == '%') { + // set ru_wid if 'ruf' starts with "%99(" + if (*++s == '-') { // ignore a '-' + s++; + } + int wid = getdigits_int(&s, true, 0); + if (wid && *s == '(' && (*errmsg = check_stl_option(p_ruf)) == NULL) { + ru_wid = wid; + } else { + *errmsg = check_stl_option(p_ruf); + } + } else if (varp == &p_ruf || s[0] != '%' || s[1] != '!') { + // check 'statusline', 'winbar', 'tabline' or 'statuscolumn' + // only if it doesn't start with "%!" + *errmsg = check_stl_option(s); + } + if (varp == &p_ruf && *errmsg == NULL) { + comp_col(); + } + // add / remove window bars for 'winbar' + if (gvarp == &p_wbr) { + set_winbar(true); + } +} + +static void did_set_complete(char **varp, char *errbuf, size_t errbuflen, char **errmsg) +{ + // check if it is a valid value for 'complete' -- Acevedo + for (char *s = *varp; *s;) { + while (*s == ',' || *s == ' ') { + s++; + } + if (!*s) { + break; + } + if (vim_strchr(".wbuksid]tU", (uint8_t)(*s)) == NULL) { + *errmsg = illegal_char(errbuf, errbuflen, *s); + break; + } + if (*++s != NUL && *s != ',' && *s != ' ') { + if (s[-1] == 'k' || s[-1] == 's') { + // skip optional filename after 'k' and 's' + while (*s && *s != ',' && *s != ' ') { + if (*s == '\\' && s[1] != NUL) { + s++; + } + s++; + } + } else { + if (errbuf != NULL) { + vim_snprintf(errbuf, errbuflen, + _("E535: Illegal character after <%c>"), + *--s); + *errmsg = errbuf; + } else { + *errmsg = ""; + } + break; + } + } + } +} + +static void did_set_completeopt(char **errmsg) +{ + if (check_opt_strings(p_cot, p_cot_values, true) != OK) { + *errmsg = e_invarg; + } else { + completeopt_was_set(); + } +} + +static void did_set_signcolumn(win_T *win, char **varp, const char *oldval, char **errmsg) +{ + if (check_signcolumn(*varp) != OK) { + *errmsg = e_invarg; + } + // When changing the 'signcolumn' to or from 'number', recompute the + // width of the number column if 'number' or 'relativenumber' is set. + if (((*oldval == 'n' && *(oldval + 1) == 'u') + || (*win->w_p_scl == 'n' && *(win->w_p_scl + 1) == 'u')) + && (win->w_p_nu || win->w_p_rnu)) { + win->w_nrwidth_line_count = 0; + } +} + +static void did_set_foldcolumn(char **varp, char **errmsg) +{ + if (**varp == NUL || check_opt_strings(*varp, p_fdc_values, false) != OK) { + *errmsg = e_invarg; + } +} + +static void did_set_pastetoggle(void) +{ + // 'pastetoggle': translate key codes like in a mapping + if (*p_pt) { + char *p = NULL; + (void)replace_termcodes(p_pt, + strlen(p_pt), + &p, REPTERM_FROM_PART | REPTERM_DO_LT, NULL, + CPO_TO_CPO_FLAGS); + if (p != NULL) { + free_string_option(p_pt); + p_pt = p; + } + } +} + +static void did_set_backspace(char **errmsg) +{ + if (ascii_isdigit(*p_bs)) { + if (*p_bs > '3' || p_bs[1] != NUL) { + *errmsg = e_invarg; + } + } else if (check_opt_strings(p_bs, p_bs_values, true) != OK) { + *errmsg = e_invarg; + } +} + +static void did_set_tagcase(buf_T *buf, int opt_flags, char **errmsg) +{ + unsigned int *flags; + char *p; + + if (opt_flags & OPT_LOCAL) { + p = buf->b_p_tc; + flags = &buf->b_tc_flags; + } else { + p = p_tc; + flags = &tc_flags; + } + + if ((opt_flags & OPT_LOCAL) && *p == NUL) { + // make the local value empty: use the global value + *flags = 0; + } else if (*p == NUL + || opt_strings_flags(p, p_tc_values, flags, false) != OK) { + *errmsg = e_invarg; + } +} + +static void did_set_diffopt(char **errmsg) +{ + if (diffopt_changed() == FAIL) { + *errmsg = e_invarg; + } +} + +static void did_set_foldmethod(win_T *win, char **varp, char **errmsg) +{ + if (check_opt_strings(*varp, p_fdm_values, false) != OK + || *win->w_p_fdm == NUL) { + *errmsg = e_invarg; + } else { + foldUpdateAll(win); + if (foldmethodIsDiff(win)) { + newFoldLevel(); + } + } +} + +static void did_set_foldmarker(win_T *win, char **varp, char **errmsg) +{ + char *p = vim_strchr(*varp, ','); + if (p == NULL) { + *errmsg = N_("E536: comma required"); + } else if (p == *varp || p[1] == NUL) { + *errmsg = e_invarg; + } else if (foldmethodIsMarker(win)) { + foldUpdateAll(win); + } +} + +static void did_set_commentstring(char **varp, char **errmsg) +{ + if (**varp != NUL && strstr(*varp, "%s") == NULL) { + *errmsg = N_("E537: 'commentstring' must be empty or contain %s"); + } +} + +static void did_set_foldignore(win_T *win) +{ + if (foldmethodIsIndent(win)) { + foldUpdateAll(win); + } +} + +static void did_set_virtualedit(win_T *win, int opt_flags, char *oldval, char **errmsg) +{ + char *ve = p_ve; + unsigned int *flags = &ve_flags; + + if (opt_flags & OPT_LOCAL) { + ve = win->w_p_ve; + flags = &win->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_win(win); + coladvance(win->w_virtcol); + } + } +} + +static void did_set_lispoptions(char **varp, char **errmsg) +{ + if (**varp != NUL && strcmp(*varp, "expr:0") != 0 && strcmp(*varp, "expr:1") != 0) { + *errmsg = e_invarg; + } +} + +static void did_set_filetype_or_syntax(char **varp, char *oldval, int *value_checked, + bool *value_changed, char **errmsg) +{ + if (!valid_filetype(*varp)) { + *errmsg = e_invarg; + return; + } + + *value_changed = strcmp(oldval, *varp) != 0; + + // Since we check the value, there is no need to set P_INSECURE, + // even when the value comes from a modeline. + *value_checked = true; +} + +static void did_set_winhl(win_T *win, char **errmsg) +{ + if (!parse_winhl_opt(win)) { + *errmsg = e_invarg; + } +} + +static void did_set_varsoftabstop(buf_T *buf, char **varp, char **errmsg) +{ + if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { + XFREE_CLEAR(buf->b_p_vsts_array); + return; + } + + for (char *cp = *varp; *cp; cp++) { + if (ascii_isdigit(*cp)) { + continue; + } + if (*cp == ',' && cp > *varp && *(cp - 1) != ',') { + continue; + } + *errmsg = e_invarg; + return; + } + + long *oldarray = buf->b_p_vsts_array; + if (tabstop_set(*varp, &(buf->b_p_vsts_array))) { + xfree(oldarray); + } else { + *errmsg = e_invarg; + } +} + +static void did_set_vartabstop(buf_T *buf, win_T *win, char **varp, char **errmsg) +{ + if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { + XFREE_CLEAR(buf->b_p_vts_array); + return; + } + + for (char *cp = *varp; *cp; cp++) { + if (ascii_isdigit(*cp)) { + continue; + } + if (*cp == ',' && cp > *varp && *(cp - 1) != ',') { + continue; + } + *errmsg = e_invarg; + return; + } + + long *oldarray = buf->b_p_vts_array; + if (tabstop_set(*varp, &(buf->b_p_vts_array))) { + xfree(oldarray); + if (foldmethodIsIndent(win)) { + foldUpdateAll(win); + } + } else { + *errmsg = e_invarg; + } +} + +static void did_set_optexpr(win_T *win, char **p_opt, char **varp, char **gvarp) +{ + char *name = get_scriptlocal_funcname(*p_opt); + if (name != NULL) { + free_string_option(*p_opt); + *p_opt = name; + } +} + +// handle option that is a list of flags. +static void did_set_option_listflag(char **varp, char *flags, char *errbuf, size_t errbuflen, + char **errmsg) +{ + for (char *s = *varp; *s; s++) { + if (vim_strchr(flags, *s) == NULL) { + *errmsg = illegal_char(errbuf, errbuflen, *s); + break; + } + } +} + +// When 'syntax' is set, load the syntax of that name +static void do_syntax_autocmd(buf_T *buf, bool value_changed) +{ + static int syn_recursive = 0; + + syn_recursive++; + // Only pass true for "force" when the value changed or not used + // recursively, to avoid endless recurrence. + apply_autocmds(EVENT_SYNTAX, buf->b_p_syn, buf->b_fname, + value_changed || syn_recursive == 1, buf); + buf->b_flags |= BF_SYN_SET; + syn_recursive--; +} + +static void do_filetype_autocmd(buf_T *buf, char **varp, int opt_flags, bool value_changed) +{ + // 'filetype' is set, trigger the FileType autocommand + // Skip this when called from a modeline and the filetype was + // already set to this value. + if (!(opt_flags & OPT_MODELINE) || value_changed) { + static int ft_recursive = 0; + int secure_save = secure; + + // Reset the secure flag, since the value of 'filetype' has + // been checked to be safe. + secure = 0; + + ft_recursive++; + did_filetype = true; + // Only pass true for "force" when the value changed or not + // used recursively, to avoid endless recurrence. + apply_autocmds(EVENT_FILETYPE, buf->b_p_ft, buf->b_fname, + value_changed || ft_recursive == 1, buf); + ft_recursive--; + // Just in case the old "buf" is now invalid + if (varp != &(buf->b_p_ft)) { + varp = NULL; + } + secure = secure_save; + } +} + +static void did_set_spelllang_source(win_T *win) +{ + char fname[200]; + char *q = win->w_s->b_p_spl; + + // Skip the first name if it is "cjk". + if (strncmp(q, "cjk,", 4) == 0) { + q += 4; + } + + // Source the spell/LANG.vim in 'runtimepath'. + // They could set 'spellcapcheck' depending on the language. + // Use the first name in 'spelllang' up to '_region' or + // '.encoding'. + char *p; + for (p = q; *p != NUL; p++) { + if (!ASCII_ISALNUM(*p) && *p != '-') { + break; + } + } + if (p > q) { + vim_snprintf(fname, sizeof(fname), "spell/%.*s.vim", (int)(p - q), q); + source_runtime(fname, DIP_ALL); + } +} + /// Handle string options that need some action to perform when changed. /// The new value must be allocated. /// @@ -635,12 +1598,12 @@ static int shada_idx = -1; /// @param value_checked value was checked to be safe, no need to set P_INSECURE /// /// @return NULL for success, or an untranslated error message for an error -char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf, size_t errbuflen, - int opt_flags, int *value_checked) +static char *did_set_string_option_for(buf_T *buf, win_T *win, int opt_idx, char **varp, + char *oldval, char *errbuf, size_t errbuflen, int opt_flags, + int *value_checked) { char *errmsg = NULL; - char *s, *p; - int did_chartab = false; + bool did_chartab = false; vimoption_T *opt = get_option(opt_idx); bool free_oldval = (opt->flags & P_ALLOCED); bool value_changed = false; @@ -653,864 +1616,244 @@ char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf if ((secure || sandbox != 0) && (opt->flags & P_SECURE)) { errmsg = e_secure; - } else if (((opt->flags & P_NFNAME) - && strpbrk(*varp, (secure ? "/\\*?[|;&<>\r\n" : "/\\*?[<>\r\n")) != NULL) - || ((opt->flags & P_NDNAME) - && strpbrk(*varp, "*?[|;&<>\r\n") != NULL)) { - // Check for a "normal" directory or file name in some options. Disallow a - // path separator (slash and/or backslash), wildcards and characters that - // are often illegal in a file name. Be more permissive if "secure" is off. + } else if (check_illegal_path_names(*varp, opt->flags)) { + // Check for a "normal" directory or file name in some options. errmsg = e_invarg; } else if (gvarp == &p_bkc) { // 'backupcopy' - char *bkc = p_bkc; - unsigned int *flags = &bkc_flags; - - if (opt_flags & OPT_LOCAL) { - bkc = curbuf->b_p_bkc; - flags = &curbuf->b_bkc_flags; - } - - if ((opt_flags & OPT_LOCAL) && *bkc == NUL) { - // make the local value empty: use the global value - *flags = 0; - } else { - if (opt_strings_flags(bkc, p_bkc_values, flags, true) != OK) { - errmsg = e_invarg; - } - - if (((*flags & BKC_AUTO) != 0) - + ((*flags & BKC_YES) != 0) - + ((*flags & BKC_NO) != 0) != 1) { - // Must have exactly one of "auto", "yes" and "no". - (void)opt_strings_flags(oldval, p_bkc_values, flags, true); - errmsg = e_invarg; - } - } + did_set_backupcopy(buf, oldval, opt_flags, &errmsg); } else if (varp == &p_bex || varp == &p_pm) { // 'backupext' and 'patchmode' - if (strcmp(*p_bex == '.' ? p_bex + 1 : p_bex, - *p_pm == '.' ? p_pm + 1 : p_pm) == 0) { - errmsg = e_backupext_and_patchmode_are_equal; - } - } else if (varp == &curwin->w_p_briopt) { // 'breakindentopt' - if (briopt_check(curwin) == FAIL) { - errmsg = e_invarg; - } + did_set_backupext_or_patchmode(&errmsg); + } else if (varp == &win->w_p_briopt) { // 'breakindentopt' + did_set_breakindentopt(win, &errmsg); } else if (varp == &p_isi - || varp == &(curbuf->b_p_isk) + || varp == &buf->b_p_isk || varp == &p_isp || varp == &p_isf) { - // 'isident', 'iskeyword', 'isprint or 'isfname' option: refill g_chartab[] - // If the new option is invalid, use old value. 'lisp' option: refill - // g_chartab[] for '-' char - if (init_chartab() == FAIL) { - did_chartab = true; // need to restore it below - errmsg = e_invarg; // error in value - } + // 'isident', 'iskeyword', 'isprint or 'isfname' option + did_set_isopt(buf, &did_chartab, &errmsg); } else if (varp == &p_hf) { // 'helpfile' - // May compute new values for $VIM and $VIMRUNTIME - if (didset_vim) { - vim_unsetenv_ext("VIM"); - } - if (didset_vimruntime) { - vim_unsetenv_ext("VIMRUNTIME"); - } + did_set_helpfile(); } else if (varp == &p_rtp || varp == &p_pp) { // 'runtimepath' 'packpath' runtime_search_path_invalidate(); - } else if (varp == &curwin->w_p_culopt - || gvarp == &curwin->w_allbuf_opt.wo_culopt) { // 'cursorlineopt' - if (**varp == NUL || fill_culopt_flags(*varp, curwin) != OK) { - errmsg = e_invarg; - } - } else if (varp == &curwin->w_p_cc) { // 'colorcolumn' - errmsg = check_colorcolumn(curwin); - } else if (varp == (char **)&p_hlg) { // 'helplang' - // Check for "", "ab", "ab,cd", etc. - for (s = (char *)p_hlg; *s != NUL; s += 3) { - if (s[1] == NUL || ((s[2] != ',' || s[3] == NUL) && s[2] != NUL)) { - errmsg = e_invarg; - break; - } - if (s[2] == NUL) { - break; - } - } + } else if (varp == &win->w_p_culopt + || gvarp == &win->w_allbuf_opt.wo_culopt) { // 'cursorlineopt' + did_set_cursorlineopt(win, varp, &errmsg); + } else if (varp == &win->w_p_cc) { // 'colorcolumn' + errmsg = check_colorcolumn(win); + } else if (varp == &p_hlg) { // 'helplang' + did_set_helplang(&errmsg); } else if (varp == &p_hl) { // 'highlight' - if (strcmp(*varp, HIGHLIGHT_INIT) != 0) { - errmsg = e_unsupportedoption; - } + did_set_highlight(varp, &errmsg); } else if (varp == &p_jop) { // 'jumpoptions' - if (opt_strings_flags(p_jop, p_jop_values, &jop_flags, true) != OK) { - errmsg = e_invarg; - } + did_set_opt_flags(p_jop, p_jop_values, &jop_flags, true, &errmsg); } else if (gvarp == &p_nf) { // 'nrformats' - if (check_opt_strings(*varp, p_nf_values, true) != OK) { - errmsg = e_invarg; - } + did_set_opt_strings(*varp, p_nf_values, true, &errmsg); } else if (varp == &p_ssop) { // 'sessionoptions' - if (opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true) != OK) { - errmsg = e_invarg; - } - if ((ssop_flags & SSOP_CURDIR) && (ssop_flags & SSOP_SESDIR)) { - // Don't allow both "sesdir" and "curdir". - (void)opt_strings_flags(oldval, p_ssop_values, &ssop_flags, true); - errmsg = e_invarg; - } + did_set_sessionoptions(oldval, &errmsg); } else if (varp == &p_vop) { // 'viewoptions' - if (opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true) != OK) { - errmsg = e_invarg; - } + did_set_opt_flags(p_vop, p_ssop_values, &vop_flags, true, &errmsg); } else if (varp == &p_rdb) { // 'redrawdebug' - if (opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true) != OK) { - errmsg = e_invarg; - } + did_set_opt_flags(p_rdb, p_rdb_values, &rdb_flags, true, &errmsg); } else if (varp == &p_sbo) { // 'scrollopt' - if (check_opt_strings(p_sbo, p_scbopt_values, true) != OK) { - errmsg = e_invarg; - } + did_set_opt_strings(p_sbo, p_scbopt_values, true, &errmsg); } else if (varp == &p_ambw || (int *)varp == &p_emoji) { // 'ambiwidth' - if (check_opt_strings(p_ambw, p_ambw_values, false) != OK) { - errmsg = e_invarg; - } else { - errmsg = check_chars_options(); - } + did_set_ambiwidth(&errmsg); } else if (varp == &p_bg) { // 'background' - if (check_opt_strings(p_bg, p_bg_values, false) == OK) { - int dark = (*p_bg == 'd'); - - init_highlight(false, false); - - if (dark != (*p_bg == 'd') && get_var_value("g:colors_name") != NULL) { - // The color scheme must have set 'background' back to another - // value, that's not what we want here. Disable the color - // scheme and set the colors again. - do_unlet(S_LEN("g:colors_name"), true); - free_string_option(p_bg); - p_bg = xstrdup((dark ? "dark" : "light")); - check_string_option(&p_bg); - init_highlight(false, false); - } - } else { - errmsg = e_invarg; - } + did_set_background(&errmsg); } else if (varp == &p_wim) { // 'wildmode' - if (check_opt_wim() == FAIL) { - errmsg = e_invarg; - } + did_set_wildmode(&errmsg); } else if (varp == &p_wop) { // 'wildoptions' - if (opt_strings_flags(p_wop, p_wop_values, &wop_flags, true) != OK) { - errmsg = e_invarg; - } + did_set_opt_flags(p_wop, p_wop_values, &wop_flags, true, &errmsg); } else if (varp == &p_wak) { // 'winaltkeys' - if (*p_wak == NUL - || check_opt_strings(p_wak, p_wak_values, false) != OK) { - errmsg = e_invarg; - } + did_set_winaltkeys(&errmsg); } else if (varp == &p_ei) { // 'eventignore' - if (check_ei() == FAIL) { - errmsg = e_invarg; - } + did_set_eventignore(&errmsg); } else if (varp == &p_enc || gvarp == &p_fenc || gvarp == &p_menc) { // 'encoding', 'fileencoding' and 'makeencoding' - if (gvarp == &p_fenc) { - if (!MODIFIABLE(curbuf) && opt_flags != OPT_GLOBAL) { - errmsg = e_modifiable; - } else if (vim_strchr(*varp, ',') != NULL) { - // No comma allowed in 'fileencoding'; catches confusing it - // with 'fileencodings'. - errmsg = e_invarg; - } else { - // May show a "+" in the title now. - redraw_titles(); - // Add 'fileencoding' to the swap file. - ml_setflags(curbuf); - } - } - - if (errmsg == NULL) { - // canonize the value, so that strcmp() can be used on it - p = enc_canonize(*varp); - xfree(*varp); - *varp = p; - if (varp == &p_enc) { - // only encoding=utf-8 allowed - if (strcmp(p_enc, "utf-8") != 0) { - errmsg = e_unsupportedoption; - } else { - spell_reload(); - } - } - } - } else if (varp == &p_penc) { - // Canonize printencoding if VIM standard one - p = enc_canonize(p_penc); - xfree(p_penc); - p_penc = p; - } else if (varp == &curbuf->b_p_keymap) { - if (!valid_filetype(*varp)) { - errmsg = e_invarg; - } else { - int secure_save = secure; - - // Reset the secure flag, since the value of 'keymap' has - // been checked to be safe. - secure = 0; - - // load or unload key mapping tables - errmsg = keymap_init(); - - secure = secure_save; - - // Since we check the value, there is no need to set P_INSECURE, - // even when the value comes from a modeline. - *value_checked = true; - } - - if (errmsg == NULL) { - if (*curbuf->b_p_keymap != NUL) { - // Installed a new keymap, switch on using it. - curbuf->b_p_iminsert = B_IMODE_LMAP; - if (curbuf->b_p_imsearch != B_IMODE_USE_INSERT) { - curbuf->b_p_imsearch = B_IMODE_LMAP; - } - } else { - // Cleared the keymap, may reset 'iminsert' and 'imsearch'. - if (curbuf->b_p_iminsert == B_IMODE_LMAP) { - curbuf->b_p_iminsert = B_IMODE_NONE; - } - if (curbuf->b_p_imsearch == B_IMODE_LMAP) { - curbuf->b_p_imsearch = B_IMODE_USE_INSERT; - } - } - if ((opt_flags & OPT_LOCAL) == 0) { - set_iminsert_global(); - set_imsearch_global(); - } - status_redraw_curbuf(); - } + did_set_encoding(buf, varp, gvarp, opt_flags, &errmsg); + } else if (varp == &buf->b_p_keymap) { + did_set_keymap(buf, varp, opt_flags, value_checked, &errmsg); } else if (gvarp == &p_ff) { // 'fileformat' - if (!MODIFIABLE(curbuf) && !(opt_flags & OPT_GLOBAL)) { - errmsg = e_modifiable; - } else if (check_opt_strings(*varp, p_ff_values, false) != OK) { - errmsg = e_invarg; - } else { - redraw_titles(); - // update flag in swap file - ml_setflags(curbuf); - // Redraw needed when switching to/from "mac": a CR in the text - // will be displayed differently. - if (get_fileformat(curbuf) == EOL_MAC || *oldval == 'm') { - redraw_curbuf_later(UPD_NOT_VALID); - } - } + did_set_fileformat(buf, varp, oldval, opt_flags, &errmsg); } else if (varp == &p_ffs) { // 'fileformats' - if (check_opt_strings(p_ffs, p_ff_values, true) != OK) { - errmsg = e_invarg; - } + did_set_opt_strings(p_ffs, p_ff_values, true, &errmsg); } else if (gvarp == &p_mps) { // 'matchpairs' - for (p = *varp; *p != NUL; p++) { - int x2 = -1; - int x3 = -1; - - p += utfc_ptr2len(p); - if (*p != NUL) { - x2 = (unsigned char)(*p++); - } - if (*p != NUL) { - x3 = utf_ptr2char(p); - p += utfc_ptr2len(p); - } - if (x2 != ':' || x3 == -1 || (*p != NUL && *p != ',')) { - errmsg = e_invarg; - break; - } - if (*p == NUL) { - break; - } - } + did_set_matchpairs(varp, &errmsg); } else if (gvarp == &p_com) { // 'comments' - for (s = *varp; *s;) { - while (*s && *s != ':') { - if (vim_strchr(COM_ALL, *s) == NULL - && !ascii_isdigit(*s) && *s != '-') { - errmsg = illegal_char(errbuf, errbuflen, *s); - break; - } - s++; - } - if (*s++ == NUL) { - errmsg = N_("E524: Missing colon"); - } else if (*s == ',' || *s == NUL) { - errmsg = N_("E525: Zero length string"); - } - if (errmsg != NULL) { - break; - } - while (*s && *s != ',') { - if (*s == '\\' && s[1] != NUL) { - s++; - } - s++; - } - s = skip_to_option_part(s); - } + did_set_comments(varp, errbuf, errbuflen, &errmsg); } else if (varp == &p_lcs || varp == &p_fcs) { // global 'listchars' or 'fillchars' - char **local_ptr = varp == &p_lcs ? &curwin->w_p_lcs : &curwin->w_p_fcs; - // only apply the global value to "curwin" when it does not have a local value - errmsg = - set_chars_option(curwin, varp, **local_ptr == NUL || !(opt_flags & OPT_GLOBAL)); - if (errmsg == NULL) { - // If the current window is set to use the global - // 'listchars'/'fillchars' value, clear the window-local value. - if (!(opt_flags & OPT_GLOBAL)) { - clear_string_option(local_ptr); - } - FOR_ALL_TAB_WINDOWS(tp, wp) { - // If the current window has a local value need to apply it - // again, it was changed when setting the global value. - // If no error was returned above, we don't expect an error - // here, so ignore the return value. - local_ptr = varp == &p_lcs ? &wp->w_p_lcs : &wp->w_p_fcs; - if (**local_ptr == NUL) { - (void)set_chars_option(wp, local_ptr, true); - } - } - redraw_all_later(UPD_NOT_VALID); - } - } else if (varp == &curwin->w_p_lcs) { // local 'listchars' - errmsg = set_chars_option(curwin, varp, true); - } else if (varp == &curwin->w_p_fcs) { // local 'fillchars' - errmsg = set_chars_option(curwin, varp, true); + did_set_global_listfillchars(win, varp, opt_flags, &errmsg); + } else if (varp == &win->w_p_lcs) { // local 'listchars' + errmsg = set_chars_option(win, varp, true); + } else if (varp == &win->w_p_fcs) { // local 'fillchars' + errmsg = set_chars_option(win, varp, true); } else if (varp == &p_cedit) { // 'cedit' errmsg = check_cedit(); } else if (varp == &p_vfile) { // 'verbosefile' - verbose_stop(); - if (*p_vfile != NUL && verbose_open() == FAIL) { - errmsg = e_invarg; - } + did_set_verbosefile(&errmsg); } else if (varp == &p_shada) { // 'shada' - // TODO(ZyX-I): Remove this code in the future, alongside with &viminfo - // option. - opt_idx = ((opt->fullname[0] == 'v') - ? (shada_idx == -1 ? ((shada_idx = findoption("shada"))) : shada_idx) - : opt_idx); - opt = get_option(opt_idx); - // Update free_oldval now that we have the opt_idx for 'shada', otherwise - // there would be a disconnect between the check for P_ALLOCED at the start - // of the function and the set of P_ALLOCED at the end of the function. - free_oldval = (opt->flags & P_ALLOCED); - for (s = p_shada; *s;) { - // Check it's a valid character - if (vim_strchr("!\"%'/:<@cfhnrs", *s) == NULL) { - errmsg = illegal_char(errbuf, errbuflen, *s); - break; - } - if (*s == 'n') { // name is always last one - break; - } else if (*s == 'r') { // skip until next ',' - while (*++s && *s != ',') {} - } else if (*s == '%') { - // optional number - while (ascii_isdigit(*++s)) {} - } else if (*s == '!' || *s == 'h' || *s == 'c') { - s++; // no extra chars - } else { // must have a number - while (ascii_isdigit(*++s)) {} - - if (!ascii_isdigit(*(s - 1))) { - if (errbuf != NULL) { - vim_snprintf(errbuf, errbuflen, - _("E526: Missing number after <%s>"), - transchar_byte(*(s - 1))); - errmsg = errbuf; - } else { - errmsg = ""; - } - break; - } - } - if (*s == ',') { - s++; - } else if (*s) { - if (errbuf != NULL) { - errmsg = N_("E527: Missing comma"); - } else { - errmsg = ""; - } - break; - } - } - if (*p_shada && errmsg == NULL && get_shada_parameter('\'') < 0) { - errmsg = N_("E528: Must specify a ' value"); - } + did_set_shada(&opt, &opt_idx, &free_oldval, errbuf, errbuflen, &errmsg); } else if (gvarp == &p_sbr) { // 'showbreak' - for (s = *varp; *s;) { - if (ptr2cells(s) != 1) { - errmsg = e_showbreak_contains_unprintable_or_wide_character; - } - MB_PTR_ADV(s); - } + did_set_showbreak(varp, &errmsg); } else if (varp == &p_guicursor) { // 'guicursor' errmsg = parse_shape_opt(SHAPE_CURSOR); - } else if (varp == &p_popt) { - errmsg = parse_printoptions(); - } else if (varp == &p_pmfn) { - errmsg = parse_printmbfont(); } else if (varp == &p_langmap) { // 'langmap' langmap_set(); } else if (varp == &p_breakat) { // 'breakat' fill_breakat_flags(); } else if (varp == &p_titlestring || varp == &p_iconstring) { // 'titlestring' and 'iconstring' - int flagval = (varp == &p_titlestring) ? STL_IN_TITLE : STL_IN_ICON; - - // NULL => statusline syntax - if (vim_strchr(*varp, '%') && check_stl_option(*varp) == NULL) { - stl_syntax |= flagval; - } else { - stl_syntax &= ~flagval; - } - did_set_title(); + did_set_titleiconstring(varp); } else if (varp == &p_sel) { // 'selection' - if (*p_sel == NUL - || check_opt_strings(p_sel, p_sel_values, false) != OK) { - errmsg = e_invarg; - } + did_set_selection(&errmsg); } else if (varp == &p_slm) { // 'selectmode' - if (check_opt_strings(p_slm, p_slm_values, true) != OK) { - errmsg = e_invarg; - } + did_set_opt_strings(p_slm, p_slm_values, true, &errmsg); } else if (varp == &p_km) { // 'keymodel' - if (check_opt_strings(p_km, p_km_values, true) != OK) { - errmsg = e_invarg; - } else { - km_stopsel = (vim_strchr(p_km, 'o') != NULL); - km_startsel = (vim_strchr(p_km, 'a') != NULL); - } + did_set_keymodel(&errmsg); } else if (varp == &p_mousem) { // 'mousemodel' - if (check_opt_strings(p_mousem, p_mousem_values, false) != OK) { - errmsg = e_invarg; - } + did_set_opt_strings(p_mousem, p_mousem_values, false, &errmsg); } else if (varp == &p_mousescroll) { // 'mousescroll' errmsg = check_mousescroll(p_mousescroll); } else if (varp == &p_swb) { // 'switchbuf' - if (opt_strings_flags(p_swb, p_swb_values, &swb_flags, true) != OK) { - errmsg = e_invarg; - } + did_set_opt_flags(p_swb, p_swb_values, &swb_flags, true, &errmsg); } else if (varp == &p_spk) { // 'splitkeep' - if (check_opt_strings(p_spk, p_spk_values, false) != OK) { - errmsg = e_invarg; - } + did_set_opt_strings(p_spk, p_spk_values, false, &errmsg); } else if (varp == &p_debug) { // 'debug' - if (check_opt_strings(p_debug, p_debug_values, true) != OK) { - errmsg = e_invarg; - } + did_set_opt_strings(p_debug, p_debug_values, true, &errmsg); } else if (varp == &p_dy) { // 'display' - if (opt_strings_flags(p_dy, p_dy_values, &dy_flags, true) != OK) { - errmsg = e_invarg; - } else { - (void)init_chartab(); - msg_grid_validate(); - } + did_set_display(&errmsg); } else if (varp == &p_ead) { // 'eadirection' - if (check_opt_strings(p_ead, p_ead_values, false) != OK) { - errmsg = e_invarg; - } + did_set_opt_strings(p_ead, p_ead_values, false, &errmsg); } else if (varp == &p_cb) { // 'clipboard' - if (opt_strings_flags(p_cb, p_cb_values, &cb_flags, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == &(curwin->w_s->b_p_spl) // 'spell' - || varp == &(curwin->w_s->b_p_spf)) { - // When 'spelllang' or 'spellfile' is set and there is a window for this - // buffer in which 'spell' is set load the wordlists. - const bool is_spellfile = varp == &(curwin->w_s->b_p_spf); - - if ((is_spellfile && !valid_spellfile(*varp)) - || (!is_spellfile && !valid_spelllang(*varp))) { - errmsg = e_invarg; - } else { - errmsg = did_set_spell_option(is_spellfile); - } - } else if (varp == &(curwin->w_s->b_p_spc)) { - // When 'spellcapcheck' is set compile the regexp program. - errmsg = compile_cap_prog(curwin->w_s); - } else if (varp == &(curwin->w_s->b_p_spo)) { // 'spelloptions' - if (opt_strings_flags(curwin->w_s->b_p_spo, p_spo_values, &(curwin->w_s->b_p_spo_flags), - true) != OK) { - errmsg = e_invarg; - } + did_set_opt_flags(p_cb, p_cb_values, &cb_flags, true, &errmsg); + } else if (varp == &win->w_s->b_p_spf) { + did_set_spellfile(varp, &errmsg); + } else if (varp == &win->w_s->b_p_spl) { // 'spell' + did_set_spell(varp, &errmsg); + } else if (varp == &win->w_s->b_p_spc) { + did_set_spellcapcheck(win, &errmsg); + } else if (varp == &win->w_s->b_p_spo) { // 'spelloptions' + did_set_spelloptions(win, &errmsg); } else if (varp == &p_sps) { // 'spellsuggest' - if (spell_check_sps() != OK) { - errmsg = e_invarg; - } + did_set_spellsuggest(&errmsg); } else if (varp == &p_msm) { // 'mkspellmem' - if (spell_check_msm() != OK) { - errmsg = e_invarg; - } + did_set_mkspellmem(&errmsg); } else if (gvarp == &p_bh) { - // When 'bufhidden' is set, check for valid value. - if (check_opt_strings(curbuf->b_p_bh, p_bufhidden_values, false) != OK) { - errmsg = e_invarg; - } - } else if (gvarp == &p_bt) { - // When 'buftype' is set, check for valid value. - if ((curbuf->terminal && curbuf->b_p_bt[0] != 't') - || (!curbuf->terminal && curbuf->b_p_bt[0] == 't') - || check_opt_strings(curbuf->b_p_bt, p_buftype_values, false) != OK) { - errmsg = e_invarg; - } else { - if (curwin->w_status_height || global_stl_height()) { - curwin->w_redr_status = true; - redraw_later(curwin, UPD_VALID); - } - curbuf->b_help = (curbuf->b_p_bt[0] == 'h'); - redraw_titles(); - } + did_set_opt_strings(buf->b_p_bh, p_bufhidden_values, false, &errmsg); + } else if (gvarp == &p_bt) { // 'buftype' + did_set_buftype(buf, win, &errmsg); } else if (gvarp == &p_stl || gvarp == &p_wbr || varp == &p_tal - || varp == &p_ruf) { - // 'statusline', 'winbar', 'tabline' or 'rulerformat' - int wid; - - if (varp == &p_ruf) { // reset ru_wid first - ru_wid = 0; - } - s = *varp; - if (varp == &p_ruf && *s == '%') { - // set ru_wid if 'ruf' starts with "%99(" - if (*++s == '-') { // ignore a '-' - s++; - } - wid = getdigits_int(&s, true, 0); - if (wid && *s == '(' && (errmsg = check_stl_option(p_ruf)) == NULL) { - ru_wid = wid; - } else { - errmsg = check_stl_option(p_ruf); - } - } else if (varp == &p_ruf || s[0] != '%' || s[1] != '!') { - // check 'statusline', 'winbar' or 'tabline' only if it doesn't start with "%!" - errmsg = check_stl_option(s); - } - if (varp == &p_ruf && errmsg == NULL) { - comp_col(); - } - // add / remove window bars for 'winbar' - if (gvarp == &p_wbr) { - set_winbar(true); - } - } else if (gvarp == &p_cpt) { - // check if it is a valid value for 'complete' -- Acevedo - for (s = *varp; *s;) { - while (*s == ',' || *s == ' ') { - s++; - } - if (!*s) { - break; - } - if (vim_strchr(".wbuksid]tU", *s) == NULL) { - errmsg = illegal_char(errbuf, errbuflen, *s); - break; - } - if (*++s != NUL && *s != ',' && *s != ' ') { - if (s[-1] == 'k' || s[-1] == 's') { - // skip optional filename after 'k' and 's' - while (*s && *s != ',' && *s != ' ') { - if (*s == '\\' && s[1] != NUL) { - s++; - } - s++; - } - } else { - if (errbuf != NULL) { - vim_snprintf(errbuf, errbuflen, - _("E535: Illegal character after <%c>"), - *--s); - errmsg = errbuf; - } else { - errmsg = ""; - } - break; - } - } - } + || varp == &p_ruf || varp == &win->w_p_stc) { + // 'statusline', 'winbar', 'tabline', 'rulerformat' or 'statuscolumn' + did_set_statusline(win, varp, gvarp, &errmsg); + } else if (gvarp == &p_cpt) { // 'complete' + did_set_complete(varp, errbuf, errbuflen, &errmsg); } else if (varp == &p_cot) { // 'completeopt' - if (check_opt_strings(p_cot, p_cot_values, true) != OK) { - errmsg = e_invarg; - } else { - completeopt_was_set(); - } + did_set_completeopt(&errmsg); #ifdef BACKSLASH_IN_FILENAME } else if (gvarp == &p_csl) { // 'completeslash' if (check_opt_strings(p_csl, p_csl_values, false) != OK - || check_opt_strings(curbuf->b_p_csl, p_csl_values, false) != OK) { + || check_opt_strings(buf->b_p_csl, p_csl_values, false) != OK) { errmsg = e_invarg; } #endif - } else if (varp == &curwin->w_p_scl) { // 'signcolumn' - if (check_signcolumn(*varp) != OK) { - errmsg = e_invarg; - } - // When changing the 'signcolumn' to or from 'number', recompute the - // width of the number column if 'number' or 'relativenumber' is set. - if (((*oldval == 'n' && *(oldval + 1) == 'u') - || (*curwin->w_p_scl == 'n' && *(curwin->w_p_scl + 1) == 'u')) - && (curwin->w_p_nu || curwin->w_p_rnu)) { - curwin->w_nrwidth_line_count = 0; - } - } else if (varp == &curwin->w_p_fdc - || varp == &curwin->w_allbuf_opt.wo_fdc) { + } else if (varp == &win->w_p_scl) { // 'signcolumn' + did_set_signcolumn(win, varp, oldval, &errmsg); + } else if (varp == &p_sloc) { // 'showcmdloc' + did_set_opt_strings(*varp, p_sloc_values, false, &errmsg); + } else if (varp == &win->w_p_fdc + || varp == &win->w_allbuf_opt.wo_fdc) { // 'foldcolumn' - if (**varp == NUL || check_opt_strings(*varp, p_fdc_values, false) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_pt) { - // 'pastetoggle': translate key codes like in a mapping - if (*p_pt) { - p = NULL; - (void)replace_termcodes(p_pt, - strlen(p_pt), - &p, REPTERM_FROM_PART | REPTERM_DO_LT, NULL, - CPO_TO_CPO_FLAGS); - if (p != NULL) { - free_string_option(p_pt); - p_pt = p; - } - } + did_set_foldcolumn(varp, &errmsg); + } else if (varp == &p_pt) { // 'pastetoggle' + did_set_pastetoggle(); } else if (varp == &p_bs) { // 'backspace' - if (ascii_isdigit(*p_bs)) { - if (*p_bs > '3' || p_bs[1] != NUL) { - errmsg = e_invarg; - } - } else if (check_opt_strings(p_bs, p_bs_values, true) != OK) { - errmsg = e_invarg; - } + did_set_backspace(&errmsg); } else if (varp == &p_bo) { - if (opt_strings_flags(p_bo, p_bo_values, &bo_flags, true) != OK) { - errmsg = e_invarg; - } + did_set_opt_flags(p_bo, p_bo_values, &bo_flags, true, &errmsg); } else if (gvarp == &p_tc) { // 'tagcase' - unsigned int *flags; - - if (opt_flags & OPT_LOCAL) { - p = curbuf->b_p_tc; - flags = &curbuf->b_tc_flags; - } else { - p = p_tc; - flags = &tc_flags; - } - - if ((opt_flags & OPT_LOCAL) && *p == NUL) { - // make the local value empty: use the global value - *flags = 0; - } else if (*p == NUL - || opt_strings_flags(p, p_tc_values, flags, false) != OK) { - errmsg = e_invarg; - } + did_set_tagcase(buf, opt_flags, &errmsg); } else if (varp == &p_cmp) { // 'casemap' - if (opt_strings_flags(p_cmp, p_cmp_values, &cmp_flags, true) != OK) { - errmsg = e_invarg; - } + did_set_opt_flags(p_cmp, p_cmp_values, &cmp_flags, true, &errmsg); } else if (varp == &p_dip) { // 'diffopt' - if (diffopt_changed() == FAIL) { - errmsg = e_invarg; - } - } else if (gvarp == &curwin->w_allbuf_opt.wo_fdm) { // 'foldmethod' - if (check_opt_strings(*varp, p_fdm_values, false) != OK - || *curwin->w_p_fdm == NUL) { - errmsg = e_invarg; - } else { - foldUpdateAll(curwin); - if (foldmethodIsDiff(curwin)) { - newFoldLevel(); - } - } - } else if (varp == &curwin->w_p_fde) { // 'foldexpr' - if (foldmethodIsExpr(curwin)) { - foldUpdateAll(curwin); - } - } else if (gvarp == &curwin->w_allbuf_opt.wo_fmr) { // 'foldmarker' - p = vim_strchr(*varp, ','); - if (p == NULL) { - errmsg = N_("E536: comma required"); - } else if (p == *varp || p[1] == NUL) { - errmsg = e_invarg; - } else if (foldmethodIsMarker(curwin)) { - foldUpdateAll(curwin); - } + did_set_diffopt(&errmsg); + } else if (gvarp == &win->w_allbuf_opt.wo_fdm) { // 'foldmethod' + did_set_foldmethod(win, varp, &errmsg); + } else if (gvarp == &win->w_allbuf_opt.wo_fmr) { // 'foldmarker' + did_set_foldmarker(win, varp, &errmsg); } else if (gvarp == &p_cms) { // 'commentstring' - if (**varp != NUL && strstr(*varp, "%s") == NULL) { - errmsg = N_("E537: 'commentstring' must be empty or contain %s"); - } + did_set_commentstring(varp, &errmsg); } else if (varp == &p_fdo) { // 'foldopen' - if (opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true) != OK) { - errmsg = e_invarg; - } + did_set_opt_flags(p_fdo, p_fdo_values, &fdo_flags, true, &errmsg); } else if (varp == &p_fcl) { // 'foldclose' - if (check_opt_strings(p_fcl, p_fcl_values, true) != OK) { - errmsg = e_invarg; - } - } else if (gvarp == &curwin->w_allbuf_opt.wo_fdi) { // 'foldignore' - if (foldmethodIsIndent(curwin)) { - foldUpdateAll(curwin); - } + did_set_opt_strings(*varp, p_fcl_values, true, &errmsg); + } else if (gvarp == &win->w_allbuf_opt.wo_fdi) { // 'foldignore' + did_set_foldignore(win); } else if (gvarp == &p_ve) { // 'virtualedit' - char *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) { - p = p_csqf; - while (*p != NUL) { - if (vim_strchr(CSQF_CMDS, *p) == NULL - || p[1] == NUL - || vim_strchr(CSQF_FLAGS, p[1]) == NULL - || (p[2] != NUL && p[2] != ',')) { - errmsg = e_invarg; - break; - } else if (p[2] == NUL) { - break; - } else { - p += 3; - } - } - } + did_set_virtualedit(win, opt_flags, oldval, &errmsg); } else if (gvarp == &p_cino) { // 'cinoptions' // TODO(vim): recognize errors - parse_cino(curbuf); + parse_cino(buf); + } else if (gvarp == &p_lop) { // 'lispoptions' + did_set_lispoptions(varp, &errmsg); } else if (varp == &p_icm) { // 'inccommand' - if (check_opt_strings(p_icm, p_icm_values, false) != OK) { - errmsg = e_invarg; - } - } else if (gvarp == &p_ft) { - if (!valid_filetype(*varp)) { - errmsg = e_invarg; - } else { - value_changed = strcmp(oldval, *varp) != 0; - - // Since we check the value, there is no need to set P_INSECURE, - // even when the value comes from a modeline. - *value_checked = true; - } - } else if (gvarp == &p_syn) { - if (!valid_filetype(*varp)) { - errmsg = e_invarg; - } else { - value_changed = strcmp(oldval, *varp) != 0; - - // Since we check the value, there is no need to set P_INSECURE, - // even when the value comes from a modeline. - *value_checked = true; - } - } else if (varp == &curwin->w_p_winhl) { - if (!parse_winhl_opt(curwin)) { - errmsg = e_invarg; - } + did_set_opt_strings(*varp, p_icm_values, false, &errmsg); + } else if (gvarp == &p_ft || gvarp == &p_syn) { + did_set_filetype_or_syntax(varp, oldval, value_checked, &value_changed, &errmsg); + } else if (varp == &win->w_p_winhl) { + did_set_winhl(win, &errmsg); } else if (varp == &p_tpf) { - if (opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == &(curbuf->b_p_vsts)) { // 'varsofttabstop' - char *cp; - - if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { - XFREE_CLEAR(curbuf->b_p_vsts_array); - } else { - for (cp = *varp; *cp; cp++) { - if (ascii_isdigit(*cp)) { - continue; - } - if (*cp == ',' && cp > *varp && *(cp - 1) != ',') { - continue; - } - errmsg = e_invarg; - break; - } - if (errmsg == NULL) { - long *oldarray = curbuf->b_p_vsts_array; - if (tabstop_set(*varp, &(curbuf->b_p_vsts_array))) { - xfree(oldarray); - } else { - errmsg = e_invarg; - } - } - } - } else if (varp == &(curbuf->b_p_vts)) { // 'vartabstop' - char *cp; - - if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { - XFREE_CLEAR(curbuf->b_p_vts_array); - } else { - for (cp = *varp; *cp; cp++) { - if (ascii_isdigit(*cp)) { - continue; - } - if (*cp == ',' && cp > *varp && *(cp - 1) != ',') { - continue; - } - errmsg = e_invarg; - break; - } - if (errmsg == NULL) { - long *oldarray = curbuf->b_p_vts_array; - if (tabstop_set(*varp, &(curbuf->b_p_vts_array))) { - xfree(oldarray); - if (foldmethodIsIndent(curwin)) { - foldUpdateAll(curwin); - } - } else { - errmsg = e_invarg; - } - } - } + did_set_opt_flags(p_tpf, p_tpf_values, &tpf_flags, true, &errmsg); + } else if (varp == &buf->b_p_vsts) { // 'varsofttabstop' + did_set_varsoftabstop(buf, varp, &errmsg); + } else if (varp == &buf->b_p_vts) { // 'vartabstop' + did_set_vartabstop(buf, win, varp, &errmsg); + } else if (varp == &p_dex) { // 'diffexpr' + did_set_optexpr(win, &p_dex, varp, gvarp); + } else if (varp == &win->w_p_fde) { // 'foldexpr' + did_set_optexpr(win, &win->w_p_fde, varp, gvarp); + if (foldmethodIsExpr(win)) { + foldUpdateAll(win); + } + } else if (varp == &win->w_p_fdt) { // 'foldtext' + did_set_optexpr(win, &win->w_p_fdt, varp, gvarp); + } else if (varp == &p_pex) { // 'patchexpr' + did_set_optexpr(win, &p_pex, varp, gvarp); + } else if (gvarp == &p_fex) { // 'formatexpr' + did_set_optexpr(win, &buf->b_p_fex, varp, gvarp); + } else if (gvarp == &p_inex) { // 'includeexpr' + did_set_optexpr(win, &buf->b_p_inex, varp, gvarp); + } else if (gvarp == &p_inde) { // 'indentexpr' + did_set_optexpr(win, &buf->b_p_inde, varp, gvarp); + } else if (gvarp == &p_cfu) { // 'completefunc' + set_completefunc_option(&errmsg); + } else if (gvarp == &p_ofu) { // 'omnifunc' + set_omnifunc_option(buf, &errmsg); + } else if (gvarp == &p_tsrfu) { // 'thesaurusfunc' + set_thesaurusfunc_option(&errmsg); } else if (varp == &p_opfunc) { // 'operatorfunc' - if (set_operatorfunc_option() == FAIL) { - errmsg = e_invarg; - } + set_operatorfunc_option(&errmsg); } else if (varp == &p_qftf) { // 'quickfixtextfunc' - if (qf_process_qftf_option() == FAIL) { - errmsg = e_invarg; - } - } else { - // Options that are a list of flags. - p = NULL; - if (varp == &p_ww) { // 'whichwrap' - p = WW_ALL; - } - if (varp == &p_shm) { // 'shortmess' - p = SHM_ALL; - } else if (varp == &(p_cpo)) { // 'cpoptions' - p = CPO_VI; - } else if (varp == &(curbuf->b_p_fo)) { // 'formatoptions' - p = FO_ALL; - } else if (varp == &curwin->w_p_cocu) { // 'concealcursor' - p = COCU_ALL; - } else if (varp == &p_mouse) { // 'mouse' - p = MOUSE_ALL; - } - if (p != NULL) { - for (s = *varp; *s; s++) { - if (vim_strchr(p, *s) == NULL) { - errmsg = illegal_char(errbuf, errbuflen, *s); - break; - } - } + qf_process_qftf_option(&errmsg); + } else if (gvarp == &p_tfu) { // 'tagfunc' + set_tagfunc_option(&errmsg); + } else if (varp == &p_ww) { // 'whichwrap' + did_set_option_listflag(varp, WW_ALL, errbuf, errbuflen, &errmsg); + } else if (varp == &p_shm) { // 'shortmess' + did_set_option_listflag(varp, SHM_ALL, errbuf, errbuflen, &errmsg); + } else if (varp == &p_cpo) { // 'cpoptions' + did_set_option_listflag(varp, CPO_VI, errbuf, errbuflen, &errmsg); + } else if (varp == &buf->b_p_fo) { // 'formatoptions' + did_set_option_listflag(varp, FO_ALL, errbuf, errbuflen, &errmsg); + } else if (varp == &win->w_p_cocu) { // 'concealcursor' + did_set_option_listflag(varp, COCU_ALL, errbuf, errbuflen, &errmsg); + } else if (varp == &p_mouse) { // 'mouse' + did_set_option_listflag(varp, MOUSE_ALL, errbuf, errbuflen, &errmsg); + } else if (gvarp == &p_flp) { + if (win->w_briopt_list) { + // Changing Formatlistpattern when briopt includes the list setting: + // redraw + redraw_all_later(UPD_NOT_VALID); } } @@ -1520,7 +1863,7 @@ char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf *varp = oldval; // When resetting some values, need to act on it. if (did_chartab) { - (void)init_chartab(); + (void)buf_init_chartab(buf, true); } } else { // Remember where the option was set. @@ -1537,7 +1880,7 @@ char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf && (opt->indir & PV_BOTH)) { // global option with local value set to use global value; free // the local value and make it empty - p = get_varp_scope(opt, OPT_LOCAL); + char *p = get_varp_scope(opt, OPT_LOCAL); free_string_option(*(char **)p); *(char **)p = empty_option; } else if (!(opt_flags & OPT_LOCAL) && opt_flags != OPT_GLOBAL) { @@ -1546,65 +1889,12 @@ char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf } // Trigger the autocommand only after setting the flags. - // When 'syntax' is set, load the syntax of that name - if (varp == &(curbuf->b_p_syn)) { - static int syn_recursive = 0; - - syn_recursive++; - // Only pass true for "force" when the value changed or not used - // recursively, to avoid endless recurrence. - apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn, curbuf->b_fname, - value_changed || syn_recursive == 1, curbuf); - curbuf->b_flags |= BF_SYN_SET; - syn_recursive--; - } else if (varp == &(curbuf->b_p_ft)) { - // 'filetype' is set, trigger the FileType autocommand - // Skip this when called from a modeline and the filetype was - // already set to this value. - if (!(opt_flags & OPT_MODELINE) || value_changed) { - static int ft_recursive = 0; - int secure_save = secure; - - // Reset the secure flag, since the value of 'filetype' has - // been checked to be safe. - secure = 0; - - ft_recursive++; - did_filetype = true; - // Only pass true for "force" when the value changed or not - // used recursively, to avoid endless recurrence. - apply_autocmds(EVENT_FILETYPE, curbuf->b_p_ft, curbuf->b_fname, - value_changed || ft_recursive == 1, curbuf); - ft_recursive--; - // Just in case the old "curbuf" is now invalid - if (varp != &(curbuf->b_p_ft)) { - varp = NULL; - } - secure = secure_save; - } - } - if (varp == &(curwin->w_s->b_p_spl)) { - char fname[200]; - char *q = curwin->w_s->b_p_spl; - - // Skip the first name if it is "cjk". - if (STRNCMP(q, "cjk,", 4) == 0) { - q += 4; - } - - // Source the spell/LANG.vim in 'runtimepath'. - // They could set 'spellcapcheck' depending on the language. - // Use the first name in 'spelllang' up to '_region' or - // '.encoding'. - for (p = q; *p != NUL; p++) { - if (!ASCII_ISALNUM(*p) && *p != '-') { - break; - } - } - if (p > q) { - vim_snprintf(fname, sizeof(fname), "spell/%.*s.vim", (int)(p - q), q); - source_runtime(fname, DIP_ALL); - } + if (varp == &buf->b_p_syn) { + do_syntax_autocmd(buf, value_changed); + } else if (varp == &buf->b_p_ft) { + do_filetype_autocmd(buf, varp, opt_flags, value_changed); + } else if (varp == &win->w_s->b_p_spl) { + did_set_spelllang_source(win); } } @@ -1612,16 +1902,23 @@ char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf setmouse(); // in case 'mouse' changed } - if (curwin->w_curswant != MAXCOL + if (win->w_curswant != MAXCOL && (opt->flags & (P_CURSWANT | P_RALL)) != 0) { - curwin->w_set_curswant = true; + win->w_set_curswant = true; } - check_redraw(opt->flags); + check_redraw_for(buf, win, opt->flags); return errmsg; } +char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf, size_t errbuflen, + int opt_flags, int *value_checked) +{ + return did_set_string_option_for(curbuf, curwin, opt_idx, varp, oldval, errbuf, errbuflen, + opt_flags, value_checked); +} + /// Check an option that can be a range of string values. /// /// @param list when true: accept a list of values @@ -1651,7 +1948,7 @@ static int opt_strings_flags(char *val, char **values, unsigned *flagp, bool lis } size_t len = strlen(values[i]); - if (STRNCMP(values[i], val, len) == 0 + if (strncmp(values[i], val, len) == 0 && ((list && val[len] == ',') || val[len] == NUL)) { val += len + (val[len] == ','); assert(i < sizeof(1U) * 8); diff --git a/src/nvim/optionstr.h b/src/nvim/optionstr.h index ac8d90e10e..3520cc2061 100644 --- a/src/nvim/optionstr.h +++ b/src/nvim/optionstr.h @@ -1,7 +1,7 @@ #ifndef NVIM_OPTIONSTR_H #define NVIM_OPTIONSTR_H -#include "nvim/buffer_defs.h" // for buf_T, win_T +#include "nvim/buffer_defs.h" #include "nvim/option_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/os/dl.c b/src/nvim/os/dl.c index 7d095d31e3..519cef7876 100644 --- a/src/nvim/os/dl.c +++ b/src/nvim/os/dl.c @@ -4,13 +4,14 @@ /// Functions for using external native libraries #include <stdbool.h> +#include <stddef.h> #include <stdint.h> #include <uv.h> +#include "nvim/gettext.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/os/dl.h" -#include "nvim/os/os.h" /// possible function prototypes that can be called by os_libcall() /// int -> int diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index bd79b43574..0611de14aa 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -4,19 +4,32 @@ // Environment inspection #include <assert.h> +#include <limits.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> #include <uv.h> +#include "auto/config.h" #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" #include "nvim/eval.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/log.h" #include "nvim/macros.h" #include "nvim/map.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/option_defs.h" #include "nvim/os/os.h" #include "nvim/path.h" #include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/version.h" #include "nvim/vim.h" @@ -475,8 +488,8 @@ void init_homedir(void) if (var != NULL) { // Change to the directory and get the actual path. This resolves // links. Don't do it when we can't return. - if (os_dirname((char_u *)os_buf, MAXPATHL) == OK && os_chdir(os_buf) == 0) { - if (!os_chdir(var) && os_dirname((char_u *)IObuff, IOSIZE) == OK) { + if (os_dirname(os_buf, MAXPATHL) == OK && os_chdir(os_buf) == 0) { + if (!os_chdir(var) && os_dirname(IObuff, IOSIZE) == OK) { var = (char *)IObuff; } if (os_chdir(os_buf) != 0) { @@ -487,7 +500,7 @@ void init_homedir(void) // Fall back to current working directory if home is not found if ((var == NULL || *var == NUL) - && os_dirname((char_u *)os_buf, sizeof(os_buf)) == OK) { + && os_dirname(os_buf, sizeof(os_buf)) == OK) { var = os_buf; } #endif @@ -530,7 +543,7 @@ void free_homedir(void) /// @see {expand_env} char *expand_env_save(char *src) { - return (char *)expand_env_save_opt((char_u *)src, false); + return expand_env_save_opt(src, false); } /// Similar to expand_env_save() but when "one" is `true` handle the string as @@ -538,9 +551,9 @@ char *expand_env_save(char *src) /// @param src String containing environment variables to expand /// @param one Should treat as only one file name /// @see {expand_env} -char_u *expand_env_save_opt(char_u *src, bool one) +char *expand_env_save_opt(char *src, bool one) { - char_u *p = xmalloc(MAXPATHL); + char *p = xmalloc(MAXPATHL); expand_env_esc(src, p, MAXPATHL, false, one, NULL); return p; } @@ -555,7 +568,7 @@ char_u *expand_env_save_opt(char_u *src, bool one) /// @param dstlen Maximum length of the result void expand_env(char *src, char *dst, int dstlen) { - expand_env_esc((char_u *)src, (char_u *)dst, dstlen, false, false, NULL); + expand_env_esc(src, dst, dstlen, false, false, NULL); } /// Expand environment variable with path name and escaping. @@ -567,34 +580,34 @@ void expand_env(char *src, char *dst, int dstlen) /// @param esc Escape spaces in expanded variables /// @param one `srcp` is a single filename /// @param prefix Start again after this (can be NULL) -void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, bool esc, bool one, - char_u *prefix) +void expand_env_esc(char *restrict srcp, char *restrict dst, int dstlen, bool esc, bool one, + char *prefix) FUNC_ATTR_NONNULL_ARG(1, 2) { - char_u *tail; - char_u *var; + char *tail; + char *var; bool copy_char; bool mustfree; // var was allocated, need to free it later bool at_start = true; // at start of a name - int prefix_len = (prefix == NULL) ? 0 : (int)STRLEN(prefix); + int prefix_len = (prefix == NULL) ? 0 : (int)strlen(prefix); - char *src = skipwhite((char *)srcp); + char *src = skipwhite(srcp); dstlen--; // leave one char space for "\," while (*src && dstlen > 0) { // Skip over `=expr`. if (src[0] == '`' && src[1] == '=') { - var = (char_u *)src; + var = src; src += 2; (void)skip_expr(&src); if (*src == '`') { src++; } - size_t len = (size_t)(src - (char *)var); + size_t len = (size_t)(src - var); if (len > (size_t)dstlen) { len = (size_t)dstlen; } - memcpy((char *)dst, (char *)var, len); + memcpy(dst, var, len); dst += len; dstlen -= (int)len; continue; @@ -607,7 +620,7 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo // The variable name is copied into dst temporarily, because it may // be a string in read-only memory and a NUL needs to be appended. if (*src != '~') { // environment var - tail = (char_u *)src + 1; + tail = src + 1; var = dst; int c = dstlen - 1; @@ -621,7 +634,7 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo } else // NOLINT #endif { - while (c-- > 0 && *tail != NUL && vim_isIDc(*tail)) { + while (c-- > 0 && *tail != NUL && vim_isIDc((uint8_t)(*tail))) { *var++ = *tail++; } } @@ -636,25 +649,25 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo } #endif *var = NUL; - var = (char_u *)vim_getenv((char *)dst); + var = vim_getenv(dst); mustfree = true; #if defined(UNIX) } #endif } else if (src[1] == NUL // home directory || vim_ispathsep(src[1]) - || vim_strchr(" ,\t\n", src[1]) != NULL) { - var = (char_u *)homedir; - tail = (char_u *)src + 1; + || vim_strchr(" ,\t\n", (uint8_t)src[1]) != NULL) { + var = homedir; + tail = src + 1; } else { // user directory #if defined(UNIX) // Copy ~user to dst[], so we can put a NUL after it. - tail = (char_u *)src; + tail = src; var = dst; int c = dstlen - 1; while (c-- > 0 && *tail - && vim_isfilec(*tail) + && vim_isfilec((uint8_t)(*tail)) && !vim_ispathsep(*tail)) { *var++ = *tail++; } @@ -662,21 +675,21 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo // Get the user directory. If this fails the shell is used to expand // ~user, which is slower and may fail on old versions of /bin/sh. var = (*dst == NUL) ? NULL - : (char_u *)os_get_userdir((char *)dst + 1); + : os_get_userdir(dst + 1); mustfree = true; if (var == NULL) { expand_T xpc; ExpandInit(&xpc); xpc.xp_context = EXPAND_FILES; - var = (char_u *)ExpandOne(&xpc, (char *)dst, NULL, - WILD_ADD_SLASH|WILD_SILENT, WILD_EXPAND_FREE); + var = ExpandOne(&xpc, dst, NULL, + WILD_ADD_SLASH|WILD_SILENT, WILD_EXPAND_FREE); mustfree = true; } #else // cannot expand user's home directory, so don't try var = NULL; - tail = (char_u *)""; // for gcc + tail = ""; // for gcc #endif // UNIX } @@ -684,7 +697,7 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo // If 'shellslash' is set change backslashes to forward slashes. // Can't use slash_adjust(), p_ssl may be set temporarily. if (p_ssl && var != NULL && vim_strchr(var, '\\') != NULL) { - char_u *p = xstrdup(var); + char *p = xstrdup(var); if (mustfree) { xfree(var); @@ -697,8 +710,8 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo // If "var" contains white space, escape it with a backslash. // Required for ":e ~/tt" when $HOME includes a space. - if (esc && var != NULL && strpbrk((char *)var, " \t") != NULL) { - char_u *p = vim_strsave_escaped(var, (char_u *)" \t"); + if (esc && var != NULL && strpbrk(var, " \t") != NULL) { + char *p = vim_strsave_escaped(var, " \t"); if (mustfree) { xfree(var); @@ -708,13 +721,13 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo } if (var != NULL && *var != NUL - && (STRLEN(var) + STRLEN(tail) + 1 < (unsigned)dstlen)) { + && (strlen(var) + strlen(tail) + 1 < (unsigned)dstlen)) { STRCPY(dst, var); - dstlen -= (int)STRLEN(var); - int c = (int)STRLEN(var); + dstlen -= (int)strlen(var); + int c = (int)strlen(var); // if var[] ends in a path separator and tail[] starts // with it, skip a character - if (after_pathsep((char *)dst, (char *)dst + c) + if (after_pathsep(dst, dst + c) #if defined(BACKSLASH_IN_FILENAME) && dst[-1] != ':' #endif @@ -722,7 +735,7 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo tail++; } dst += c; - src = (char *)tail; + src = tail; copy_char = false; } if (mustfree) { @@ -736,18 +749,18 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo // ":edit foo ~ foo". at_start = false; if (src[0] == '\\' && src[1] != NUL) { - *dst++ = (char_u)(*src++); + *dst++ = *src++; dstlen--; } else if ((src[0] == ' ' || src[0] == ',') && !one) { at_start = true; } if (dstlen > 0) { - *dst++ = (char_u)(*src++); + *dst++ = *src++; dstlen--; if (prefix != NULL - && src - prefix_len >= (char *)srcp - && STRNCMP(src - prefix_len, prefix, prefix_len) == 0) { + && src - prefix_len >= srcp + && strncmp(src - prefix_len, prefix, (size_t)prefix_len) == 0) { at_start = true; } } @@ -837,10 +850,9 @@ const void *vim_env_iter(const char delim, const char *const val, const void *co if (dirend == NULL) { *len = strlen(varval); return NULL; - } else { - *len = (size_t)(dirend - varval); - return dirend + 1; } + *len = (size_t)(dirend - varval); + return dirend + 1; } /// Iterates $PATH-like delimited list `val` in reverse order. @@ -870,11 +882,10 @@ const void *vim_env_iter_rev(const char delim, const char *const val, const void *len = varlen; *dir = val; return NULL; - } else { - *dir = colon + 1; - *len = (size_t)(varend - colon); - return colon - 1; } + *dir = colon + 1; + *len = (size_t)(varend - colon); + return colon - 1; } /// @param[out] exe_name should be at least MAXPATHL in size @@ -1045,7 +1056,7 @@ size_t home_replace(const buf_T *const buf, const char *src, char *const dst, si } if (buf != NULL && buf->b_help) { - const size_t dlen = STRLCPY(dst, path_tail((char *)src), dstlen); + const size_t dlen = xstrlcpy(dst, path_tail((char *)src), dstlen); return MIN(dlen, dstlen - 1); } @@ -1119,10 +1130,16 @@ size_t home_replace(const buf_T *const buf, const char *src, char *const dst, si len = envlen; } + if (dstlen == 0) { + break; // Avoid overflowing below. + } // if (!one) skip to separator: space or comma. while (*src && (one || (*src != ',' && *src != ' ')) && --dstlen > 0) { *dst_p++ = *src++; } + if (dstlen == 0) { + break; // Avoid overflowing below. + } // Skip separator. while ((*src == ' ' || *src == ',') && --dstlen > 0) { *dst_p++ = *src++; @@ -1159,7 +1176,7 @@ char *get_env_name(expand_T *xp, int idx) assert(idx >= 0); char *envname = os_getenvname_at_index((size_t)idx); if (envname) { - STRLCPY(xp->xp_buf, envname, EXPAND_BUF_LEN); + xstrlcpy(xp->xp_buf, envname, EXPAND_BUF_LEN); xfree(envname); return xp->xp_buf; } @@ -1181,7 +1198,7 @@ bool os_setenv_append_path(const char *fname) // No prescribed maximum on unix. # define MAX_ENVPATHLEN INT_MAX #endif - if (!path_is_absolute((char_u *)fname)) { + if (!path_is_absolute(fname)) { internal_error("os_setenv_append_path()"); return false; } diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index b1710737d0..5af39555c9 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -11,22 +11,21 @@ #include <fcntl.h> #include <stdbool.h> #include <stddef.h> - -#include "auto/config.h" - -#ifdef HAVE_SYS_UIO_H -# include <sys/uio.h> -#endif - +#include <stdint.h> #include <uv.h> +#include "auto/config.h" +#include "nvim/gettext.h" #include "nvim/globals.h" +#include "nvim/log.h" #include "nvim/macros.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/os/fileio.h" #include "nvim/os/os.h" +#include "nvim/os/os_defs.h" #include "nvim/rbuffer.h" +#include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/fileio.c.generated.h" @@ -71,6 +70,7 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname, const int f FLAG(flags, kFileReadOnly, O_RDONLY, kFalse, wr != kTrue); #ifdef O_NOFOLLOW FLAG(flags, kFileNoSymlink, O_NOFOLLOW, kNone, true); + FLAG(flags, kFileMkDir, O_CREAT|O_WRONLY, kTrue, !(flags & kFileCreateOnly)); #endif #undef FLAG // wr is used for kFileReadOnly flag, but on @@ -78,6 +78,13 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname, const int f // `error: variable ‘wr’ set but not used [-Werror=unused-but-set-variable]` (void)wr; + if (flags & kFileMkDir) { + int mkdir_ret = os_file_mkdir((char *)fname, 0755); + if (mkdir_ret < 0) { + return mkdir_ret; + } + } + const int fd = os_open(fname, os_open_flags, mode); if (fd < 0) { @@ -162,6 +169,30 @@ FileDescriptor *file_open_fd_new(int *const error, const int fd, const int flags return fp; } +/// Opens standard input as a FileDescriptor. +FileDescriptor *file_open_stdin(void) + FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT +{ + int error; + int stdin_dup_fd; + if (stdin_fd > 0) { + stdin_dup_fd = stdin_fd; + } else { + stdin_dup_fd = os_dup(STDIN_FILENO); +#ifdef MSWIN + // Replace the original stdin with the console input handle. + os_replace_stdin_to_conin(); +#endif + } + FileDescriptor *const stdin_dup = file_open_fd_new(&error, stdin_dup_fd, + kFileReadOnly|kFileNonBlocking); + assert(stdin_dup != NULL); + if (error != 0) { + ELOG("failed to open stdin: %s", os_strerror(error)); + } + return stdin_dup; +} + /// Close file and free its buffer /// /// @param[in,out] fp File to close. diff --git a/src/nvim/os/fileio.h b/src/nvim/os/fileio.h index 426dc422c2..5e47bbf921 100644 --- a/src/nvim/os/fileio.h +++ b/src/nvim/os/fileio.h @@ -35,9 +35,10 @@ typedef enum { ///< be used with kFileCreateOnly. kFileNonBlocking = 128, ///< Do not restart read() or write() syscall if ///< EAGAIN was encountered. + kFileMkDir = 256, } FileOpenFlags; -static inline bool file_eof(const FileDescriptor *const fp) +static inline bool file_eof(const FileDescriptor *fp) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; /// Check whether end of file was encountered @@ -51,7 +52,7 @@ static inline bool file_eof(const FileDescriptor *const fp) return fp->eof && rbuffer_size(fp->rv) == 0; } -static inline int file_fd(const FileDescriptor *const fp) +static inline int file_fd(const FileDescriptor *fp) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; /// Return the file descriptor associated with the FileDescriptor structure diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 68e96eea6e..302faa8140 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -5,11 +5,23 @@ #include <assert.h> #include <errno.h> #include <fcntl.h> -#include <limits.h> #include <stdbool.h> #include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> #include "auto/config.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/log.h" +#include "nvim/macros.h" +#include "nvim/option_defs.h" +#include "nvim/os/fs_defs.h" +#include "nvim/types.h" +#include "nvim/vim.h" #ifdef HAVE_SYS_UIO_H # include <sys/uio.h> @@ -18,14 +30,12 @@ #include <uv.h> #include "nvim/ascii.h" -#include "nvim/assert.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/option.h" #include "nvim/os/os.h" -#include "nvim/os/os_defs.h" #include "nvim/path.h" -#include "nvim/strings.h" + +struct iovec; #ifdef MSWIN # include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8 @@ -95,12 +105,12 @@ int os_chdir(const char *path) /// @param buf Buffer to store the directory name. /// @param len Length of `buf`. /// @return `OK` for success, `FAIL` for failure. -int os_dirname(char_u *buf, size_t len) +int os_dirname(char *buf, size_t len) FUNC_ATTR_NONNULL_ALL { int error_number; - if ((error_number = uv_cwd((char *)buf, &len)) != kLibuvSuccess) { - STRLCPY(buf, uv_strerror(error_number), len); + if ((error_number = uv_cwd(buf, &len)) != kLibuvSuccess) { + xstrlcpy(buf, uv_strerror(error_number), len); return FAIL; } return OK; @@ -121,9 +131,8 @@ bool os_isrealdir(const char *name) fs_loop_unlock(); if (S_ISLNK(request.statbuf.st_mode)) { return false; - } else { - return S_ISDIR(request.statbuf.st_mode); } + return S_ISDIR(request.statbuf.st_mode); } /// Check if the given path exists and is a directory. @@ -171,7 +180,7 @@ int os_nodetype(const char *name) // Edge case from Vim os_win32.c: // We can't open a file with a name "\\.\con" or "\\.\prn", trying to read // from it later will cause Vim to hang. Thus return NODE_WRITABLE here. - if (STRNCMP(name, "\\\\.\\", 4) == 0) { + if (strncmp(name, "\\\\.\\", 4) == 0) { return NODE_WRITABLE; } @@ -249,9 +258,8 @@ bool os_can_exe(const char *name, char **abspath, bool use_path) && is_executable(name, abspath)) { #endif return true; - } else { - return false; } + return false; } return is_executable_in_path(name, abspath); @@ -294,7 +302,9 @@ static bool is_executable(const char *name, char **abspath) static bool is_executable_ext(const char *name, char **abspath) FUNC_ATTR_NONNULL_ARG(1) { - const bool is_unix_shell = strstr((char *)path_tail(p_sh), "sh") != NULL; + const bool is_unix_shell = strstr(path_tail(p_sh), "powershell") == NULL + && strstr(path_tail(p_sh), "pwsh") == NULL + && strstr(path_tail(p_sh), "sh") != NULL; char *nameext = strrchr(name, '.'); size_t nameext_len = nameext ? strlen(nameext) : 0; xstrlcpy(os_buf, name, sizeof(os_buf)); @@ -320,11 +330,11 @@ static bool is_executable_ext(const char *name, char **abspath) const char *ext_end = ext; size_t ext_len = - copy_option_part(&ext_end, (char_u *)buf_end, + copy_option_part((char **)&ext_end, buf_end, sizeof(os_buf) - (size_t)(buf_end - os_buf), ENV_SEPSTR); if (ext_len != 0) { bool in_pathext = nameext_len == ext_len - && 0 == mb_strnicmp((char_u *)nameext, (char_u *)ext, ext_len); + && 0 == mb_strnicmp(nameext, ext, ext_len); if (((in_pathext || is_unix_shell) && is_executable(name, abspath)) || is_executable(os_buf, abspath)) { @@ -371,7 +381,7 @@ static bool is_executable_in_path(const char *name, char **abspath) char *e = xstrchrnul(p, ENV_SEPCHAR); // Combine the $PATH segment with `name`. - STRLCPY(buf, p, e - p + 1); + xstrlcpy(buf, p, (size_t)(e - p) + 1); append_path(buf, name, buf_len); #ifdef MSWIN @@ -756,9 +766,8 @@ int32_t os_getperm(const char *name) int stat_result = os_stat(name, &statbuf); if (stat_result == kLibuvSuccess) { return (int32_t)statbuf.st_mode; - } else { - return stat_result; } + return stat_result; } /// Set the permission of a file. @@ -772,6 +781,38 @@ int os_setperm(const char *const name, int perm) return (r == kLibuvSuccess ? OK : FAIL); } +#if defined(HAVE_ACL) +# ifdef HAVE_SYS_ACL_H +# include <sys/acl.h> +# endif +# ifdef HAVE_SYS_ACCESS_H +# include <sys/access.h> +# endif + +// Return a pointer to the ACL of file "fname" in allocated memory. +// Return NULL if the ACL is not available for whatever reason. +vim_acl_T os_get_acl(const char *fname) +{ + vim_acl_T ret = NULL; + return ret; +} + +// Set the ACL of file "fname" to "acl" (unless it's NULL). +void os_set_acl(const char *fname, vim_acl_T aclent) +{ + if (aclent == NULL) { + return; + } +} + +void os_free_acl(vim_acl_T aclent) +{ + if (aclent == NULL) { + return; + } +} +#endif + #ifdef UNIX /// Checks if the current user owns a file. /// @@ -873,12 +914,11 @@ int os_file_is_writable(const char *name) /// Rename a file or directory. /// /// @return `OK` for success, `FAIL` for failure. -int os_rename(const char_u *path, const char_u *new_path) +int os_rename(const char *path, const char *new_path) FUNC_ATTR_NONNULL_ALL { int r; - RUN_UV_FS_FUNC(r, uv_fs_rename, (const char *)path, (const char *)new_path, - NULL); + RUN_UV_FS_FUNC(r, uv_fs_rename, path, new_path, NULL); return (r == kLibuvSuccess ? OK : FAIL); } @@ -946,6 +986,37 @@ int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_di return 0; } +/// Create the parent directory of a file if it does not exist +/// +/// @param[in] fname Full path of the file name whose parent directories +/// we want to create +/// @param[in] mode Permissions for the newly-created directory. +/// +/// @return `0` for success, libuv error code for failure. +int os_file_mkdir(char *fname, int32_t mode) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (!dir_of_file_exists(fname)) { + char *tail = path_tail_with_sep(fname); + char *last_char = tail + strlen(tail) - 1; + if (vim_ispathsep(*last_char)) { + emsg(_(e_noname)); + return -1; + } + char c = *tail; + *tail = NUL; + int r; + char *failed_dir; + if (((r = os_mkdir_recurse(fname, mode, &failed_dir)) < 0)) { + semsg(_(e_mkdir), failed_dir, os_strerror(r)); + xfree(failed_dir); + } + *tail = c; + return r; + } + return 0; +} + /// Create a unique temporary directory. /// /// @param[in] template Template of the path to the directory with XXXXXX @@ -1310,7 +1381,7 @@ bool os_is_reparse_point_include(const char *path) } p = utf16_path; - if (isalpha(p[0]) && p[1] == L':' && IS_PATH_SEP(p[2])) { + if (isalpha((uint8_t)p[0]) && p[1] == L':' && IS_PATH_SEP(p[2])) { p += 3; } else if (IS_PATH_SEP(p[0]) && IS_PATH_SEP(p[1])) { p += 2; diff --git a/src/nvim/os/fs.h b/src/nvim/os/fs.h index c68081da02..75c24b8db2 100644 --- a/src/nvim/os/fs.h +++ b/src/nvim/os/fs.h @@ -1,8 +1,8 @@ #ifndef NVIM_OS_FS_H #define NVIM_OS_FS_H -#include "nvim/os/fs_defs.h" // for uv_* -#include "nvim/types.h" // for char_u +#include "nvim/os/fs_defs.h" +#include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/fs.h.generated.h" diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index cb0dba8cac..759b3cf83c 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -3,25 +3,33 @@ #include <assert.h> #include <stdbool.h> +#include <stdint.h> +#include <stdio.h> #include <string.h> #include <uv.h> #include "nvim/api/private/defs.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" #include "nvim/event/rstream.h" +#include "nvim/event/stream.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/keycodes.h" +#include "nvim/log.h" +#include "nvim/macros.h" #include "nvim/main.h" -#include "nvim/mbyte.h" -#include "nvim/memory.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/option_defs.h" #include "nvim/os/input.h" +#include "nvim/os/time.h" #include "nvim/profile.h" -#include "nvim/screen.h" +#include "nvim/rbuffer.h" #include "nvim/state.h" -#include "nvim/ui.h" #include "nvim/vim.h" #define READ_BUFFER_SIZE 0xfff @@ -36,7 +44,6 @@ typedef enum { static Stream read_stream = { .closed = true }; // Input before UI starts. static RBuffer *input_buffer = NULL; static bool input_eof = false; -static int global_fd = -1; static bool blocking = false; static int cursorhold_time = 0; ///< time waiting for CursorHold event static int cursorhold_tb_change_cnt = 0; ///< tb_change_cnt when waiting started @@ -50,25 +57,14 @@ void input_init(void) input_buffer = rbuffer_new(INPUT_BUFFER_SIZE + MAX_KEY_CODE_LEN); } -void input_global_fd_init(int fd) -{ - global_fd = fd; -} - -/// Global TTY (or pipe for "-es") input stream, before UI starts. -int input_global_fd(void) -{ - return global_fd; -} - -void input_start(int fd) +void input_start(void) { if (!read_stream.closed) { return; } - input_global_fd_init(fd); - rstream_init_fd(&main_loop, &read_stream, fd, READ_BUFFER_SIZE); + used_stdin = true; + rstream_init_fd(&main_loop, &read_stream, STDIN_FILENO, READ_BUFFER_SIZE); rstream_start(&read_stream, input_read_cb, NULL); } @@ -258,7 +254,8 @@ size_t input_enqueue(String keys) uint8_t buf[19] = { 0 }; // Do not simplify the keys here. Simplification will be done later. unsigned int new_size - = trans_special((const uint8_t **)&ptr, (size_t)(end - ptr), buf, FSK_KEYCODE, true, NULL); + = trans_special((const char **)&ptr, (size_t)(end - ptr), (char *)buf, FSK_KEYCODE, true, + NULL); if (new_size) { new_size = handle_mouse_event(&ptr, buf, new_size); @@ -469,9 +466,8 @@ static InbufPollResult inbuf_poll(int ms, MultiQueue *events) if (input_ready(events)) { return kInputAvail; - } else { - return input_eof ? kInputEof : kInputNone; } + return input_eof ? kInputEof : kInputNone; } void input_done(void) diff --git a/src/nvim/os/input.h b/src/nvim/os/input.h index 7026781407..6f25efdc7b 100644 --- a/src/nvim/os/input.h +++ b/src/nvim/os/input.h @@ -7,6 +7,8 @@ #include "nvim/api/private/defs.h" #include "nvim/event/multiqueue.h" +EXTERN bool used_stdin INIT(= false); + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/input.h.generated.h" #endif diff --git a/src/nvim/os/lang.c b/src/nvim/os/lang.c index 28f43ff3af..57c82bba86 100644 --- a/src/nvim/os/lang.c +++ b/src/nvim/os/lang.c @@ -7,16 +7,16 @@ # include <CoreServices/CoreServices.h> # undef Boolean # undef FileInfo -#endif -#include "auto/config.h" +# include "auto/config.h" +# ifdef HAVE_LOCALE_H +# include <locale.h> +# endif +# include "nvim/os/os.h" -#ifdef HAVE_LOCALE_H -# include <locale.h> #endif #include "nvim/os/lang.h" -#include "nvim/os/os.h" void lang_init(void) { diff --git a/src/nvim/os/mem.c b/src/nvim/os/mem.c index eccb3c97e5..0b7e8065ef 100644 --- a/src/nvim/os/mem.c +++ b/src/nvim/os/mem.c @@ -3,6 +3,7 @@ /// Functions for accessing system memory information. +#include <stdint.h> #include <uv.h> #include "nvim/os/os.h" diff --git a/src/nvim/os/nvim.manifest b/src/nvim/os/nvim.manifest new file mode 100644 index 0000000000..8878822a5d --- /dev/null +++ b/src/nvim/os/nvim.manifest @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3"> + <assemblyIdentity + processorArchitecture="*" + version="0.9.0.0" + type="win32" + name="Neovim" + /> + <description>Neovim</description> + <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <!-- Windows 10 and Windows 11 --> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> + <!-- Windows 8.1 --> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> + <!-- Windows 8 --> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> + </application> + </compatibility> +</assembly> diff --git a/src/nvim/os/os_win_console.c b/src/nvim/os/os_win_console.c index 20b7f869f1..006e27d28f 100644 --- a/src/nvim/os/os_win_console.c +++ b/src/nvim/os/os_win_console.c @@ -2,6 +2,7 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include "nvim/os/input.h" +#include "nvim/os/os.h" #include "nvim/os/os_win_console.h" #include "nvim/vim.h" @@ -9,7 +10,12 @@ # include "os/os_win_console.c.generated.h" #endif -int os_get_conin_fd(void) +static char origTitle[256] = { 0 }; +static HWND hWnd = NULL; +static HICON hOrigIconSmall = NULL; +static HICON hOrigIcon = NULL; + +int os_open_conin_fd(void) { const HANDLE conin_handle = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, @@ -25,7 +31,7 @@ int os_get_conin_fd(void) void os_replace_stdin_to_conin(void) { close(STDIN_FILENO); - const int conin_fd = os_get_conin_fd(); + const int conin_fd = os_open_conin_fd(); assert(conin_fd == STDIN_FILENO); } @@ -45,3 +51,57 @@ void os_replace_stdout_and_stderr_to_conout(void) const int conerr_fd = _open_osfhandle((intptr_t)conout_handle, 0); assert(conerr_fd == STDERR_FILENO); } + +/// Sets Windows console icon, or pass NULL to restore original icon. +void os_icon_set(HICON hIconSmall, HICON hIcon) +{ + if (hWnd == NULL) { + return; + } + hIconSmall = hIconSmall ? hIconSmall : hOrigIconSmall; + hIcon = hIcon ? hIcon : hOrigIcon; + + if (hIconSmall != NULL) { + SendMessage(hWnd, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)hIconSmall); + } + if (hIcon != NULL) { + SendMessage(hWnd, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)hIcon); + } +} + +/// Sets Nvim logo as Windows console icon. +/// +/// Saves the original icon so it can be restored at exit. +void os_icon_init(void) +{ + if ((hWnd = GetConsoleWindow()) == NULL) { + return; + } + // Save Windows console icon to be restored later. + hOrigIconSmall = (HICON)SendMessage(hWnd, WM_GETICON, (WPARAM)ICON_SMALL, (LPARAM)0); + hOrigIcon = (HICON)SendMessage(hWnd, WM_GETICON, (WPARAM)ICON_BIG, (LPARAM)0); + + const char *vimruntime = os_getenv("VIMRUNTIME"); + if (vimruntime != NULL) { + snprintf(NameBuff, MAXPATHL, "%s" _PATHSEPSTR "neovim.ico", vimruntime); + if (!os_path_exists(NameBuff)) { + WLOG("neovim.ico not found: %s", NameBuff); + } else { + HICON hVimIcon = LoadImage(NULL, NameBuff, IMAGE_ICON, 64, 64, + LR_LOADFROMFILE | LR_LOADMAP3DCOLORS); + os_icon_set(hVimIcon, hVimIcon); + } + } +} + +/// Saves the original Windows console title. +void os_title_save(void) +{ + GetConsoleTitle(origTitle, sizeof(origTitle)); +} + +/// Resets the original Windows console title. +void os_title_reset(void) +{ + SetConsoleTitle(origTitle); +} diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c index 28aea08595..f4d95e141b 100644 --- a/src/nvim/os/process.c +++ b/src/nvim/os/process.c @@ -6,10 +6,21 @@ /// psutil is a good reference for cross-platform syscall voodoo: /// https://github.com/giampaolo/psutil/tree/master/psutil/arch -#include <uv.h> // for HANDLE (win32) +#include <assert.h> +#include <signal.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <uv.h> + +#include "nvim/log.h" +#include "nvim/memory.h" +#include "nvim/os/process.h" #ifdef MSWIN -# include <tlhelp32.h> // for CreateToolhelp32Snapshot +# include <tlhelp32.h> + +# include "nvim/api/private/helpers.h" #endif #if defined(__FreeBSD__) // XXX: OpenBSD ? @@ -27,15 +38,8 @@ # include <sys/sysctl.h> #endif -#include "nvim/api/private/helpers.h" -#include "nvim/globals.h" -#include "nvim/log.h" -#include "nvim/os/os.h" -#include "nvim/os/os_defs.h" -#include "nvim/os/process.h" - #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os/process.c.generated.h" +# include "os/process.c.generated.h" // IWYU pragma: export #endif #ifdef MSWIN @@ -260,5 +264,16 @@ Dictionary os_proc_info(int pid) /// Return true if process `pid` is running. bool os_proc_running(int pid) { - return uv_kill(pid, 0) == 0; + int err = uv_kill(pid, 0); + // If there is no error the process must be running. + if (err == 0) { + return true; + } + // If the error is ESRCH then the process is not running. + if (err == UV_ESRCH) { + return false; + } + // If the process is running and owned by another user we get EPERM. With + // other errors the process might be running, assuming it is then. + return true; } diff --git a/src/nvim/os/pty_conpty_win.c b/src/nvim/os/pty_conpty_win.c index f9478d951f..43c89f8865 100644 --- a/src/nvim/os/pty_conpty_win.c +++ b/src/nvim/os/pty_conpty_win.c @@ -67,7 +67,7 @@ conpty_t *os_conpty_init(char **in_name, char **out_name, uint16_t width, uint16 | PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE; sa.nLength = sizeof(sa); - snprintf(buf, sizeof(buf), "\\\\.\\pipe\\nvim-term-in-%d-%d", + snprintf(buf, sizeof(buf), "\\\\.\\pipe\\nvim-term-in-%lld-%d", os_get_pid(), count); *in_name = xstrdup(buf); if ((in_read = CreateNamedPipeA(*in_name, @@ -81,7 +81,7 @@ conpty_t *os_conpty_init(char **in_name, char **out_name, uint16_t width, uint16 emsg = "create input pipe failed"; goto failed; } - snprintf(buf, sizeof(buf), "\\\\.\\pipe\\nvim-term-out-%d-%d", + snprintf(buf, sizeof(buf), "\\\\.\\pipe\\nvim-term-out-%lld-%d", os_get_pid(), count); *out_name = xstrdup(buf); if ((out_write = CreateNamedPipeA(*out_name, diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index 0b7af87267..2413f0339b 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -2,13 +2,15 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com // Some of the code came from pangoterm and libuv -#include <stdbool.h> + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> -#include <sys/types.h> #include <sys/wait.h> -#include <termios.h> // forkpty is not in POSIX, so headers are platform-specific #if defined(__FreeBSD__) || defined(__DragonFly__) @@ -31,13 +33,16 @@ #include <uv.h> +#include "auto/config.h" #include "klib/klist.h" +#include "nvim/eval/typval.h" #include "nvim/event/loop.h" #include "nvim/event/process.h" -#include "nvim/event/rstream.h" -#include "nvim/event/wstream.h" +#include "nvim/event/stream.h" #include "nvim/log.h" #include "nvim/os/os.h" +#include "nvim/os/os_defs.h" +#include "nvim/os/pty_process.h" #include "nvim/os/pty_process_unix.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -155,31 +160,13 @@ static pid_t forkpty(int *amaster, char *name, struct termios *termp, struct win #endif -/// termios saved at startup (for TUI) or initialized by pty_process_spawn(). -static struct termios termios_default; - -/// Saves the termios properties associated with `tty_fd`. -/// -/// @param tty_fd TTY file descriptor, or -1 if not in a terminal. -void pty_process_save_termios(int tty_fd) -{ - if (tty_fd == -1) { - return; - } - int rv = tcgetattr(tty_fd, &termios_default); - if (rv != 0) { - ELOG("tcgetattr failed (tty_fd=%d): %s", tty_fd, strerror(errno)); - } else { - DLOG("tty_fd=%d", tty_fd); - } -} - /// @returns zero on success, or negative error code int pty_process_spawn(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL { + // termios initialized at first use + static struct termios termios_default; if (!termios_default.c_cflag) { - // TODO(jkeyes): We could pass NULL to forkpty() instead ... init_termios(&termios_default); } diff --git a/src/nvim/os/pty_process_unix.h b/src/nvim/os/pty_process_unix.h index 765490b92b..0cc68cf3e9 100644 --- a/src/nvim/os/pty_process_unix.h +++ b/src/nvim/os/pty_process_unix.h @@ -1,8 +1,10 @@ #ifndef NVIM_OS_PTY_PROCESS_UNIX_H #define NVIM_OS_PTY_PROCESS_UNIX_H +#include <stdint.h> #include <sys/ioctl.h> +#include "nvim/event/loop.h" #include "nvim/event/process.h" typedef struct pty_process { diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 750d2f342f..f1e2c5440f 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -3,30 +3,45 @@ #include <assert.h> #include <stdbool.h> -#include <stdlib.h> +#include <stdint.h> +#include <stdio.h> #include <string.h> #include <uv.h> +#include "auto/config.h" #include "klib/kvec.h" #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/eval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/event/libuv_process.h" #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" +#include "nvim/event/process.h" #include "nvim/event/rstream.h" +#include "nvim/event/stream.h" +#include "nvim/event/wstream.h" #include "nvim/ex_cmds.h" #include "nvim/fileio.h" -#include "nvim/log.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/macros.h" #include "nvim/main.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/option_defs.h" +#include "nvim/os/fs.h" +#include "nvim/os/os_defs.h" #include "nvim/os/shell.h" #include "nvim/os/signal.h" +#include "nvim/os/time.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/profile.h" -#include "nvim/screen.h" +#include "nvim/rbuffer.h" #include "nvim/strings.h" #include "nvim/tag.h" #include "nvim/types.h" @@ -50,7 +65,7 @@ typedef struct { static void save_patterns(int num_pat, char **pat, int *num_file, char ***file) { - *file = xmalloc((size_t)num_pat * sizeof(char_u *)); + *file = xmalloc((size_t)num_pat * sizeof(char *)); for (int i = 0; i < num_pat; i++) { char *s = xstrdup(pat[i]); // Be compatible with expand_filename(): halve the number of @@ -105,15 +120,15 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in { int i; size_t len; - char_u *p; + char *p; bool dir; - char_u *extra_shell_arg = NULL; + char *extra_shell_arg = NULL; ShellOpts shellopts = kShellOptExpand | kShellOptSilent; int j; - char_u *tempname; - char_u *command; + char *tempname; + char *command; FILE *fd; - char_u *buffer; + char *buffer; #define STYLE_ECHO 0 // use "echo", the default #define STYLE_GLOB 1 // use "glob", for csh #define STYLE_VIMGLOB 2 // use "vimglob", for Posix sh @@ -129,7 +144,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in bool is_fish_shell = #if defined(UNIX) - STRNCMP(invocation_path_tail((char_u *)p_sh, NULL), "fish", 4) == 0; + strncmp((char *)invocation_path_tail(p_sh, NULL), "fish", 4) == 0; #else false; #endif @@ -160,7 +175,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in } // get a name for the temp file - if ((tempname = (char_u *)vim_tempname()) == NULL) { + if ((tempname = vim_tempname()) == NULL) { emsg(_(e_notmp)); return FAIL; } @@ -181,7 +196,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in && (len = strlen(pat[0])) > 2 && *(pat[0] + len - 1) == '`') { shell_style = STYLE_BT; - } else if ((len = STRLEN(p_sh)) >= 3) { + } else if ((len = strlen(p_sh)) >= 3) { if (strcmp(p_sh + len - 3, "csh") == 0) { shell_style = STYLE_GLOB; } else if (strcmp(p_sh + len - 3, "zsh") == 0) { @@ -196,7 +211,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in // Compute the length of the command. We need 2 extra bytes: for the // optional '&' and for the NUL. // Worst case: "unset nonomatch; print -N >" plus two is 29 - len = STRLEN(tempname) + 29; + len = strlen(tempname) + 29; if (shell_style == STYLE_VIMGLOB) { len += strlen(sh_vimglob_func); } @@ -206,7 +221,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in // "command" below. len++; // add space for (j = 0; pat[i][j] != NUL; j++) { - if (vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) { + if (vim_strchr(SHELL_SPECIAL, (uint8_t)pat[i][j]) != NULL) { len++; // may add a backslash } len++; @@ -233,7 +248,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in STRCPY(command, "("); } STRCAT(command, pat[0] + 1); // exclude first backtick - p = command + STRLEN(command) - 1; + p = command + strlen(command) - 1; if (is_fish_shell) { *p-- = ';'; STRCAT(command, " end"); @@ -279,7 +294,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in // characters, except inside ``. bool intick = false; - p = command + STRLEN(command); + p = command + strlen(command); *p++ = ' '; for (j = 0; pat[i][j] != NUL; j++) { if (pat[i][j] == '`') { @@ -289,14 +304,14 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in // backslash inside backticks, before a special character // and before a backtick. if (intick - || vim_strchr(SHELL_SPECIAL, pat[i][j + 1]) != NULL + || vim_strchr(SHELL_SPECIAL, (uint8_t)pat[i][j + 1]) != NULL || pat[i][j + 1] == '`') { *p++ = '\\'; } j++; } else if (!intick && ((flags & EW_KEEPDOLLAR) == 0 || pat[i][j] != '$') - && vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) { + && vim_strchr(SHELL_SPECIAL, (uint8_t)pat[i][j]) != NULL) { // Put a backslash before a special character, but not // when inside ``. And not for $var when EW_KEEPDOLLAR is // set. @@ -304,7 +319,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in } // Copy one character. - *p++ = (char_u)pat[i][j]; + *p++ = pat[i][j]; } *p = NUL; } @@ -322,13 +337,13 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in // the argument list, otherwise zsh gives an error message and doesn't // expand any other pattern. if (shell_style == STYLE_PRINT) { - extra_shell_arg = (char_u *)"-G"; // Use zsh NULL_GLOB option + extra_shell_arg = "-G"; // Use zsh NULL_GLOB option // If we use -f then shell variables set in .cshrc won't get expanded. // vi can do it, so we will too, but it is only necessary if there is a "$" // in one of the patterns, otherwise we can still use the fast option. } else if (shell_style == STYLE_GLOB && !have_dollars(num_pat, pat)) { - extra_shell_arg = (char_u *)"-f"; // Use csh fast option + extra_shell_arg = "-f"; // Use csh fast option } // execute the shell command @@ -343,7 +358,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in xfree(command); if (i) { // os_call_shell() failed - os_remove((char *)tempname); + os_remove(tempname); xfree(tempname); // With interactive completion, the error message is not printed. if (!(flags & EW_SILENT)) { @@ -362,7 +377,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in } // read the names from the file into memory - fd = fopen((char *)tempname, READBIN); + fd = fopen(tempname, READBIN); if (fd == NULL) { // Something went wrong, perhaps a file name with a special char. if (!(flags & EW_SILENT)) { @@ -392,9 +407,9 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in buffer = xmalloc(len + 1); // fread() doesn't terminate buffer with NUL; // appropriate termination (not always NUL) is done below. - size_t readlen = fread((char *)buffer, 1, len, fd); + size_t readlen = fread(buffer, 1, len, fd); fclose(fd); - os_remove((char *)tempname); + os_remove(tempname); if (readlen != len) { // unexpected read error semsg(_(e_notread), tempname); @@ -412,7 +427,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in while (*p != ' ' && *p != '\n') { p++; } - p = (char_u *)skipwhite((char *)p); // skip to next entry + p = skipwhite(p); // skip to next entry } // file names are separated with NL } else if (shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB) { @@ -425,7 +440,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in if (*p != NUL) { p++; } - p = (char_u *)skipwhite((char *)p); // skip leading white space + p = skipwhite(p); // skip leading white space } // file names are separated with NUL } else { @@ -439,7 +454,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in if (shell_style == STYLE_PRINT && !did_find_nul) { // If there is a NUL, set did_find_nul, else set check_spaces buffer[len] = NUL; - if (len && (int)STRLEN(buffer) < (int)len) { + if (len && (int)strlen(buffer) < (int)len) { did_find_nul = true; } else { check_spaces = true; @@ -473,12 +488,12 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in goto notfound; } *num_file = i; - *file = xmalloc(sizeof(char_u *) * (size_t)i); + *file = xmalloc(sizeof(char *) * (size_t)i); // Isolate the individual file names. p = buffer; for (i = 0; i < *num_file; i++) { - (*file)[i] = (char *)p; + (*file)[i] = p; // Space or NL separates if (shell_style == STYLE_ECHO || shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB) { @@ -490,7 +505,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in *p = NUL; } else { *p++ = NUL; - p = (char_u *)skipwhite((char *)p); // skip to next entry + p = skipwhite(p); // skip to next entry } } else { // NUL separates while (*p && p < buffer + len) { // skip entry @@ -522,9 +537,9 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in p = xmalloc(strlen((*file)[i]) + 1 + dir); STRCPY(p, (*file)[i]); if (dir) { - add_pathsep((char *)p); // add '/' to a directory name + add_pathsep(p); // add '/' to a directory name } - (*file)[j++] = (char *)p; + (*file)[j++] = p; } xfree(buffer); *num_file = j; @@ -555,11 +570,11 @@ notfound: char **shell_build_argv(const char *cmd, const char *extra_args) FUNC_ATTR_NONNULL_RET { - size_t argc = tokenize((char_u *)p_sh, NULL) + (cmd ? tokenize(p_shcf, NULL) : 0); + size_t argc = tokenize(p_sh, NULL) + (cmd ? tokenize(p_shcf, NULL) : 0); char **rv = xmalloc((argc + 4) * sizeof(*rv)); // Split 'shell' - size_t i = tokenize((char_u *)p_sh, rv); + size_t i = tokenize(p_sh, rv); if (extra_args) { rv[i++] = xstrdup(extra_args); // Push a copy of `extra_args` @@ -638,7 +653,7 @@ char *shell_argv_to_str(char **const argv) /// @param extra_args Extra arguments to the shell, or NULL. /// /// @return shell command exit code -int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args) +int os_call_shell(char *cmd, ShellOpts opts, char *extra_args) { DynamicBuffer input = DYNAMIC_BUFFER_INIT; char *output = NULL, **output_ptr = NULL; @@ -667,7 +682,7 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args) } size_t nread; - int exitcode = do_os_system(shell_build_argv((char *)cmd, (char *)extra_args), + int exitcode = do_os_system(shell_build_argv(cmd, extra_args), input.data, input.len, output_ptr, &nread, emsg_silent, forward_output); xfree(input.data); @@ -693,14 +708,14 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args) /// Invalidates cached tags. /// /// @return shell command exit code -int call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg) +int call_shell(char *cmd, ShellOpts opts, char *extra_shell_arg) { int retval; proftime_T wait_time; if (p_verbose > 3) { verbose_enter(); - smsg(_("Executing command: \"%s\""), cmd == NULL ? p_sh : (char *)cmd); + smsg(_("Executing command: \"%s\""), cmd == NULL ? p_sh : cmd); msg_putchar('\n'); verbose_leave(); } @@ -737,23 +752,23 @@ int call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg) /// @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 *get_cmd_output(char *cmd, char *infile, ShellOpts flags, size_t *ret_len) { - char_u *buffer = NULL; + char *buffer = NULL; if (check_secure()) { return NULL; } // get a name for the temp file - char_u *tempname = (char_u *)vim_tempname(); + char *tempname = vim_tempname(); if (tempname == NULL) { emsg(_(e_notmp)); return NULL; } // Add the redirection stuff - char_u *command = (char_u *)make_filter_cmd((char *)cmd, (char *)infile, (char *)tempname); + char *command = make_filter_cmd(cmd, infile, tempname); // Call the shell to execute the command (errors are ignored). // Don't check timestamps here. @@ -764,7 +779,7 @@ char_u *get_cmd_output(char_u *cmd, char_u *infile, ShellOpts flags, size_t *ret xfree(command); // read the names from the file into memory - FILE *fd = os_fopen((char *)tempname, READBIN); + FILE *fd = os_fopen(tempname, READBIN); if (fd == NULL) { semsg(_(e_notopen), tempname); @@ -776,9 +791,9 @@ char_u *get_cmd_output(char_u *cmd, char_u *infile, ShellOpts flags, size_t *ret fseek(fd, 0L, SEEK_SET); buffer = xmalloc(len + 1); - size_t i = fread((char *)buffer, 1, len, fd); + size_t i = fread(buffer, 1, len, fd); fclose(fd); - os_remove((char *)tempname); + os_remove(tempname); if (i != len) { semsg(_(e_notread), tempname); XFREE_CLEAR(buffer); @@ -1150,14 +1165,14 @@ static void out_data_cb(Stream *stream, RBuffer *buf, size_t count, void *data, /// @param argv The vector that will be filled with copies of the parsed /// words. It can be NULL if the caller only needs to count words. /// @return The number of words parsed. -static size_t tokenize(const char_u *const str, char **const argv) +static size_t tokenize(const char *const str, char **const argv) FUNC_ATTR_NONNULL_ARG(1) { size_t argc = 0; - const char *p = (const char *)str; + const char *p = str; while (*p != NUL) { - const size_t len = word_length((const char_u *)p); + const size_t len = word_length(p); if (argv != NULL) { // Fill the slot @@ -1175,9 +1190,9 @@ static size_t tokenize(const char_u *const str, char **const argv) /// /// @param str A pointer to the first character of the word /// @return The offset from `str` at which the word ends. -static size_t word_length(const char_u *str) +static size_t word_length(const char *str) { - const char_u *p = str; + const char *p = str; bool inquote = false; size_t length = 0; @@ -1208,10 +1223,10 @@ static void read_input(DynamicBuffer *buf) { size_t written = 0, l = 0, len = 0; linenr_T lnum = curbuf->b_op_start.lnum; - char_u *lp = (char_u *)ml_get(lnum); + char *lp = ml_get(lnum); for (;;) { - l = strlen((char *)lp + written); + l = strlen(lp + written); if (l == 0) { len = 0; } else if (lp[written] == NL) { @@ -1220,7 +1235,7 @@ static void read_input(DynamicBuffer *buf) dynamic_buffer_ensure(buf, buf->len + len); buf->data[buf->len++] = NUL; } else { - char_u *s = (char_u *)vim_strchr((char *)lp + written, NL); + char *s = vim_strchr(lp + written, NL); len = s == NULL ? l : (size_t)(s - (lp + written)); dynamic_buffer_ensure(buf, buf->len + len); memcpy(buf->data + buf->len, lp + written, len); @@ -1240,7 +1255,7 @@ static void read_input(DynamicBuffer *buf) if (lnum > curbuf->b_op_end.lnum) { break; } - lp = (char_u *)ml_get(lnum); + lp = ml_get(lnum); written = 0; } else if (len > 0) { written += len; @@ -1317,7 +1332,7 @@ static char *shell_xescape_xquote(const char *cmd) const char *ecmd = cmd; if (*p_sxe != NUL && strcmp(p_sxq, "(") == 0) { - ecmd = (char *)vim_strsave_escaped_ext((char_u *)cmd, p_sxe, '^', false); + ecmd = vim_strsave_escaped_ext(cmd, p_sxe, '^', false); } size_t ncmd_size = strlen(ecmd) + strlen(p_sxq) * 2 + 1; char *ncmd = xmalloc(ncmd_size); diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index 9aa8d8051b..b8daaabba2 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -3,23 +3,20 @@ #include <assert.h> #include <stdbool.h> -#include <uv.h> +#include <stdio.h> #ifndef MSWIN # include <signal.h> // for sigset_t #endif -#include "nvim/ascii.h" #include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/eval.h" -#include "nvim/event/loop.h" #include "nvim/event/signal.h" #include "nvim/globals.h" #include "nvim/log.h" #include "nvim/main.h" #include "nvim/memline.h" -#include "nvim/memory.h" #include "nvim/os/signal.h" -#include "nvim/vim.h" static SignalWatcher spipe, shup, squit, sterm, susr1, swinch; #ifdef SIGPWR @@ -175,7 +172,7 @@ static void deadly_signal(int signum) ILOG("got signal %d (%s)", signum, signal_name(signum)); - snprintf((char *)IObuff, sizeof(IObuff), "Vim: Caught deadly signal '%s'\r\n", + snprintf(IObuff, sizeof(IObuff), "Vim: Caught deadly signal '%s'\r\n", signal_name(signum)); // Preserve files and exit. diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c index 31d85ac2eb..6b07b6ef70 100644 --- a/src/nvim/os/stdpaths.c +++ b/src/nvim/os/stdpaths.c @@ -2,6 +2,7 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <stdbool.h> +#include <string.h> #include "nvim/ascii.h" #include "nvim/fileio.h" @@ -87,6 +88,9 @@ char *stdpaths_get_xdg_var(const XDGVarType idx) } else if (idx == kXDGRuntimeDir) { // Special-case: stdpath('run') is defined at startup. ret = vim_gettempdir(); + if (ret == NULL) { + ret = "/tmp/"; + } size_t len = strlen(ret); ret = xstrndup(ret, len >= 2 ? len - 1 : 0); // Trim trailing slash. } @@ -117,7 +121,7 @@ char *get_xdg_home(const XDGVarType idx) #endif #ifdef BACKSLASH_IN_FILENAME - slash_adjust((char_u *)dir); + slash_adjust(dir); #endif } return dir; diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index 396bf6986a..873302a27d 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -1,22 +1,34 @@ // 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 <assert.h> +#include <inttypes.h> #include <limits.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + #include <uv.h> -#include "nvim/assert.h" +#include "auto/config.h" #include "nvim/event/loop.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/log.h" +#include "nvim/macros.h" #include "nvim/main.h" +#include "nvim/memory.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/time.h" +struct tm; + static uv_mutex_t delay_mutex; static uv_cond_t delay_cond; #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os/time.c.generated.h" +# include "os/time.c.generated.h" // IWYU pragma: export #endif /// Initializes the time module @@ -67,7 +79,7 @@ void os_delay(uint64_t ms, bool ignoreinput) } LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, (int)ms, got_int); } else { - os_microdelay(ms * 1000u, ignoreinput); + os_microdelay(ms * 1000U, ignoreinput); } } @@ -80,10 +92,10 @@ void os_delay(uint64_t ms, bool ignoreinput) /// If false, waiting is aborted on any input. void os_microdelay(uint64_t us, bool ignoreinput) { - uint64_t elapsed = 0u; + uint64_t elapsed = 0U; uint64_t base = uv_hrtime(); // Convert microseconds to nanoseconds, or UINT64_MAX on overflow. - const uint64_t ns = (us < UINT64_MAX / 1000u) ? us * 1000u : UINT64_MAX; + const uint64_t ns = (us < UINT64_MAX / 1000U) ? us * 1000U : UINT64_MAX; uv_mutex_lock(&delay_mutex); @@ -92,7 +104,7 @@ void os_microdelay(uint64_t us, bool ignoreinput) // Else we check for input in ~100ms intervals. const uint64_t ns_delta = ignoreinput ? ns - elapsed - : MIN(ns - elapsed, 100000000u); // 100ms + : MIN(ns - elapsed, 100000000U); // 100ms const int rv = uv_cond_timedwait(&delay_cond, &delay_mutex, ns_delta); if (0 != rv && UV_ETIMEDOUT != rv) { @@ -167,18 +179,28 @@ struct tm *os_localtime(struct tm *result) FUNC_ATTR_NONNULL_ALL /// @param result[out] Pointer to a 'char' where the result should be placed /// @param result_len length of result buffer /// @return human-readable string of current local time -char *os_ctime_r(const time_t *restrict clock, char *restrict result, size_t result_len) +char *os_ctime_r(const time_t *restrict clock, char *restrict result, size_t result_len, + bool add_newline) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { struct tm clock_local; struct tm *clock_local_ptr = os_localtime_r(clock, &clock_local); // MSVC returns NULL for an invalid value of seconds. if (clock_local_ptr == NULL) { - xstrlcpy(result, _("(Invalid)"), result_len); + xstrlcpy(result, _("(Invalid)"), result_len - 1); } else { - strftime(result, result_len, _("%a %b %d %H:%M:%S %Y"), clock_local_ptr); + // xgettext:no-c-format + if (strftime(result, result_len - 1, _("%a %b %d %H:%M:%S %Y"), clock_local_ptr) == 0) { + // Quoting "man strftime": + // > If the length of the result string (including the terminating + // > null byte) would exceed max bytes, then strftime() returns 0, + // > and the contents of the array are undefined. + xstrlcpy(result, _("(Invalid)"), result_len - 1); + } + } + if (add_newline) { + xstrlcat(result, "\n", result_len); } - xstrlcat(result, "\n", result_len); return result; } @@ -187,11 +209,11 @@ char *os_ctime_r(const time_t *restrict clock, char *restrict result, size_t res /// @param result[out] Pointer to a 'char' where the result should be placed /// @param result_len length of result buffer /// @return human-readable string of current local time -char *os_ctime(char *result, size_t result_len) +char *os_ctime(char *result, size_t result_len, bool add_newline) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { time_t rawtime = time(NULL); - return os_ctime_r(&rawtime, result, result_len); + return os_ctime_r(&rawtime, result, result_len, add_newline); } /// Portable version of POSIX strptime() diff --git a/src/nvim/os/tty.c b/src/nvim/os/tty.c index 1b15613a93..b5124bd83a 100644 --- a/src/nvim/os/tty.c +++ b/src/nvim/os/tty.c @@ -5,11 +5,11 @@ // Terminal/console utils // -#include "nvim/os/os.h" +#include "nvim/os/os.h" // IWYU pragma: keep (Windows) #include "nvim/os/tty.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os/tty.c.generated.h" +# include "os/tty.c.generated.h" // IWYU pragma: export #endif #ifdef MSWIN diff --git a/src/nvim/os/unix_defs.h b/src/nvim/os/unix_defs.h index 4ed3b51694..8d002fc5e9 100644 --- a/src/nvim/os/unix_defs.h +++ b/src/nvim/os/unix_defs.h @@ -3,6 +3,9 @@ #include <sys/param.h> #include <unistd.h> +#if defined(HAVE_TERMIOS_H) +# include <termios.h> +#endif // POSIX.1-2008 says that NAME_MAX should be in here #include <limits.h> diff --git a/src/nvim/os/users.c b/src/nvim/os/users.c index 33e6563c4c..ef2986246b 100644 --- a/src/nvim/os/users.c +++ b/src/nvim/os/users.c @@ -3,6 +3,9 @@ // users.c -- operating system user information +#include <stdbool.h> +#include <stdio.h> +#include <string.h> #include <uv.h> #include "auto/config.h" @@ -10,7 +13,8 @@ #include "nvim/garray.h" #include "nvim/memory.h" #include "nvim/os/os.h" -#include "nvim/strings.h" +#include "nvim/types.h" +#include "nvim/vim.h" #ifdef HAVE_PWD_H # include <pwd.h> #endif @@ -142,7 +146,7 @@ int os_get_uname(uv_uid_t uid, char *s, size_t len) if ((pw = getpwuid(uid)) != NULL // NOLINT(runtime/threadsafe_fn) && pw->pw_name != NULL && *(pw->pw_name) != NUL) { - STRLCPY(s, pw->pw_name, len); + xstrlcpy(s, pw->pw_name, len); return OK; } #endif @@ -218,7 +222,7 @@ int match_user(char *name) if (strcmp(((char **)ga_users.ga_data)[i], name) == 0) { return 2; // full match } - if (STRNCMP(((char_u **)ga_users.ga_data)[i], name, n) == 0) { + if (strncmp(((char **)ga_users.ga_data)[i], name, (size_t)n) == 0) { result = 1; // partial match } } diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c deleted file mode 100644 index 473bf5072c..0000000000 --- a/src/nvim/os_unix.c +++ /dev/null @@ -1,73 +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 <assert.h> -#include <errno.h> -#include <inttypes.h> -#include <stdbool.h> -#include <string.h> - -#include "nvim/ascii.h" -#include "nvim/buffer.h" -#include "nvim/charset.h" -#include "nvim/eval.h" -#include "nvim/ex_cmds.h" -#include "nvim/fileio.h" -#include "nvim/garray.h" -#include "nvim/getchar.h" -#include "nvim/main.h" -#include "nvim/mbyte.h" -#include "nvim/memline.h" -#include "nvim/memory.h" -#include "nvim/message.h" -#include "nvim/mouse.h" -#include "nvim/msgpack_rpc/helpers.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/path.h" -#include "nvim/screen.h" -#include "nvim/strings.h" -#include "nvim/syntax.h" -#include "nvim/types.h" -#include "nvim/ui.h" -#include "nvim/vim.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os_unix.c.generated.h" -#endif - -#if defined(HAVE_ACL) -# ifdef HAVE_SYS_ACL_H -# include <sys/acl.h> -# endif -# ifdef HAVE_SYS_ACCESS_H -# include <sys/access.h> -# endif - -// Return a pointer to the ACL of file "fname" in allocated memory. -// Return NULL if the ACL is not available for whatever reason. -vim_acl_T mch_get_acl(const char_u *fname) -{ - vim_acl_T ret = NULL; - return ret; -} - -// Set the ACL of file "fname" to "acl" (unless it's NULL). -void mch_set_acl(const char_u *fname, vim_acl_T aclent) -{ - if (aclent == NULL) { - return; - } -} - -void mch_free_acl(vim_acl_T aclent) -{ - if (aclent == NULL) { - return; - } -} -#endif diff --git a/src/nvim/os_unix.h b/src/nvim/os_unix.h deleted file mode 100644 index aae05f7fcc..0000000000 --- a/src/nvim/os_unix.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef NVIM_OS_UNIX_H -#define NVIM_OS_UNIX_H - -#include "nvim/os/shell.h" -#include "nvim/types.h" // for vim_acl_T - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os_unix.h.generated.h" -#endif -#endif // NVIM_OS_UNIX_H diff --git a/src/nvim/path.c b/src/nvim/path.c index 9295905415..513f366a27 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -2,11 +2,17 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> -#include <inttypes.h> +#include <ctype.h> +#include <limits.h> #include <stdbool.h> +#include <stddef.h> +#include <stdint.h> #include <stdlib.h> +#include <string.h> +#include "auto/config.h" #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" #include "nvim/eval.h" @@ -14,27 +20,29 @@ #include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/garray.h" -#include "nvim/memfile.h" -#include "nvim/memline.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/option.h" +#include "nvim/os/fs_defs.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/shell.h" -#include "nvim/os_unix.h" #include "nvim/path.h" -#include "nvim/quickfix.h" +#include "nvim/pos.h" #include "nvim/regexp.h" -#include "nvim/screen.h" #include "nvim/strings.h" -#include "nvim/tag.h" #include "nvim/types.h" #include "nvim/vim.h" #include "nvim/window.h" -#define URL_SLASH 1 // path_is_url() has found ":/" -#define URL_BACKSLASH 2 // path_is_url() has found ":\\" +enum { + URL_SLASH = 1, // path_is_url() has found ":/" + URL_BACKSLASH = 2, // path_is_url() has found ":\\" +}; #ifdef gen_expand_wildcards # undef gen_expand_wildcards @@ -64,7 +72,7 @@ FileComparison path_full_compare(char *const s1, char *const s2, const bool chec if (expandenv) { expand_env(s1, exp1, MAXPATHL); } else { - STRLCPY(exp1, s1, MAXPATHL); + xstrlcpy(exp1, s1, MAXPATHL); } bool id_ok_1 = os_fileid(exp1, &file_id_1); bool id_ok_2 = os_fileid(s2, &file_id_2); @@ -147,11 +155,11 @@ char *path_tail_with_sep(char *fname) /// @post if `len` is not null, stores the length of the executable name. /// /// @return The position of the last path separator + 1. -const char_u *invocation_path_tail(const char_u *invocation, size_t *len) +const char *invocation_path_tail(const char *invocation, size_t *len) FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) { - const char_u *tail = (char_u *)get_past_head((char *)invocation); - const char_u *p = tail; + const char *tail = get_past_head(invocation); + const char *p = tail; while (*p != NUL && *p != ' ') { bool was_sep = vim_ispathsep_nocolon(*p); MB_PTR_ADV(p); @@ -203,10 +211,10 @@ int path_head_length(void) /// @return /// - True if path begins with a path head /// - False otherwise -bool is_path_head(const char_u *path) +bool is_path_head(const char *path) { #ifdef MSWIN - return isalpha(path[0]) && path[1] == ':'; + return isalpha((uint8_t)path[0]) && path[1] == ':'; #else return vim_ispathsep(*path); #endif @@ -221,7 +229,7 @@ char *get_past_head(const char *path) #ifdef MSWIN // May skip "c:" - if (is_path_head((char_u *)path)) { + if (is_path_head(path)) { retval = path + 2; } #endif @@ -272,13 +280,13 @@ int vim_ispathlistsep(int c) /// "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) +void shorten_dir_len(char *str, int trim_len) { - char_u *tail = (char_u *)path_tail((char *)str); - char_u *d = str; + char *tail = path_tail(str); + char *d = str; bool skip = false; int dirchunk_len = 0; - for (char_u *s = str;; s++) { + for (char *s = str;; s++) { if (s >= tail) { // copy the whole tail *d++ = *s; if (*s == NUL) { @@ -298,7 +306,7 @@ void shorten_dir_len(char_u *str, int trim_len) skip = true; } } - int l = utfc_ptr2len((char *)s); + int l = utfc_ptr2len(s); while (--l > 0) { *d++ = *++s; } @@ -310,21 +318,21 @@ void shorten_dir_len(char_u *str, int trim_len) /// It's done in-place. void shorten_dir(char *str) { - shorten_dir_len((char_u *)str, 1); + shorten_dir_len(str, 1); } /// Return true if the directory of "fname" exists, false otherwise. /// Also returns true if there is no directory name. /// "fname" must be writable!. -bool dir_of_file_exists(char_u *fname) +bool dir_of_file_exists(char *fname) { - char *p = path_tail_with_sep((char *)fname); - if ((char_u *)p == fname) { + char *p = path_tail_with_sep(fname); + if (p == fname) { return true; } char c = *p; *p = NUL; - bool retval = os_isdir((char *)fname); + bool retval = os_isdir(fname); *p = c; return retval; } @@ -503,7 +511,7 @@ char *FullName_save(const char *fname, bool force) char *save_abs_path(const char *name) FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { - if (!path_is_absolute((char_u *)name)) { + if (!path_is_absolute(name)) { return FullName_save(name, true); } return xstrdup(name); @@ -528,7 +536,7 @@ bool path_has_wildcard(const char *p) // Windows: const char *wildcards = "?*$[`"; #endif - if (vim_strchr(wildcards, *p) != NULL + if (vim_strchr(wildcards, (uint8_t)(*p)) != NULL || (p[0] == '~' && p[1] != NUL)) { return true; } @@ -546,7 +554,7 @@ static int pstrcmp(const void *a, const void *b) /// @param p The path to expand. /// @returns Unix: True if it contains one of *?[{. /// @returns Windows: True if it contains one of *?[. -bool path_has_exp_wildcard(const char_u *p) +bool path_has_exp_wildcard(const char *p) FUNC_ATTR_NONNULL_ALL { for (; *p != NUL; MB_PTR_ADV(p)) { @@ -560,7 +568,7 @@ bool path_has_exp_wildcard(const char_u *p) #else const char *wildcards = "*?["; // Windows. #endif - if (vim_strchr(wildcards, *p) != NULL) { + if (vim_strchr(wildcards, (uint8_t)(*p)) != NULL) { return true; } } @@ -578,10 +586,10 @@ bool path_has_exp_wildcard(const char_u *p) /// - EW_NOERROR: Silence error messages. /// - EW_NOTWILD: Add matches literally. /// @returns the number of matches found. -static size_t path_expand(garray_T *gap, const char_u *path, int flags) +static size_t path_expand(garray_T *gap, const char *path, int flags) FUNC_ATTR_NONNULL_ALL { - return do_path_expand(gap, (char *)path, 0, flags, false); + return do_path_expand(gap, path, 0, flags, false); } static const char *scandir_next_with_dots(Directory *dir) @@ -621,31 +629,31 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in // Make room for file name. When doing encoding conversion the actual // length may be quite a bit longer, thus use the maximum possible length. - char_u *buf = xmalloc(MAXPATHL); + char *buf = xmalloc(MAXPATHL); // Find the first part in the path name that contains a wildcard. // When EW_ICASE is set every letter is considered to be a wildcard. // Copy it into "buf", including the preceding characters. - char_u *p = buf; - char_u *s = buf; - char_u *e = NULL; - const char_u *path_end = (char_u *)path; + char *p = buf; + char *s = buf; + char *e = NULL; + const char *path_end = path; while (*path_end != NUL) { // May ignore a wildcard that has a backslash before it; it will // be removed by rem_backslash() or file_pat_to_reg_pat() below. - if (path_end >= (char_u *)path + wildoff && rem_backslash((char *)path_end)) { + if (path_end >= path + wildoff && rem_backslash((char *)path_end)) { *p++ = *path_end++; } else if (vim_ispathsep_nocolon(*path_end)) { if (e != NULL) { break; } s = p + 1; - } else if (path_end >= (char_u *)path + wildoff - && (vim_strchr("*?[{~$", *path_end) != NULL + } else if (path_end >= path + wildoff + && (vim_strchr("*?[{~$", (uint8_t)(*path_end)) != NULL #ifndef MSWIN || (!p_fic && (flags & EW_ICASE) && mb_isalpha(utf_ptr2char((char *)path_end))) #endif - )) { + )) { // NOLINT(whitespace/parens) e = p; } len = (size_t)(utfc_ptr2len((char *)path_end)); @@ -660,7 +668,7 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in // Remove backslashes between "wildoff" and the start of the wildcard // component. for (p = buf + wildoff; p < s; p++) { - if (rem_backslash((char *)p)) { + if (rem_backslash(p)) { STRMOVE(p, p + 1); e--; s--; @@ -676,7 +684,7 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in // convert the file pattern to a regexp pattern int starts_with_dot = *s == '.'; - char *pat = file_pat_to_reg_pat((char *)s, (char *)e, NULL, false); + char *pat = file_pat_to_reg_pat(s, e, NULL, false); if (pat == NULL) { xfree(buf); return 0; @@ -710,13 +718,13 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in && *path_end == '/') { STRCPY(s, path_end + 1); stardepth++; - (void)do_path_expand(gap, (char *)buf, (size_t)(s - buf), flags, true); + (void)do_path_expand(gap, buf, (size_t)(s - buf), flags, true); stardepth--; } *s = NUL; Directory dir; - char *dirpath = (*buf == NUL ? "." : (char *)buf); + char *dirpath = (*buf == NUL ? "." : buf); if (os_file_is_readable(dirpath) && os_scandir(&dir, dirpath)) { // Find all matching entries. char *name; @@ -731,7 +739,7 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in || ((flags & EW_NOTWILD) && path_fnamencmp(path + (s - buf), name, (size_t)(e - s)) == 0))) { STRCPY(s, name); - len = STRLEN(buf); + len = strlen(buf); if (starstar && stardepth < 100) { // For "**" in the pattern first go deeper in the tree to @@ -739,7 +747,7 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in STRCPY(buf + len, "/**"); // NOLINT STRCPY(buf + len + 3, path_end); stardepth++; - (void)do_path_expand(gap, (char *)buf, len + 1, flags, true); + (void)do_path_expand(gap, buf, len + 1, flags, true); stardepth--; } @@ -747,19 +755,19 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in if (path_has_exp_wildcard(path_end)) { // handle more wildcards // need to expand another component of the path // remove backslashes for the remaining components only - (void)do_path_expand(gap, (char *)buf, len + 1, flags, false); + (void)do_path_expand(gap, buf, len + 1, flags, false); } else { FileInfo file_info; // no more wildcards, check if there is a match // remove backslashes for the remaining components only if (*path_end != NUL) { - backslash_halve((char *)buf + len + 1); + backslash_halve(buf + len + 1); } // add existing file or symbolic link if ((flags & EW_ALLLINKS) - ? os_fileinfo_link((char *)buf, &file_info) - : os_path_exists((char *)buf)) { + ? os_fileinfo_link(buf, &file_info) + : os_path_exists(buf)) { addfile(gap, buf, flags); } } @@ -775,19 +783,19 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in // slow, thus skip it. size_t matches = (size_t)(gap->ga_len - start_len); if (matches > 0 && !got_int) { - qsort(((char_u **)gap->ga_data) + start_len, matches, - sizeof(char_u *), pstrcmp); + qsort(((char **)gap->ga_data) + start_len, matches, + sizeof(char *), pstrcmp); } return matches; } // Moves "*psep" back to the previous path separator in "path". // Returns FAIL is "*psep" ends up at the beginning of "path". -static int find_previous_pathsep(char_u *path, char_u **psep) +static int find_previous_pathsep(char *path, char **psep) { // skip the current separator if (*psep > path && vim_ispathsep(**psep)) { - --*psep; + (*psep)--; } // find the previous separator @@ -832,13 +840,13 @@ static bool is_unique(char *maybe_unique, garray_T *gap, int i) // // TODO(vim): handle upward search (;) and path limiter (**N) notations by // expanding each into their equivalent path(s). -static void expand_path_option(char_u *curdir, garray_T *gap) +static void expand_path_option(char *curdir, garray_T *gap) { - char_u *path_option = *curbuf->b_p_path == NUL ? p_path : (char_u *)curbuf->b_p_path; + char *path_option = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; char *buf = xmalloc(MAXPATHL); while (*path_option != NUL) { - copy_option_part((char **)&path_option, buf, MAXPATHL, " ,"); + copy_option_part(&path_option, buf, MAXPATHL, " ,"); if (buf[0] == '.' && (buf[1] == NUL || vim_ispathsep(buf[1]))) { // Relative to current buffer: @@ -847,8 +855,8 @@ static void expand_path_option(char_u *curdir, garray_T *gap) if (curbuf->b_ffname == NULL) { continue; } - char_u *p = (char_u *)path_tail(curbuf->b_ffname); - size_t len = (size_t)(p - (char_u *)curbuf->b_ffname); + char *p = path_tail(curbuf->b_ffname); + size_t len = (size_t)(p - curbuf->b_ffname); if (len + strlen(buf) >= MAXPATHL) { continue; } @@ -858,21 +866,21 @@ static void expand_path_option(char_u *curdir, garray_T *gap) STRMOVE(buf + len, buf + 2); } memmove(buf, curbuf->b_ffname, len); - simplify_filename((char_u *)buf); + simplify_filename(buf); } else if (buf[0] == NUL) { STRCPY(buf, curdir); // relative to current directory } else if (path_with_url(buf)) { continue; // URL can't be used here - } else if (!path_is_absolute((char_u *)buf)) { + } else if (!path_is_absolute(buf)) { // Expand relative path to their full path equivalent - size_t len = STRLEN(curdir); + size_t len = strlen(curdir); if (len + strlen(buf) + 3 > MAXPATHL) { continue; } STRMOVE(buf + len + 1, buf); STRCPY(buf, curdir); buf[len] = (char_u)PATHSEP; - simplify_filename((char_u *)buf); + simplify_filename(buf); } GA_APPEND(char *, gap, xstrdup(buf)); @@ -887,11 +895,11 @@ static void expand_path_option(char_u *curdir, garray_T *gap) // path: /foo/bar/baz // fname: /foo/bar/baz/quux.txt // returns: ^this -static char_u *get_path_cutoff(char_u *fname, garray_T *gap) +static char *get_path_cutoff(char *fname, garray_T *gap) { int maxlen = 0; - char_u **path_part = (char_u **)gap->ga_data; - char_u *cutoff = NULL; + char **path_part = gap->ga_data; + char *cutoff = NULL; for (int i = 0; i < gap->ga_len; i++) { int j = 0; @@ -920,10 +928,10 @@ static char_u *get_path_cutoff(char_u *fname, garray_T *gap) return cutoff; } -// Sorts, removes duplicates and modifies all the fullpath names in "gap" so -// that they are unique with respect to each other while conserving the part -// that matches the pattern. Beware, this is at least O(n^2) wrt "gap->ga_len". -static void uniquefy_paths(garray_T *gap, char_u *pattern) +/// Sorts, removes duplicates and modifies all the fullpath names in "gap" so +/// that they are unique with respect to each other while conserving the part +/// that matches the pattern. Beware, this is at least O(n^2) wrt "gap->ga_len". +static void uniquefy_paths(garray_T *gap, char *pattern) { char **fnames = gap->ga_data; bool sort_again = false; @@ -933,12 +941,12 @@ static void uniquefy_paths(garray_T *gap, char_u *pattern) char *short_name; ga_remove_duplicate_strings(gap); - ga_init(&path_ga, (int)sizeof(char_u *), 1); + ga_init(&path_ga, (int)sizeof(char *), 1); // We need to prepend a '*' at the beginning of file_pattern so that the // regex matches anywhere in the path. FIXME: is this valid for all // possible patterns? - size_t len = STRLEN(pattern); + size_t len = strlen(pattern); char *file_pattern = xmalloc(len + 2); file_pattern[0] = '*'; file_pattern[1] = NUL; @@ -957,10 +965,10 @@ static void uniquefy_paths(garray_T *gap, char_u *pattern) } char *curdir = xmalloc(MAXPATHL); - os_dirname((char_u *)curdir, MAXPATHL); - expand_path_option((char_u *)curdir, &path_ga); + os_dirname(curdir, MAXPATHL); + expand_path_option(curdir, &path_ga); - in_curdir = xcalloc((size_t)gap->ga_len, sizeof(char_u *)); + in_curdir = xcalloc((size_t)gap->ga_len, sizeof(char *)); for (int i = 0; i < gap->ga_len && !got_int; i++) { char *path = fnames[i]; @@ -969,7 +977,7 @@ static void uniquefy_paths(garray_T *gap, char_u *pattern) char *pathsep_p; char *path_cutoff; - len = STRLEN(path); + len = strlen(path); is_in_curdir = path_fnamencmp(curdir, path, (size_t)(dir_end - path)) == 0 && curdir[dir_end - path] == NUL; if (is_in_curdir) { @@ -977,7 +985,7 @@ static void uniquefy_paths(garray_T *gap, char_u *pattern) } // Shorten the filename while maintaining its uniqueness - path_cutoff = (char *)get_path_cutoff((char_u *)path, &path_ga); + path_cutoff = get_path_cutoff(path, &path_ga); // Don't assume all files can be reached without path when search // pattern starts with **/, so only remove path_cutoff @@ -993,7 +1001,7 @@ static void uniquefy_paths(garray_T *gap, char_u *pattern) // Here all files can be reached without path, so get shortest // unique path. We start at the end of the path. */ pathsep_p = path + len - 1; - while (find_previous_pathsep((char_u *)path, (char_u **)&pathsep_p)) { + while (find_previous_pathsep(path, &pathsep_p)) { if (vim_regexec(®match, pathsep_p + 1, (colnr_T)0) && is_unique(pathsep_p + 1, gap, i) && path_cutoff != NULL && pathsep_p + 1 >= path_cutoff) { @@ -1004,7 +1012,7 @@ static void uniquefy_paths(garray_T *gap, char_u *pattern) } } - if (path_is_absolute((char_u *)path)) { + if (path_is_absolute(path)) { // Last resort: shorten relative to curdir if possible. // 'possible' means: // 1. It is under the current directory. @@ -1019,7 +1027,7 @@ static void uniquefy_paths(garray_T *gap, char_u *pattern) if (short_name != NULL && short_name > path + 1) { STRCPY(path, "."); add_pathsep(path); - STRMOVE(path + STRLEN(path), short_name); + STRMOVE(path + strlen(path), short_name); } } os_breakcheck(); @@ -1106,21 +1114,21 @@ const char *gettail_dir(const char *const fname) /// Returns the total number of matches. /// /// @param flags EW_* flags -static int expand_in_path(garray_T *const gap, char_u *const pattern, const int flags) +static int expand_in_path(garray_T *const gap, char *const pattern, const int flags) { garray_T path_ga; - char_u *const curdir = xmalloc(MAXPATHL); + char *const curdir = xmalloc(MAXPATHL); os_dirname(curdir, MAXPATHL); - ga_init(&path_ga, (int)sizeof(char_u *), 1); + ga_init(&path_ga, (int)sizeof(char *), 1); expand_path_option(curdir, &path_ga); xfree(curdir); if (GA_EMPTY(&path_ga)) { return 0; } - char_u *const paths = ga_concat_strings(&path_ga); + char *const paths = ga_concat_strings(&path_ga); ga_clear_strings(&path_ga); int glob_flags = 0; @@ -1130,7 +1138,7 @@ static int expand_in_path(garray_T *const gap, char_u *const pattern, const int if (flags & EW_ADDSLASH) { glob_flags |= WILD_ADD_SLASH; } - globpath((char *)paths, (char *)pattern, gap, glob_flags); + globpath(paths, pattern, gap, glob_flags); xfree(paths); return gap->ga_len; @@ -1138,12 +1146,12 @@ static int expand_in_path(garray_T *const gap, char_u *const pattern, const int /// Return true if "p" contains what looks like an environment variable. /// Allowing for escaping. -static bool has_env_var(char_u *p) +static bool has_env_var(char *p) { for (; *p; MB_PTR_ADV(p)) { if (*p == '\\' && p[1] != NUL) { p++; - } else if (vim_strchr("$", *p) != NULL) { + } else if (vim_strchr("$", (uint8_t)(*p)) != NULL) { return true; } } @@ -1154,7 +1162,7 @@ static bool has_env_var(char_u *p) // Return true if "p" contains a special wildcard character, one that Vim // cannot expand, requires using a shell. -static bool has_special_wildchar(char_u *p) +static bool has_special_wildchar(char *p) { for (; *p; MB_PTR_ADV(p)) { // Disallow line break characters. @@ -1164,13 +1172,13 @@ static bool has_special_wildchar(char_u *p) // Allow for escaping. if (*p == '\\' && p[1] != NUL && p[1] != '\r' && p[1] != '\n') { p++; - } else if (vim_strchr(SPECIAL_WILDCHAR, *p) != NULL) { + } else if (vim_strchr(SPECIAL_WILDCHAR, (uint8_t)(*p)) != NULL) { // A { must be followed by a matching }. - if (*p == '{' && vim_strchr((char *)p, '}') == NULL) { + if (*p == '{' && vim_strchr(p, '}') == NULL) { continue; } // A quote and backtick must be followed by another one. - if ((*p == '`' || *p == '\'') && vim_strchr((char *)p, *p) == NULL) { + if ((*p == '`' || *p == '\'') && vim_strchr(p, (uint8_t)(*p)) == NULL) { continue; } return true; @@ -1201,7 +1209,7 @@ static bool has_special_wildchar(char_u *p) int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, int flags) { garray_T ga; - char_u *p; + char *p; static bool recursive = false; int add_pat; bool did_expand_in_path = false; @@ -1224,8 +1232,8 @@ int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, i // avoids starting the shell for each argument separately. // For `=expr` do use the internal function. for (int i = 0; i < num_pat; i++) { - if (has_special_wildchar((char_u *)pat[i]) - && !(vim_backtick((char_u *)pat[i]) && pat[i][1] == '=')) { + if (has_special_wildchar(pat[i]) + && !(vim_backtick(pat[i]) && pat[i][1] == '=')) { return os_expand_wildcards(num_pat, pat, num_file, file, flags); } } @@ -1234,17 +1242,17 @@ int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, i recursive = true; // The matching file names are stored in a growarray. Init it empty. - ga_init(&ga, (int)sizeof(char_u *), 30); + ga_init(&ga, (int)sizeof(char *), 30); for (int i = 0; i < num_pat && !got_int; i++) { add_pat = -1; - p = (char_u *)pat[i]; + p = pat[i]; if (vim_backtick(p)) { - add_pat = expand_backtick(&ga, (char *)p, flags); + add_pat = expand_backtick(&ga, p, flags); if (add_pat == -1) { recursive = false; - FreeWild(ga.ga_len, ga.ga_data); + ga_clear_strings(&ga); *num_file = 0; *file = NULL; return FAIL; @@ -1254,7 +1262,7 @@ int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, i if ((has_env_var(p) && !(flags & EW_NOTENV)) || *p == '~') { p = expand_env_save_opt(p, true); if (p == NULL) { - p = (char_u *)pat[i]; + p = pat[i]; } else { #ifdef UNIX // On Unix, if expand_env() can't expand an environment @@ -1299,7 +1307,7 @@ int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, i } if (add_pat == -1 || (add_pat == 0 && (flags & EW_NOTFOUND))) { - char_u *t = (char_u *)backslash_halve_save((char *)p); + char *t = backslash_halve_save(p); // When EW_NOTFOUND is used, always add files and dirs. Makes // "vim c:/" work. @@ -1317,7 +1325,7 @@ int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, i if (did_expand_in_path && !GA_EMPTY(&ga) && (flags & EW_PATH)) { uniquefy_paths(&ga, p); } - if (p != (char_u *)pat[i]) { + if (p != pat[i]) { xfree(p); } } @@ -1342,10 +1350,10 @@ void FreeWild(int count, char **files) xfree(files); } -/// Return true if we can expand this backtick thing here. -static int vim_backtick(char_u *p) +/// @return true if we can expand this backtick thing here. +static int vim_backtick(char *p) { - return *p == '`' && *(p + 1) != NUL && *(p + STRLEN(p) - 1) == '`'; + return *p == '`' && *(p + 1) != NUL && *(p + strlen(p) - 1) == '`'; } /// Expand an item in `backticks` by executing it as a command. @@ -1365,8 +1373,7 @@ static int expand_backtick(garray_T *gap, char *pat, int flags) if (*cmd == '=') { // `={expr}`: Expand expression buffer = eval_to_string(cmd + 1, &p, true); } else { - buffer = (char *)get_cmd_output((char_u *)cmd, NULL, (flags & EW_SILENT) ? kShellOptSilent : 0, - NULL); + buffer = get_cmd_output(cmd, NULL, (flags & EW_SILENT) ? kShellOptSilent : 0, NULL); } xfree(cmd); if (buffer == NULL) { @@ -1384,7 +1391,7 @@ static int expand_backtick(garray_T *gap, char *pat, int flags) if (p > cmd) { char i = *p; *p = NUL; - addfile(gap, (char_u *)cmd, flags); + addfile(gap, cmd, flags); *p = i; cnt++; } @@ -1407,9 +1414,9 @@ static int expand_backtick(garray_T *gap, char *pat, int flags) /// backslash twice. /// When 'shellslash' set do it the other way around. /// When the path looks like a URL leave it unmodified. -void slash_adjust(char_u *p) +void slash_adjust(char *p) { - if (path_with_url((const char *)p)) { + if (path_with_url(p)) { return; } @@ -1422,8 +1429,8 @@ void slash_adjust(char_u *p) } while (*p) { - if (*p == (char_u)psepcN) { - *p = (char_u)psepc; + if (*p == psepcN) { + *p = psepc; } MB_PTR_ADV(p); } @@ -1439,7 +1446,7 @@ void slash_adjust(char_u *p) /// EW_ALLLINKS add symlink also when the referred file does not exist /// /// @param f filename -void addfile(garray_T *gap, char_u *f, int flags) +void addfile(garray_T *gap, char *f, int flags) { bool isdir; FileInfo file_info; @@ -1447,19 +1454,19 @@ void addfile(garray_T *gap, char_u *f, int flags) // if the file/dir/link doesn't exist, may not add it if (!(flags & EW_NOTFOUND) && ((flags & EW_ALLLINKS) - ? !os_fileinfo_link((char *)f, &file_info) - : !os_path_exists((char *)f))) { + ? !os_fileinfo_link(f, &file_info) + : !os_path_exists(f))) { return; } #ifdef FNAME_ILLEGAL // if the file/dir contains illegal characters, don't add it - if (strpbrk((char *)f, FNAME_ILLEGAL) != NULL) { + if (strpbrk(f, FNAME_ILLEGAL) != NULL) { return; } #endif - isdir = os_isdir((char *)f); + isdir = os_isdir(f); if ((isdir && !(flags & EW_DIR)) || (!isdir && !(flags & EW_FILE))) { return; } @@ -1467,11 +1474,11 @@ void addfile(garray_T *gap, char_u *f, int flags) // If the file isn't executable, may not add it. Do accept directories. // When invoked from expand_shellcmd() do not use $PATH. if (!isdir && (flags & EW_EXEC) - && !os_can_exe((char *)f, NULL, !(flags & EW_SHELLCMD))) { + && !os_can_exe(f, NULL, !(flags & EW_SHELLCMD))) { return; } - char_u *p = xmalloc(STRLEN(f) + 1 + isdir); + char *p = xmalloc(strlen(f) + 1 + isdir); STRCPY(p, f); #ifdef BACKSLASH_IN_FILENAME @@ -1479,19 +1486,19 @@ void addfile(garray_T *gap, char_u *f, int flags) #endif // Append a slash or backslash after directory names if none is present. if (isdir && (flags & EW_ADDSLASH)) { - add_pathsep((char *)p); + add_pathsep(p); } - GA_APPEND(char_u *, gap, p); + GA_APPEND(char *, gap, p); } // Converts a file name into a canonical form. It simplifies a file name into // its simplest form by stripping out unneeded components, if any. The // resulting file name is simplified in place and will either be the same // length as that supplied, or shorter. -void simplify_filename(char_u *filename) +void simplify_filename(char *filename) { int components = 0; - char_u *p, *tail, *start; + char *p, *tail, *start; bool stripping_disabled = false; bool relative = true; @@ -1544,7 +1551,7 @@ void simplify_filename(char_u *filename) if (components > 0) { // strip one preceding component bool do_strip = false; - char_u saved_char; + char saved_char; // Don't strip for an erroneous file name. if (!stripping_disabled) { @@ -1554,14 +1561,14 @@ void simplify_filename(char_u *filename) saved_char = p[-1]; p[-1] = NUL; FileInfo file_info; - if (!os_fileinfo_link((char *)filename, &file_info)) { + if (!os_fileinfo_link(filename, &file_info)) { do_strip = true; } p[-1] = saved_char; p--; // Skip back to after previous '/'. - while (p > start && !after_pathsep((char *)start, (char *)p)) { + while (p > start && !after_pathsep(start, p)) { MB_PTR_BACK(start, p); } @@ -1578,7 +1585,7 @@ void simplify_filename(char_u *filename) // components. saved_char = *tail; *tail = NUL; - if (os_fileinfo((char *)filename, &file_info)) { + if (os_fileinfo(filename, &file_info)) { do_strip = true; } else { stripping_disabled = true; @@ -1598,7 +1605,7 @@ void simplify_filename(char_u *filename) } else { saved_char = *p; *p = NUL; - os_fileinfo((char *)filename, &new_file_info); + os_fileinfo(filename, &new_file_info); *p = saved_char; } @@ -1647,7 +1654,7 @@ void simplify_filename(char_u *filename) } } else { components++; // Simple path component. - p = (char_u *)path_next_component((const char *)p); + p = (char *)path_next_component(p); } } while (*p != NUL); } @@ -1683,8 +1690,8 @@ char *find_file_name_in_path(char *ptr, size_t len, int options, long count, cha } if (options & FNAME_EXP) { - file_name = (char *)find_file_in_path((char_u *)ptr, len, options & ~FNAME_MESS, true, - (char_u *)rel_fname); + file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS, true, + rel_fname); // If the file could not be found in a normal way, try applying // 'includeexpr' (unless done already). @@ -1694,8 +1701,8 @@ char *find_file_name_in_path(char *ptr, size_t len, int options, long count, cha if (tofree != NULL) { ptr = tofree; len = strlen(ptr); - file_name = (char *)find_file_in_path((char_u *)ptr, len, options & ~FNAME_MESS, - true, (char_u *)rel_fname); + file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS, + true, rel_fname); } } if (file_name == NULL && (options & FNAME_MESS)) { @@ -1710,7 +1717,7 @@ char *find_file_name_in_path(char *ptr, size_t len, int options, long count, cha while (file_name != NULL && --count > 0) { xfree(file_name); file_name = - (char *)find_file_in_path((char_u *)ptr, len, options, false, (char_u *)rel_fname); + find_file_in_path(ptr, len, options, false, rel_fname); } } else { file_name = xstrnsave(ptr, len); @@ -1761,7 +1768,7 @@ int path_with_url(const char *fname) // non-URL text. // first character must be alpha - if (!isalpha(*fname)) { + if (!ASCII_ISALPHA(*fname)) { return 0; } @@ -1770,7 +1777,7 @@ int path_with_url(const char *fname) } // check body: alpha or dash - for (p = fname + 1; (isalpha(*p) || (*p == '-')); p++) {} + for (p = fname + 1; (ASCII_ISALPHA(*p) || (*p == '-')); p++) {} // check last char is not a dash if (p[-1] == '-') { @@ -1791,9 +1798,9 @@ bool path_with_extension(const char *path, const char *extension) } /// Return true if "name" is a full (absolute) path name or URL. -bool vim_isAbsName(char_u *name) +bool vim_isAbsName(char *name) { - return path_with_url((char *)name) != 0 || path_is_absolute(name); + return path_with_url(name) != 0 || path_is_absolute(name); } /// Save absolute file name to "buf[len]". @@ -1817,7 +1824,7 @@ int vim_FullName(const char *fname, char *buf, size_t len, bool force) if (strlen(fname) > (len - 1)) { xstrlcpy(buf, fname, len); // truncate #ifdef MSWIN - slash_adjust((char_u *)buf); + slash_adjust(buf); #endif return FAIL; } @@ -1832,7 +1839,7 @@ int vim_FullName(const char *fname, char *buf, size_t len, bool force) xstrlcpy(buf, fname, len); // something failed; use the filename } #ifdef MSWIN - slash_adjust((char_u *)buf); + slash_adjust(buf); #endif return rv; } @@ -1852,7 +1859,7 @@ char *fix_fname(const char *fname) #ifdef UNIX return FullName_save(fname, true); #else - if (!vim_isAbsName((char_u *)fname) + if (!vim_isAbsName((char *)fname) || strstr(fname, "..") != NULL || strstr(fname, "//") != NULL # ifdef BACKSLASH_IN_FILENAME @@ -1865,7 +1872,7 @@ char *fix_fname(const char *fname) fname = xstrdup(fname); # ifdef USE_FNAME_CASE - path_fix_case(fname); // set correct case for file name + path_fix_case((char *)fname); // set correct case for file name # endif return (char *)fname; @@ -1909,12 +1916,12 @@ void path_fix_case(char *name) // Only accept names that differ in case and are the same byte // length. TODO: accept different length name. if (STRICMP(tail, entry) == 0 && strlen(tail) == strlen(entry)) { - char_u newname[MAXPATHL + 1]; + char newname[MAXPATHL + 1]; // Verify the inode is equal. - STRLCPY(newname, name, MAXPATHL + 1); - STRLCPY(newname + (tail - name), entry, - MAXPATHL - (tail - name) + 1); + xstrlcpy(newname, name, MAXPATHL + 1); + xstrlcpy(newname + (tail - name), entry, + (size_t)(MAXPATHL - (tail - name) + 1)); FileInfo file_info_new; if (os_fileinfo_link((char *)newname, &file_info_new) && os_fileinfo_id_equal(&file_info, &file_info_new)) { @@ -1938,7 +1945,7 @@ int after_pathsep(const char *b, const char *p) /// Return true if file names "f1" and "f2" are in the same directory. /// "f1" may be a short name, "f2" must be a full path. -bool same_directory(char_u *f1, char_u *f2) +bool same_directory(char *f1, char *f2) { char ffname[MAXPATHL]; char *t1; @@ -1949,11 +1956,11 @@ bool same_directory(char_u *f1, char_u *f2) return false; } - (void)vim_FullName((char *)f1, (char *)ffname, MAXPATHL, false); + (void)vim_FullName(f1, (char *)ffname, MAXPATHL, false); t1 = path_tail_with_sep(ffname); - t2 = path_tail_with_sep((char *)f2); - return t1 - ffname == (char_u *)t2 - f2 - && pathcmp((char *)ffname, (char *)f2, (int)(t1 - ffname)) == 0; + t2 = path_tail_with_sep(f2); + return t1 - ffname == t2 - f2 + && pathcmp((char *)ffname, f2, (int)(t1 - ffname)) == 0; } // Compare path "p[]" to "q[]". @@ -2037,13 +2044,13 @@ int pathcmp(const char *p, const char *q, int maxlen) /// - Pointer into `full_path` if shortened. /// - `full_path` unchanged if no shorter name is possible. /// - NULL if `full_path` is NULL. -char_u *path_try_shorten_fname(char_u *full_path) +char *path_try_shorten_fname(char *full_path) { - char_u *dirname = xmalloc(MAXPATHL); - char_u *p = full_path; + char *dirname = xmalloc(MAXPATHL); + char *p = full_path; if (os_dirname(dirname, MAXPATHL) == OK) { - p = (char_u *)path_shorten_fname((char *)full_path, (char *)dirname); + p = path_shorten_fname(full_path, dirname); if (p == NULL || *p == NUL) { p = full_path; } @@ -2069,7 +2076,7 @@ char *path_shorten_fname(char *full_path, char *dir_name) size_t len = strlen(dir_name); // If dir_name is a path head, full_path can always be made relative. - if (len == (size_t)path_head_length() && is_path_head((char_u *)dir_name)) { + if (len == (size_t)path_head_length() && is_path_head(dir_name)) { return full_path + len; } @@ -2079,7 +2086,7 @@ char *path_shorten_fname(char *full_path, char *dir_name) return NULL; } - char_u *p = (char_u *)full_path + len; + char *p = full_path + len; // If *p is not pointing to a path separator, this means that full_path's // last directory name is longer than *dir_name's last directory, so they @@ -2088,7 +2095,7 @@ char *path_shorten_fname(char *full_path, char *dir_name) return NULL; } - return (char *)p + 1; + return p + 1; } /// Invoke expand_wildcards() for one pattern @@ -2117,9 +2124,9 @@ int expand_wildcards_eval(char **pat, int *num_file, char ***file, int flags) if (is_cur_alt_file || *exp_pat == '<') { emsg_off++; - eval_pat = (char *)eval_vars((char_u *)exp_pat, (char_u *)exp_pat, &usedlen, NULL, &ignored_msg, - NULL, - true); + eval_pat = eval_vars(exp_pat, exp_pat, &usedlen, NULL, &ignored_msg, + NULL, + true); emsg_off--; if (eval_pat != NULL) { star_follows = strcmp(exp_pat + usedlen, "*") == 0; @@ -2167,7 +2174,7 @@ int expand_wildcards(int num_pat, char **pat, int *num_files, char ***files, int { int retval; int i, j; - char_u *p; + char *p; int non_suf_match; // number without matching suffix retval = gen_expand_wildcards(num_pat, pat, num_files, files, flags); @@ -2179,15 +2186,15 @@ int expand_wildcards(int num_pat, char **pat, int *num_files, char ***files, int // Remove names that match 'wildignore'. if (*p_wig) { - char_u *ffname; + char *ffname; // check all files in (*files)[] assert(*num_files == 0 || *files != NULL); for (i = 0; i < *num_files; i++) { - ffname = (char_u *)FullName_save((*files)[i], false); + ffname = FullName_save((*files)[i], false); assert((*files)[i] != NULL); assert(ffname != NULL); - if (match_file_list((char_u *)p_wig, (char_u *)(*files)[i], ffname)) { + if (match_file_list(p_wig, (*files)[i], ffname)) { // remove this matching file from the list xfree((*files)[i]); for (j = i; j + 1 < *num_files; j++) { @@ -2208,11 +2215,11 @@ int expand_wildcards(int num_pat, char **pat, int *num_files, char ***files, int for (i = 0; i < *num_files; i++) { if (!match_suffix((*files)[i])) { // Move the name without matching suffix to the front of the list. - p = (char_u *)(*files)[i]; + p = (*files)[i]; for (j = i; j > non_suf_match; j--) { (*files)[j] = (*files)[j - 1]; } - (*files)[non_suf_match++] = (char *)p; + (*files)[non_suf_match++] = p; } } } @@ -2234,8 +2241,8 @@ int match_suffix(char *fname) size_t fnamelen = strlen(fname); size_t setsuflen = 0; - for (char_u *setsuf = p_su; *setsuf;) { - setsuflen = copy_option_part((char **)&setsuf, (char *)suf_buf, MAXSUFLEN, ".,"); + for (char *setsuf = p_su; *setsuf;) { + setsuflen = copy_option_part(&setsuf, suf_buf, MAXSUFLEN, ".,"); if (setsuflen == 0) { char *tail = path_tail(fname); @@ -2265,13 +2272,13 @@ int path_full_dir_name(char *directory, char *buffer, size_t len) int retval = OK; if (strlen(directory) == 0) { - return os_dirname((char_u *)buffer, len); + return os_dirname(buffer, len); } char old_dir[MAXPATHL]; // Get current directory name. - if (os_dirname((char_u *)old_dir, MAXPATHL) == FAIL) { + if (os_dirname(old_dir, MAXPATHL) == FAIL) { return FAIL; } @@ -2284,14 +2291,14 @@ int path_full_dir_name(char *directory, char *buffer, size_t len) // 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)) { + if (path_is_absolute(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) { + } else if (os_dirname(buffer, len) == FAIL) { // Do not return immediately since we are in the wrong directory. retval = FAIL; } @@ -2354,7 +2361,7 @@ static int path_to_absolute(const char *fname, char *buf, size_t len, int force) char *end_of_path = (char *)fname; // expand it if forced or not an absolute path - if (force || !path_is_absolute((char_u *)fname)) { + if (force || !path_is_absolute(fname)) { p = strrchr(fname, '/'); #ifdef MSWIN if (p == NULL) { @@ -2362,16 +2369,9 @@ static int path_to_absolute(const char *fname, char *buf, size_t len, int force) } #endif if (p != NULL) { - // relative to root - if (p == fname) { - // only one path component - relative_directory[0] = PATHSEP; - relative_directory[1] = NUL; - } else { - assert(p >= fname); - memcpy(relative_directory, fname, (size_t)(p - fname)); - relative_directory[p - fname] = NUL; - } + assert(p >= fname); + memcpy(relative_directory, fname, (size_t)(p - fname + 1)); + relative_directory[p - fname + 1] = NUL; end_of_path = p + 1; } else { relative_directory[0] = NUL; @@ -2390,14 +2390,14 @@ static int path_to_absolute(const char *fname, char *buf, size_t len, int force) /// Check if file `fname` is a full (absolute) path. /// /// @return `true` if "fname" is absolute. -int path_is_absolute(const char_u *fname) +int path_is_absolute(const char *fname) { #ifdef MSWIN 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((uint8_t)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 '~'. @@ -2417,11 +2417,11 @@ void path_guess_exepath(const char *argv0, char *buf, size_t bufsize) { const char *path = os_getenv("PATH"); - if (path == NULL || path_is_absolute((char_u *)argv0)) { + if (path == NULL || path_is_absolute(argv0)) { xstrlcpy(buf, argv0, bufsize); } else if (argv0[0] == '.' || strchr(argv0, PATHSEP)) { // Relative to CWD. - if (os_dirname((char_u *)buf, MAXPATHL) != OK) { + if (os_dirname(buf, MAXPATHL) != OK) { buf[0] = NUL; } xstrlcat(buf, PATHSEPSTR, bufsize); @@ -2439,11 +2439,11 @@ void path_guess_exepath(const char *argv0, char *buf, size_t bufsize) if (dir_len + 1 > sizeof(NameBuff)) { continue; } - STRLCPY(NameBuff, dir, dir_len + 1); + xstrlcpy(NameBuff, dir, dir_len + 1); xstrlcat(NameBuff, PATHSEPSTR, sizeof(NameBuff)); xstrlcat(NameBuff, argv0, sizeof(NameBuff)); - if (os_can_exe((char *)NameBuff, NULL, false)) { - xstrlcpy(buf, (char *)NameBuff, bufsize); + if (os_can_exe(NameBuff, NULL, false)) { + xstrlcpy(buf, NameBuff, bufsize); return; } } while (iter != NULL); diff --git a/src/nvim/path.h b/src/nvim/path.h index 37a0883c7f..c8d192dffe 100644 --- a/src/nvim/path.h +++ b/src/nvim/path.h @@ -39,4 +39,4 @@ typedef enum file_comparison { #ifdef INCLUDE_GENERATED_DECLARATIONS # include "path.h.generated.h" #endif -#endif +#endif // NVIM_PATH_H diff --git a/src/nvim/plines.c b/src/nvim/plines.c index cbde0cfff9..5469d94800 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -10,25 +10,20 @@ #include <string.h> #include "nvim/ascii.h" -#include "nvim/buffer.h" #include "nvim/charset.h" -#include "nvim/cursor.h" #include "nvim/decoration.h" #include "nvim/diff.h" #include "nvim/fold.h" -#include "nvim/func_attr.h" +#include "nvim/globals.h" #include "nvim/indent.h" -#include "nvim/main.h" +#include "nvim/macros.h" #include "nvim/mbyte.h" #include "nvim/memline.h" -#include "nvim/memory.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" -#include "nvim/screen.h" -#include "nvim/strings.h" +#include "nvim/pos.h" #include "nvim/vim.h" -#include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "plines.c.generated.h" @@ -37,6 +32,9 @@ /// Functions calculating vertical size of text when displayed inside a window. /// Calls horizontal size functions defined below. +/// Return the number of window lines occupied by buffer line "lnum". +/// Includes any filler lines. +/// /// @param winheight when true limit to window height int plines_win(win_T *wp, linenr_T lnum, bool winheight) { @@ -53,7 +51,7 @@ int plines_win(win_T *wp, linenr_T lnum, bool winheight) /// @return Number of filler lines above lnum int win_get_fill(win_T *wp, linenr_T lnum) { - int virt_lines = decor_virt_lines(wp, lnum, NULL); + int virt_lines = decor_virt_lines(wp, lnum, NULL, kNone); // be quick when there are no filler lines if (diffopt_filler()) { @@ -71,6 +69,9 @@ bool win_may_fill(win_T *wp) return (wp->w_p_diff && diffopt_filler()) || wp->w_buffer->b_virt_line_blocks; } +/// Return the number of window lines occupied by buffer line "lnum". +/// Does not include filler lines. +/// /// @param winheight when true limit to window height int plines_win_nofill(win_T *wp, linenr_T lnum, bool winheight) { @@ -106,7 +107,7 @@ int plines_win_nofold(win_T *wp, linenr_T lnum) if (*s == NUL) { // empty line return 1; } - col = win_linetabsize(wp, lnum, (char_u *)s, MAXCOL); + col = win_linetabsize(wp, lnum, s, MAXCOL); // If list mode is on, then the '$' at the end of the line may take up one // extra column. @@ -144,12 +145,12 @@ int plines_win_col(win_T *wp, linenr_T lnum, long column) return lines + 1; } - char_u *line = (char_u *)ml_get_buf(wp->w_buffer, lnum, false); + char *line = ml_get_buf(wp->w_buffer, lnum, false); colnr_T col = 0; chartabsize_T cts; - init_chartabsize_arg(&cts, wp, lnum, 0, (char *)line, (char *)line); + init_chartabsize_arg(&cts, wp, lnum, 0, line, line); while (*cts.cts_ptr != NUL && --column >= 0) { cts.cts_vcol += win_lbr_chartabsize(&cts, NULL); MB_PTR_ADV(cts.cts_ptr); @@ -232,9 +233,8 @@ int win_chartabsize(win_T *wp, char *p, colnr_T col) buf_T *buf = wp->w_buffer; if (*p == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { return tabstop_padding(col, buf->b_p_ts, buf->b_p_vts_array); - } else { - return ptr2cells(p); } + return ptr2cells(p); } /// Return the number of characters the string 's' will take on the screen, @@ -243,9 +243,9 @@ int win_chartabsize(win_T *wp, char *p, colnr_T col) /// @param s /// /// @return Number of characters the string will take on the screen. -int linetabsize(char_u *s) +int linetabsize(char *s) { - return linetabsize_col(0, (char *)s); + return linetabsize_col(0, s); } /// Like linetabsize(), but "s" starts at column "startcol". @@ -272,11 +272,11 @@ int linetabsize_col(int startcol, char *s) /// @param len /// /// @return Number of characters the string will take on the screen. -unsigned int win_linetabsize(win_T *wp, linenr_T lnum, char_u *line, colnr_T len) +unsigned int win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T len) { chartabsize_T cts; - init_chartabsize_arg(&cts, wp, lnum, 0, (char *)line, (char *)line); - for (; *cts.cts_ptr != NUL && (len == MAXCOL || cts.cts_ptr < (char *)line + len); + init_chartabsize_arg(&cts, wp, lnum, 0, line, line); + for (; *cts.cts_ptr != NUL && (len == MAXCOL || cts.cts_ptr < line + len); MB_PTR_ADV(cts.cts_ptr)) { cts.cts_vcol += win_lbr_chartabsize(&cts, NULL); } @@ -353,7 +353,7 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) { win_T *wp = cts->cts_win; char *line = cts->cts_line; // start of the line - char_u *s = (char_u *)cts->cts_ptr; + char *s = cts->cts_ptr; colnr_T vcol = cts->cts_vcol; colnr_T col2; @@ -362,7 +362,7 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) int added; int mb_added = 0; int numberextra; - char_u *ps; + char *ps; int n; cts->cts_cur_text_width = 0; @@ -373,16 +373,16 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) if (wp->w_p_wrap) { return win_nolbr_chartabsize(cts, headp); } - return win_chartabsize(wp, (char *)s, vcol); + return win_chartabsize(wp, s, vcol); } // First get normal size, without 'linebreak' or virtual text - int size = win_chartabsize(wp, (char *)s, vcol); + int size = win_chartabsize(wp, s, vcol); if (cts->cts_has_virt_text) { // TODO(bfredl): inline virtual text } - int c = *s; + int c = (uint8_t)(*s); if (*s == TAB) { col_adj = size - 1; } @@ -391,7 +391,7 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) // needs a break here if (wp->w_p_lbr && vim_isbreak(c) - && !vim_isbreak((int)s[1]) + && !vim_isbreak((uint8_t)s[1]) && wp->w_p_wrap && (wp->w_width_inner != 0)) { // Count all characters from first non-blank after a blank up to next @@ -412,14 +412,14 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) for (;;) { ps = s; MB_PTR_ADV(s); - c = *s; + c = (uint8_t)(*s); if (!(c != NUL - && (vim_isbreak(c) || col2 == vcol || !vim_isbreak((int)(*ps))))) { + && (vim_isbreak(c) || col2 == vcol || !vim_isbreak((uint8_t)(*ps))))) { break; } - col2 += win_chartabsize(wp, (char *)s, col2); + col2 += win_chartabsize(wp, s, col2); if (col2 >= colmax) { // doesn't fit size = colmax - vcol + col_adj; @@ -427,7 +427,7 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) } } } else if ((size == 2) - && (MB_BYTE2LEN(*s) > 1) + && (MB_BYTE2LEN((uint8_t)(*s)) > 1) && wp->w_p_wrap && in_win_border(wp, vcol)) { // Count the ">" in the last column. @@ -438,9 +438,9 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) // May have to add something for 'breakindent' and/or 'showbreak' // string at start of line. // Set *headp to the size of what we add. + // Do not use 'showbreak' at the NUL after the text. added = 0; - - char *const sbr = (char *)get_showbreak_value(wp); + char *const sbr = c == NUL ? empty_option : get_showbreak_value(wp); if ((*sbr != NUL || wp->w_p_bri) && wp->w_p_wrap && vcol != 0) { colnr_T sbrlen = 0; int numberwidth = win_col_off(wp); @@ -455,7 +455,7 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) vcol %= numberextra; } if (*sbr != NUL) { - sbrlen = (colnr_T)mb_charlen((char_u *)sbr); + sbrlen = (colnr_T)mb_charlen(sbr); if (vcol >= sbrlen) { vcol -= sbrlen; } @@ -491,7 +491,7 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) } if (wp->w_p_bri) { - added += get_breakindent_win(wp, (char_u *)line); + added += get_breakindent_win(wp, line); } size += added; diff --git a/src/nvim/plines.h b/src/nvim/plines.h index f463d82f10..808f6d284e 100644 --- a/src/nvim/plines.h +++ b/src/nvim/plines.h @@ -1,6 +1,9 @@ #ifndef NVIM_PLINES_H #define NVIM_PLINES_H +#include <stdbool.h> + +#include "nvim/buffer_defs.h" #include "nvim/vim.h" // Argument for lbr_chartabsize(). diff --git a/src/nvim/po/CMakeLists.txt b/src/nvim/po/CMakeLists.txt index 57896b74ce..1db21880bb 100644 --- a/src/nvim/po/CMakeLists.txt +++ b/src/nvim/po/CMakeLists.txt @@ -48,10 +48,16 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) list(SORT NVIM_RELATIVE_SOURCES) add_custom_command( OUTPUT ${NVIM_POT} + COMMAND $<TARGET_FILE:nvim> -u NONE -i NONE -n --headless --cmd "set cpo+=+" + -S ${CMAKE_CURRENT_SOURCE_DIR}/tojavascript.vim ${NVIM_POT} ${PROJECT_SOURCE_DIR}/runtime/optwin.vim COMMAND ${XGETTEXT_PRG} -o ${NVIM_POT} --default-domain=nvim - --add-comments --keyword=_ --keyword=N_ -D ${CMAKE_CURRENT_SOURCE_DIR} - ${NVIM_RELATIVE_SOURCES} - DEPENDS ${NVIM_SOURCES}) + --add-comments --keyword=_ --keyword=N_ --keyword=NGETTEXT:1,2 + -D ${CMAKE_CURRENT_SOURCE_DIR} -D ${CMAKE_CURRENT_BINARY_DIR} + ${NVIM_RELATIVE_SOURCES} optwin.js + COMMAND $<TARGET_FILE:nvim> -u NONE -i NONE -n --headless --cmd "set cpo+=+" + -S ${CMAKE_CURRENT_SOURCE_DIR}/fixfilenames.vim ${NVIM_POT} ../../../runtime/optwin.vim + VERBATIM + DEPENDS ${NVIM_SOURCES} nvim nvim_runtime_deps) set(LANGUAGE_MO_FILES) set(UPDATE_PO_TARGETS) diff --git a/src/nvim/po/af.po b/src/nvim/po/af.po index d64789660e..92a1a6ca3c 100644 --- a/src/nvim/po/af.po +++ b/src/nvim/po/af.po @@ -2794,101 +2794,6 @@ msgstr "soektog het BO getref, gaan voort van ONDER af" msgid "search hit BOTTOM, continuing at TOP" msgstr "soektog het ONDER getref, gaan voort van BO af" -msgid "E550: Missing colon" -msgstr "E550: Ontbrekende dubbelpunt" - -msgid "E551: Illegal component" -msgstr "E551: Ongeldige komponent" - -msgid "E552: digit expected" -msgstr "E552: syfer verwag" - -#, c-format -msgid "Page %d" -msgstr "Bladsy %d" - -msgid "No text to be printed" -msgstr "Geen teks om te druk nie" - -#, fuzzy, c-format -#~ msgid "Printing page %d (%zu%%)" -#~ msgstr "Druk nou bladsy %d (%d%%)" - -#, c-format -msgid " Copy %d of %d" -msgstr " Kopie %d van %d" - -#, c-format -msgid "Printed: %s" -msgstr "Gedruk: %s" - -msgid "Printing aborted" -msgstr "Drukkery gestaak" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Kan nie na 'PostScript' afvoerler skryf nie" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Kan nie ler \"%s\" oopmaak nie" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Kan nie 'PostScript' hulpbron-ler \"%s\" lees nie" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: Ler \"%s\" is nie 'n 'PostScript' hulpbron-ler nie" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "" -"E619: Ler \"%s\" is nie 'n ondersteunde 'PostScript' hulpbron-ler nie" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" die hulpbron ler het die verkeerde weergawe" - -#~ msgid "E673: Incompatible multi-byte encoding and character set." -#~ msgstr "" - -#~ msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -#~ msgstr "" - -#~ msgid "E675: No default font specified for multi-byte printing." -#~ msgstr "" - -msgid "E324: Can't open PostScript output file" -msgstr "E324: Kan nie 'PostScript' afvoerler oopmaak nie" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Kan nie ler %s oopmaak nie" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Kan nie 'PostScript' hulpbron-ler \"prolog.ps\" lees nie" - -#, fuzzy -#~ msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -#~ msgstr "E456: Kan nie 'PostScript' hulpbron-ler \"%s\" vind nie" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Kan nie 'PostScript' hulpbron-ler \"%s\" vind nie" - -#, fuzzy, c-format -#~ msgid "E620: Unable to convert to print encoding \"%s\"" -#~ msgstr "E620: Kon nie van wye-greep na \"%s\" enkodering verander nie" - -msgid "Sending to printer..." -msgstr "Besig om te stuur na drukker..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: Kon nie 'PostScript' ler druk nie" - -msgid "Print job sent." -msgstr "Druktaak gestuur." - msgid "Add a new database" msgstr "Voeg 'n nuwe databasis by" diff --git a/src/nvim/po/ca.po b/src/nvim/po/ca.po index 5869e6567c..964fcc9325 100644 --- a/src/nvim/po/ca.po +++ b/src/nvim/po/ca.po @@ -3013,128 +3013,6 @@ msgstr "la cerca ha arribat a DALT, es continua a BAIX" msgid "search hit BOTTOM, continuing at TOP" msgstr "la cerca ha arribat a BAIX, es continua a DALT" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: Falta un carcter \":\"" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: Component illegal" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: S'esperava un dgit" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Pgina %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "No hi ha text per imprimir" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Imprimint la pgina %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Cpia %d de %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "S'ha imprs: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "S'ha avortat la impressi" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Error en escriure el fitxer PostScript" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: No s'ha pogut obrir el fitxer \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: No s'ha pogut llegir el fitxer de recursos PostScript \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: El fitxer \"%s\" no s un fitxer de recursos PostScript" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: El fitxer de recursos PostScript \"%s\" no est suportat" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: La versi del fitxer de recursos \"%s\" no s vlida" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Joc de carcters i codificaci multi-octet no compatibles." - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: printmbcharset no pot estar buit si la codificaci s multi-octet" - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "" -"E675: No heu especificat cap fosa per defecte per a la impressi multi-octet." - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: No s'ha pogut obrir el fitxer PostScript generat" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: No s'ha pogut obrir el fitxer \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: No s'ha trobat el fitxer de recursos PostScript \"prolog.ps\"" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: No s'ha trobat el fitxer de recursos PostScript \"cidfont.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: No s'ha trobat el fitxer de recursos PostScript \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: No s'ha pogut convertir a la codificaci d'impressi \"%s\"" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "S'est enviant a la impressora..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Error en imprimir el fitxer PostScript" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "S'ha enviat la tasca d'impressi." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Afegeix una base de dades nova" diff --git a/src/nvim/po/cleanup.vim b/src/nvim/po/cleanup.vim index 8384286b0d..6df5edd498 100644 --- a/src/nvim/po/cleanup.vim +++ b/src/nvim/po/cleanup.vim @@ -22,7 +22,9 @@ silent g/^msgstr"/s//msgstr "/ silent g/^msgid"/s//msgid "/ silent g/^msgstr ""\(\n"\)\@!/?^msgid?,.s/^/#\~ / +" clean up empty lines silent g/^\n\n\n/.d +silent! %s/\n\+\%$// if s:was_diff setl diff diff --git a/src/nvim/po/cs.cp1250.po b/src/nvim/po/cs.cp1250.po index bce5c0fa76..b939139fb5 100644 --- a/src/nvim/po/cs.cp1250.po +++ b/src/nvim/po/cs.cp1250.po @@ -3077,131 +3077,6 @@ msgstr "hledn doshlo zatku, pokraovn od konce" msgid "search hit BOTTOM, continuing at TOP" msgstr "hledn doshlo konce, pokraovn od zatku" -#: ../hardcopy.c:240 -#, fuzzy -msgid "E550: Missing colon" -msgstr "Chyb dvojteka" - -#: ../hardcopy.c:252 -#, fuzzy -msgid "E551: Illegal component" -msgstr "neppustn soust" - -#: ../hardcopy.c:259 -#, fuzzy -msgid "E552: digit expected" -msgstr "oekvna slice" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "dn text k vytitn" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Tisknu stranu %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Kopie %d z %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Vytitno: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Tisk zruen" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Nelze zapisovat do vstupnho PostScriptovho souboru" - -#: ../hardcopy.c:1747 -#, fuzzy, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E456: Nelze otevt soubor \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:1772 -#, fuzzy, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, fuzzy, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "" - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "" - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Nelze otevt vstupn PostScriptov soubor" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Nelze otevt soubor \"%s\"" - -#: ../hardcopy.c:2583 -#, fuzzy -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:2593 -#, fuzzy -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, fuzzy, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Odeslm na tiskrnu..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Selhal tisk PostScriptovho souboru" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Tiskov loha odeslna." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Pidat novou databzi" diff --git a/src/nvim/po/cs.po b/src/nvim/po/cs.po index f4eab3f0d4..f98dc2828e 100644 --- a/src/nvim/po/cs.po +++ b/src/nvim/po/cs.po @@ -3077,131 +3077,6 @@ msgstr "hledn doshlo zatku, pokraovn od konce" msgid "search hit BOTTOM, continuing at TOP" msgstr "hledn doshlo konce, pokraovn od zatku" -#: ../hardcopy.c:240 -#, fuzzy -msgid "E550: Missing colon" -msgstr "Chyb dvojteka" - -#: ../hardcopy.c:252 -#, fuzzy -msgid "E551: Illegal component" -msgstr "neppustn soust" - -#: ../hardcopy.c:259 -#, fuzzy -msgid "E552: digit expected" -msgstr "oekvna slice" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "dn text k vytitn" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Tisknu stranu %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Kopie %d z %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Vytitno: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Tisk zruen" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Nelze zapisovat do vstupnho PostScriptovho souboru" - -#: ../hardcopy.c:1747 -#, fuzzy, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E456: Nelze otevt soubor \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:1772 -#, fuzzy, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, fuzzy, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "" - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "" - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Nelze otevt vstupn PostScriptov soubor" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Nelze otevt soubor \"%s\"" - -#: ../hardcopy.c:2583 -#, fuzzy -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:2593 -#, fuzzy -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, fuzzy, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Odeslm na tiskrnu..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Selhal tisk PostScriptovho souboru" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Tiskov loha odeslna." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Pidat novou databzi" diff --git a/src/nvim/po/da.po b/src/nvim/po/da.po index 1c3284f867..c55918abc9 100644 --- a/src/nvim/po/da.po +++ b/src/nvim/po/da.po @@ -2384,99 +2384,6 @@ msgstr "Størrelse:" msgid "E256: Hangul automata ERROR" msgstr "E256: FEJL ved Hangul automata" -msgid "E550: Missing colon" -msgstr "E550: Manglende kolon" - -msgid "E551: Illegal component" -msgstr "E551: Ulovlig komponent" - -msgid "E552: digit expected" -msgstr "E552: ciffer ventet" - -#, c-format -msgid "Page %d" -msgstr "Side %d" - -msgid "No text to be printed" -msgstr "Ingen tekst at udskrive" - -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Udskriver side %d (%d%%)" - -#, c-format -msgid " Copy %d of %d" -msgstr " Kopi %d af %d" - -#, c-format -msgid "Printed: %s" -msgstr "Udskrev: %s" - -msgid "Printing aborted" -msgstr "Udskrivning afbrudt" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Fejl ved skrivning til PostScript-output-fil" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Kan ikke åbne filen \"%s\"" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Kan ikke læse PostScript-ressourcefilen \"%s\"" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: filen \"%s\" er ikke en PostScript-ressourcefil" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: filen \"%s\" er ikke en understøttet PostScript-ressourcefil" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\"-ressourcefilen har forkert version" - -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Inkompatibel multibyte-kodning og -tegnsæt." - -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset må ikke være tom med multibyte-kodning." - -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Ingen standardskrifttype angivet for multibyte-udskrivning." - -msgid "E324: Can't open PostScript output file" -msgstr "E324: Kan ikke åbne PostScript-output-fil" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Kan ikke åbne filen \"%s\"" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Kan ikke finde PostScript-ressourcefilen \"prolog.ps\"" - -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Kan ikke finde PostScript-ressourcefilen \"cidfont.ps\"" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Kan ikke finde PostScript-ressourcefilen \"%s.ps\"" - -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Kan ikke konvertere til udskrivningskodningen \"%s\"" - -msgid "Sending to printer..." -msgstr "Sender til printer..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: Kunne ikke udskrive PostScript-fil" - -msgid "Print job sent." -msgstr "Udskrivningsjob sendt." - msgid "Add a new database" msgstr "Tilføj en ny database" diff --git a/src/nvim/po/de.po b/src/nvim/po/de.po index ce3fd77ede..efd52d3efe 100644 --- a/src/nvim/po/de.po +++ b/src/nvim/po/de.po @@ -2434,126 +2434,6 @@ msgstr "Suche erreichte den ANFANG und wurde am ENDE fortgesetzt" msgid "search hit BOTTOM, continuing at TOP" msgstr "Suche erreichte das ENDE und wurde am ANFANG fortgesetzt" -#: ../hardcopy.c:296 -msgid "E550: Missing colon" -msgstr "E550: Fehlender Doppelpunkt" - -#: ../hardcopy.c:308 -msgid "E551: Illegal component" -msgstr "E551: Unzulssige Komponente" - -#: ../hardcopy.c:315 -msgid "E552: digit expected" -msgstr "E552: Ziffer erwartet" - -#: ../hardcopy.c:529 -#, c-format -msgid "Page %d" -msgstr "Seite %d" - -#: ../hardcopy.c:653 -msgid "No text to be printed" -msgstr "Kein Text zum Drucken" - -#: ../hardcopy.c:724 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Drucke Seite %d (%d%%)" - -#: ../hardcopy.c:736 -#, c-format -msgid " Copy %d of %d" -msgstr " Kopiere %d von %d" - -#: ../hardcopy.c:789 -#, c-format -msgid "Printed: %s" -msgstr "Gedruckt: %s" - -#: ../hardcopy.c:796 -msgid "Printing aborted" -msgstr "Druck abgebrochen" - -#: ../hardcopy.c:1307 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Fehler beim Schreiben der PostScript-Ausgabedatei" - -#: ../hardcopy.c:1679 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Datei \"%s\" kann nicht geffnet werden" - -#: ../hardcopy.c:1688 ../hardcopy.c:2401 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: PostScript-Ressourcendatei \"%s\" kann nicht gelesen werden" - -#: ../hardcopy.c:1704 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: Datei \"%s\" ist keine PostScript-Ressourcendatei" - -#: ../hardcopy.c:1720 ../hardcopy.c:1737 ../hardcopy.c:1776 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: Datei \"%s\" ist keine untersttzte PostScript-Ressourcendatei" - -#: ../hardcopy.c:1788 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" Ressource-Datei hat die falsche Version" - -#: ../hardcopy.c:2157 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Unzulssiger Multibyte-Zeichensatz" - -#: ../hardcopy.c:2169 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: Bei Multibyte-Zeichensatz darf 'printmbcharset' nicht leer sein." - -#: ../hardcopy.c:2185 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Keine Standardschriftart fr Multibyte-Druck angegeben." - -#: ../hardcopy.c:2357 -msgid "E324: Can't open PostScript output file" -msgstr "E324: PostScript-Ausgabedatei kann nicht geffnet werden" - -#: ../hardcopy.c:2389 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Datei \"%s\" kann nicht geffnet werden" - -#: ../hardcopy.c:2514 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: PostScript-Ressourcendatei \"prolog.ps\" nicht gefunden" - -#: ../hardcopy.c:2524 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: PostScript-Ressourcendatei \"cidfont.ps\" nicht gefunden" - -#: ../hardcopy.c:2553 ../hardcopy.c:2570 ../hardcopy.c:2596 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: PostScript-Ressourcendatei \"%s\" nicht gefunden" - -#: ../hardcopy.c:2585 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Umwandlung zu Drucker-Zeichensatz \"%s\" fehlgeschlagen" - -#: ../hardcopy.c:2808 -msgid "Sending to printer..." -msgstr "Schicke zum Drucker..." - -#: ../hardcopy.c:2812 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Druck der PostScript-Datei fehlgeschlagen" - -#: ../hardcopy.c:2814 -msgid "Print job sent." -msgstr "Druckauftrag abgeschickt" - #: ../if_cscope.c:49 msgid "Add a new database" msgstr "Eine neue Datenbank hinzufgen" diff --git a/src/nvim/po/en_GB.po b/src/nvim/po/en_GB.po index 81ee9ed6a0..1c03b305f9 100644 --- a/src/nvim/po/en_GB.po +++ b/src/nvim/po/en_GB.po @@ -2923,128 +2923,6 @@ msgstr "" msgid "search hit BOTTOM, continuing at TOP" msgstr "" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr "" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "" - -#: ../hardcopy.c:1365 -#, fuzzy -msgid "E455: Error writing to PostScript output file" -msgstr "E324: Cannot open PostScript output file" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Cannot open file \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Cannot read PostScript resource file \"%s\"" - -#: ../hardcopy.c:1772 -#, fuzzy, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E457: Cannot read PostScript resource file \"%s\"" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, fuzzy, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E457: Cannot read PostScript resource file \"%s\"" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "" - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "" - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Cannot open PostScript output file" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Cannot open file \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Cannot find PostScript resource file \"prolog.ps\"" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Cannot find PostScript resource file \"cidfont.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Cannot find PostScript resource file \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "" - -#: ../hardcopy.c:2881 -#, fuzzy -msgid "E365: Failed to print PostScript file" -msgstr "E324: Cannot open PostScript output file" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "" - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "" diff --git a/src/nvim/po/eo.po b/src/nvim/po/eo.po index 263fb61b18..aaa39d6041 100644 --- a/src/nvim/po/eo.po +++ b/src/nvim/po/eo.po @@ -2246,100 +2246,6 @@ msgstr "Stilo:" msgid "Size:" msgstr "Grando:" -msgid "E550: Missing colon" -msgstr "E550: Mankas dupunkto" - -msgid "E551: Illegal component" -msgstr "E551: Nevalida komponento" - -msgid "E552: digit expected" -msgstr "E552: cifero atendita" - -#, c-format -msgid "Page %d" -msgstr "Paĝo %d" - -msgid "No text to be printed" -msgstr "Neniu presenda teksto" - -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Presas paĝon %d (%d%%)" - -#, c-format -msgid " Copy %d of %d" -msgstr " Kopio %d de %d" - -#, c-format -msgid "Printed: %s" -msgstr "Presis: %s" - -msgid "Printing aborted" -msgstr "Presado ĉesigita" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Eraro dum skribo de PostSkripta eliga dosiero" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Ne eblas malfermi dosieron \"%s\"" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Ne eblas legi dosieron de PostSkripta rimedo \"%s\"" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: \"%s\" ne estas dosiero de PostSkripta rimedo" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: \"%s\" ne estas subtenata dosiero de PostSkripta rimedo" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" dosiero de rimedo havas neĝustan version" - -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Nekongrua plurbajta kodoprezento kaj signaro." - -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: printmbcharset ne rajtas esti malplena kun plurbajta kodoprezento." - -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Neniu defaŭlta tiparo specifita por plurbajta presado." - -msgid "E324: Can't open PostScript output file" -msgstr "E324: Ne eblas malfermi eligan PostSkriptan dosieron" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Ne eblas malfermi dosieron \"%s\"" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Dosiero de PostSkripta rimedo \"prolog.ps\" ne troveblas" - -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Dosiero de PostSkripta rimedo \"cidfont.ps\" ne troveblas" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Dosiero de PostSkripta rimedo \"%s.ps\" ne troveblas" - -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Ne eblas konverti al la presa kodoprezento \"%s\"" - -msgid "Sending to printer..." -msgstr "Sendas al presilo..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: Presado de PostSkripta dosiero malsukcesis" - -msgid "Print job sent." -msgstr "Laboro de presado sendita." - msgid "Add a new database" msgstr "Aldoni novan datumbazon" diff --git a/src/nvim/po/es.po b/src/nvim/po/es.po index 8a44f6a534..120a29af15 100644 --- a/src/nvim/po/es.po +++ b/src/nvim/po/es.po @@ -3054,129 +3054,6 @@ msgstr "La búsqueda ha llegado al PRINCIPIO, continuando desde el FINAL" msgid "search hit BOTTOM, continuing at TOP" msgstr "La búsqueda ha llegado al FINAL, continuando desde el PRINCIPIO" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: Falta un símbolo de dos puntos" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: Componente ilegal" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: Se esperaba un dígito" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Página %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "No hay texto que imprimir" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Imprimiendo la página %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr "Copia %d de %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Impreso: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Impresión interrumpida" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Error escribiendo al archivo PostScript de salida" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: No se pudo abrir el archivo \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: No se pudo leer el archivo de recursos de PostScript \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: El archivo \"%s\" no es un archivo de recursos PostScript" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: El archivo \"%s\" no es un recurso PostScript que pueda usar" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: La versión del archivo de recursos \"%s\" es incorrecta" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Codificación y set de caracteres multi-byte incompatibles" - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: \"printmbcharset\" no puede estar vacío en una codificación multi-byte" - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "" -"E675: No se ha definido un tipo de letra predeterminado para impresión " -"multi-byte" - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: No se pudo abrir el archivo PostScript de salida" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: No se pudo abrir el archivo %s" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: No se encontró el archivo de recursos PostScript \"prolog.ps\"" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: No se encontró el archivo de recursos PostScript \"cidfont.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: No se encontró el archivo de recursos PostScript \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: No se pudo convertir a la codificación de impresión \"%s\"" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Enviando a la impresora..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Falló la impresión del archivo PostScript" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Se ha enviado la tarea de impresión." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Añadir una nueva base de datos" diff --git a/src/nvim/po/fi.po b/src/nvim/po/fi.po index 1c0da244ba..c8db9a64b5 100644 --- a/src/nvim/po/fi.po +++ b/src/nvim/po/fi.po @@ -2896,99 +2896,6 @@ msgstr "haku pääsi ALKUUN, jatketaan LOPUSTA" msgid "search hit BOTTOM, continuing at TOP" msgstr "haku pääsi LOPPUUN, jatketaan ALUSTA" -msgid "E550: Missing colon" -msgstr "E550: kaksoispiste puuttuu" - -msgid "E551: Illegal component" -msgstr "E551: Virheellinen komponentti" - -msgid "E552: digit expected" -msgstr "E552: pitäisi olla numero" - -#, c-format -msgid "Page %d" -msgstr "Sivu %d" - -msgid "No text to be printed" -msgstr "Ei tekstiä tulostettavaksi" - -#, fuzzy, c-format -#~ msgid "Printing page %d (%zu%%)" -#~ msgstr "Tulostetaan sivua %d (%d %%)" - -#, c-format -msgid " Copy %d of %d" -msgstr " Kopio %d/%d" - -#, c-format -msgid "Printed: %s" -msgstr "Tulostettu: %s" - -msgid "Printing aborted" -msgstr "Tulostus peruttu" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Virhe kirjoitettaessa PostScriptiä tiedostoon" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Ei voi avata tiedostoa %s" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Ei voi lukea PostScript-resurssitiedostoa %s" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: tiedosto %s ei ole PostScript-resurssitiedosto" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: tiedosto %s ei ole tuettu PostScript-resurssitiedosto" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: resurssitiedoston %s versio on väärä" - -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Tukematon monitvauinen merkistökoodaus ja merkistö." - -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset ei voi olla tyhjä monitavuiselle koodaukselle." - -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Ei oletusfonttia monitavuiseen tulostukseen" - -msgid "E324: Can't open PostScript output file" -msgstr "E324: PostScript-tulostetiedoston avaus ei onnistu" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Tiedoston %s avaus ei onnistu" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: PostScript-resurssitiedostoa prolog.ps ei löydy" - -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: PostScript-resurssitiedostoa cidfont.ps ei löydy" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Postscript-resurssitiedosta %s.ps ei löydy" - -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Tulostuskoodaukseen %s muunto ei onnistu" - -msgid "Sending to printer..." -msgstr "Lähetetään tulostimelle..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: PostScript-tiedoston tulostus epäonnistui" - -msgid "Print job sent." -msgstr "Tulostustyö lähetetty." - msgid "Add a new database" msgstr "Lisää uusi tietokanta" diff --git a/src/nvim/po/fixfilenames.vim b/src/nvim/po/fixfilenames.vim new file mode 100644 index 0000000000..04bc0791c0 --- /dev/null +++ b/src/nvim/po/fixfilenames.vim @@ -0,0 +1,13 @@ +" Invoked with the name "vim.pot" and a list of Vim script names. +" Converts them to a .js file, stripping comments, so that xgettext works. + +set shortmess+=A + +for name in argv()[1:] + let jsname = fnamemodify(name, ":t:r") .. ".js" + exe "%s+" .. jsname .. "+" .. substitute(name, '\\', '/', 'g') .. "+" +endfor + +write +last +quit diff --git a/src/nvim/po/fr.po b/src/nvim/po/fr.po index be2141cd6d..f4fce68ac5 100644 --- a/src/nvim/po/fr.po +++ b/src/nvim/po/fr.po @@ -1881,103 +1881,6 @@ msgstr "Style :" msgid "Size:" msgstr "Taille :" -msgid "E550: Missing colon" -msgstr "E550: ':' manquant" - -# DB - Il s'agit ici d'un problme lors du parsing d'une option dont le contenu -# est une liste d'lments spars par des virgules. -msgid "E551: Illegal component" -msgstr "E551: lment invalide" - -msgid "E552: digit expected" -msgstr "E552: chiffre attendu" - -#, c-format -msgid "Page %d" -msgstr "Page %d" - -msgid "No text to be printed" -msgstr "Aucun texte imprimer" - -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Impression de la page %d (%d%%)" - -#, c-format -msgid " Copy %d of %d" -msgstr " Copie %d sur %d" - -#, c-format -msgid "Printed: %s" -msgstr "Imprim : %s" - -msgid "Printing aborted" -msgstr "Impression interrompue" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Erreur lors de l'criture du fichier PostScript" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Impossible d'ouvrir le fichier \"%s\"" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Impossible de lire le fichier de ressource PostScript \"%s\"" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: \"%s\" n'est pas un fichier de ressource PostScript" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: \"%s\" n'est pas un fichier de ressource PostScript support" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: La version du fichier de ressource \"%s\" est errone" - -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Jeu de caractres et encodage multi-octets incompatibles" - -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: 'printmbcharset' ne peut pas tre vide avec un encodage multi-octets" - -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Aucune police par dfaut pour l'impression multi-octets" - -msgid "E324: Can't open PostScript output file" -msgstr "E324: Impossible d'ouvrir le fichier PostScript de sortie" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Impossible d'ouvrir le fichier \"%s\"" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Le fichier de ressource PostScript \"prolog.ps\" est introuvable" - -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "" -"E456: Le fichier de ressource PostScript \"cidfont.ps\" est introuvable" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Le fichier de ressource PostScript \"%s.ps\" est introuvable" - -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: La conversion pour imprimer dans l'encodage \"%s\" a chou" - -msgid "Sending to printer..." -msgstr "Envoi l'imprimante..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: L'impression du fichier PostScript a chou" - -msgid "Print job sent." -msgstr "Tche d'impression envoye." - msgid "E679: recursive loop loading syncolor.vim" msgstr "E679: boucle rcursive lors du chargement de syncolor.vim" diff --git a/src/nvim/po/ga.po b/src/nvim/po/ga.po index 346f0faa84..5107aed75f 100644 --- a/src/nvim/po/ga.po +++ b/src/nvim/po/ga.po @@ -2385,99 +2385,6 @@ msgstr "Mid:" msgid "E256: Hangul automata ERROR" msgstr "E256: EARRID leis na huathoibrein Hangul" -msgid "E550: Missing colon" -msgstr "E550: Idirstad ar iarraidh" - -msgid "E551: Illegal component" -msgstr "E551: Comhphirt neamhcheadaithe" - -msgid "E552: digit expected" -msgstr "E552: ag sil le digit" - -#, c-format -msgid "Page %d" -msgstr "Leathanach %d" - -msgid "No text to be printed" -msgstr "Nl aon tacs le priontil" - -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Leathanach %d (%d%%) phriontil" - -#, c-format -msgid " Copy %d of %d" -msgstr " Cip %d de %d" - -#, c-format -msgid "Printed: %s" -msgstr "Priontilte: %s" - -msgid "Printing aborted" -msgstr "Priontil tobscortha" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Earrid le linn scrobh chuig aschomhad PostScript" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: N fidir an comhad \"%s\" a oscailt" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: N fidir comhad acmhainne PostScript \"%s\" a lamh" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: Nl comhad \"%s\" ina chomhad acmhainne PostScript" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: T \"%s\" ina chomhad acmhainne PostScript gan tac" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: T an leagan mcheart ar an gcomhad acmhainne \"%s\"" - -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Ionchd agus tacar carachtar ilbhirt neamh-chomhoirinach." - -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: n cheadatear printmbcharset a bheith folamh le hionchd ilbhirt." - -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Nor ramhshocraodh cl le haghaidh priontla ilbhirt." - -msgid "E324: Can't open PostScript output file" -msgstr "E324: N fidir aschomhad PostScript a oscailt" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: N fidir an comhad \"%s\" a oscailt" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Comhad acmhainne PostScript \"prolog.ps\" gan aimsi" - -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Comhad acmhainne PostScript \"cidfont.ps\" gan aimsi" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Comhad acmhainne PostScript \"%s.ps\" gan aimsi" - -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: N fidir an t-ionchd priontla \"%s\" a thiont" - -msgid "Sending to printer..." -msgstr " sheoladh chuig an phrintir..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: Theip ar phriontil comhaid PostScript" - -msgid "Print job sent." -msgstr "Seoladh jab priontla." msgid "Add a new database" msgstr "Bunachar sonra nua" diff --git a/src/nvim/po/it.po b/src/nvim/po/it.po index 152ed2cbe3..2191876724 100644 --- a/src/nvim/po/it.po +++ b/src/nvim/po/it.po @@ -3049,126 +3049,6 @@ msgstr "raggiunta la CIMA nella ricerca, continuo dal FONDO" msgid "search hit BOTTOM, continuing at TOP" msgstr "raggiunto il FONDO nella ricerca, continuo dalla CIMA" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: Manca ':'" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: Componente non valido" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: aspettavo un numero" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Pagina %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "Manca testo da stampare" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Sto stampando pagina %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Copia %d di %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Stampato: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Stampa non completata" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Errore in scrittura a file PostScript di output" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Non riesco ad aprire il file \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Non riesco a leggere file risorse PostScript \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: file \"%s\" non un file di risorse PostScript" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: file \"%s\" non un file di risorse PostScript supportato" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: il file di risorse \"%s\" ha una versione sbagliata" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Codifica e set di caratteri multi-byte non compatibili." - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset non pu essere nullo con codifica multi-byte." - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Font predefinito non specificato per stampa multi-byte." - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Non riesco ad aprire file PostScript di output" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Non riesco ad aprire il file \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Non trovo file risorse PostScript \"prolog.ps\"" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Non trovo file risorse PostScript \"cidfont.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Non trovo file risorse PostScript \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Impossibile convertire a codifica di stampa \"%s\"" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Invio a stampante..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Non riesco ad aprire file PostScript" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Richiesta di stampa inviata." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Aggiungi un nuovo database" diff --git a/src/nvim/po/ja.euc-jp.po b/src/nvim/po/ja.euc-jp.po index d7d0faca80..87b4fc5ccd 100644 --- a/src/nvim/po/ja.euc-jp.po +++ b/src/nvim/po/ja.euc-jp.po @@ -2126,101 +2126,6 @@ msgstr ":" msgid "E256: Hangul automata ERROR" msgstr "E256: ϥ륪ȥޥȥ顼" -msgid "E550: Missing colon" -msgstr "E550: ޤ" - -msgid "E551: Illegal component" -msgstr "E551: ʹʸǤǤ" - -msgid "E552: digit expected" -msgstr "E552: ͤɬפǤ" - -#, c-format -msgid "Page %d" -msgstr "%d ڡ" - -msgid "No text to be printed" -msgstr "ƥȤޤ" - -#, c-format -msgid "Printing page %d (%d%%)" -msgstr ": ڡ %d (%d%%)" - -#, c-format -msgid " Copy %d of %d" -msgstr " ԡ %d ( %d )" - -#, c-format -msgid "Printed: %s" -msgstr "ޤ: %s" - -msgid "Printing aborted" -msgstr "ߤޤ" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: PostScriptϥեνߥ顼Ǥ" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: ե \"%s\" ޤ" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: PostScriptΥե \"%s\" ɹޤ" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: ե \"%s\" PostScript եǤϤޤ" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: ե \"%s\" бƤʤ PostScript եǤ" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: ե \"%s\" ϥСۤʤޤ" - -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: ߴ̵ޥХȥǥʸåȤǤ" - -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: ޥХȥǥǤ printmbcharset ˤǤޤ" - -msgid "E675: No default font specified for multi-byte printing." -msgstr "" -"E675: ޥХʸ뤿ΥǥեȥեȤꤵƤޤ" -"" - -msgid "E324: Can't open PostScript output file" -msgstr "E324: PostScriptѤΥեޤ" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: ե \"%s\" ޤ" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: PostScriptΥե \"prolog.ps\" Ĥޤ" - -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: PostScriptΥե \"cidfont.ps\" Ĥޤ" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: PostScriptΥե \"%s.ps\" Ĥޤ" - -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: \"%s\" ѴǤޤ" - -msgid "Sending to printer..." -msgstr "ץ..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: PostScriptեΰ˼Ԥޤ" - -msgid "Print job sent." -msgstr "֤ޤ" #, c-format msgid "E799: Invalid ID: %d (must be greater than or equal to 1)" diff --git a/src/nvim/po/ja.po b/src/nvim/po/ja.po index b56345e066..bdf67933f3 100644 --- a/src/nvim/po/ja.po +++ b/src/nvim/po/ja.po @@ -2126,102 +2126,6 @@ msgstr "サイズ:" msgid "E256: Hangul automata ERROR" msgstr "E256: ハングルオートマトンエラー" -msgid "E550: Missing colon" -msgstr "E550: コロンがありません" - -msgid "E551: Illegal component" -msgstr "E551: 不正な構文要素です" - -msgid "E552: digit expected" -msgstr "E552: 数値が必要です" - -#, c-format -msgid "Page %d" -msgstr "%d ページ" - -msgid "No text to be printed" -msgstr "印刷するテキストがありません" - -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "印刷中: ページ %d (%d%%)" - -#, c-format -msgid " Copy %d of %d" -msgstr " コピー %d (全 %d 中)" - -#, c-format -msgid "Printed: %s" -msgstr "印刷しました: %s" - -msgid "Printing aborted" -msgstr "印刷が中止されました" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: PostScript出力ファイルの書込みエラーです" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: ファイル \"%s\" を開けません" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: PostScriptのリソースファイル \"%s\" を読込めません" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: ファイル \"%s\" は PostScript リソースファイルではありません" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: ファイル \"%s\" は対応していない PostScript リソースファイルです" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: リソースファイル \"%s\" はバージョンが異なります" - -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: 互換性の無いマルチバイトエンコーディングと文字セットです。" - -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: マルチバイトエンコーディングでは printmbcharset を空にできません。" - -msgid "E675: No default font specified for multi-byte printing." -msgstr "" -"E675: マルチバイト文字を印刷するためのデフォルトフォントが指定されていませ" -"ん。" - -msgid "E324: Can't open PostScript output file" -msgstr "E324: PostScript出力用のファイルを開けません" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: ファイル \"%s\" を開けません" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: PostScriptのリソースファイル \"prolog.ps\" が見つかりません" - -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: PostScriptのリソースファイル \"cidfont.ps\" が見つかりません" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: PostScriptのリソースファイル \"%s.ps\" が見つかりません" - -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: 印刷エンコード \"%s\" へ変換できません" - -msgid "Sending to printer..." -msgstr "プリンタに送信中..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: PostScriptファイルの印刷に失敗しました" - -msgid "Print job sent." -msgstr "印刷ジョブを送信しました。" - #, c-format msgid "E799: Invalid ID: %d (must be greater than or equal to 1)" msgstr "E799: 無効な ID: %d (1 以上でなければなりません)" diff --git a/src/nvim/po/ko.UTF-8.po b/src/nvim/po/ko.UTF-8.po index 09be710374..8a6b228b18 100644 --- a/src/nvim/po/ko.UTF-8.po +++ b/src/nvim/po/ko.UTF-8.po @@ -2973,126 +2973,6 @@ msgstr "처음까지 찾았음, 끝에서 계속" msgid "search hit BOTTOM, continuing at TOP" msgstr "끝까지 찾았음, 처음부터 계속" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: 콜론이 없습니다" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: 이상한 컴포넌트" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: 숫자가 필요합니다" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "페이지 %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "인쇄될 텍스트가 없습니다" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "페이지 %d 인쇄중 (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " 복사 %d / %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "인쇄됨: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "인쇄가 취소되었습니다." - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: 포스트스크립트 출력파일에 쓸 수 없습니다." - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: \"%s\" 파일을 열 수 없습니다" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: 포스트스크립트 리소스 파일 \"%s\"을(를) 읽을 수 없습니다" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: 파일 \"%s\"은(는) 포스트스크립트 리소스 파일이 아닙니다" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: 파일 \"%s\"은(는) 지원되는 포스트스크립트 리소스 파일이 아닙니다" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" 리소스 파일은 버전이 잘못되었습니다" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: 호환되지 않는 다중문자 인코딩과 문자셋." - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset는 다중문자 인코딩에서 반드시 설정되어야 합니다." - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: 다중문자 인쇄를 위한 글꼴이 설정되어 있지 않습니다" - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: 포스트스크립트 출력파일을 열 수 없습니다" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: \"%s\" 파일을 열 수 없습니다" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: 포스트스크립트 리소스 파일 \"prolog.ps\"를 찾을 수 없습니다" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: 포스트스크립트 리소스 파일 \"cidfont.ps\"를 찾을 수 없습니다" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: 포스트스크립트 리소스 파일 \"%s.ps\"를 찾을 수 없습니다" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: \"%s\" 인쇄 인코딩으로 변환할 수 없습니다" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "프린터로 보내는 중..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: 포스트스크립트 파일을 인쇄할 수 없습니다" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "인쇄작업이 끝났습니다." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "새 데이터베이스 더하기" diff --git a/src/nvim/po/nb.po b/src/nvim/po/nb.po index 9bc730ae71..75f4eb28a3 100644 --- a/src/nvim/po/nb.po +++ b/src/nvim/po/nb.po @@ -2997,126 +2997,6 @@ msgstr "Sket traff TOPPEN, fortsetter fra BUNNEN" msgid "search hit BOTTOM, continuing at TOP" msgstr "Sket traff BUNNEN, fortsetter fra TOPPEN" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: Mangler kolon" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: Ulovlig komponent" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: Siffer forventet" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Side %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "Ingen tekst for utskrift" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Skriver ut side %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Kopi %d av %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Skrevet ut: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Utskrift avbrutt" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Feil under skriving til Postscript-fil" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Kan ikke pne filen \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Kan ikke lese Postscript-ressursfil \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: Filen \"%s\" er ikke en Postscript-ressursfil" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: Det er ikke sttte for Postscript-ressursfilen \"%s\"" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: Ressursfilen \"%s\" er feil versjon" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Inkompatibel multibytekoding og tegnsett" - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset kan ikke vre tom med multibytekoding" - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Ingen standardfont spesifisert for multibyteutskrift" - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Kan ikke pne Postscript-fil for skriving" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Kan ikke pne filen \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Fant ikke Postscript-ressursfilen \"prolog.ps\"" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Fant ikke Postscript-ressursfilen \"cidfont.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Fant ikke Postscript-ressursfilen \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Klarte ikke konvertere til utskriftskoding \"%s\"" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Sender til skriver..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Feil under utskrift av Postscript-fil" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Skriverjobb sendt." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Legg til en ny database" diff --git a/src/nvim/po/nl.po b/src/nvim/po/nl.po index 4d2e55adc6..d07c566e28 100644 --- a/src/nvim/po/nl.po +++ b/src/nvim/po/nl.po @@ -2985,126 +2985,6 @@ msgstr "zoeken bereikte TOP, verder vanaf BODEM" msgid "search hit BOTTOM, continuing at TOP" msgstr "zoeken bereikte BODEM, verder vanaf TOP" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: dubbelepunt ontbreekt" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: ongeldige component" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: cijfer verwacht" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Pagina %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "Geen tekst om af te drukken" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Afdrukken van pagina %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr "Kopie %d van %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Afgedrukt: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Afdrukken afgebroken" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: wegschrijven Postscript-uitvoerbestand is mislukt" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: openen bestand \"%s\" is mislukt" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: kan 'Postscript resource'-bestand \"%s\" niet lezen" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: bestand \"%s\" is geen 'Postscript resource'-bestand" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: bestand \"%s\" is geen ondersteund 'Postscript resource'-bestand" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: 'resource'-bestand \"%s\" heeft verkeerde versie" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Multi-byte-codering en de tekenverzameling zijn onverenigbaar." - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset mag bij multi-byte-codering niet leeg zijn." - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: geen standaard lettertype opgegeven voor multi-byte-afdrukken." - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: openen van PostScript-uitoverbestand is mislukt" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Bestand \"%s\" kan niet worden geopend" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: 'PostScript resource'-bestand \"prolog.ps\" is niet gevonden" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: 'PostScript resource'-bestand \"cidfont.ps\" is niet gevonden" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: 'PostScript resource'-bestand \"%s.ps\" is niet gevonden" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: omzetten naar afdrukcodering \"%s\" is mislukt" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Naar printer versturen..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Afdrukken van PostScript-bestand is mislukt" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Afdrukopdracht verzonden" - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Nieuwe databank toevoegen" diff --git a/src/nvim/po/no.po b/src/nvim/po/no.po index 9bc730ae71..75f4eb28a3 100644 --- a/src/nvim/po/no.po +++ b/src/nvim/po/no.po @@ -2997,126 +2997,6 @@ msgstr "Sket traff TOPPEN, fortsetter fra BUNNEN" msgid "search hit BOTTOM, continuing at TOP" msgstr "Sket traff BUNNEN, fortsetter fra TOPPEN" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: Mangler kolon" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: Ulovlig komponent" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: Siffer forventet" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Side %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "Ingen tekst for utskrift" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Skriver ut side %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Kopi %d av %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Skrevet ut: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Utskrift avbrutt" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Feil under skriving til Postscript-fil" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Kan ikke pne filen \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Kan ikke lese Postscript-ressursfil \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: Filen \"%s\" er ikke en Postscript-ressursfil" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: Det er ikke sttte for Postscript-ressursfilen \"%s\"" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: Ressursfilen \"%s\" er feil versjon" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Inkompatibel multibytekoding og tegnsett" - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset kan ikke vre tom med multibytekoding" - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Ingen standardfont spesifisert for multibyteutskrift" - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Kan ikke pne Postscript-fil for skriving" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Kan ikke pne filen \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Fant ikke Postscript-ressursfilen \"prolog.ps\"" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Fant ikke Postscript-ressursfilen \"cidfont.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Fant ikke Postscript-ressursfilen \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Klarte ikke konvertere til utskriftskoding \"%s\"" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Sender til skriver..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Feil under utskrift av Postscript-fil" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Skriverjobb sendt." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Legg til en ny database" diff --git a/src/nvim/po/pl.UTF-8.po b/src/nvim/po/pl.UTF-8.po index 7e978113f6..c00130ac8c 100644 --- a/src/nvim/po/pl.UTF-8.po +++ b/src/nvim/po/pl.UTF-8.po @@ -2954,126 +2954,6 @@ msgstr "szukanie dobiło GÓRY; kontynuacja od KOŃCA" msgid "search hit BOTTOM, continuing at TOP" msgstr "szukanie dobiło KOŃCA; kontynuacja od GÓRY" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: Brak dwukropka" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: Niedozwolona część" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: oczekiwałem na cyfrę" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Strona %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "Brak tekstu do drukowania" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Drukuję stronę %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Kopia %d z %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Wydrukowano: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Drukowanie odwołane" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Nie można zapisać do wyjściowego pliku PostScriptu" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Nie mogę otworzyć pliku \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Nie można odczytać pliku zasobów PostScriptu \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: plik \"%s\" nie jest plikiem zasobów PostScriptu" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: plik \"%s\" nie jest wspieranym plikiem zasobów PostScriptu" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" nieprawidłowa wersja pliku zasobów" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Niekompatybilne kodowanie wielobajtowe i zestaw znaków." - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset nie może być pusty przy kodowaniu wielobajtowym." - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Nie określono domyślnej czcionki dla drukowania wielobajtowego." - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Nie można otworzyć pliku PostScript do wyjścia" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Nie mogę otworzyć pliku \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Nie można znaleźć pliku zasobów PostScriptu \"prolog.ps\"" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Nie można znaleźć pliku zasobów PostScriptu \"cidfont.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Nie można znaleźć pliku zasobów PostScriptu \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Nie można przekonwertować by drukować kodowanie \"%s\"" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Przesyłam do drukarki..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Drukowanie pliku PostScript nie powiodło się" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Zadanie drukowanie przesłane." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Dodaj nową bazę danych" diff --git a/src/nvim/po/pt_BR.po b/src/nvim/po/pt_BR.po index bfd64b4d28..ca5a4508c7 100644 --- a/src/nvim/po/pt_BR.po +++ b/src/nvim/po/pt_BR.po @@ -4417,129 +4417,6 @@ msgstr "E663: No final da lista de modificaes" msgid "Type :quit<Enter> to exit Nvim" msgstr "Digite :quit<Enter> para sair do Vim" -#: ../hardcopy.c:296 -msgid "E550: Missing colon" -msgstr "E550: Dois-pontos faltando" - -#: ../hardcopy.c:308 -msgid "E551: Illegal component" -msgstr "E551: Elemento invlido" - -#: ../hardcopy.c:315 -msgid "E552: digit expected" -msgstr "E552: era esperado um algarismo" - -#: ../hardcopy.c:529 -#, c-format -msgid "Page %d" -msgstr "Pgina %d" - -#: ../hardcopy.c:653 -msgid "No text to be printed" -msgstr "Sem texto para imprimir" - -#: ../hardcopy.c:724 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Imprimindo pgina %d (%d%%)" - -#: ../hardcopy.c:736 -#, c-format -msgid " Copy %d of %d" -msgstr " Cpia %d de %d" - -#: ../hardcopy.c:789 -#, c-format -msgid "Printed: %s" -msgstr "Impresso: %s" - -#: ../hardcopy.c:796 -msgid "Printing aborted" -msgstr "Impresso cancelada" - -#: ../hardcopy.c:1307 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Erro ao escrever no arquivo PostScript" - -#: ../hardcopy.c:1679 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Impossvel abrir arquivo \"%s\"" - -#: ../hardcopy.c:1688 ../hardcopy.c:2402 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Impossvel ler o arquivo de recursos de PostScript \"%s\"" - -#: ../hardcopy.c:1704 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: arquivo \"%s\" no um arquivo de recursos de PostScript" - -#: ../hardcopy.c:1720 ../hardcopy.c:1737 ../hardcopy.c:1776 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "" -"E619: arquivo \"%s\" no um arquivo de recursos de PostScript suportado" - -#: ../hardcopy.c:1788 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: verso errada do arquivo de recursos \"%s\"" - -#: ../hardcopy.c:2157 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "" -"E673: Codificao multi-byte incompatvel com o conjunto de caracteres." - -#: ../hardcopy.c:2170 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: 'printmbcharset' no pode estar vazio com codificaes multi-byte." - -#: ../hardcopy.c:2186 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Nenhuma fonte padro especificada para impresso em multi-byte." - -#: ../hardcopy.c:2358 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Impossvel abrir arquivo PostScript para sada" - -#: ../hardcopy.c:2390 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Impossvel abrir arquivo \"%s\"" - -#: ../hardcopy.c:2515 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Arquivo de recursos de PostScript \"prolog.ps\" no encontrado" - -#: ../hardcopy.c:2525 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Arquivo de recursos de PostScript \"cidfont.ps\" no encontrado" - -#: ../hardcopy.c:2554 ../hardcopy.c:2571 ../hardcopy.c:2597 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Arquivo de recursos de PostScript \"%s.ps\" no encontrado" - -#: ../hardcopy.c:2586 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Impossvel converter para a codificao \"%s\" para impresso" - -#: ../hardcopy.c:2809 -msgid "Sending to printer..." -msgstr "Enviando impressora..." - -#: ../hardcopy.c:2813 -msgid "E365: Failed to print PostScript file" -msgstr "E365: No foi possvel imprimir o arquivo PostScript" - -#: ../hardcopy.c:2815 -msgid "Print job sent." -msgstr "Trabalho de impresso enviado." - #. This happens when the FileChangedRO autocommand changes the #. * file in a way it becomes shorter. #: ../undo.c:355 diff --git a/src/nvim/po/ru.po b/src/nvim/po/ru.po index da356770d7..1235111c58 100644 --- a/src/nvim/po/ru.po +++ b/src/nvim/po/ru.po @@ -2979,126 +2979,6 @@ msgstr "Поиск будет продолжен с КОНЦА документ msgid "search hit BOTTOM, continuing at TOP" msgstr "Поиск будет продолжен с НАЧАЛА документа" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: Пропущено двоеточие" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: Недопустимый компонент" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: Требуется указать цифру" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Страница %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "Печатать нечего" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Печать стр. %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Копия %d из %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Напечатано: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Печать прекращена" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Ошибка записи в файл PostScript" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Невозможно открыть файл \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Невозможно прочитать файл ресурсов PostScript \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: Файл \"%s\" не является файлом ресурсов PostScript" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: Файл \"%s\" не является допустимым файлом ресурсов PostScript" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: Файл ресурсов \"%s\" неизвестной версии" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Несовместимые многобайтовая кодировка и набор символов." - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset не может быть пустым при многобайтовой кодировке." - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Нет определения шрифта по умолчанию для многобайтовой печати." - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Невозможно открыть файл PostScript" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Невозможно открыть файл \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Файл ресурсов PostScript \"prolog.ps\" не найден" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Файл ресурсов PostScript \"cidfont.ps\" не найден" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Файл ресурсов PostScript \"%s.ps\" не найден" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Невозможно преобразовать в кодировку печать \"%s\"" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Отправка на печать..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Не удалось выполнить печать файла PostScript" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Задание на печать отправлено." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Добавить новую базу данных" diff --git a/src/nvim/po/sk.cp1250.po b/src/nvim/po/sk.cp1250.po index cb8b8c6abb..f0fdc47a1e 100644 --- a/src/nvim/po/sk.cp1250.po +++ b/src/nvim/po/sk.cp1250.po @@ -2994,127 +2994,6 @@ msgstr "hadanie dosiahlo zaiatok, pokraovanie od konca" msgid "search hit BOTTOM, continuing at TOP" msgstr "hadanie dosiahlo koniec, pokraovanie od zaiatku" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: Chba dvojbodka" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: Neprpustn komponent" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: oakvan slica" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Strana %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "iadny text na tla" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Tlam stranu %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Kpia %d z %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Vytlaen: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Tla bola zruen" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Ned sa zapisova do vstupnho PostScriptovho sboru" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Ned sa otvori sbor \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Ned sa ta PostScriptov sbor \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: sbor \"%s\" nie je vo formte PostScript" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: sbor \"%s\" nie je podporvan PostScriptov sbor" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" zdrojov sbor m zl slo verzie" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: nekompatibiln viacbajtov kdovanie a znakov sada." - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: voba printmbcharset neme by przdna pri viacbajtovom kdovan." - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Nie je pecifikovan iadne psmo pre viacbajtov tlaenie." - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Ned sa otvori vstupn PostScriptov sbor" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Ned sa otvori sbor \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Nemono njs PostScriptov zdrojov sbor \"prolog.ps\"" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Nemono njs PostScriptov zdrojov sbor \"cidfont.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Nemono njs PostScriptov zdrojov sbor \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Nemono skonvertova do kdovania na tlaenie \"%s\"" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Posielam na tlaiare..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: PostScriptov sbor sa nepodarilo vytlai" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Tlaov loha bola odoslan." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Prida nov databzu" diff --git a/src/nvim/po/sk.po b/src/nvim/po/sk.po index 4f9e1fe185..2d6b4ed901 100644 --- a/src/nvim/po/sk.po +++ b/src/nvim/po/sk.po @@ -2994,127 +2994,6 @@ msgstr "hadanie dosiahlo zaiatok, pokraovanie od konca" msgid "search hit BOTTOM, continuing at TOP" msgstr "hadanie dosiahlo koniec, pokraovanie od zaiatku" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: Chba dvojbodka" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: Neprpustn komponent" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: oakvan slica" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Strana %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "iadny text na tla" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Tlam stranu %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Kpia %d z %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Vytlaen: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Tla bola zruen" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Ned sa zapisova do vstupnho PostScriptovho sboru" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Ned sa otvori sbor \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Ned sa ta PostScriptov sbor \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: sbor \"%s\" nie je vo formte PostScript" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: sbor \"%s\" nie je podporvan PostScriptov sbor" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" zdrojov sbor m zl slo verzie" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: nekompatibiln viacbajtov kdovanie a znakov sada." - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: voba printmbcharset neme by przdna pri viacbajtovom kdovan." - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Nie je pecifikovan iadne psmo pre viacbajtov tlaenie." - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Ned sa otvori vstupn PostScriptov sbor" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Ned sa otvori sbor \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Nemono njs PostScriptov zdrojov sbor \"prolog.ps\"" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Nemono njs PostScriptov zdrojov sbor \"cidfont.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Nemono njs PostScriptov zdrojov sbor \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Nemono skonvertova do kdovania na tlaenie \"%s\"" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Posielam na tlaiare..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: PostScriptov sbor sa nepodarilo vytlai" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Tlaov loha bola odoslan." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Prida nov databzu" diff --git a/src/nvim/po/sr.po b/src/nvim/po/sr.po index cbdf736d0f..4a9dcf4a5d 100644 --- a/src/nvim/po/sr.po +++ b/src/nvim/po/sr.po @@ -2573,100 +2573,6 @@ msgstr "Стил:" msgid "Size:" msgstr "Величина:" -msgid "E550: Missing colon" -msgstr "E550: Недостаје двотачка" - -msgid "E551: Illegal component" -msgstr "E551: Неисправна компонента" - -msgid "E552: digit expected" -msgstr "E552: очекује се цифра" - -#, c-format -msgid "Page %d" -msgstr "Страна %d" - -msgid "No text to be printed" -msgstr "Нема текста за штампу" - -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Штампање стране %d (%d%%)" - -#, c-format -msgid " Copy %d of %d" -msgstr " Копија %d од %d" - -#, c-format -msgid "Printed: %s" -msgstr "Одштампано: %s" - -msgid "Printing aborted" -msgstr "Штампање прекинуто" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Грешка приликом уписа у PostScript излазну датотеку" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Датотека \"%s\" не може да се отвори" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: PostScript resource датотека \"%s\" не може да се чита" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: датотека \"%s\" није PostScript resource датотека" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: датотека \"%s\" није подржана PostScript resource датотека" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" resource датотека је погрешне верзије" - -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Вишебајтно кодирање и скуп карактера нису компатибилни." - -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset не може бити празно са вишебајтним кодирањем." - -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Није наведен подразумевани фонт за вишебајтно штампање." - -msgid "E324: Can't open PostScript output file" -msgstr "E324: PostScript излазна датотека не може да се отвори" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Датотека \"%s\" не може да се отвори" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: PostScript resource датотека \"prolog.ps\" не може да се пронађе" - -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "" -"E456: PostScript resource датотека \"cidfont.ps\" не може да се пронађе" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: PostScript resource датотека \"%s.ps\" не може да се пронађе" - -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Није могућа конверзија у кодирање за штампу \"%s\"" - -msgid "Sending to printer..." -msgstr "Слање штампачу..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: PostScript датотека није успела да се одштампа" - -msgid "Print job sent." -msgstr "Задатак штампе је послат" - msgid "Add a new database" msgstr "Додај нову базу" diff --git a/src/nvim/po/sv.po b/src/nvim/po/sv.po index 406900f7b2..e46579c067 100644 --- a/src/nvim/po/sv.po +++ b/src/nvim/po/sv.po @@ -4168,126 +4168,6 @@ msgstr "E782: fel vid lsning av .sug-fil: %s" msgid "E783: duplicate char in MAP entry" msgstr "E783: dubblerat tecken i MAP-post" -#: ../hardcopy.c:296 -msgid "E550: Missing colon" -msgstr "E550: Kolon saknas" - -#: ../hardcopy.c:308 -msgid "E551: Illegal component" -msgstr "E551: Otillten komponent" - -#: ../hardcopy.c:315 -msgid "E552: digit expected" -msgstr "E552: siffra frvntades" - -#: ../hardcopy.c:529 -#, c-format -msgid "Page %d" -msgstr "Sida %d" - -#: ../hardcopy.c:653 -msgid "No text to be printed" -msgstr "Ingen text att skriva ut" - -#: ../hardcopy.c:724 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Skriver ut sida %d (%d%%)" - -#: ../hardcopy.c:736 -#, c-format -msgid " Copy %d of %d" -msgstr " Kopia %d av %d" - -#: ../hardcopy.c:789 -#, c-format -msgid "Printed: %s" -msgstr "Skrev ut: %s" - -#: ../hardcopy.c:796 -msgid "Printing aborted" -msgstr "Utskrift avbruten" - -#: ../hardcopy.c:1307 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Fel vid skrivning av utdata till PostScript-fil" - -#: ../hardcopy.c:1679 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Kan inte ppna fil \"%s\"" - -#: ../hardcopy.c:1688 ../hardcopy.c:2401 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Kan inte lsa PostScript-resursfil \"%s\"" - -#: ../hardcopy.c:1704 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: fil \"%s\" r inte en PostScript-resursfil" - -#: ../hardcopy.c:1720 ../hardcopy.c:1737 ../hardcopy.c:1776 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: fil \"%s\" r inte en PostScript-resursfil som stds" - -#: ../hardcopy.c:1788 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\"-kllfilen har fel version" - -#: ../hardcopy.c:2157 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Inkompatibel flerbitskodning och teckenuppsttning." - -#: ../hardcopy.c:2169 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset kan inte vara tom med flerbitskodning." - -#: ../hardcopy.c:2185 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Ingen standardfont angiven fr flerbitsutskrifter." - -#: ../hardcopy.c:2357 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Kan inte ppna PostScript-utfil" - -#: ../hardcopy.c:2389 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Kan inte ppna fil \"%s\"" - -#: ../hardcopy.c:2514 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Kan inte hitta PostScript-kllfilen \"prolog.ps\"" - -#: ../hardcopy.c:2524 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Kan inte hitta PostScript-kllfilen \"cidfont.ps\"" - -#: ../hardcopy.c:2553 ../hardcopy.c:2570 ../hardcopy.c:2596 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Kan inte hitta PostScript-kllfilen \"%s.ps\"" - -#: ../hardcopy.c:2585 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Kunde inte konvertera frn utskriftkodning \"%s\"" - -#: ../hardcopy.c:2808 -msgid "Sending to printer..." -msgstr "Skickar till skrivare..." - -#: ../hardcopy.c:2812 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Misslyckades med att skriva ut PostScript-fil" - -#: ../hardcopy.c:2814 -msgid "Print job sent." -msgstr "Skrivarjobb skickat." - #: ../mark.c:673 msgid "No marks set" msgstr "Inga mrken satta" diff --git a/src/nvim/po/tojavascript.vim b/src/nvim/po/tojavascript.vim new file mode 100644 index 0000000000..7868570be7 --- /dev/null +++ b/src/nvim/po/tojavascript.vim @@ -0,0 +1,18 @@ +" Invoked with the name "vim.pot" and a list of Vim script names. +" Converts them to a .js file, stripping comments, so that xgettext works. +" Javascript is used because, like Vim, it accepts both single and double +" quoted strings. + +set shortmess+=A + +for name in argv()[1:] + exe 'edit ' .. fnameescape(name) + + " Strip comments + g/^\s*"/s/.*// + + " Write as .js file, xgettext recognizes them + exe 'w! ' .. fnamemodify(name, ":t:r") .. ".js" +endfor + +quit diff --git a/src/nvim/po/tr.po b/src/nvim/po/tr.po index 0cf91fa4d5..eb7879efc4 100644 --- a/src/nvim/po/tr.po +++ b/src/nvim/po/tr.po @@ -2625,99 +2625,6 @@ msgstr "Arama dosyanın SONUNU geçti, dosyanın BAŞINDAN sürüyor" msgid " line " msgstr " satır " -msgid "E550: Missing colon" -msgstr "E550: İki nokta eksik" - -msgid "E551: Illegal component" -msgstr "E551: Geçersiz bileşen" - -msgid "E552: digit expected" -msgstr "E552: Basamak bekleniyordu" - -#, c-format -msgid "Page %d" -msgstr "Sayfa %d" - -msgid "No text to be printed" -msgstr "Yazdırılacak metin yok" - -#, c-format -msgid "Printing page %d (%zu%%)" -msgstr "Sayfa %d yazdırılıyor (%%%zu)" - -#, c-format -msgid " Copy %d of %d" -msgstr " Kopya %d/%d" - -#, c-format -msgid "Printed: %s" -msgstr "Yazdırıldı: %s" - -msgid "Printing aborted" -msgstr "Yazdırma durduruldu" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: PostScript çıktı dosyasına yazarken hata" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: \"%s\" dosyası açılamıyor" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: PostScript özkaynak dosyası \"%s\" okunamıyor" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: \"%s\" dosyası bir PostScript özkaynak dosyası değil" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: \"%s\" dosyası desteklenen bir PostScript özkaynak dosyası değil" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" özkaynak dosyası sürümü hatalı" - -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Uyumsuz çoklu bayt kodlaması ve karakter kümesi." - -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset çoklu bayt kodlamada boş olamaz." - -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Çoklu bayt yazdırma için öntanımlı yazıtipi ayarlanmamış." - -msgid "E324: Can't open PostScript output file" -msgstr "E324: PostScript çıktı dosyası açılamıyor" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: \"%s\" dosyası açılamıyor" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: PostScript özkaynak dosyası \"prolog.ps\" bulunamıyor" - -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: PostScript özkaynak dosyası \"cidfont.ps\" bulunamıyor" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: PostScript özkaynak dosyası \"%s.ps\" bulunamıyor" - -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: \"%s\" yazdırma kodlamasına dönüştürülemiyor" - -msgid "Sending to printer..." -msgstr "Yazıcıya gönderiliyor..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: PostScript dosyası yazdırılamadı" - -msgid "Print job sent." -msgstr "Yazdırma işi gönderildi." - msgid "E478: Don't panic!" msgstr "E478: Panik yok!" @@ -5718,9 +5625,6 @@ msgstr "$VIMRUNTIME öntanımlı konumu: \"" msgid "Nvim is open source and freely distributable" msgstr "Nvim açık kaynaklıdır ve özgürce dağıtılabilir" -msgid "https://neovim.io/#chat" -msgstr "https://neovim.io/#chat" - msgid "type :help nvim<Enter> if you are new! " msgstr "eğer yeniyseniz :help nvim<Enter> " diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po index 427abd9b77..06f845f113 100644 --- a/src/nvim/po/uk.po +++ b/src/nvim/po/uk.po @@ -2913,99 +2913,6 @@ msgstr "Пошук дійшов до КІНЦЯ, продовжується з msgid " line " msgstr " рядок " -msgid "E550: Missing colon" -msgstr "E550: Пропущено двокрапку" - -msgid "E551: Illegal component" -msgstr "E551: Некоректний компонент" - -msgid "E552: digit expected" -msgstr "E552: очікується цифра" - -#, c-format -msgid "Page %d" -msgstr "Сторінка %d" - -msgid "No text to be printed" -msgstr "Нічого друкувати" - -#, c-format -msgid "Printing page %d (%zu%%)" -msgstr "Друкується сторінка %d (%zu%%)" - -#, c-format -msgid " Copy %d of %d" -msgstr " Копія %d з %d" - -#, c-format -msgid "Printed: %s" -msgstr "Надруковано: %s" - -msgid "Printing aborted" -msgstr "Друк перервано" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Не вдалося записати вихідний файл PostScript" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Не вдалося відкрити файл «%s»" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Не вдалося прочитати файл ресурсів PostScript «%s»" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: «%s» не є файлом ресурсів PostScript" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: «%s» не є підтримуваним файлом ресурсів PostScript" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: Неправильна версія файлу ресурсів «%s»" - -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Несумісні багатобайтове кодування й набір символів." - -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: printmbcharset не може бути порожнім з багатобайтовим кодуванням." - -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Не зазначено шрифт для багатобайтового друку." - -msgid "E324: Can't open PostScript output file" -msgstr "E324: Не вдалося відкрити файл PostScript для виводу" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Не вдалося відкрити файл «%s»" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Не вдалося знайти файл ресурсів PostScript «prolog.ps»" - -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Не вдалося знайти файл ресурсів PostScript «cidfont.ps»" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Не вдалося знайти файл ресурсів PostScript «%s.ps»" - -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Не вдалося перетворити до кодування друку «%s»" - -msgid "Sending to printer..." -msgstr "Відсилається на принтер..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: Не вдалося надрукувати файл PostScript" - -msgid "Print job sent." -msgstr "Завдання друку відіслано." msgid "E424: Too many different highlighting attributes in use" msgstr "E424: Використано забагато різних атрибутів кольору" @@ -5643,9 +5550,6 @@ msgstr " заміна для $VIMRUNTIME: \"" msgid "Nvim is open source and freely distributable" msgstr "Nvim — це відкрита й вільно розповсюджувана програма" -msgid "https://neovim.io/#chat" -msgstr "https://neovim.io/#chat" - msgid "type :help nvim<Enter> if you are new! " msgstr ":help nvim<Enter> якщо ви вперше! " diff --git a/src/nvim/po/vi.po b/src/nvim/po/vi.po index ad59718a30..44772c99ad 100644 --- a/src/nvim/po/vi.po +++ b/src/nvim/po/vi.po @@ -3020,127 +3020,6 @@ msgstr "tìm kiếm sẽ được tiếp tục từ CUỐI tài liệu" msgid "search hit BOTTOM, continuing at TOP" msgstr "tìm kiếm sẽ được tiếp tục từ ĐẦU tài liệu" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: Thiếu dấu hai chấm" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: Thành phần không cho phép" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: Cần chỉ ra một số" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Trang %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "Không có gì để in" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "In trang %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Sao chép %d của %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Đã in: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "In bị dừng" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Lỗi ghi nhớ vào tập tin PostScript" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Không thể mở tập tin \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Không thể đọc tập tin tài nguyên PostScript \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: \"%s\" không phải là tập tin tài nguyên PostScript" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: \"%s\" không phải là tập tin tài nguyên PostScript được hỗ trợ" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: tập tin tài nguyên \"%s\" có phiên bản không đúng" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "" - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "" - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Không thể mở tập tin PostScript" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Không thể mở tập tin \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Không tìm thấy tập tin tài nguyên PostScript \"prolog.ps\"" - -#: ../hardcopy.c:2593 -#, fuzzy -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Không tìm thấy tập tin tài nguyên PostScript \"%s.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Không tìm thấy tập tin tài nguyên PostScript \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, fuzzy, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Không thể chuyển từ các ký tự nhiều byte thành bảng mã \"%s\"" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Gửi tới máy in..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: In tập tin PostScript không thành công" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Đã gửi công việc in." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Thêm một cơ sở dữ liệu mới" diff --git a/src/nvim/po/zh_CN.UTF-8.po b/src/nvim/po/zh_CN.UTF-8.po index afa2f29029..af050823d3 100644 --- a/src/nvim/po/zh_CN.UTF-8.po +++ b/src/nvim/po/zh_CN.UTF-8.po @@ -8,6 +8,9 @@ # TRANSLATORS # Edyfox <edyfox@gmail.com> # Yuheng Xie <elephant@linux.net.cn> +# lilydjwg <lilydjwg@gmail.com> +# Ada (Haowen) Yu <me@yuhaowen.com> +# Sizhe Zhao <prc.zhao@outlook.com> # # Original translations. # @@ -15,1181 +18,1939 @@ msgid "" msgstr "" "Project-Id-Version: Vim(Simplified Chinese)\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-05-26 14:21+0200\n" -"PO-Revision-Date: 2006-04-21 14:00+0800\n" -"Last-Translator: Yuheng Xie <elephant@linux.net.cn>\n" +"POT-Creation-Date: 2022-11-17 08:00+0800\n" +"PO-Revision-Date: 2023-01-24 13:00+0800\n" +"Last-Translator: Sizhe Zhao <prc.zhao@outlook.com>\n" "Language-Team: Simplified Chinese <i18n-translation@lists.linux.net.cn>\n" -"Language: \n" +"Language: zh_CN\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8-bit\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: ../api/private/helpers.c:201 +#: ../arglist.c:67 +msgid "E1156: Cannot change the argument list recursively" +msgstr "E1156: 不能递归修改参数列表" + +#: ../arglist.c:628 +msgid "E163: There is only one file to edit" +msgstr "E163: 只有一个文件可编辑" + +#: ../arglist.c:630 +msgid "E164: Cannot go before first file" +msgstr "E164: 无法切换,已是第一个文件" + +#: ../arglist.c:632 +msgid "E165: Cannot go beyond last file" +msgstr "E165: 无法切换,已是最后一个文件" + +#: ../arglist.c:782 #, fuzzy -msgid "Unable to get option value" -msgstr "选项参数后的内容无效" +msgid "E610: No argument to delete" +msgstr "E144: :z 不接受非数字的参数" -#: ../api/private/helpers.c:204 -msgid "internal error: unknown option type" -msgstr "内部错误:未知的选项类型" +#: ../arglist.c:980 +msgid "E249: window layout changed unexpectedly" +msgstr "E249: 窗口布局意外地改变了" -#: ../buffer.c:92 -msgid "[Location List]" -msgstr "[Location 列表]" +#: ../autocmd.c:160 +msgid "--Deleted--" +msgstr "--已删除--" -#: ../buffer.c:93 -msgid "[Quickfix List]" -msgstr "[Quickfix 列表]" +#: ../autocmd.c:451 +#, c-format +msgid "auto-removing autocommand: %s <buffer=%d>" +msgstr "自动删除自动命令: %s <buffer=%d>" + +#. the group doesn't exist +#: ../autocmd.c:501 +#, c-format +msgid "E367: No such group: \"%s\"" +msgstr "E367: 无此组: \"%s\"" + +#: ../autocmd.c:505 +#, fuzzy +msgid "E936: Cannot delete the current group" +msgstr "E351: 不能在当前的 'foldmethod' 下删除折叠" + +#: ../autocmd.c:513 +msgid "W19: Deleting augroup that is still in use" +msgstr "W19: 删除依旧在使用中的自动组" + +#. Highlight title +#: ../autocmd.c:896 +msgid "" +"\n" +"--- Autocommands ---" +msgstr "" +"\n" +"--- 自动命令 ---" + +#: ../autocmd.c:1106 +#, c-format +msgid "E680: <buffer=%d>: invalid buffer number " +msgstr "E680: <buffer=%d>: 无效的缓冲区号 " + +#: ../autocmd.c:1254 +msgid "E217: Can't execute autocommands for ALL events" +msgstr "E217: 不能对所有事件执行自动命令" + +#: ../autocmd.c:1276 +#, c-format +msgid "No matching autocommands: %s" +msgstr "没有匹配的自动命令:%s" + +#: ../autocmd.c:1693 +msgid "E218: autocommand nesting too deep" +msgstr "E218: 自动命令嵌套层数过深" + +#: ../autocmd.c:2040 +#, c-format +msgid "%s Autocommands for \"%s\"" +msgstr "%s 自动命令 \"%s\"" + +#: ../autocmd.c:2049 +#, c-format +msgid "Executing %s" +msgstr "执行 %s" + +#: ../autocmd.c:2178 +#, c-format +msgid "autocommand %s" +msgstr "自动命令 %s" -#: ../buffer.c:94 +#: ../autocmd.c:2617 +#, c-format +msgid "E215: Illegal character after *: %s" +msgstr "E215: * 后面有无效字符: %s" + +#: ../autocmd.c:2625 +#, c-format +msgid "E216: No such event: %s" +msgstr "E216: 无此事件: %s" + +#: ../autocmd.c:2627 +#, c-format +msgid "E216: No such group or event: %s" +msgstr "E216: 无此组或事件: %s" + +#: ../buffer.c:109 msgid "E855: Autocommands caused command to abort" msgstr "E855: 自动命令导致命令被停止" -#: ../buffer.c:135 +#: ../buffer.c:111 +#, c-format +msgid "E937: Attempt to delete a buffer that is in use: %s" +msgstr "E937: 试图删除使用中的缓冲区:%s" + +#: ../buffer.c:219 msgid "E82: Cannot allocate any buffer, exiting..." msgstr "E82: 无法分配任何缓冲区,退出程序..." -#: ../buffer.c:138 +#: ../buffer.c:227 msgid "E83: Cannot allocate buffer, using other one..." msgstr "E83: 无法分配缓冲区,使用另一个缓冲区..." -#: ../buffer.c:763 +#: ../buffer.c:1070 msgid "E515: No buffers were unloaded" msgstr "E515: 没有释放任何缓冲区" -#: ../buffer.c:765 +#: ../buffer.c:1072 msgid "E516: No buffers were deleted" msgstr "E516: 没有删除任何缓冲区" -#: ../buffer.c:767 +#: ../buffer.c:1074 msgid "E517: No buffers were wiped out" msgstr "E517: 没有清除任何缓冲区" -#: ../buffer.c:772 -msgid "1 buffer unloaded" -msgstr "释放了 1 个缓冲区" - -#: ../buffer.c:774 -#, c-format -msgid "%d buffers unloaded" -msgstr "释放了 %d 个缓冲区" - -#: ../buffer.c:777 -msgid "1 buffer deleted" -msgstr "删除了 1 个缓冲区" - -#: ../buffer.c:779 -#, c-format -msgid "%d buffers deleted" -msgstr "删除了 %d 个缓冲区" +#: ../buffer.c:1079 +#, fuzzy, c-format +msgid "%d buffer unloaded" +msgid_plural "%d buffers unloaded" +msgstr[0] "释放了 %d 个缓冲区" -#: ../buffer.c:782 -msgid "1 buffer wiped out" -msgstr "清除了 1 个缓冲区" +#: ../buffer.c:1082 +#, fuzzy, c-format +msgid "%d buffer deleted" +msgid_plural "%d buffers deleted" +msgstr[0] "删除了 %d 个缓冲区" -#: ../buffer.c:784 -#, c-format -msgid "%d buffers wiped out" -msgstr "清除了 %d 个缓冲区" +#: ../buffer.c:1085 +#, fuzzy, c-format +msgid "%d buffer wiped out" +msgid_plural "%d buffers wiped out" +msgstr[0] "清除了 %d 个缓冲区" -#: ../buffer.c:806 +#: ../buffer.c:1102 msgid "E90: Cannot unload last buffer" msgstr "E90: 无法释放最后一个缓冲区" -#: ../buffer.c:874 +#: ../buffer.c:1191 msgid "E84: No modified buffer found" msgstr "E84: 没有修改过的缓冲区" #. back where we started, didn't find anything. -#: ../buffer.c:903 +#: ../buffer.c:1224 msgid "E85: There is no listed buffer" msgstr "E85: 没有可列出的缓冲区" -#: ../buffer.c:913 -#, c-format -msgid "E86: Buffer %<PRId64> does not exist" -msgstr "E86: 缓冲区 %<PRId64> 不存在" - -#: ../buffer.c:915 +#: ../buffer.c:1237 msgid "E87: Cannot go beyond last buffer" msgstr "E87: 无法切换,已是最后一个缓冲区" -#: ../buffer.c:917 +#: ../buffer.c:1239 msgid "E88: Cannot go before first buffer" msgstr "E88: 无法切换,已是第一个缓冲区" -#: ../buffer.c:945 +#: ../buffer.c:1277 #, c-format msgid "" "E89: No write since last change for buffer %<PRId64> (add ! to override)" msgstr "E89: 缓冲区 %<PRId64> 已修改但尚未保存 (请加 ! 强制执行)" +#: ../buffer.c:1290 +#, c-format +msgid "E89: %s will be killed (add ! to override)" +msgstr "E89: \"%s\" 会被结束 (请加 ! 强制执行)" + +#: ../buffer.c:1698 +msgid "E948: Job still running (add ! to end the job)" +msgstr "E948: 任务仍在运行(添加 ! 来结束此任务)" + +#: ../buffer.c:1700 +msgid "E37: No write since last change (add ! to override)" +msgstr "E37: 已修改但尚未保存 (可用 ! 强制执行)" + +#: ../buffer.c:1709 +msgid "E948: Job still running" +msgstr "E948: 任务仍在运行" + +#: ../buffer.c:1711 +msgid "E37: No write since last change" +msgstr "E37: 已修改但尚未保存" + #. wrap around (may cause duplicates) -#: ../buffer.c:1423 +#: ../buffer.c:1858 msgid "W14: Warning: List of file names overflow" msgstr "W14: 警告: 文件名过多" -#: ../buffer.c:1555 ../quickfix.c:3361 +#: ../buffer.c:2032 ../quickfix.c:6290 ../window.c:195 #, c-format msgid "E92: Buffer %<PRId64> not found" msgstr "E92: 找不到缓冲区 %<PRId64>" -#: ../buffer.c:1798 +#: ../buffer.c:2289 #, c-format msgid "E93: More than one match for %s" msgstr "E93: 找到不止一个 %s" -#: ../buffer.c:1800 +#: ../buffer.c:2291 #, c-format msgid "E94: No matching buffer for %s" msgstr "E94: 没有匹配的缓冲区 %s" -#: ../buffer.c:2161 +#: ../buffer.c:2786 #, c-format msgid "line %<PRId64>" msgstr "第 %<PRId64> 行" -#: ../buffer.c:2233 +#: ../buffer.c:2856 msgid "E95: Buffer with this name already exists" msgstr "E95: 已有缓冲区使用该名称" -#: ../buffer.c:2498 +#: ../buffer.c:3109 msgid " [Modified]" msgstr " [已修改]" -#: ../buffer.c:2501 +#: ../buffer.c:3111 msgid "[Not edited]" msgstr "[未编辑]" -#: ../buffer.c:2504 -msgid "[New file]" -msgstr "[新文件]" - -#: ../buffer.c:2505 +#: ../buffer.c:3115 msgid "[Read errors]" msgstr "[读错误]" -#: ../buffer.c:2506 ../buffer.c:3217 ../fileio.c:1807 ../screen.c:4895 +#: ../buffer.c:3117 ../fileio.c:1777 ../statusline.c:122 ../statusline.c:1545 msgid "[RO]" msgstr "[只读]" -#: ../buffer.c:2507 ../fileio.c:1807 +#: ../buffer.c:3117 ../fileio.c:1777 msgid "[readonly]" msgstr "[只读]" -#: ../buffer.c:2524 -#, c-format -msgid "1 line --%d%%--" -msgstr "1 行 --%d%%--" - -#: ../buffer.c:2526 -#, c-format -msgid "%<PRId64> lines --%d%%--" -msgstr "%<PRId64> 行 --%d%%--" +#: ../buffer.c:3136 +#, fuzzy, c-format +msgid "%<PRId64> line --%d%%--" +msgid_plural "%<PRId64> lines --%d%%--" +msgstr[0] "%<PRId64> 行 --%d%%--" -#: ../buffer.c:2530 +#: ../buffer.c:3142 #, c-format msgid "line %<PRId64> of %<PRId64> --%d%%-- col " msgstr "行 %<PRId64> / %<PRId64> --%d%%-- 列 " -#: ../buffer.c:2632 ../buffer.c:4292 ../memline.c:1554 +#: ../buffer.c:3234 ../buffer.c:4143 ../memline.c:1456 msgid "[No Name]" msgstr "[未命名]" -#. must be a help buffer -#: ../buffer.c:2667 +#. Must be a help buffer. +#: ../buffer.c:3279 msgid "help" msgstr "帮助" -#: ../buffer.c:3225 ../screen.c:4883 -msgid "[Help]" -msgstr "[帮助]" - -#: ../buffer.c:3254 ../screen.c:4887 -msgid "[Preview]" -msgstr "[预览]" - -#: ../buffer.c:3528 +#: ../buffer.c:3421 msgid "All" msgstr "全部" -#: ../buffer.c:3528 +#: ../buffer.c:3421 msgid "Bot" msgstr "底端" -#: ../buffer.c:3531 +#: ../buffer.c:3423 msgid "Top" msgstr "顶端" -#: ../buffer.c:4244 -msgid "" -"\n" -"# Buffer list:\n" -msgstr "" -"\n" -"# 缓冲区列表:\n" +#: ../buffer.c:3912 +msgid "E382: Cannot write, 'buftype' option is set" +msgstr "E382: 无法写入,已设定选项 'buftype'" -#: ../buffer.c:4289 +#: ../buffer.c:3954 +msgid "[Prompt]" +msgstr "[提示符]" + +#: ../buffer.c:3956 msgid "[Scratch]" -msgstr "" +msgstr "[涂鸦]" -#: ../buffer.c:4529 -msgid "" -"\n" -"--- Signs ---" +#: ../buffer.h:72 +msgid "[Location List]" +msgstr "[Location 列表]" + +#: ../buffer.h:73 +msgid "[Quickfix List]" +msgstr "[Quickfix 列表]" + +#: ../change.c:68 +msgid "W10: Warning: Changing a readonly file" +msgstr "W10: 警告: 正在修改只读文件" + +#: ../channel.c:499 +msgid "can only be opened in headless mode" msgstr "" -"\n" -"--- Signs ---" -#: ../buffer.c:4538 -#, c-format -msgid "Signs for %s:" -msgstr "%s 的 Signs:" +#: ../channel.c:504 +msgid "channel was already open" +msgstr "channel 已打开" -#: ../buffer.c:4543 -#, c-format -msgid " line=%<PRId64> id=%d name=%s" -msgstr " 行=%<PRId64> id=%d 名称=%s" +#: ../channel.c:550 ../channel.c:564 ../channel.c:574 +msgid "Can't send data to closed stream" +msgstr "无法将数据发送到关闭的 stream" + +#: ../channel.c:560 ../channel.c:579 +msgid "Can't send raw data to rpc channel" +msgstr "无法将 raw data 发送到 rpc channel" + +#: ../cmdexpand.c:925 +msgid "tagname" +msgstr "tag 名" + +#: ../cmdexpand.c:928 +msgid " kind file\n" +msgstr " 类型 文件\n" + +#: ../cmdhist.c:616 +msgid "'history' option is zero" +msgstr "选项 'history' 为零" -#: ../cursor_shape.c:68 +#: ../context.c:335 +msgid "E474: Failed to convert list to msgpack string buffer" +msgstr "" + +#: ../cursor_shape.c:136 msgid "E545: Missing colon" msgstr "E545: 缺少冒号" -#: ../cursor_shape.c:70 ../cursor_shape.c:94 +#: ../cursor_shape.c:139 ../cursor_shape.c:164 msgid "E546: Illegal mode" msgstr "E546: 无效的模式" -#: ../cursor_shape.c:134 +#: ../cursor_shape.c:197 ../optionstr.c:510 msgid "E548: digit expected" msgstr "E548: 此处需要数字" -#: ../cursor_shape.c:138 +#: ../cursor_shape.c:202 msgid "E549: Illegal percentage" msgstr "E549: 无效的百分比" -#: ../diff.c:146 +#: ../debugger.c:106 +msgid "Entering Debug mode. Type \"cont\" to continue." +msgstr "进入调试模式。输入 \"cont\" 继续运行。" + +# do not translate +#: ../debugger.c:109 +#, c-format +msgid "Oldval = \"%s\"" +msgstr "" + +#: ../debugger.c:114 +#, c-format +msgid "Newval = \"%s\"" +msgstr "新值 = \"%s\"" + +#: ../debugger.c:124 ../debugger.c:388 +#, c-format +msgid "line %<PRId64>: %s" +msgstr "第 %<PRId64> 行: %s" + +#: ../debugger.c:126 ../debugger.c:390 #, c-format -msgid "E96: Can not diff more than %<PRId64> buffers" -msgstr "E96: 不能比较(diff) %<PRId64> 个以上的缓冲区" +msgid "cmd: %s" +msgstr "命令: %s" -#: ../diff.c:753 +#: ../debugger.c:347 #, fuzzy +msgid "frame is zero" +msgstr "E726: 步长为零" + +#: ../debugger.c:354 +#, c-format +msgid "frame at highest level: %d" +msgstr "帧级别最高:%d" + +#: ../debugger.c:434 +#, c-format +msgid "Breakpoint in \"%s%s\" line %<PRId64>" +msgstr "断点 \"%s%s\" 第 %<PRId64> 行" + +#: ../debugger.c:684 +#, c-format +msgid "E161: Breakpoint not found: %s" +msgstr "E161: 找不到断点: %s" + +#: ../debugger.c:717 +msgid "No breakpoints defined" +msgstr "没有定义断点" + +#: ../debugger.c:725 +#, c-format +msgid "%3d %s %s line %<PRId64>" +msgstr "%3d %s %s 第 %<PRId64> 行" + +#: ../debugger.c:731 +#, c-format +msgid "%3d expr %s" +msgstr "%3d 表达式 %s" + +#: ../diff.c:206 +#, c-format +msgid "E96: Cannot diff more than %<PRId64> buffers" +msgstr "E96: 不能比较 %<PRId64> 个以上的缓冲区" + +#: ../diff.c:767 +#, c-format +msgid "Not enough memory to use internal diff for buffer \"%s\"" +msgstr "为缓冲区 \"%s\" 使用内部比对(diff)时无足够的内存" + +#: ../diff.c:1082 msgid "E810: Cannot read or write temp files" -msgstr "E557: 无法打开 termcap 文件" +msgstr "E810: 无法读写临时文件" -#: ../diff.c:755 +#: ../diff.c:1084 msgid "E97: Cannot create diffs" msgstr "E97: 无法创建 diff" -#: ../diff.c:966 -#, fuzzy +#: ../diff.c:1125 +msgid "E960: Problem creating the internal diff" +msgstr "E960: 创建内部 diff 时遇到问题" + +#: ../diff.c:1284 msgid "E816: Cannot read patch output" -msgstr "E98: 无法读取 diff 的输出" +msgstr "E816: 无法读取 patch 的输出" -#: ../diff.c:1220 +#: ../diff.c:1740 msgid "E98: Cannot read diff output" msgstr "E98: 无法读取 diff 的输出" -#: ../diff.c:2081 +#: ../diff.c:2847 msgid "E99: Current buffer is not in diff mode" -msgstr "E99: 当前缓冲区不在 diff 模式" +msgstr "E99: 当前缓冲区不在差异模式" -#: ../diff.c:2100 -#, fuzzy +#: ../diff.c:2867 msgid "E793: No other buffer in diff mode is modifiable" -msgstr "E100: 没有其它处于 diff 模式的缓冲区" +msgstr "E793: 没有其它处于差异模式的缓冲区可修改" -#: ../diff.c:2102 +#: ../diff.c:2869 msgid "E100: No other buffer in diff mode" -msgstr "E100: 没有其它处于 diff 模式的缓冲区" +msgstr "E100: 没有其它处于差异模式的缓冲区" -#: ../diff.c:2112 +#: ../diff.c:2880 msgid "E101: More than two buffers in diff mode, don't know which one to use" -msgstr "E101: 有两个以上的缓冲区处于 diff 模式,不能决定用哪一个" +msgstr "E101: 有两个以上的缓冲区处于差异模式,不能决定用哪一个" -#: ../diff.c:2141 +#: ../diff.c:2909 #, c-format msgid "E102: Can't find buffer \"%s\"" msgstr "E102: 找不到缓冲区 \"%s\"" -#: ../diff.c:2152 +#: ../diff.c:2920 #, c-format msgid "E103: Buffer \"%s\" is not in diff mode" -msgstr "E103: 缓冲区 \"%s\" 不在 diff 模式" +msgstr "E103: 缓冲区 \"%s\" 不处于差异模式" -#: ../diff.c:2193 +#: ../diff.c:2960 msgid "E787: Buffer changed unexpectedly" msgstr "E787: 意外地改变了缓冲区" -#: ../digraph.c:1598 +#: ../digraph.c:50 +#, c-format +msgid "E1214: Digraph must be just two characters: %s" +msgstr "E1214: 二合字符必须只有两个字符:%s" + +#: ../digraph.c:52 +#, c-format +msgid "E1215: Digraph must be one character: %s" +msgstr "E1215: 二合字符必须是一个字符:%s" + +#: ../digraph.c:54 +msgid "" +"E1216: digraph_setlist() argument must be a list of lists with two items" +msgstr "E1216: digraph_setlist() 参数必须是包含两项的列表的列表" + +#: ../digraph.c:1664 msgid "E104: Escape not allowed in digraph" -msgstr "E104: 复合字符(digraph)中不能使用 Escape" +msgstr "E104: 二合字符中不能使用 Escape" + +#: ../digraph.c:1737 +msgid "Custom" +msgstr "自定义" + +#: ../digraph.c:1794 +msgid "Latin supplement" +msgstr "拉丁文补充" + +#: ../digraph.c:1795 +msgid "Greek and Coptic" +msgstr "希腊和科普特文" + +#: ../digraph.c:1796 +msgid "Cyrillic" +msgstr "西里尔文" + +#: ../digraph.c:1797 +msgid "Hebrew" +msgstr "希伯来文" + +#: ../digraph.c:1798 +msgid "Arabic" +msgstr "阿拉伯文" + +#: ../digraph.c:1799 +msgid "Latin extended" +msgstr "拉丁文扩展" -#: ../digraph.c:1760 +#: ../digraph.c:1800 +msgid "Greek extended" +msgstr "希腊文扩展" + +#: ../digraph.c:1801 +msgid "Punctuation" +msgstr "标点符号" + +#: ../digraph.c:1802 +msgid "Super- and subscripts" +msgstr "上下标" + +#: ../digraph.c:1803 +msgid "Currency" +msgstr "货币符号" + +#: ../digraph.c:1804 ../digraph.c:1809 ../digraph.c:1819 +msgid "Other" +msgstr "其他" + +#: ../digraph.c:1805 +msgid "Roman numbers" +msgstr "罗马数字" + +#: ../digraph.c:1806 +msgid "Arrows" +msgstr "箭头" + +#: ../digraph.c:1807 +msgid "Mathematical operators" +msgstr "数学运算符" + +#: ../digraph.c:1808 +msgid "Technical" +msgstr "技术符号" + +#: ../digraph.c:1810 +msgid "Box drawing" +msgstr "方框绘图" + +#: ../digraph.c:1811 +msgid "Block elements" +msgstr "块状元素" + +#: ../digraph.c:1812 +msgid "Geometric shapes" +msgstr "几何形状" + +#: ../digraph.c:1813 +msgid "Symbols" +msgstr "符号" + +#: ../digraph.c:1814 +msgid "Dingbats" +msgstr "杂锦字符" + +#: ../digraph.c:1815 +msgid "CJK symbols and punctuation" +msgstr "中日韩标点符号" + +#: ../digraph.c:1816 +msgid "Hiragana" +msgstr "日文平假名" + +#: ../digraph.c:1817 +msgid "Katakana" +msgstr "日文片假名" + +#: ../digraph.c:1818 +msgid "Bopomofo" +msgstr "注音符号" + +#: ../digraph.c:2062 msgid "E544: Keymap file not found" msgstr "E544: 找不到 Keymap 文件" -#: ../digraph.c:1785 +#: ../digraph.c:2083 msgid "E105: Using :loadkeymap not in a sourced file" -msgstr "E105: 不是在脚本文件中使用 :loadkeymap " +msgstr "E105: 不是在脚本文件中使用 :loadkeymap" -#: ../digraph.c:1821 +#: ../digraph.c:2118 msgid "E791: Empty keymap entry" msgstr "E791: 空的键位映射项" -#: ../edit.c:82 -msgid " Keyword completion (^N^P)" -msgstr " 关键字补全 (^N^P)" +#. TODO(ZyX-I): Remove DICT_MAXNEST, make users be non-recursive instead +#. maximum nesting of lists and dicts +#: ../eval.c:92 +msgid "E111: Missing ']'" +msgstr "E111: 缺少 ']'" -#. ctrl_x_mode == 0, ^P/^N compl. -#: ../edit.c:83 -msgid " ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)" -msgstr " ^X 模式 (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)" +#: ../eval.c:93 +msgid "E719: Cannot use [:] with a Dictionary" +msgstr "E719: 不能对 Dictionary 使用 [:]" -#: ../edit.c:85 -msgid " Whole line completion (^L^N^P)" -msgstr " 整行补全 (^L^N^P)" +#: ../eval.c:95 +msgid "E274: No white space allowed before parenthesis" +msgstr "E274: 小括号前不允许空白字符" -#: ../edit.c:86 -msgid " File name completion (^F^N^P)" -msgstr " 文件名补全 (^F^N^P)" +#: ../eval.c:96 +#, c-format +msgid "E80: Error while writing: %s" +msgstr "E80: 写入时出错:%s" -#: ../edit.c:87 -msgid " Tag completion (^]^N^P)" -msgstr " Tag 补全 (^]^N^P)" +#: ../eval.c:97 +msgid "E1098: String, List or Blob required" +msgstr "E1098: 需要字符串、列表或 Blob" -#: ../edit.c:88 -msgid " Path pattern completion (^N^P)" -msgstr " 头文件模式补全 (^N^P)" +#: ../eval.c:98 +#, c-format +msgid "E1169: Expression too recursive: %s" +msgstr "E1169: 表达式递归层数过多:%s" -#: ../edit.c:89 -msgid " Definition completion (^D^N^P)" -msgstr " 定义补全 (^D^N^P)" +#: ../eval.c:100 +#, c-format +msgid "E1203: Dot can only be used on a dictionary: %s" +msgstr "E1203: 点只能用于字典:%s" -#: ../edit.c:91 -msgid " Dictionary completion (^K^N^P)" -msgstr " Dictionary 补全 (^K^N^P)" +#: ../eval.c:1095 +msgid "" +"E5700: Expression from 'spellsuggest' must yield lists with exactly two " +"values" +msgstr "" -#: ../edit.c:92 -msgid " Thesaurus completion (^T^N^P)" -msgstr " Thesaurus 补全 (^T^N^P)" +#: ../eval.c:1327 ../eval/vars.c:1112 +#, c-format +msgid "E121: Undefined variable: %.*s" +msgstr "E121: 变量未定义: %.*s" -#: ../edit.c:93 -msgid " Command-line completion (^V^N^P)" -msgstr " 命令行补全 (^V^N^P)" +#: ../eval.c:1358 +msgid "E689: Can only index a List, Dictionary or Blob" +msgstr "E689: 只能索引列表、字典或 Blob" -#: ../edit.c:94 -msgid " User defined completion (^U^N^P)" -msgstr " 用户自定义补全 (^U^N^P)" +#: ../eval.c:1364 +msgid "E708: [:] must come last" +msgstr "E708: [:] 必须在最后" -#: ../edit.c:95 -msgid " Omni completion (^O^N^P)" -msgstr " 全能补全 (^O^N^P)" +#: ../eval.c:1376 +#, fuzzy +msgid "E713: Cannot use empty key after ." +msgstr "E713: Dictionary 的键不能为空" -#: ../edit.c:96 -msgid " Spelling suggestion (s^N^P)" -msgstr " 拼写建议 (s^N^P)" +#: ../eval.c:1412 +#, fuzzy +msgid "E709: [:] requires a List or Blob value" +msgstr "E709: [:] 需要一个 List 值" -#: ../edit.c:97 -msgid " Keyword Local completion (^N^P)" -msgstr " 关键字局部补全 (^N^P)" +#: ../eval.c:1665 +msgid "E972: Blob value does not have the right number of bytes" +msgstr "E972: Blob 值的字节数不对" -#: ../edit.c:100 -msgid "Hit end of paragraph" -msgstr "已到段落结尾" +#: ../eval.c:1728 +msgid "E996: Cannot lock a range" +msgstr "E996: 不能锁定范围" -#: ../edit.c:101 -msgid "E839: Completion function changed window" -msgstr "E839: 补全函数更改了窗口" +#: ../eval.c:1771 +msgid "E710: List value has more items than target" +msgstr "E710: List 值的项比目标多" -#: ../edit.c:102 -msgid "E840: Completion function deleted text" -msgstr "E840: 补全函数删除了文本" +#: ../eval.c:1776 +msgid "E711: List value has not enough items" +msgstr "E711: List 值没有足够多的项" -#: ../edit.c:1847 -msgid "'dictionary' option is empty" -msgstr "选项 'dictionary' 为空" +#: ../eval.c:1784 +msgid "E996: Cannot lock a list or dict" +msgstr "E996: 不能锁定列表或字典" -#: ../edit.c:1848 -msgid "'thesaurus' option is empty" -msgstr "选项 'thesaurus' 为空" +#: ../eval.c:1866 +msgid "E690: Missing \"in\" after :for" +msgstr "E690: :for 后缺少 \"in\"" -#: ../edit.c:2655 -#, c-format -msgid "Scanning dictionary: %s" -msgstr "正在扫描 dictionary: %s" +#: ../eval.c:2389 +msgid "E109: Missing ':' after '?'" +msgstr "E109: '?' 后缺少 ':'" -#: ../edit.c:3079 -msgid " (insert) Scroll (^E/^Y)" -msgstr " (插入) Scroll (^E/^Y)" +#: ../eval.c:2893 +msgid "E804: Cannot use '%' with Float" +msgstr "E804: 不能对浮点数使用 '%'" -#: ../edit.c:3081 -msgid " (replace) Scroll (^E/^Y)" -msgstr " (替换) Scroll (^E/^Y)" +#: ../eval.c:3037 +msgid "E973: Blob literal should have an even number of hex characters" +msgstr "E973: Blob 字面量应该有偶数个十六进制字符" + +#: ../eval.c:3137 +msgid "E110: Missing ')'" +msgstr "E110: 缺少 ')'" + +#: ../eval.c:3372 +#, fuzzy +msgid "E260: Missing name after ->" +msgstr "E526: <%s> 后面缺少数字" -#: ../edit.c:3587 +#: ../eval.c:3431 +msgid "E695: Cannot index a Funcref" +msgstr "E695: 不能索引 Funcref" + +#: ../eval.c:3442 +msgid "E909: Cannot index a special variable" +msgstr "E909: 不能索引特殊变量" + +#: ../eval.c:3730 #, c-format -msgid "Scanning: %s" -msgstr "正在扫描: %s" +msgid "E112: Option name missing: %s" +msgstr "E112: 缺少选项名称:%s" -#: ../edit.c:3614 -msgid "Scanning tags." -msgstr "扫描标签." +#: ../eval.c:3751 +#, c-format +msgid "E113: Unknown option: %s" +msgstr "E113: 未知的选项:%s" -#: ../edit.c:4519 -msgid " Adding" -msgstr " 增加" +#: ../eval.c:3798 +#, c-format +msgid "E114: Missing quote: %s" +msgstr "E114: 缺少引号:%s" -#. showmode might reset the internal line pointers, so it must -#. * be called before line = ml_get(), or when this address is no -#. * longer needed. -- Acevedo. -#. -#: ../edit.c:4562 -msgid "-- Searching..." -msgstr "-- 查找中..." +#: ../eval.c:3936 +#, c-format +msgid "E115: Missing quote: %s" +msgstr "E115: 缺少引号:%s" -#: ../edit.c:4618 -msgid "Back at original" -msgstr "回到起点" +#: ../eval.c:4031 +#, c-format +msgid "E696: Missing comma in List: %s" +msgstr "E696: 列表中缺少逗号:%s" -#: ../edit.c:4621 -msgid "Word from other line" -msgstr "另一行的词" +#: ../eval.c:4038 +#, c-format +msgid "E697: Missing end of List ']': %s" +msgstr "E697: 列表缺少结束符 ']':%s" -#: ../edit.c:4624 -msgid "The only match" -msgstr "唯一匹配" +#: ../eval.c:4338 +msgid "Not enough memory to set references, garbage collection aborted!" +msgstr "没有足够的内存来设置引用,垃圾回收已中止!" -#: ../edit.c:4680 +#: ../eval.c:4687 #, c-format -msgid "match %d of %d" -msgstr "匹配 %d / %d" +msgid "E720: Missing colon in Dictionary: %s" +msgstr "E720: 字典中缺少冒号:%s" -#: ../edit.c:4684 +#: ../eval.c:4710 #, c-format -msgid "match %d" -msgstr "匹配 %d" +msgid "E721: Duplicate key in Dictionary: \"%s\"" +msgstr "E721: Dictionary 中出现重复的键: \"%s\"" -#: ../eval.c:137 -msgid "E18: Unexpected characters in :let" -msgstr "E18: :let 中出现异常字符" +#: ../eval.c:4728 +#, c-format +msgid "E722: Missing comma in Dictionary: %s" +msgstr "E722: 字典中缺少逗号:%s" -#: ../eval.c:138 +#: ../eval.c:4735 #, c-format -msgid "E684: list index out of range: %<PRId64>" -msgstr "E684: List 索引超出范围: %<PRId64>" +msgid "E723: Missing end of Dictionary '}': %s" +msgstr "E723: 字典缺少结束符 '}':%s" + +#: ../eval.c:4854 +msgid "map() argument" +msgstr "map() 参数" -#: ../eval.c:139 +#: ../eval.c:4855 +msgid "filter() argument" +msgstr "filter() 参数" + +#: ../eval.c:5076 #, c-format -msgid "E121: Undefined variable: %s" -msgstr "E121: 未定义的变量: %s" +msgid "E700: Unknown function: %s" +msgstr "E700: 未知的函数:%s" -#: ../eval.c:140 -msgid "E111: Missing ']'" -msgstr "E111: 缺少 ']'" +#: ../eval.c:5112 +msgid "E922: expected a dict" +msgstr "E922: 需要字典" + +#: ../eval.c:5122 +msgid "E923: Second argument of function() must be a list or a dict" +msgstr "E923: function() 的第二个参数必须是列表或字典" + +#: ../eval.c:5397 +msgid "E5050: {opts} must be the only argument" +msgstr "E5050: {opts} 必须是唯一的参数" -#: ../eval.c:141 +#: ../eval.c:5794 ../os/shell.c:718 #, c-format -msgid "E686: Argument of %s must be a List" -msgstr "E686: %s 的参数必须是 List" +msgid "Executing command: \"%s\"" +msgstr "执行命令:\"%s\"" + +#: ../eval.c:5902 +msgid "E921: Invalid callback argument" +msgstr "E921: 回调参数无效" + +#: ../eval.c:7603 +msgid "E698: variable nested too deep for making a copy" +msgstr "E698: 变量嵌套过深,无法复制" + +#: ../eval.c:8078 +msgid "" +"\n" +"\tLast set from " +msgstr "" +"\n" +"\t最近修改于 " + +#: ../eval.c:8690 +msgid "E5009: $VIMRUNTIME is empty or unset" +msgstr "E5009: $VIMRUNTIME 为空或未设置" -#: ../eval.c:143 +#: ../eval.c:8694 #, c-format -msgid "E712: Argument of %s must be a List or Dictionary" -msgstr "E712: %s 的参数必须是 List 或者 Dictionary" +msgid "E5009: Invalid $VIMRUNTIME: %s" +msgstr "E5009: $VIMRUNTIME 无效:%s" -#: ../eval.c:144 -msgid "E713: Cannot use empty key for Dictionary" -msgstr "E713: Dictionary 的键不能为空" +#: ../eval.c:8696 +msgid "E5009: Invalid 'runtimepath'" +msgstr "E5009: 'runtimepath' 无效" -#: ../eval.c:145 -msgid "E714: List required" -msgstr "E714: 需要 List" +#: ../eval.c:8783 +msgid "E977: Can only compare Blob with Blob" +msgstr "E977: Blob 只能与 Blob 比较" -#: ../eval.c:146 -msgid "E715: Dictionary required" -msgstr "E715: 需要 Dictionary" +#: ../eval.c:8806 +msgid "E691: Can only compare List with List" +msgstr "E691: 列表只能与列表比较" + +#: ../eval.c:8808 +msgid "E692: Invalid operation for List" +msgstr "E692: 操作对列表无效" + +#: ../eval.c:8829 +msgid "E735: Can only compare Dictionary with Dictionary" +msgstr "E735: 字典只能与字典比较" -#: ../eval.c:147 +#: ../eval.c:8831 +msgid "E736: Invalid operation for Dictionary" +msgstr "E736: 操作对字典无效" + +#: ../eval.c:8845 +msgid "E694: Invalid operation for Funcrefs" +msgstr "E694: 操作对 Funcrefs 无效" + +#: ../eval/decode.c:132 #, c-format -msgid "E118: Too many arguments for function: %s" -msgstr "E118: 函数的参数过多: %s" +msgid "E474: Expected comma before list item: %s" +msgstr "E474: 列表项前应有逗号:%s" -#: ../eval.c:148 +#: ../eval/decode.c:140 #, c-format -msgid "E716: Key not present in Dictionary: %s" -msgstr "E716: Dictionary 中不存在键: %s" +msgid "E474: Expected colon before dictionary value: %s" +msgstr "E474: 字典值前应有冒号:%s" -#: ../eval.c:150 +#: ../eval/decode.c:167 #, c-format -msgid "E122: Function %s already exists, add ! to replace it" -msgstr "E122: 函数 %s 已存在,请加 ! 强制替换" +msgid "E474: Expected string key: %s" +msgstr "E474: 需要字符串键:%s" -#: ../eval.c:151 -msgid "E717: Dictionary entry already exists" -msgstr "E717: Dictionary 项已存在" +#: ../eval/decode.c:173 +#, c-format +msgid "E474: Expected comma before dictionary key: %s" +msgstr "E474: 字典键前应有逗号:%s" -#: ../eval.c:152 -msgid "E718: Funcref required" -msgstr "E718: 需要 Funcref" +#: ../eval/decode.c:340 +#, c-format +msgid "E474: Unfinished escape sequence: %.*s" +msgstr "E474: 转义序列不完整:%.*s" -#: ../eval.c:153 -msgid "E719: Cannot use [:] with a Dictionary" -msgstr "E719: 不能对 Dictionary 使用 [:]" +#: ../eval/decode.c:347 +#, c-format +msgid "E474: Unfinished unicode escape sequence: %.*s" +msgstr "E474: Unicode 转义序列不完整:%.*s" -#: ../eval.c:154 +#: ../eval/decode.c:354 #, c-format -msgid "E734: Wrong variable type for %s=" -msgstr "E734: %s= 的变量类型不正确" +msgid "E474: Expected four hex digits after \\u: %.*s" +msgstr "E474: \\u 之后应有四位十六进制数字:%.*s" -#: ../eval.c:155 +#: ../eval/decode.c:375 #, c-format -msgid "E130: Unknown function: %s" -msgstr "E130: 未知的函数: %s" +msgid "E474: Unknown escape sequence: %.*s" +msgstr "E474: 未知的转义序列:%.*s" -#: ../eval.c:156 +#: ../eval/decode.c:382 #, c-format -msgid "E461: Illegal variable name: %s" -msgstr "E461: 无效的变量名: %s" +msgid "E474: ASCII control characters cannot be present inside string: %.*s" +msgstr "E474: 字符串中不能出现 ASCII 控制字符:%.*s" -#: ../eval.c:157 -#, fuzzy -msgid "E806: using Float as a String" -msgstr "E730: 将 List 作 String 使用" +#: ../eval/decode.c:395 +#, c-format +msgid "E474: Only UTF-8 strings allowed: %.*s" +msgstr "E474: 只允许 UTF-8 字符串:%.*s" -#: ../eval.c:1830 -msgid "E687: Less targets than List items" -msgstr "E687: 目标比 List 项数少" +#: ../eval/decode.c:398 +#, c-format +msgid "" +"E474: Only UTF-8 code points up to U+10FFFF are allowed to appear unescaped: " +"%.*s" +msgstr "" -#: ../eval.c:1834 -msgid "E688: More targets than List items" -msgstr "E688: 目标比 List 项数多" +#: ../eval/decode.c:409 +#, c-format +msgid "E474: Expected string end: %.*s" +msgstr "" -#: ../eval.c:1906 -msgid "Double ; in list of variables" -msgstr "变量列表中出现两个 ;" +#: ../eval/decode.c:555 +#, c-format +msgid "E474: Leading zeroes are not allowed: %.*s" +msgstr "E474: 不允许前导零:%.*s" -#: ../eval.c:2078 +#: ../eval/decode.c:584 #, c-format -msgid "E738: Can't list variables for %s" -msgstr "E738: 无法列出 %s 的变量" +msgid "E474: Missing number after minus sign: %.*s" +msgstr "E474: 负号后缺少数字:%.*s" -#: ../eval.c:2391 -msgid "E689: Can only index a List or Dictionary" -msgstr "E689: 只能索引 List 或 Dictionary" +#: ../eval/decode.c:587 +#, c-format +msgid "E474: Missing number after decimal dot: %.*s" +msgstr "E474: 小数点后缺少数字:%.*s" -#: ../eval.c:2396 -msgid "E708: [:] must come last" -msgstr "E708: [:] 必须在最后" +#: ../eval/decode.c:590 +#, c-format +msgid "E474: Missing exponent: %.*s" +msgstr "E474: 缺少指数:%.*s" -#: ../eval.c:2439 -msgid "E709: [:] requires a List value" -msgstr "E709: [:] 需要一个 List 值" +#: ../eval/decode.c:602 +#, c-format +msgid "" +"E685: internal error: while converting number \"%.*s\" to float string2float " +"consumed %zu bytes in place of %zu" +msgstr "" -#: ../eval.c:2674 -msgid "E710: List value has more items than target" -msgstr "E710: List 值的项比目标多" +#: ../eval/decode.c:613 +#, c-format +msgid "" +"E685: internal error: while converting number \"%.*s\" to integer vim_str2nr " +"consumed %i bytes in place of %zu" +msgstr "" -#: ../eval.c:2678 -msgid "E711: List value has not enough items" -msgstr "E711: List 值没有足够多的项" +#: ../eval/decode.c:665 +msgid "E474: Attempt to decode a blank string" +msgstr "E474: 试图解码空字符串" -#: ../eval.c:2867 -msgid "E690: Missing \"in\" after :for" -msgstr "E690: :for 后缺少 \"in\"" +#: ../eval/decode.c:682 +#, c-format +msgid "E474: No container to close: %.*s" +msgstr "E474: 没有容器可以关闭:%.*s" -#: ../eval.c:3063 +#: ../eval/decode.c:687 #, c-format -msgid "E107: Missing parentheses: %s" -msgstr "E107: 缺少括号: %s" +msgid "E474: Closing list with curly bracket: %.*s" +msgstr "E474: 用花括号结束列表:%.*s" -#: ../eval.c:3263 +#: ../eval/decode.c:690 #, c-format -msgid "E108: No such variable: \"%s\"" -msgstr "E108: 无此变量: \"%s\"" +msgid "E474: Closing dictionary with square bracket: %.*s" +msgstr "E474: 用方括号结束字典:%.*s" -#: ../eval.c:3333 -msgid "E743: variable nested too deep for (un)lock" -msgstr "E743: (un)lock 的变量嵌套过深" +#: ../eval/decode.c:694 +#, c-format +msgid "E474: Trailing comma: %.*s" +msgstr "E474: 尾随逗号:%.*s" -#: ../eval.c:3630 -msgid "E109: Missing ':' after '?'" -msgstr "E109: '?' 后缺少 ':'" +#: ../eval/decode.c:697 +#, c-format +msgid "E474: Expected value after colon: %.*s" +msgstr "E474: 冒号后应有值:%.*s" -#: ../eval.c:3893 -msgid "E691: Can only compare List with List" -msgstr "E691: 只能比较 List 和 List" +#: ../eval/decode.c:701 +#, fuzzy, c-format +msgid "E474: Expected value: %.*s" +msgstr "E415: 不该有的等号: %s" -#: ../eval.c:3895 -msgid "E692: Invalid operation for Lists" -msgstr "E692: 对 List 无效的操作" +#: ../eval/decode.c:720 +#, fuzzy, c-format +msgid "E474: Comma not inside container: %.*s" +msgstr "E242: %s 为不能识别的颜色名称" -#: ../eval.c:3915 -msgid "E735: Can only compare Dictionary with Dictionary" -msgstr "E735: 只能比较 Dictionary 和 Dictionary" +#: ../eval/decode.c:725 +#, fuzzy, c-format +msgid "E474: Duplicate comma: %.*s" +msgstr "E721: Dictionary 中出现重复的键: \"%s\"" -#: ../eval.c:3917 -msgid "E736: Invalid operation for Dictionary" -msgstr "E736: 对 Dictionary 无效的操作" +#: ../eval/decode.c:728 +#, c-format +msgid "E474: Comma after colon: %.*s" +msgstr "E474: 冒号后有逗号:%.*s" -#: ../eval.c:3932 -msgid "E693: Can only compare Funcref with Funcref" -msgstr "E693: 只能比较 Funcref 和 Funcref" +#: ../eval/decode.c:732 +#, c-format +msgid "E474: Using comma in place of colon: %.*s" +msgstr "E474: 应使用冒号,但用了逗号:%.*s" -#: ../eval.c:3934 -msgid "E694: Invalid operation for Funcrefs" -msgstr "E694: 对 Funcrefs 无效的操作" +#: ../eval/decode.c:740 +#, c-format +msgid "E474: Leading comma: %.*s" +msgstr "E474: 前导逗号:%.*s" -#: ../eval.c:4277 -#, fuzzy -msgid "E804: Cannot use '%' with Float" -msgstr "E719: 不能对 Dictionary 使用 [:]" +#: ../eval/decode.c:748 +#, fuzzy, c-format +msgid "E474: Colon not inside container: %.*s" +msgstr "E242: %s 为不能识别的颜色名称" -#: ../eval.c:4478 -msgid "E110: Missing ')'" -msgstr "E110: 缺少 ')'" +#: ../eval/decode.c:753 +#, c-format +msgid "E474: Using colon not in dictionary: %.*s" +msgstr "E474: 在字典之外使用冒号:%.*s" -#: ../eval.c:4609 -msgid "E695: Cannot index a Funcref" -msgstr "E695: 不能索引一个 Funcref" +#: ../eval/decode.c:756 +#, fuzzy, c-format +msgid "E474: Unexpected colon: %.*s" +msgstr "E415: 不该有的等号: %s" -#: ../eval.c:4839 +#: ../eval/decode.c:759 #, c-format -msgid "E112: Option name missing: %s" -msgstr "E112: 缺少选项名称: %s" +msgid "E474: Colon after comma: %.*s" +msgstr "E474: 逗号后有冒号:%.*s" -#: ../eval.c:4855 +#: ../eval/decode.c:762 #, c-format -msgid "E113: Unknown option: %s" -msgstr "E113: 未知的选项: %s" +msgid "E474: Duplicate colon: %.*s" +msgstr "E474: 重复的冒号:%.*s" + +#: ../eval/decode.c:775 +#, fuzzy, c-format +msgid "E474: Expected null: %.*s" +msgstr "E415: 不该有的等号: %s" + +#: ../eval/decode.c:787 +#, fuzzy, c-format +msgid "E474: Expected true: %.*s" +msgstr "E415: 不该有的等号: %s" + +#: ../eval/decode.c:799 +#, fuzzy, c-format +msgid "E474: Expected false: %.*s" +msgstr "E415: 不该有的等号: %s" + +#: ../eval/decode.c:879 +#, fuzzy, c-format +msgid "E474: Unidentified byte: %.*s" +msgstr "E121: 未定义的变量: %s" -#: ../eval.c:4904 +#: ../eval/decode.c:898 +#, fuzzy, c-format +msgid "E474: Trailing characters: %.*s" +msgstr "E488: 多余的尾部字符" + +#: ../eval/decode.c:906 #, c-format -msgid "E114: Missing quote: %s" -msgstr "E114: 缺少引号: %s" +msgid "E474: Unexpected end of input: %.*s" +msgstr "E474: 输入意外结束:%.*s" -#: ../eval.c:5020 +#: ../eval/encode.c:123 #, c-format -msgid "E115: Missing quote: %s" -msgstr "E115: 缺少引号: %s" +msgid "key %s" +msgstr "" -#: ../eval.c:5084 +#: ../eval/encode.c:124 #, c-format -msgid "E696: Missing comma in List: %s" -msgstr "E696: List 中缺少逗号: %s" +msgid "key %s at index %i from special map" +msgstr "" -#: ../eval.c:5091 +#: ../eval/encode.c:125 #, c-format -msgid "E697: Missing end of List ']': %s" -msgstr "E697: List 缺少结束符 ']': %s" +msgid "index %i" +msgstr "索引 %i" -#: ../eval.c:6475 +#: ../eval/encode.c:126 +msgid "partial" +msgstr "部分" + +#: ../eval/encode.c:127 #, c-format -msgid "E720: Missing colon in Dictionary: %s" -msgstr "E720: Dictionary 中缺少冒号: %s" +msgid "argument %i" +msgstr "参数 %i" + +#: ../eval/encode.c:128 +msgid "partial self dictionary" +msgstr "" + +#: ../eval/encode.c:203 +msgid "itself" +msgstr "" + +#. Only give this message once for a recursive call to avoid +#. flooding the user with errors. +#: ../eval/encode.c:453 ../eval/encode.c:533 +msgid "E724: unable to correctly dump variable with self-referencing container" +msgstr "" + +#: ../eval/encode.c:563 +msgid "E474: Unable to represent NaN value in JSON" +msgstr "E474: 在 JSON 中无法表示 NaN 值" + +#: ../eval/encode.c:567 +msgid "E474: Unable to represent infinity in JSON" +msgstr "E474: 在 JSON 中无法表示无穷大" -#: ../eval.c:6499 +#: ../eval/encode.c:635 #, c-format -msgid "E721: Duplicate key in Dictionary: \"%s\"" -msgstr "E721: Dictionary 中出现重复的键: \"%s\"" +msgid "" +"E474: String \"%.*s\" contains byte that does not start any UTF-8 character" +msgstr "E474: 字符串 \"%.*s\" 包含不起始任何 UTF-8 字符的字节" -#: ../eval.c:6517 +#: ../eval/encode.c:642 #, c-format -msgid "E722: Missing comma in Dictionary: %s" -msgstr "E722: Dictionary 中缺少逗号: %s" +msgid "" +"E474: UTF-8 string contains code point which belongs to a surrogate pair: " +"%.*s" +msgstr "E474: UTF-8 字符串包含属于代理对的码点:%.*s" + +#: ../eval/encode.c:724 +msgid "E474: Unable to convert EXT string to JSON" +msgstr "E474: 无法将 EXT 字符串转换为 JSON" -#: ../eval.c:6524 +#: ../eval/encode.c:752 #, c-format -msgid "E723: Missing end of Dictionary '}': %s" -msgstr "E723: Dictionary 缺少结束符 '}': %s" +msgid "E474: Error while dumping %s, %s: attempt to dump function reference" +msgstr "" -#: ../eval.c:6555 -msgid "E724: variable nested too deep for displaying" -msgstr "E724: 变量嵌套过深无法显示" +#: ../eval/encode.c:797 +#, fuzzy +msgid "E474: Invalid key in special dictionary" +msgstr "E736: 对 Dictionary 无效的操作" -#: ../eval.c:7188 -#, fuzzy, c-format -msgid "E740: Too many arguments for function %s" -msgstr "E118: 函数的参数过多: %s" +#: ../eval/encode.c:853 +msgid "encode_tv2string() argument" +msgstr "encode_tv2string() 参数" -#: ../eval.c:7190 -#, fuzzy, c-format -msgid "E116: Invalid arguments for function %s" -msgstr "E118: 函数的参数过多: %s" +#: ../eval/encode.c:881 +msgid ":echo argument" +msgstr ":echo 参数" -#: ../eval.c:7377 -#, fuzzy, c-format -msgid "E117: Unknown function: %s" -msgstr "E130: 未知的函数: %s" +#: ../eval/encode.c:905 +msgid "encode_tv2json() argument" +msgstr "encode_tv2json() 参数" -#: ../eval.c:7383 +#: ../eval/encode.c:966 #, c-format -msgid "E119: Not enough arguments for function: %s" -msgstr "E119: 函数 %s 的参数太少" +msgid "E5004: Error while dumping %s, %s: attempt to dump function reference" +msgstr "E5004: dump %s, %s 时出错:试图 dump 函数引用" -#: ../eval.c:7387 +#: ../eval/encode.c:1018 #, c-format -msgid "E120: Using <SID> not in a script context: %s" -msgstr "E120: <SID> 不能在 script 上下文外使用: %s" +msgid "E5005: Unable to dump %s: container references itself in %s" +msgstr "E5005: 无法 dump %s:容器在 %s 中引用了自己" -#: ../eval.c:7391 +#: ../eval/executor.c:23 #, c-format -msgid "E725: Calling dict function without Dictionary: %s" -msgstr "E725: 调用字典函数但是没有字典:%s" +msgid "E684: list index out of range: %<PRId64>" +msgstr "E684: List 索引超出范围: %<PRId64>" -#: ../eval.c:7453 -#, fuzzy -msgid "E808: Number or Float required" -msgstr "E521: = 后面需要数字" +#: ../eval/funcs.c:147 +#, c-format +msgid "E899: Argument of %s must be a List or Blob" +msgstr "E899: %s 的参数必须是列表或 Blob" -#: ../eval.c:7503 -#, fuzzy +#: ../eval/funcs.c:148 ../match.c:45 +msgid "E957: Invalid window number" +msgstr "E957: 无效的窗口号" + +#: ../eval/funcs.c:149 +#, c-format +msgid "E998: Reduce of an empty %s with no initial value" +msgstr "E998: 在没有初始值的情况下 reduce 空的 %s" + +#: ../eval/funcs.c:151 +#, c-format +msgid "E1023: Using a Number as a Bool: %d" +msgstr "E1023: 将整数作布尔值使用:%d" + +#: ../eval/funcs.c:153 +msgid "E1308: Cannot resize a window in another tab page" +msgstr "E1308: 不能修改其他标签页中窗口的大小" + +#: ../eval/funcs.c:328 ../eval/funcs.c:7006 +#, c-format +msgid "Error converting the call result: %s" +msgstr "转换调用结果时出错:%s" + +#: ../eval/funcs.c:366 ../eval/funcs.c:374 msgid "add() argument" -msgstr "-c 参数" +msgstr "add() 参数" -#: ../eval.c:7907 -msgid "E699: Too many arguments" -msgstr "E699: 参数过多" +#: ../eval/funcs.c:679 ../sign.c:1451 +#, c-format +msgid "E158: Invalid buffer name: %s" +msgstr "E158: 缓冲区名无效:%s" -#: ../eval.c:8073 -msgid "E785: complete() can only be used in Insert mode" -msgstr "E785: complete() 只能在插入模式中使用" +#: ../eval/funcs.c:814 +#, c-format +msgid "Invalid channel stream \"%s\"" +msgstr "channel stream \"%s\" 无效" -#: ../eval.c:8156 +#: ../eval/funcs.c:1109 msgid "&Ok" msgstr "确定(&O)" -#: ../eval.c:8676 -#, c-format -msgid "E737: Key already exists: %s" -msgstr "E737: 键已存在: %s" +#: ../eval/funcs.c:1239 +msgid "Context stack is empty" +msgstr "上下文堆栈为空" + +#: ../eval/funcs.c:1483 +msgid "dictwatcheradd() argument" +msgstr "dictwatcheradd() 参数" -#: ../eval.c:8692 +#: ../eval/funcs.c:2178 +msgid "E900: maxdepth must be non-negative number" +msgstr "E900: maxdepth 必须是非负整数" + +#: ../eval/funcs.c:2186 +msgid "flatten() argument" +msgstr "flatten() 参数" + +#: ../eval/funcs.c:2197 ../eval/typval.c:2458 msgid "extend() argument" msgstr "extend() 参数" -#: ../eval.c:8915 -msgid "map() argument" -msgstr "map() 参数" - -#: ../eval.c:8916 -msgid "filter() argument" -msgstr "filter() 参数" +#: ../eval/funcs.c:2877 ../eval/funcs.c:3891 +msgid "E5000: Cannot find tab number." +msgstr "E5000: 找不到标签页号。" -#: ../eval.c:9229 -#, c-format -msgid "+-%s%3ld line: " -msgid_plural "+-%s%3ld lines: " -msgstr[0] "+-%s%3ld 行: " +#: ../eval/funcs.c:2885 ../eval/funcs.c:3899 +msgid "E5001: Higher scope cannot be -1 if lower scope is >= 0." +msgstr "E5001: 低边界 >= 0 时高边界不能为 -1。" -#: ../eval.c:9291 -#, c-format -msgid "E700: Unknown function: %s" -msgstr "E700: 未知的函数: %s" +#: ../eval/funcs.c:2892 ../eval/funcs.c:3906 +msgid "E5002: Cannot find window number." +msgstr "E5002: 找不到窗口号。" -#: ../eval.c:10729 +#: ../eval/funcs.c:4120 msgid "called inputrestore() more often than inputsave()" msgstr "inputrestore() 的调用次数多于 inputsave()" -#: ../eval.c:10771 -#, fuzzy +#: ../eval/funcs.c:4153 ../eval/funcs.c:4190 msgid "insert() argument" -msgstr "-c 参数" +msgstr "insert() 参数" -#: ../eval.c:10841 +#: ../eval/funcs.c:4260 msgid "E786: Range not allowed" msgstr "E786: 不允许的范围" -#: ../eval.c:11140 +#: ../eval/funcs.c:4743 +msgid "E474: Failed to convert list to string" +msgstr "E474: 无法将列表转换为字符串" + +#: ../eval/funcs.c:4760 +#, c-format +msgid "E474: Failed to parse %.*s" +msgstr "E474: 无法解析 %.*s" + +#: ../eval/funcs.c:4826 msgid "E701: Invalid type for len()" msgstr "E701: len() 的类型无效" -#: ../eval.c:11980 +#: ../eval/funcs.c:5348 +#, c-format +msgid "msgpackdump() argument, index %i" +msgstr "msgpackdump() 参数,索引 %i" + +#: ../eval/funcs.c:5517 +msgid "E5070: Character number must not be less than zero" +msgstr "E5070: 字符数不能小于零" + +#: ../eval/funcs.c:5521 +#, c-format +msgid "E5071: Character number must not be greater than INT_MAX (%i)" +msgstr "E5071: 字符数不能大于 INT_MAX (%i)" + +#: ../eval/funcs.c:5907 msgid "E726: Stride is zero" msgstr "E726: 步长为零" -#: ../eval.c:11982 +#: ../eval/funcs.c:5909 msgid "E727: Start past end" msgstr "E727: 起始值在终止值后" -#: ../eval.c:12024 ../eval.c:15297 +#: ../eval/funcs.c:6006 msgid "<empty>" msgstr "<空>" -#: ../eval.c:12282 -#, fuzzy +#: ../eval/funcs.c:6329 msgid "remove() argument" -msgstr "--cmd 参数" +msgstr "remove() 参数" -#: ../eval.c:12466 +#: ../eval/funcs.c:6447 msgid "E655: Too many symbolic links (cycle?)" msgstr "E655: 符号连接过多(循环?)" -#: ../eval.c:12593 -#, fuzzy +#: ../eval/funcs.c:6577 msgid "reverse() argument" -msgstr "-c 参数" +msgstr "reverse() 参数" -#: ../eval.c:13721 -#, fuzzy +#: ../eval/funcs.c:7040 +#, c-format +msgid "E5010: List item %d of the second argument is not a string" +msgstr "E5010: 第二个参数的列表项 %d 不是字符串" + +#: ../eval/funcs.c:7916 +#, c-format +msgid "E962: Invalid action: '%s'" +msgstr "E962: 动作无效:'%s'" + +#: ../eval/funcs.c:8056 +#, c-format +msgid "connection failed: %s" +msgstr "连接失败:%s" + +#: ../eval/funcs.c:8328 +#, fuzzy, c-format +msgid "E6100: \"%s\" is not a valid stdpath" +msgstr "E236: 字体 \"%s\" 不是等宽字体" + +#: ../eval/funcs.c:8420 ../os/time.c:189 +msgid "(Invalid)" +msgstr "(无效)" + +#: ../eval/funcs.c:8774 +#, c-format +msgid "E935: invalid submatch number: %d" +msgstr "E935: 子匹配号无效:%d" + +#: ../eval/funcs.c:9213 +msgid "Can only call this function in an unmodified buffer" +msgstr "只能在未修改的缓冲区中调用这个函数" + +#: ../eval/funcs.c:10026 +msgid "writefile() first argument must be a List or a Blob" +msgstr "writefile() 的第一个参数必须是列表或者 blob" + +#. Using %s, p and not %c, *p to preserve multibyte characters +#: ../eval/funcs.c:10053 +#, c-format +msgid "E5060: Unknown flag: %s" +msgstr "E5060: 未知的标志:%s" + +#: ../eval/funcs.c:10067 +msgid "E482: Can't open file with an empty name" +msgstr "E482: 无法打开名称为空的文件" + +#: ../eval/funcs.c:10072 +#, c-format +msgid "E482: Can't open file %s for writing: %s" +msgstr "" + +#: ../eval/funcs.c:10085 +#, c-format +msgid "E80: Error when closing file %s: %s" +msgstr "E80: 关闭文件 %s 时出错: %s" + +#: ../eval/typval.c:44 +#, c-format +msgid "E1174: String required for argument %d" +msgstr "E1174: 参数 %d 需要字符串" + +#: ../eval/typval.c:46 +#, c-format +msgid "E1175: Non-empty string required for argument %d" +msgstr "E1175: 参数 %d 需要非空字符串" + +#: ../eval/typval.c:48 +#, c-format +msgid "E1210: Number required for argument %d" +msgstr "E1210: 参数 %d 需要整数" + +#: ../eval/typval.c:50 +#, c-format +msgid "E1222: String or List required for argument %d" +msgstr "E1222: 参数 %d 需要字符串或列表" + +#: ../eval/typval.c:76 +#, c-format +msgid "E5142: Failed to open file %s: %s" +msgstr "E5142: 无法打开文件 %s:%s" + +#: ../eval/typval.c:98 +#, c-format +msgid "E5143: Failed to write to file %s: %s" +msgstr "E5143: 无法写入文件 %s:%s" + +#: ../eval/typval.c:109 +#, c-format +msgid "E5144: Failed to close file %s: %s" +msgstr "E5144: 无法关闭文件 %s:%s" + +#: ../eval/typval.c:1150 msgid "sort() argument" -msgstr "-c 参数" +msgstr "sort() 参数" -#: ../eval.c:13721 -#, fuzzy +#: ../eval/typval.c:1151 msgid "uniq() argument" -msgstr "-c 参数" +msgstr "uniq() 参数" -#: ../eval.c:13776 +#: ../eval/typval.c:1242 msgid "E702: Sort compare function failed" msgstr "E702: Sort 比较函数失败" -#: ../eval.c:13806 +#: ../eval/typval.c:1265 msgid "E882: Uniq compare function failed" msgstr "E882: Uniq 比较函数失败" -#: ../eval.c:14085 -msgid "(Invalid)" -msgstr "(无效)" +#: ../eval/typval.c:2198 +msgid "E6000: Argument is not a function or function name" +msgstr "" -#: ../eval.c:14590 -msgid "E677: Error writing temp file" -msgstr "E677: 写临时文件出错" +#: ../eval/typval.c:2476 +#, c-format +msgid "E737: Key already exists: %s" +msgstr "E737: 键已存在:%s" -#: ../eval.c:16159 -#, fuzzy -msgid "E805: Using a Float as a Number" -msgstr "E805: 将浮点数当做数字使用" +#: ../eval/typval.c:3315 +msgid "E743: variable nested too deep for (un)lock" +msgstr "E743: 变量嵌套过深,无法锁定/解锁" -#: ../eval.c:16162 -msgid "E703: Using a Funcref as a Number" -msgstr "E703: 将函数当做数字使用" +#: ../eval/typval.c:3455 +#, c-format +msgid "E741: Value is locked: %.*s" +msgstr "E741: 值已锁定:%.*s" + +#: ../eval/typval.c:3458 +#, c-format +msgid "E742: Cannot change value of %.*s" +msgstr "E742: 无法改变 %.*s 的值" + +#: ../eval/typval.c:3464 ../message.c:2469 +msgid "Unknown" +msgstr "未知" + +#: ../eval/typval.c:3591 +msgid "E805: Expected a Number or a String, Float found" +msgstr "E805: 需要整数或字符串,但是提供了浮点数" + +#: ../eval/typval.c:3595 +msgid "E703: Expected a Number or a String, Funcref found" +msgstr "E703: 需要整数或字符串,但是提供了 Funcref" + +#: ../eval/typval.c:3598 +msgid "E745: Expected a Number or a String, List found" +msgstr "E745: 需要整数或字符串,但是提供了列表" + +#: ../eval/typval.c:3601 +msgid "E728: Expected a Number or a String, Dictionary found" +msgstr "E728: 需要整数或字符串,但是提供了字典" -#: ../eval.c:16170 +#: ../eval/typval.c:3604 +msgid "E974: Expected a Number or a String, Blob found" +msgstr "E974: 需要整数或字符串,但是提供了 Blob" + +#: ../eval/typval.c:3607 +msgid "E5299: Expected a Number or a String, Boolean found" +msgstr "E5299: 需要整数或字符串,但是提供了布尔值" + +#: ../eval/typval.c:3610 +msgid "E5300: Expected a Number or a String" +msgstr "E5300: 需要整数或字符串" + +#: ../eval/typval.c:3625 msgid "E745: Using a List as a Number" -msgstr "E745: 将列表当做数字使用" +msgstr "E745: 将列表当作整数使用" -#: ../eval.c:16173 +#: ../eval/typval.c:3626 msgid "E728: Using a Dictionary as a Number" -msgstr "E728: 将字典当做数字使用" +msgstr "E728: 将字典当作整数使用" + +#: ../eval/typval.c:3627 +msgid "E805: Using a Float as a Number" +msgstr "E805: 将浮点数当作整数使用" -#: ../eval.c:16259 -msgid "E729: using Funcref as a String" -msgstr "E729: 将函数当做字符串使用" +#: ../eval/typval.c:3628 +msgid "E974: Using a Blob as a Number" +msgstr "E974: 将 Blob 当作整数使用" -#: ../eval.c:16262 +#: ../eval/typval.c:3629 +msgid "E685: using an invalid value as a Number" +msgstr "E685: 将无效值当作整数使用" + +#: ../eval/typval.c:3670 msgid "E730: using List as a String" -msgstr "E730: 将列表当做字符串使用" +msgstr "E730: 将列表当作字符串使用" -#: ../eval.c:16265 +#: ../eval/typval.c:3671 msgid "E731: using Dictionary as a String" -msgstr "E731: 将字典当做字符串使用" +msgstr "E731: 将字典当作字符串使用" + +#: ../eval/typval.c:3673 +msgid "E976: using Blob as a String" +msgstr "E976: 将 Blob 当作字符串使用" + +#: ../eval/typval.c:3674 +msgid "E908: using an invalid value as a String" +msgstr "E908: 将无效值当作字符串使用" + +#: ../eval/typval.c:3815 +msgid "E891: Using a Funcref as a Float" +msgstr "E891: 将 Funcref 当作浮点数使用" + +#: ../eval/typval.c:3818 +msgid "E892: Using a String as a Float" +msgstr "E892: 将字符串当作浮点数使用" -#: ../eval.c:16619 +#: ../eval/typval.c:3821 +msgid "E893: Using a List as a Float" +msgstr "E893: 将列表当作浮点数使用" + +#: ../eval/typval.c:3824 +msgid "E894: Using a Dictionary as a Float" +msgstr "E894: 将字典当作浮点数使用" + +#: ../eval/typval.c:3827 +msgid "E362: Using a boolean value as a Float" +msgstr "E362: 将布尔值当作浮点数使用" + +#: ../eval/typval.c:3830 +msgid "E907: Using a special value as a Float" +msgstr "E907: 将特殊值当作浮点数使用" + +#: ../eval/typval.c:3833 +msgid "E975: Using a Blob as a Float" +msgstr "E975: 将 Blob 当作浮点数使用" + +#: ../eval/typval.h:515 +msgid "E808: Number or Float required" +msgstr "E808: 需要整数或浮点数" + +#: ../eval/userfunc.c:67 #, c-format -msgid "E706: Variable type mismatch for: %s" -msgstr "E706: 变量类型不匹配: %s" +msgid "E122: Function %s already exists, add ! to replace it" +msgstr "E122: 函数 %s 已存在,请加 ! 强制替换" -#: ../eval.c:16705 -#, fuzzy, c-format -msgid "E795: Cannot delete variable %s" -msgstr "E738: 无法列出 %s 的变量" +#: ../eval/userfunc.c:68 +msgid "E717: Dictionary entry already exists" +msgstr "E717: 字典项已存在" + +#: ../eval/userfunc.c:69 +msgid "E718: Funcref required" +msgstr "E718: 需要 Funcref" -#: ../eval.c:16724 +#: ../eval/userfunc.c:70 #, c-format -msgid "E704: Funcref variable name must start with a capital: %s" -msgstr "E704: Funcref 变量名必须以大写字母开头: %s" +msgid "E130: Unknown function: %s" +msgstr "E130: 函数未知: %s" -#: ../eval.c:16732 +#: ../eval/userfunc.c:72 #, c-format -msgid "E705: Variable name conflicts with existing function: %s" -msgstr "E705: 变量名与已有函数名冲突: %s" +msgid "E1068: No white space allowed before '%s': %s" +msgstr "E1068: '%s' 前不允许有空白:%s" -#: ../eval.c:16763 +#: ../eval/userfunc.c:124 #, c-format -msgid "E741: Value is locked: %s" -msgstr "E741: 值已锁定: %s" +msgid "E125: Illegal argument: %s" +msgstr "E125: 参数无效: %s" -#: ../eval.c:16764 ../eval.c:16769 ../message.c:1839 -msgid "Unknown" -msgstr "未知" +#: ../eval/userfunc.c:137 +#, c-format +msgid "E853: Duplicate argument name: %s" +msgstr "E853: 参数名重复:%s" + +#: ../eval/userfunc.c:171 +msgid "E989: Non-default argument follows default argument" +msgstr "E989: 默认参数后有非默认参数" -#: ../eval.c:16768 +#: ../eval/userfunc.c:498 #, c-format -msgid "E742: Cannot change value of %s" -msgstr "E742: 无法改变 %s 的值" +msgid "E740: Too many arguments for function %s" +msgstr "E740: 函数 %s 的参数过多" -#: ../eval.c:16838 -msgid "E698: variable nested too deep for making a copy" -msgstr "E698: 变量嵌套过深无法复制" +#: ../eval/userfunc.c:500 +#, c-format +msgid "E116: Invalid arguments for function %s" +msgstr "E116: 函数 %s 的参数无效" + +#: ../eval/userfunc.c:859 +msgid "E132: Function call depth is higher than 'maxfuncdepth'" +msgstr "E132: 函数调用深度超出 'maxfuncdepth'" + +#: ../eval/userfunc.c:1039 +#, c-format +msgid "calling %s" +msgstr "调用 %s" + +#: ../eval/userfunc.c:1154 +#, c-format +msgid "%s aborted" +msgstr "%s 已中止" + +#: ../eval/userfunc.c:1156 +#, c-format +msgid "%s returning #%<PRId64>" +msgstr "%s 返回 #%<PRId64> " + +#: ../eval/userfunc.c:1173 +#, c-format +msgid "%s returning %s" +msgstr "%s 返回 %s" + +#: ../eval/userfunc.c:1196 ../runtime.c:2083 +#, c-format +msgid "continuing in %s" +msgstr "在 %s 中继续" + +#: ../eval/userfunc.c:1391 +msgid "E699: Too many arguments" +msgstr "E699: 参数过多" + +#: ../eval/userfunc.c:1441 +#, c-format +msgid "E117: Unknown function: %s" +msgstr "E117: 未知的函数:%s" + +#: ../eval/userfunc.c:1444 +#, c-format +msgid "E276: Cannot use function as a method: %s" +msgstr "E276: 不能将函数用作方法:%s" -#: ../eval.c:17249 +#: ../eval/userfunc.c:1447 +#, c-format +msgid "E933: Function was deleted: %s" +msgstr "E933: 函数已被删除:%s" + +#: ../eval/userfunc.c:1453 +#, c-format +msgid "E119: Not enough arguments for function: %s" +msgstr "E119: 函数 %s 的参数不足" + +#: ../eval/userfunc.c:1457 +#, c-format +msgid "E120: Using <SID> not in a script context: %s" +msgstr "E120: <SID> 不能在 script 上下文外使用:%s" + +#: ../eval/userfunc.c:1461 +#, c-format +msgid "E725: Calling dict function without Dictionary: %s" +msgstr "E725: 调用字典函数但是没有字典:%s" + +#: ../eval/userfunc.c:1759 +msgid "E129: Function name required" +msgstr "E129: 需要函数名" + +#: ../eval/userfunc.c:1895 +#, c-format +msgid "E128: Function name must start with a capital or \"s:\": %s" +msgstr "E128: 函数名必须以大写字母或 \"s:\" 开头:%s" + +#: ../eval/userfunc.c:1904 +#, c-format +msgid "E884: Function name cannot contain a colon: %s" +msgstr "E884: 函数名不能包含冒号:%s" + +#: ../eval/userfunc.c:1972 +msgid "E454: function list was modified" +msgstr "E454: 函数列表已被修改" + +#: ../eval/userfunc.c:2125 #, c-format msgid "E123: Undefined function: %s" -msgstr "E123: 函数 %s 尚未定义" +msgstr "E123: 函数未定义:%s" -#: ../eval.c:17260 +#: ../eval/userfunc.c:2135 #, c-format msgid "E124: Missing '(': %s" -msgstr "E124: 缺少 '(': %s" +msgstr "E124: 缺少 '(':%s" -#: ../eval.c:17293 -#, fuzzy +#: ../eval/userfunc.c:2167 msgid "E862: Cannot use g: here" -msgstr "E284: 不能设定 IC 值" +msgstr "E862: 此处不能使用 g:" -#: ../eval.c:17312 +#: ../eval/userfunc.c:2197 #, c-format -msgid "E125: Illegal argument: %s" -msgstr "E125: 无效的参数: %s" - -#: ../eval.c:17323 -#, fuzzy, c-format -msgid "E853: Duplicate argument name: %s" -msgstr "E125: 无效的参数: %s" +msgid "E932: Closure function should not be at top level: %s" +msgstr "E932: 闭包函数不能位于顶层:%s" -#: ../eval.c:17416 +#: ../eval/userfunc.c:2272 msgid "E126: Missing :endfunction" msgstr "E126: 缺少 :endfunction" -#: ../eval.c:17537 -#, fuzzy, c-format +#: ../eval/userfunc.c:2327 +#, c-format +msgid "W22: Text found after :endfunction: %s" +msgstr "W22: :endfunction 后有文本:%s" + +#: ../eval/userfunc.c:2363 +msgid "E1058: function nesting too deep" +msgstr "E1058: 函数嵌套层数过深" + +#: ../eval/userfunc.c:2472 +#, c-format msgid "E707: Function name conflicts with variable: %s" -msgstr "E746: 函数名与脚本文件名不匹配: %s" +msgstr "E707: 函数名与变量名冲突:%s" -#: ../eval.c:17549 +#: ../eval/userfunc.c:2488 #, c-format msgid "E127: Cannot redefine function %s: It is in use" msgstr "E127: 函数 %s 正在使用中,不能重新定义" -#: ../eval.c:17604 +#: ../eval/userfunc.c:2555 #, c-format msgid "E746: Function name does not match script file name: %s" msgstr "E746: 函数名与脚本文件名不匹配: %s" -#: ../eval.c:17716 -msgid "E129: Function name required" -msgstr "E129: 需要函数名" +#: ../eval/userfunc.c:2778 +#, c-format +msgid "E131: Cannot delete function %s: It is in use" +msgstr "E131: 函数 %s 正在使用中,不能删除" -#: ../eval.c:17824 -#, fuzzy, c-format -msgid "E128: Function name must start with a capital or \"s:\": %s" -msgstr "E128: 函数名必须以大写字母开头或者包含冒号: %s" +#: ../eval/userfunc.c:2784 +#, c-format +msgid "Cannot delete function %s: It is being used internally" +msgstr "无法删除函数 %s,内部使用这个函数" -#: ../eval.c:17833 -#, fuzzy, c-format -msgid "E884: Function name cannot contain a colon: %s" -msgstr "E128: 函数名必须以大写字母开头或者包含冒号: %s" +#: ../eval/userfunc.c:2916 +msgid "E133: :return not inside a function" +msgstr "E133: :return 在函数外" + +#. TODO(ZyX-I): Remove DICT_MAXNEST, make users be non-recursive instead +#. maximum nesting of lists and dicts +#: ../eval/vars.c:52 +msgid "E18: Unexpected characters in :let" +msgstr "E18: :let 中出现异常字符" -#: ../eval.c:18336 +#: ../eval/vars.c:53 #, c-format -msgid "E131: Cannot delete function %s: It is in use" -msgstr "E131: 无法删除函数 %s: 正在使用中" +msgid "E940: Cannot lock or unlock variable %s" +msgstr "E940: 不能锁定或解锁变量 %s" -#: ../eval.c:18441 -msgid "E132: Function call depth is higher than 'maxfuncdepth'" -msgstr "E132: 函数调用深度超出 'maxfuncdepth'" +#: ../eval/vars.c:77 +msgid "E991: cannot use =<< here" +msgstr "E991: 此处不能使用 =<<" + +#: ../eval/vars.c:109 +msgid "E221: Marker cannot start with lower case letter" +msgstr "E221: 标记不能以小写字母开头" -#: ../eval.c:18568 +#: ../eval/vars.c:113 +msgid "E172: Missing marker" +msgstr "E172: 缺少标记" + +#: ../eval/vars.c:124 #, c-format -msgid "calling %s" -msgstr "调用 %s" +msgid "E990: Missing end marker '%s'" +msgstr "E990: 缺少结束标记 '%s'" + +#: ../eval/vars.c:311 +msgid "E687: Less targets than List items" +msgstr "E687: 目标比 List 项数少" + +#: ../eval/vars.c:315 +msgid "E688: More targets than List items" +msgstr "E688: 目标比 List 项数多" + +#: ../eval/vars.c:391 +msgid "E452: Double ; in list of variables" +msgstr "E452: 变量列表中有两个 ;" -#: ../eval.c:18651 +#: ../eval/vars.c:532 #, c-format -msgid "%s aborted" -msgstr "%s 已中止" +msgid "E738: Can't list variables for %s" +msgstr "E738: 无法列出 %s 的变量" + +#: ../eval/vars.c:584 +msgid "E996: Cannot lock an environment variable" +msgstr "E996: 不能锁定环境变量" + +#: ../eval/vars.c:625 +msgid "E996: Cannot lock an option" +msgstr "E996: 不能锁定选项" + +#: ../eval/vars.c:716 +msgid "E996: Cannot lock a register" +msgstr "E996: 不能锁定寄存器" -#: ../eval.c:18653 +#: ../eval/vars.c:1015 #, c-format -msgid "%s returning #%<PRId64>" -msgstr "%s 返回 #%<PRId64> " +msgid "E108: No such variable: \"%s\"" +msgstr "E108: 无此变量:\"%s\"" -#: ../eval.c:18670 +#: ../eval/vars.c:1331 #, c-format -msgid "%s returning %s" -msgstr "%s 返回 %s" +msgid "E963: setting %s to value with wrong type" +msgstr "E963: 将 %s 设置为类型错误的值" -#: ../eval.c:18691 ../ex_cmds2.c:2695 +#: ../eval/vars.c:1414 #, c-format -msgid "continuing in %s" -msgstr "在 %s 中继续" +msgid "E794: Cannot set variable in the sandbox: \"%.*s\"" +msgstr "E794: 不能在沙盒中设置变量:\"%.*s\"" -#: ../eval.c:18795 -msgid "E133: :return not inside a function" -msgstr "E133: :return 不在函数中" +#: ../eval/vars.c:1460 +#, c-format +msgid "E795: Cannot delete variable %.*s" +msgstr "E795: 无法删除变量 %.*s" -#: ../eval.c:19159 -msgid "" -"\n" -"# global variables:\n" -msgstr "" -"\n" -"# 全局变量:\n" +#: ../eval/vars.c:1483 +#, c-format +msgid "E704: Funcref variable name must start with a capital: %s" +msgstr "E704: Funcref 变量名必须以大写字母开头: %s" -#: ../eval.c:19254 -msgid "" -"\n" -"\tLast set from " -msgstr "" -"\n" -"\t最近修改于 " +#: ../eval/vars.c:1490 +#, c-format +msgid "E705: Variable name conflicts with existing function: %s" +msgstr "E705: 变量名与已有函数名冲突: %s" -#: ../eval.c:19272 -#, fuzzy -msgid "No old files" -msgstr "没有包含文件" +#: ../event/socket.c:220 +msgid "tcp address must be host:port" +msgstr "TCP 地址必须为 host:port" + +#: ../event/socket.c:231 +msgid "failed to lookup host or port" +msgstr "无法查找主机或端口" + +#: ../event/socket.c:256 +msgid "connection refused" +msgstr "连接被拒绝" -#: ../ex_cmds.c:122 +#: ../ex_cmds.c:164 +#, c-format +msgid "<%s>%s%s %d, Hex %02x, Oct %03o, Digr %s" +msgstr "<%s>%s%s %d, 十六进制 %02x, 八进制 %03o, 二合字符 %s" + +#: ../ex_cmds.c:169 #, c-format msgid "<%s>%s%s %d, Hex %02x, Octal %03o" msgstr "<%s>%s%s %d, 十六进制 %02x, 八进制 %03o" -#: ../ex_cmds.c:145 +#: ../ex_cmds.c:213 +#, c-format +msgid "> %d, Hex %04x, Oct %o, Digr %s" +msgstr "> %d, 十六进制 %04x, 八进制 %o, 二合字符 %s" + +#: ../ex_cmds.c:214 +#, c-format +msgid "> %d, Hex %08x, Oct %o, Digr %s" +msgstr "> %d, 十六进制 %08x, 八进制 %o, 二合字符 %s" + +#: ../ex_cmds.c:220 #, c-format msgid "> %d, Hex %04x, Octal %o" msgstr "> %d, 十六进制 %04x, 八进制 %o" -#: ../ex_cmds.c:146 +#: ../ex_cmds.c:221 #, c-format msgid "> %d, Hex %08x, Octal %o" msgstr "> %d, 十六进制 %08x, 八进制 %o" -#: ../ex_cmds.c:684 -msgid "E134: Move lines into themselves" -msgstr "E134: 把行移动到自已中" +#: ../ex_cmds.c:914 +msgid "E134: Cannot move a range of lines into itself" +msgstr "E134: 不能把一系列行移动到自身当中" -#: ../ex_cmds.c:747 +#: ../ex_cmds.c:1021 +#, c-format msgid "1 line moved" -msgstr "移动了 1 行" +msgid_plural "%<PRId64> lines moved" +msgstr[0] "移动了 %<PRId64> 行" -#: ../ex_cmds.c:749 +#. will call wait_return() +#: ../ex_cmds.c:1335 #, c-format -msgid "%<PRId64> lines moved" -msgstr "移动了 %<PRId64> 行" +msgid "E482: Can't create file %s" +msgstr "E482: 无法创建文件 %s" -#: ../ex_cmds.c:1175 +#: ../ex_cmds.c:1436 #, c-format msgid "%<PRId64> lines filtered" msgstr "过滤了 %<PRId64> 行" -#: ../ex_cmds.c:1194 +#: ../ex_cmds.c:1458 msgid "E135: *Filter* Autocommands must not change current buffer" msgstr "E135: *Filter* 自动命令不可以改变当前缓冲区" -#: ../ex_cmds.c:1244 +#: ../ex_cmds.c:1498 msgid "[No write since last change]\n" msgstr "[已修改但尚未保存]\n" -# bad to translate -#: ../ex_cmds.c:1424 -#, c-format -msgid "%sviminfo: %s in line: " -msgstr "%sviminfo: %s 位于行: " - -#: ../ex_cmds.c:1431 -msgid "E136: viminfo: Too many errors, skipping rest of file" -msgstr "E136: viminfo: 错误过多,忽略文件的剩余部分" - -#: ../ex_cmds.c:1458 +#: ../ex_cmds.c:1793 #, c-format -msgid "Reading viminfo file \"%s\"%s%s%s" -msgstr "读取 viminfo 文件 \"%s\"%s%s%s" - -#: ../ex_cmds.c:1460 -msgid " info" -msgstr " 信息" - -#: ../ex_cmds.c:1461 -msgid " marks" -msgstr " 标记" - -#: ../ex_cmds.c:1462 -#, fuzzy -msgid " oldfiles" -msgstr "没有包含文件" +msgid "E503: \"%s\" is not a file or writable device" +msgstr "E503: \"%s\" 不是文件或可写的设备" -#: ../ex_cmds.c:1463 -msgid " FAILED" -msgstr " 失败" - -#. avoid a wait_return for this message, it's annoying -#: ../ex_cmds.c:1541 -#, c-format -msgid "E137: Viminfo file is not writable: %s" -msgstr "E137: Viminfo 文件不可写入: %s" - -#: ../ex_cmds.c:1626 -#, c-format -msgid "E138: Can't write viminfo file %s!" -msgstr "E138: 无法写入 viminfo 文件 %s!" - -#: ../ex_cmds.c:1635 -#, c-format -msgid "Writing viminfo file \"%s\"" -msgstr "写入 viminfo 文件 \"%s\"" - -# do not translate to avoid writing Chinese in files -#. Write the info: -#: ../ex_cmds.c:1720 -#, fuzzy, c-format -msgid "# This viminfo file was generated by Vim %s.\n" -msgstr "# 这个 viminfo 文件是由 Vim %s 生成的。\n" - -# do not translate to avoid writing Chinese in files -#: ../ex_cmds.c:1722 -#, fuzzy -msgid "" -"# You may edit it if you're careful!\n" -"\n" -msgstr "" -"# 如果要自行修改请特别小心!\n" -"\n" - -# do not translate to avoid writing Chinese in files -#: ../ex_cmds.c:1723 -#, fuzzy -msgid "# Value of 'encoding' when this file was written\n" -msgstr "# 'encoding' 在此文件建立时的值\n" - -#: ../ex_cmds.c:1800 -msgid "Illegal starting char" -msgstr "无效的启动字符" - -#: ../ex_cmds.c:2162 +#: ../ex_cmds.c:1877 msgid "Write partial file?" msgstr "要写入部分文件吗?" -#: ../ex_cmds.c:2166 +#: ../ex_cmds.c:1882 msgid "E140: Use ! to write partial buffer" msgstr "E140: 请使用 ! 来写入部分缓冲区" -#: ../ex_cmds.c:2281 +#: ../ex_cmds.c:2006 #, c-format msgid "Overwrite existing file \"%s\"?" msgstr "覆盖已存在的文件 \"%s\" 吗?" -#: ../ex_cmds.c:2317 +#: ../ex_cmds.c:2043 #, c-format msgid "Swap file \"%s\" exists, overwrite anyway?" msgstr "交换文件 \"%s\" 已存在,确实要覆盖吗?" -#: ../ex_cmds.c:2326 +#: ../ex_cmds.c:2052 #, c-format msgid "E768: Swap file exists: %s (:silent! overrides)" msgstr "E768: 交换文件已存在: %s (:silent! 强制执行)" -#: ../ex_cmds.c:2381 +#: ../ex_cmds.c:2110 #, c-format msgid "E141: No file name for buffer %<PRId64>" msgstr "E141: 缓冲区 %<PRId64> 没有文件名" -#: ../ex_cmds.c:2412 +#: ../ex_cmds.c:2144 msgid "E142: File not written: Writing is disabled by 'write' option" msgstr "E142: 文件未写入: 写入被 'write' 选项禁用" -#: ../ex_cmds.c:2434 +#: ../ex_cmds.c:2163 #, c-format msgid "" "'readonly' option is set for \"%s\".\n" @@ -1198,7 +1959,7 @@ msgstr "" "\"%s\" 已设定 'readonly' 选项。\n" "确实要覆盖吗?" -#: ../ex_cmds.c:2439 +#: ../ex_cmds.c:2167 #, c-format msgid "" "File permissions of \"%s\" are read-only.\n" @@ -1209,1069 +1970,744 @@ msgstr "" "它仍然有可能被写入。\n" "你想继续尝试吗?" -#: ../ex_cmds.c:2451 -#, fuzzy, c-format +#: ../ex_cmds.c:2181 +#, c-format msgid "E505: \"%s\" is read-only (add ! to override)" -msgstr "只读 (请加 ! 强制执行)" +msgstr "E505: \"%s\" 只读 (请加 ! 强制执行)" -#: ../ex_cmds.c:3120 +#: ../ex_cmds.c:2902 #, c-format msgid "E143: Autocommands unexpectedly deleted new buffer %s" msgstr "E143: 自动命令意外地删除了新缓冲区 %s" -#: ../ex_cmds.c:3313 +#: ../ex_cmds.c:3118 msgid "E144: non-numeric argument to :z" msgstr "E144: :z 不接受非数字的参数" -#: ../ex_cmds.c:3498 +#: ../ex_cmds.c:3429 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: 正则表达式不能用字母作分界" -#: ../ex_cmds.c:3964 +#. Same highlight as wait_return(). +#: ../ex_cmds.c:3952 #, c-format msgid "replace with %s (y/n/a/q/l/^E/^Y)?" msgstr "替换为 %s (y/n/a/q/l/^E/^Y)?" -#: ../ex_cmds.c:4379 +#: ../ex_cmds.c:4462 msgid "(Interrupted) " msgstr "(已中断) " -#: ../ex_cmds.c:4384 -msgid "1 match" -msgstr "1 个匹配," - -#: ../ex_cmds.c:4384 -msgid "1 substitution" -msgstr "1 次替换," - -#: ../ex_cmds.c:4387 -#, c-format -msgid "%<PRId64> matches" -msgstr "%<PRId64> 个匹配," +#: ../ex_cmds.c:4468 +#, fuzzy, c-format +msgid "%<PRId64> match on %<PRId64> line" +msgid_plural "%<PRId64> matches on %<PRId64> line" +msgstr[0] "共 %<PRId64> 行" -#: ../ex_cmds.c:4388 -#, c-format -msgid "%<PRId64> substitutions" -msgstr "%<PRId64> 次替换," +#: ../ex_cmds.c:4470 +#, fuzzy, c-format +msgid "%<PRId64> substitution on %<PRId64> line" +msgid_plural "%<PRId64> substitutions on %<PRId64> line" +msgstr[0] "%<PRId64> 次替换," -#: ../ex_cmds.c:4392 -msgid " on 1 line" -msgstr "共 1 行" +#: ../ex_cmds.c:4473 +#, fuzzy, c-format +msgid "%<PRId64> match on %<PRId64> lines" +msgid_plural "%<PRId64> matches on %<PRId64> lines" +msgstr[0] "共 %<PRId64> 行" -#: ../ex_cmds.c:4395 -#, c-format -msgid " on %<PRId64> lines" -msgstr "共 %<PRId64> 行" +#: ../ex_cmds.c:4475 +#, fuzzy, c-format +msgid "%<PRId64> substitution on %<PRId64> lines" +msgid_plural "%<PRId64> substitutions on %<PRId64> lines" +msgstr[0] "%<PRId64> 次替换," -#: ../ex_cmds.c:4438 -msgid "E147: Cannot do :global recursive" -msgstr "E147: :global 不能递归执行" +#. will increment global_busy to break out of the loop +#: ../ex_cmds.c:4536 +msgid "E147: Cannot do :global recursive with a range" +msgstr "E147: 不能带范围递归执行 :global" -#: ../ex_cmds.c:4467 +#: ../ex_cmds.c:4565 msgid "E148: Regular expression missing from global" msgstr "E148: global 缺少正则表达式" -#: ../ex_cmds.c:4508 +#: ../ex_cmds.c:4610 #, c-format msgid "Pattern found in every line: %s" msgstr "每行都匹配表达式: %s" -#: ../ex_cmds.c:4510 -#, fuzzy, c-format -msgid "Pattern not found: %s" -msgstr "找不到模式" - -# do not translate to avoid writing Chinese in files -#: ../ex_cmds.c:4587 -#, fuzzy -msgid "" -"\n" -"# Last Substitute String:\n" -"$" -msgstr "" -"\n" -"# 最近的替换字符串:\n" -"$" - -#: ../ex_cmds.c:4679 -msgid "E478: Don't panic!" -msgstr "E478: 不要慌!" - -#: ../ex_cmds.c:4717 -#, c-format -msgid "E661: Sorry, no '%s' help for %s" -msgstr "E661: 抱歉,没有 '%s' 的 %s 的说明" - -#: ../ex_cmds.c:4719 -#, c-format -msgid "E149: Sorry, no help for %s" -msgstr "E149: 抱歉,没有 %s 的说明" - -#: ../ex_cmds.c:4751 -#, c-format -msgid "Sorry, help file \"%s\" not found" -msgstr "抱歉,找不到帮助文件 \"%s\"" - -#: ../ex_cmds.c:5323 -#, c-format -msgid "E150: Not a directory: %s" -msgstr "E150: 不是目录: %s" - -#: ../ex_cmds.c:5446 -#, c-format -msgid "E152: Cannot open %s for writing" -msgstr "E152: 无法打开并写入 %s" - -#: ../ex_cmds.c:5471 -#, c-format -msgid "E153: Unable to open %s for reading" -msgstr "E153: 无法打开并读取 %s" - -#: ../ex_cmds.c:5500 -#, c-format -msgid "E670: Mix of help file encodings within a language: %s" -msgstr "E670: 在一种语言中混合了多种帮助文件编码: %s" - -#: ../ex_cmds.c:5565 -#, c-format -msgid "E154: Duplicate tag \"%s\" in file %s/%s" -msgstr "E154: Tag \"%s\" 在文件 %s/%s 中重复出现" - -#: ../ex_cmds.c:5687 +#: ../ex_cmds.c:4612 #, c-format -msgid "E160: Unknown sign command: %s" -msgstr "E160: 未知的 sign 命令: %s" - -#: ../ex_cmds.c:5704 -msgid "E156: Missing sign name" -msgstr "E156: 缺少 sign 名称" - -#: ../ex_cmds.c:5746 -msgid "E612: Too many signs defined" -msgstr "E612: Signs 定义过多" - -#: ../ex_cmds.c:5813 -#, c-format -msgid "E239: Invalid sign text: %s" -msgstr "E239: 无效的 sign 文字: %s" - -#: ../ex_cmds.c:5844 ../ex_cmds.c:6035 -#, c-format -msgid "E155: Unknown sign: %s" -msgstr "E155: 未知的 sign: %s" - -#: ../ex_cmds.c:5877 -msgid "E159: Missing sign number" -msgstr "E159: 缺少 sign 号" - -#: ../ex_cmds.c:5971 -#, c-format -msgid "E158: Invalid buffer name: %s" -msgstr "E158: 无效的缓冲区名: %s" - -#: ../ex_cmds.c:6008 -#, c-format -msgid "E157: Invalid sign ID: %<PRId64>" -msgstr "E157: 无效的 sign ID: %<PRId64>" - -#: ../ex_cmds.c:6066 -msgid " (not supported)" -msgstr " (不支持)" - -#: ../ex_cmds.c:6169 -msgid "[Deleted]" -msgstr "[已删除]" - -#: ../ex_cmds2.c:139 -msgid "Entering Debug mode. Type \"cont\" to continue." -msgstr "进入调试模式。输入 \"cont\" 继续运行。" - -#: ../ex_cmds2.c:143 ../ex_docmd.c:759 -#, c-format -msgid "line %<PRId64>: %s" -msgstr "第 %<PRId64> 行: %s" - -#: ../ex_cmds2.c:145 -#, c-format -msgid "cmd: %s" -msgstr "命令: %s" +msgid "Pattern not found: %s" +msgstr "找不到模式:%s" -#: ../ex_cmds2.c:322 -#, c-format -msgid "Breakpoint in \"%s%s\" line %<PRId64>" -msgstr "断点 \"%s%s\" 第 %<PRId64> 行" +#: ../ex_cmds.c:4921 +msgid "No old files" +msgstr "没有旧文件" -#: ../ex_cmds2.c:581 +#: ../ex_cmds2.c:204 #, c-format -msgid "E161: Breakpoint not found: %s" -msgstr "E161: 找不到断点: %s" - -#: ../ex_cmds2.c:611 -msgid "No breakpoints defined" -msgstr "没有定义断点" +msgid "Save changes to \"%s\"?" +msgstr "将改变保存到 \"%s\" 吗?" -#: ../ex_cmds2.c:617 +#: ../ex_cmds2.c:255 #, c-format -msgid "%3d %s %s line %<PRId64>" -msgstr "%3d %s %s 第 %<PRId64> 行" +msgid "Close \"%s\"?" +msgstr "关闭 \"%s\"?" -#: ../ex_cmds2.c:942 -#, fuzzy -msgid "E750: First use \":profile start {fname}\"" -msgstr "E750: 请先使用 :profile start <fname>" - -#: ../ex_cmds2.c:1269 +#: ../ex_cmds2.c:380 #, c-format -msgid "Save changes to \"%s\"?" -msgstr "将改变保存到 \"%s\" 吗?" +msgid "E947: Job still running in buffer \"%s\"" +msgstr "E947: 任务仍在缓冲区 \"%s\" 中运行" -#: ../ex_cmds2.c:1271 ../ex_docmd.c:8851 -msgid "Untitled" -msgstr "未命名" - -#: ../ex_cmds2.c:1421 +#: ../ex_cmds2.c:381 #, c-format msgid "E162: No write since last change for buffer \"%s\"" msgstr "E162: 缓冲区 \"%s\" 已修改但尚未保存" -#: ../ex_cmds2.c:1480 +#: ../ex_cmds2.c:441 msgid "Warning: Entered other buffer unexpectedly (check autocommands)" msgstr "警告: 意外地进入了其它缓冲区 (请检查自动命令)" -#: ../ex_cmds2.c:1826 -msgid "E163: There is only one file to edit" -msgstr "E163: 只有一个文件可编辑" - -#: ../ex_cmds2.c:1828 -msgid "E164: Cannot go before first file" -msgstr "E164: 无法切换,已是第一个文件" - -#: ../ex_cmds2.c:1830 -msgid "E165: Cannot go beyond last file" -msgstr "E165: 无法切换,已是最后一个文件" - -#: ../ex_cmds2.c:2175 +#: ../ex_cmds2.c:735 #, c-format msgid "E666: compiler not supported: %s" msgstr "E666: 不支持编译器: %s" -#: ../ex_cmds2.c:2257 -#, c-format -msgid "Searching for \"%s\" in \"%s\"" -msgstr "正在查找 \"%s\",在 \"%s\" 中" - -#: ../ex_cmds2.c:2284 -#, c-format -msgid "Searching for \"%s\"" -msgstr "正在查找 \"%s\"" - -#: ../ex_cmds2.c:2307 -#, c-format -msgid "not found in 'runtimepath': \"%s\"" -msgstr "在 'runtimepath' 中找不到 \"%s\"" - -#: ../ex_cmds2.c:2472 -#, c-format -msgid "Cannot source a directory: \"%s\"" -msgstr "不能执行目录: \"%s\"" - -#: ../ex_cmds2.c:2518 -#, c-format -msgid "could not source \"%s\"" -msgstr "不能执行 \"%s\"" - -#: ../ex_cmds2.c:2520 -#, c-format -msgid "line %<PRId64>: could not source \"%s\"" -msgstr "第 %<PRId64> 行: 不能执行 \"%s\"" - -#: ../ex_cmds2.c:2535 -#, c-format -msgid "sourcing \"%s\"" -msgstr "执行 \"%s\"" - -#: ../ex_cmds2.c:2537 -#, c-format -msgid "line %<PRId64>: sourcing \"%s\"" -msgstr "第 %<PRId64> 行: 执行 \"%s\"" - -#: ../ex_cmds2.c:2693 -#, c-format -msgid "finished sourcing %s" -msgstr "结束执行 %s" - -#: ../ex_cmds2.c:2765 -msgid "modeline" -msgstr "modeline" - -#: ../ex_cmds2.c:2767 -msgid "--cmd argument" -msgstr "--cmd 参数" - -#: ../ex_cmds2.c:2769 -msgid "-c argument" -msgstr "-c 参数" - -#: ../ex_cmds2.c:2771 -msgid "environment variable" -msgstr "环境变量" - -#: ../ex_cmds2.c:2773 -msgid "error handler" -msgstr "错误的处理程序" - -#: ../ex_cmds2.c:3020 -msgid "W15: Warning: Wrong line separator, ^M may be missing" -msgstr "W15: 警告: 错误的行分隔符,可能是少了 ^M" +#: ../ex_docmd.c:90 +msgid "E464: Ambiguous use of user-defined command" +msgstr "E464: 不确定的用户自定义命令" -#: ../ex_cmds2.c:3139 -msgid "E167: :scriptencoding used outside of a sourced file" -msgstr "E167: 在脚本文件外使用了 :scriptencoding" +#: ../ex_docmd.c:92 +msgid "E492: Not an editor command" +msgstr "E492: 不是编辑器的命令" -#: ../ex_cmds2.c:3166 -msgid "E168: :finish used outside of a sourced file" -msgstr "E168: 在脚本文件外使用了 :finish" +#: ../ex_docmd.c:94 +msgid "E498: no :source file name to substitute for \"<sfile>\"" +msgstr "E498: 没有用于替换 \"<sfile>\" 的 :source 文件名" -#: ../ex_cmds2.c:3389 -#, c-format -msgid "Current %slanguage: \"%s\"" -msgstr "当前的 %s语言: \"%s\"" +#: ../ex_docmd.c:96 +msgid "E489: no call stack to substitute for \"<stack>\"" +msgstr "E489: 没有用于替换 \"<stack>\" 的调用栈" -#: ../ex_cmds2.c:3404 -#, c-format -msgid "E197: Cannot set language to \"%s\"" -msgstr "E197: 不能设定语言为 \"%s\"" +#: ../ex_docmd.c:98 +msgid "E1274: No script file name to substitute for \"<script>\"" +msgstr "E1274: 没有脚本文件名可用于替换 \"<script>\"" #. don't redisplay the window #. don't wait for return -#: ../ex_docmd.c:387 +#: ../ex_docmd.c:203 msgid "Entering Ex mode. Type \"visual\" to go to Normal mode." msgstr "进入 Ex 模式。输入 \"visual\" 回到正常模式。" -#: ../ex_docmd.c:428 +#: ../ex_docmd.c:244 msgid "E501: At end-of-file" msgstr "E501: 已到文件末尾" -#: ../ex_docmd.c:513 -msgid "E169: Command too recursive" -msgstr "E169: 命令递归层数过多" +#: ../ex_docmd.c:267 +#, c-format +msgid "Executing: %s" +msgstr "执行:%s" + +#: ../ex_docmd.c:269 +msgid "line %" +msgstr "在行号 %" -#: ../ex_docmd.c:1006 +#: ../ex_docmd.c:781 #, c-format msgid "E605: Exception not caught: %s" msgstr "E605: 异常没有被捕获: %s" -#: ../ex_docmd.c:1085 +#: ../ex_docmd.c:853 msgid "End of sourced file" msgstr "脚本文件结束" -#: ../ex_docmd.c:1086 +#: ../ex_docmd.c:854 msgid "End of function" msgstr "函数结束" -#: ../ex_docmd.c:1628 -msgid "E464: Ambiguous use of user-defined command" -msgstr "E464: 不确定的用户自定义命令" - -#: ../ex_docmd.c:1638 -msgid "E492: Not an editor command" -msgstr "E492: 不是编辑器的命令" +#: ../ex_docmd.c:1279 +msgid "" +"INTERNAL: Cannot use EX_DFLALL with ADDR_NONE, ADDR_UNSIGNED or ADDR_QUICKFIX" +msgstr "" +"内部信息:不能使用 EX_DFLALL 与 ADDR_NONE, ADDR_UNSIGNED 或 ADDR_QUICKFIX" -#: ../ex_docmd.c:1729 +#: ../ex_docmd.c:2118 msgid "E493: Backwards range given" msgstr "E493: 使用了逆向的范围" -#: ../ex_docmd.c:1733 +#: ../ex_docmd.c:2121 msgid "Backwards range given, OK to swap" msgstr "使用了逆向的范围,确定交换吗" #. append #. typed wrong -#: ../ex_docmd.c:1787 +#: ../ex_docmd.c:2179 msgid "E494: Use w or w>>" msgstr "E494: 请使用 w 或 w>>" -#: ../ex_docmd.c:3454 +#: ../ex_docmd.c:2957 +msgid "E943: Command table needs to be updated, run 'make'" +msgstr "E943: 命令表需要更新,请执行 'make'" + +#: ../ex_docmd.c:3542 msgid "E319: The command is not available in this version" msgstr "E319: 抱歉,命令在此版本中不可用" -#: ../ex_docmd.c:3752 -msgid "E172: Only one file name allowed" -msgstr "E172: 只允许一个文件名" - -#: ../ex_docmd.c:4238 -msgid "1 more file to edit. Quit anyway?" -msgstr "还有 1 个文件未编辑。确实要退出吗?" - -#: ../ex_docmd.c:4242 -#, c-format -msgid "%d more files to edit. Quit anyway?" -msgstr "还有 %d 个文件未编辑。确实要退出吗?" - -#: ../ex_docmd.c:4248 -msgid "E173: 1 more file to edit" -msgstr "E173: 还有 1 个文件未编辑" - -#: ../ex_docmd.c:4250 -#, c-format -msgid "E173: %<PRId64> more files to edit" -msgstr "E173: 还有 %<PRId64> 个文件未编辑" - -#: ../ex_docmd.c:4320 -msgid "E174: Command already exists: add ! to replace it" -msgstr "E174: 命令已存在: 请加 ! 强制替换" - -#: ../ex_docmd.c:4432 -msgid "" -"\n" -" Name Args Range Complete Definition" -msgstr "" -"\n" -" 名称 参数 范围 补全 定义 " - -#: ../ex_docmd.c:4516 -msgid "No user-defined commands found" -msgstr "找不到用户自定义命令" - -#: ../ex_docmd.c:4538 -msgid "E175: No attribute specified" -msgstr "E175: 没有指定属性" - -#: ../ex_docmd.c:4583 -msgid "E176: Invalid number of arguments" -msgstr "E176: 无效的参数个数" - -#: ../ex_docmd.c:4594 -msgid "E177: Count cannot be specified twice" -msgstr "E177: 不能指定两次计数" - -#: ../ex_docmd.c:4603 -msgid "E178: Invalid default value for count" -msgstr "E178: 无效的计数默认值" - -#: ../ex_docmd.c:4625 -msgid "E179: argument required for -complete" -msgstr "E179: -complete 需要参数" - -#: ../ex_docmd.c:4635 -#, c-format -msgid "E181: Invalid attribute: %s" -msgstr "E181: 无效的属性: %s" - -#: ../ex_docmd.c:4678 -msgid "E182: Invalid command name" -msgstr "E182: 无效的命令名" - -#: ../ex_docmd.c:4691 -msgid "E183: User defined commands must start with an uppercase letter" -msgstr "E183: 用户自定义命令必须以大写字母开头" - -#: ../ex_docmd.c:4696 -#, fuzzy -msgid "E841: Reserved name, cannot be used for user defined command" -msgstr "E464: 不确定的用户自定义命令" +#: ../ex_docmd.c:4388 +#, fuzzy, c-format +msgid "%d more file to edit. Quit anyway?" +msgid_plural "%d more files to edit. Quit anyway?" +msgstr[0] "还有 %d 个文件未编辑。确实要退出吗?" -#: ../ex_docmd.c:4751 +#: ../ex_docmd.c:4395 #, c-format -msgid "E184: No such user-defined command: %s" -msgstr "E184: 没有这个用户自定义命令: %s" +msgid "E173: %<PRId64> more file to edit" +msgid_plural "E173: %<PRId64> more files to edit" +msgstr[0] "E173: 还有 %<PRId64> 个文件未编辑" -#: ../ex_docmd.c:5219 +#: ../ex_docmd.c:4430 #, c-format -msgid "E180: Invalid complete value: %s" -msgstr "E180: 无效的补全类型: %s" - -#: ../ex_docmd.c:5225 -msgid "E468: Completion argument only allowed for custom completion" -msgstr "E468: 只有 custom 补全才允许参数" - -#: ../ex_docmd.c:5231 -msgid "E467: Custom completion requires a function argument" -msgstr "E467: Custom 补全需要一个函数参数" - -#: ../ex_docmd.c:5257 -#, fuzzy, c-format msgid "E185: Cannot find color scheme '%s'" -msgstr "E185: 找不到配色方案 %s" +msgstr "E185: 找不到配色方案 '%s'" -#: ../ex_docmd.c:5263 +#: ../ex_docmd.c:4437 msgid "Greetings, Vim user!" msgstr "您好,Vim 用户!" -#: ../ex_docmd.c:5431 +#: ../ex_docmd.c:4662 msgid "E784: Cannot close last tab page" msgstr "E784: 不能关闭最后一个 tab 页" -#: ../ex_docmd.c:5462 +#: ../ex_docmd.c:4687 msgid "Already only one tab page" msgstr "已经只剩一个 tab 页了" -#: ../ex_docmd.c:6004 +#: ../ex_docmd.c:5072 #, c-format msgid "Tab page %d" msgstr "Tab 页 %d" -#: ../ex_docmd.c:6295 +#: ../ex_docmd.c:5296 +msgid "E25: Nvim does not have a built-in GUI" +msgstr "E25: 无法使用图形界面: 编译时没有启用" + +#: ../ex_docmd.c:5307 msgid "No swap file" msgstr "无交换文件" -#: ../ex_docmd.c:6478 -msgid "E747: Cannot change directory, buffer is modified (add ! to override)" -msgstr "E747: 不能改变目录,缓冲区已修改 (请加 ! 强制执行)" - -#: ../ex_docmd.c:6485 +#: ../ex_docmd.c:5518 msgid "E186: No previous directory" msgstr "E186: 前一个目录不存在" -#: ../ex_docmd.c:6530 +#: ../ex_docmd.c:5624 msgid "E187: Unknown" msgstr "E187: 未知" -#: ../ex_docmd.c:6610 +#: ../ex_docmd.c:5689 msgid "E465: :winsize requires two number arguments" msgstr "E465: :winsize 需要两个数字参数" -#: ../ex_docmd.c:6655 -msgid "E188: Obtaining window position not implemented for this platform" -msgstr "E188: 在此平台上不能获得窗口位置" - -#: ../ex_docmd.c:6662 -msgid "E466: :winpos requires two number arguments" -msgstr "E466: :winpos 需要两个数字参数" - -#: ../ex_docmd.c:7241 -#, c-format -msgid "E739: Cannot create directory: %s" -msgstr "E739: 无法创建目录: %s" - -#: ../ex_docmd.c:7268 +#: ../ex_docmd.c:6207 #, c-format msgid "E189: \"%s\" exists (add ! to override)" msgstr "E189: \"%s\" 已存在 (请加 ! 强制执行)" -#: ../ex_docmd.c:7273 +#: ../ex_docmd.c:6213 #, c-format msgid "E190: Cannot open \"%s\" for writing" msgstr "E190: 无法打开并写入 \"%s\"" #. set mark -#: ../ex_docmd.c:7294 +#: ../ex_docmd.c:6231 msgid "E191: Argument must be a letter or forward/backward quote" msgstr "E191: 参数必须是一个字母或者正/反引号" -#: ../ex_docmd.c:7333 +#: ../ex_docmd.c:6315 msgid "E192: Recursive use of :normal too deep" msgstr "E192: :normal 递归层数过深" -#: ../ex_docmd.c:7807 +#: ../ex_docmd.c:6795 msgid "E194: No alternate file name to substitute for '#'" msgstr "E194: 没有用于替换 '#' 的交替文件名" -#: ../ex_docmd.c:7841 +#: ../ex_docmd.c:6834 msgid "E495: no autocommand file name to substitute for \"<afile>\"" msgstr "E495: 没有用于替换 \"<afile>\" 的自动命令文件名" -#: ../ex_docmd.c:7850 +#: ../ex_docmd.c:6842 msgid "E496: no autocommand buffer number to substitute for \"<abuf>\"" msgstr "E496: 没有用于替换 \"<abuf>\" 的自动命令缓冲区号" -#: ../ex_docmd.c:7861 +#: ../ex_docmd.c:6852 msgid "E497: no autocommand match name to substitute for \"<amatch>\"" msgstr "E497: 没有用于替换 \"<amatch>\" 的自动命令匹配名" -#: ../ex_docmd.c:7870 -msgid "E498: no :source file name to substitute for \"<sfile>\"" -msgstr "E498: 没有用于替换 \"<sfile>\" 的 :source 文件名" - -#: ../ex_docmd.c:7876 -#, fuzzy +#: ../ex_docmd.c:6884 msgid "E842: no line number to use for \"<slnum>\"" -msgstr "E498: 没有用于替换 \"<sfile>\" 的 :source 文件名" +msgstr "E842: 没有行号可用于替换 \"<slnum>\"" -#: ../ex_docmd.c:7903 -#, fuzzy, c-format +#: ../ex_docmd.c:6893 +msgid "E961: no line number to use for \"<sflnum>\"" +msgstr "E961: 没有用于替换 \"<sflnum>\" 的行号" + +#: ../ex_docmd.c:6942 +#, fuzzy, no-c-format msgid "E499: Empty file name for '%' or '#', only works with \":p:h\"" msgstr "E499: '%' 或 '#' 为空文件名,只能用于 \":p:h\"" -#: ../ex_docmd.c:7905 +#: ../ex_docmd.c:6944 msgid "E500: Evaluates to an empty string" msgstr "E500: 结果为空字符串" -#: ../ex_docmd.c:8838 -msgid "E195: Cannot open viminfo file for reading" -msgstr "E195: 无法打开并读取 viminfo 文件" +#: ../ex_docmd.c:7018 +msgid "Untitled" +msgstr "未命名" -#: ../ex_eval.c:464 +#: ../ex_eval.c:456 msgid "E608: Cannot :throw exceptions with 'Vim' prefix" msgstr "E608: 不能 :throw 前缀为 'Vim' 的异常" -#. always scroll up, don't overwrite -#: ../ex_eval.c:496 +#: ../ex_eval.c:500 #, c-format msgid "Exception thrown: %s" msgstr "抛出异常: %s" -#: ../ex_eval.c:545 +#: ../ex_eval.c:553 #, c-format msgid "Exception finished: %s" msgstr "完成异常: %s" -#: ../ex_eval.c:546 +#: ../ex_eval.c:553 #, c-format msgid "Exception discarded: %s" msgstr "丢弃异常: %s" -#: ../ex_eval.c:588 ../ex_eval.c:634 +#: ../ex_eval.c:597 ../ex_eval.c:647 #, c-format msgid "%s, line %<PRId64>" msgstr "%s,第 %<PRId64> 行" -#. always scroll up, don't overwrite -#: ../ex_eval.c:608 +#: ../ex_eval.c:620 #, c-format msgid "Exception caught: %s" msgstr "捕获异常: %s" -#: ../ex_eval.c:676 +#: ../ex_eval.c:688 #, c-format msgid "%s made pending" msgstr "%s 待定" -#: ../ex_eval.c:679 -#, fuzzy, c-format +#: ../ex_eval.c:691 +#, c-format msgid "%s resumed" -msgstr " 已返回\n" +msgstr "%s 已恢复" -#: ../ex_eval.c:683 +#: ../ex_eval.c:695 #, c-format msgid "%s discarded" msgstr "%s 舍弃" -#: ../ex_eval.c:708 +#: ../ex_eval.c:720 msgid "Exception" msgstr "异常" -#: ../ex_eval.c:713 +#: ../ex_eval.c:724 msgid "Error and interrupt" msgstr "错误和中断" -#: ../ex_eval.c:715 +#: ../ex_eval.c:726 msgid "Error" msgstr "错误" #. if (pending & CSTP_INTERRUPT) -#: ../ex_eval.c:717 +#: ../ex_eval.c:728 msgid "Interrupt" msgstr "中断" -#: ../ex_eval.c:795 +#: ../ex_eval.c:816 msgid "E579: :if nesting too deep" msgstr "E579: :if 嵌套层数过深" -#: ../ex_eval.c:830 +#: ../ex_eval.c:844 msgid "E580: :endif without :if" msgstr "E580: :endif 缺少对应的 :if" -#: ../ex_eval.c:873 +#: ../ex_eval.c:874 msgid "E581: :else without :if" msgstr "E581: :else 缺少对应的 :if" -#: ../ex_eval.c:876 +#: ../ex_eval.c:877 msgid "E582: :elseif without :if" msgstr "E582: :elseif 缺少对应的 :if" -#: ../ex_eval.c:880 +#: ../ex_eval.c:881 msgid "E583: multiple :else" msgstr "E583: 多个 :else" -#: ../ex_eval.c:883 +#: ../ex_eval.c:884 msgid "E584: :elseif after :else" msgstr "E584: :elseif 在 :else 后面" -#: ../ex_eval.c:941 +#: ../ex_eval.c:951 msgid "E585: :while/:for nesting too deep" msgstr "E585: :while/:for 嵌套层数过深" -#: ../ex_eval.c:1028 +#: ../ex_eval.c:1022 msgid "E586: :continue without :while or :for" msgstr "E586: :continue 缺少对应的 :while 或 :for" -#: ../ex_eval.c:1061 +#: ../ex_eval.c:1052 msgid "E587: :break without :while or :for" msgstr "E587: :break 缺少对应的 :while 或 :for" -#: ../ex_eval.c:1102 +#: ../ex_eval.c:1091 msgid "E732: Using :endfor with :while" msgstr "E732: :while 以 :endfor 结尾" -#: ../ex_eval.c:1104 +#: ../ex_eval.c:1093 msgid "E733: Using :endwhile with :for" msgstr "E733: :for 以 :endwhile 结尾" -#: ../ex_eval.c:1247 +#: ../ex_eval.c:1229 msgid "E601: :try nesting too deep" msgstr "E601: :try 嵌套层数过深" -#: ../ex_eval.c:1317 +#: ../ex_eval.c:1286 msgid "E603: :catch without :try" msgstr "E603: :catch 缺少对应的 :try" #. Give up for a ":catch" after ":finally" and ignore it. -#. * Just parse. -#: ../ex_eval.c:1332 +#. Just parse. +#: ../ex_eval.c:1303 msgid "E604: :catch after :finally" msgstr "E604: :catch 在 :finally 后面" -#: ../ex_eval.c:1451 +#: ../ex_eval.c:1426 msgid "E606: :finally without :try" msgstr "E606: :finally 缺少对应的 :try" #. Give up for a multiple ":finally" and ignore it. -#: ../ex_eval.c:1467 +#: ../ex_eval.c:1445 msgid "E607: multiple :finally" msgstr "E607: 多个 :finally" -#: ../ex_eval.c:1571 +#: ../ex_eval.c:1540 msgid "E602: :endtry without :try" msgstr "E602: :endtry 缺少对应的 :try" -#: ../ex_eval.c:2026 +#: ../ex_eval.c:1984 msgid "E193: :endfunction not inside a function" msgstr "E193: :endfunction 不在函数内" -#: ../ex_getln.c:1643 -msgid "E788: Not allowed to edit another buffer now" -msgstr "E788: 目前不允许编辑别的缓冲区" - -#: ../ex_getln.c:1656 -#, fuzzy +#: ../ex_getln.c:2661 msgid "E811: Not allowed to change buffer information now" -msgstr "E788: 目前不允许编辑别的缓冲区" +msgstr "E811: 目前不允许更改缓冲区的信息" -#: ../ex_getln.c:3178 -msgid "tagname" -msgstr "tag 名" +#: ../ex_getln.c:2939 +#, c-format +msgid "E5408: Unable to get g:Nvim_color_cmdline callback: %s" +msgstr "" -#: ../ex_getln.c:3181 -msgid " kind file\n" -msgstr " 类型 文件\n" +#: ../ex_getln.c:2973 +#, c-format +msgid "E5407: Callback has thrown an exception: %s" +msgstr "" -#: ../ex_getln.c:4799 -msgid "'history' option is zero" -msgstr "选项 'history' 为零" +#: ../ex_getln.c:2986 +msgid "E5400: Callback should return list" +msgstr "E5400: 回调应返回列表" -# do not translate to avoid writing Chinese in files -#: ../ex_getln.c:5046 -#, fuzzy, c-format -msgid "" -"\n" -"# %s History (newest to oldest):\n" +#: ../ex_getln.c:2996 +#, c-format +msgid "E5401: List item %i is not a List" +msgstr "E5401: 列表项 %i 不是列表" + +#: ../ex_getln.c:3001 +#, c-format +msgid "E5402: List item %i has incorrect length: %d /= 3" msgstr "" -"\n" -"# %s 历史记录 (从新到旧):\n" -# do not translate to avoid writing Chinese in files -#: ../ex_getln.c:5047 -#, fuzzy -msgid "Command Line" -msgstr "命令行" +#: ../ex_getln.c:3011 +msgid "E5403: Chunk %i start %" +msgstr "" -# do not translate to avoid writing Chinese in files -#: ../ex_getln.c:5048 -#, fuzzy -msgid "Search String" -msgstr "查找字符串" +#: ../ex_getln.c:3016 +msgid "E5405: Chunk %i start %" +msgstr "" -# do not translate to avoid writing Chinese in files -#: ../ex_getln.c:5049 -#, fuzzy -msgid "Expression" -msgstr "表达式" +#: ../ex_getln.c:3032 +msgid "E5404: Chunk %i end %" +msgstr "" -# do not translate to avoid writing Chinese in files -#: ../ex_getln.c:5050 -#, fuzzy -msgid "Input Line" -msgstr "输入行" +#: ../ex_getln.c:3039 +msgid "E5406: Chunk %i end %" +msgstr "" -#: ../ex_getln.c:5117 -msgid "E198: cmd_pchar beyond the command length" -msgstr "E198: cmd_pchar 超过命令长度" +# do not translate to avoid writing Chinese in files +#. Create empty command-line buffer. +#: ../ex_getln.c:4223 +msgid "[Command Line]" +msgstr "" -#: ../ex_getln.c:5279 +#: ../ex_getln.c:4331 msgid "E199: Active window or buffer deleted" msgstr "E199: 活动窗口或缓冲区已被删除" -#: ../file_search.c:203 +#: ../file_search.c:185 msgid "E854: path too long for completion" msgstr "E854: 补全用的路径太长" -#: ../file_search.c:446 +#: ../file_search.c:419 #, c-format msgid "" "E343: Invalid path: '**[number]' must be at the end of the path or be " "followed by '%s'." msgstr "E343: 无效的路径: '**[number]' 必须在路径末尾或者后面接 '%s'。" -#: ../file_search.c:1505 +#: ../file_search.c:1486 #, c-format msgid "E344: Can't find directory \"%s\" in cdpath" msgstr "E344: cdpath 中找不到目录 \"%s\"" -#: ../file_search.c:1508 +#: ../file_search.c:1489 #, c-format msgid "E345: Can't find file \"%s\" in path" msgstr "E345: 在路径中找不到文件 \"%s\"" -#: ../file_search.c:1512 +#: ../file_search.c:1494 #, c-format msgid "E346: No more directory \"%s\" found in cdpath" msgstr "E346: 在路径中找不到更多的文件 \"%s\"" -#: ../file_search.c:1515 +#: ../file_search.c:1497 #, c-format msgid "E347: No more file \"%s\" found in path" msgstr "E347: 在路径中找不到更多的文件 \"%s\"" -#: ../fileio.c:137 -#, fuzzy +#: ../fileio.c:126 msgid "E812: Autocommands changed buffer or buffer name" -msgstr "E135: *Filter* 自动命令不可以改变当前缓冲区" +msgstr "E812: 自动命令改变了缓冲区或者缓冲区名称" -#: ../fileio.c:368 -msgid "Illegal file name" -msgstr "无效的文件名" +#: ../fileio.c:128 +#, c-format +msgid "E676: No matching autocommands for buftype=%s buffer" +msgstr "" -#: ../fileio.c:395 ../fileio.c:476 ../fileio.c:2543 ../fileio.c:2578 +#: ../fileio.c:262 ../fileio.c:2499 ../fileio.c:2529 msgid "is a directory" msgstr "是目录" -#: ../fileio.c:397 +#: ../fileio.c:363 +msgid "Illegal file name" +msgstr "无效的文件名" + +#: ../fileio.c:399 msgid "is not a file" msgstr "不是文件" -#: ../fileio.c:508 ../fileio.c:3522 -msgid "[New File]" -msgstr "[新文件]" - -#: ../fileio.c:511 +#: ../fileio.c:490 msgid "[New DIRECTORY]" msgstr "[新目录]" -#: ../fileio.c:529 ../fileio.c:532 +#. libuv only returns -errno +#. in Unix and in Windows +#. open() does not set +#. EOVERFLOW +#: ../fileio.c:510 ../fileio.c:516 msgid "[File too big]" msgstr "[文件过大]" -#: ../fileio.c:534 +#: ../fileio.c:518 msgid "[Permission Denied]" msgstr "[权限不足]" -#: ../fileio.c:653 +#: ../fileio.c:661 msgid "E200: *ReadPre autocommands made the file unreadable" msgstr "E200: *ReadPre 自动命令导致文件不可读" -#: ../fileio.c:655 +#: ../fileio.c:663 msgid "E201: *ReadPre autocommands must not change current buffer" msgstr "E201: *ReadPre 自动命令不允许改变当前缓冲区" -#: ../fileio.c:672 -msgid "Nvim: Reading from stdin...\n" -msgstr "Vim: 从标准输入读取...\n" - #. Re-opening the original file failed! -#: ../fileio.c:909 +#: ../fileio.c:865 msgid "E202: Conversion made file unreadable!" -msgstr "E202: 转换导致文件不可读" - -#. fifo or socket -#: ../fileio.c:1782 -msgid "[fifo/socket]" -msgstr "[fifo/socket]" +msgstr "E202: 转换导致文件不可读!" #. fifo -#: ../fileio.c:1788 +#: ../fileio.c:1762 msgid "[fifo]" msgstr "[fifo]" #. or socket -#: ../fileio.c:1794 +#: ../fileio.c:1766 msgid "[socket]" msgstr "[socket]" #. or character special -#: ../fileio.c:1801 -#, fuzzy +#: ../fileio.c:1771 msgid "[character special]" -msgstr "1 个字符" +msgstr "[字符设备]" -#: ../fileio.c:1815 +#: ../fileio.c:1785 msgid "[CR missing]" msgstr "[缺少 CR]'" -#: ../fileio.c:1819 +#: ../fileio.c:1789 msgid "[long lines split]" msgstr "[长行分割]" -#: ../fileio.c:1823 ../fileio.c:3512 +#: ../fileio.c:1793 ../fileio.c:3427 msgid "[NOT converted]" msgstr "[未转换]" -#: ../fileio.c:1826 ../fileio.c:3515 +#: ../fileio.c:1796 ../fileio.c:3430 msgid "[converted]" msgstr "[已转换]" -#: ../fileio.c:1831 +#: ../fileio.c:1801 #, c-format msgid "[CONVERSION ERROR in line %<PRId64>]" msgstr "[第 %<PRId64> 行转换错误]" -#: ../fileio.c:1835 +#: ../fileio.c:1805 #, c-format msgid "[ILLEGAL BYTE in line %<PRId64>]" msgstr "[第 %<PRId64> 行无效字符]" -#: ../fileio.c:1838 +#: ../fileio.c:1808 msgid "[READ ERRORS]" msgstr "[读错误]" -#: ../fileio.c:2104 +#: ../fileio.c:2069 msgid "Can't find temp file for conversion" msgstr "找不到用于转换的临时文件" -#: ../fileio.c:2110 +#: ../fileio.c:2075 msgid "Conversion with 'charconvert' failed" msgstr "'charconvert' 转换失败" -#: ../fileio.c:2113 +#: ../fileio.c:2078 msgid "can't read output of 'charconvert'" msgstr "无法读取 'charconvert' 的输出" -#: ../fileio.c:2437 -msgid "E676: No matching autocommands for acwrite buffer" -msgstr "E676: 找不到 acwrite 缓冲区对应的自动命令" +#: ../fileio.c:2116 +msgid "[New]" +msgstr "[新]" -#: ../fileio.c:2466 +#: ../fileio.c:2116 +msgid "[New File]" +msgstr "[新文件]" + +#: ../fileio.c:2416 msgid "E203: Autocommands deleted or unloaded buffer to be written" msgstr "E203: 自动命令删除或释放了要写入的缓冲区" -#: ../fileio.c:2486 +#: ../fileio.c:2435 msgid "E204: Autocommand changed number of lines in unexpected way" msgstr "E204: 自动命令意外地改变了行数" -#: ../fileio.c:2548 ../fileio.c:2565 +#: ../fileio.c:2503 ../fileio.c:2517 msgid "is not a file or writable device" msgstr "不是文件或可写的设备" -#: ../fileio.c:2601 +#: ../fileio.c:2547 msgid "is read-only (add ! to override)" msgstr "只读 (请加 ! 强制执行)" -#: ../fileio.c:2886 -msgid "E506: Can't write to backup file (add ! to override)" -msgstr "E506: 无法写入备份文件 (请加 ! 强制执行)" - -#: ../fileio.c:2898 -msgid "E507: Close error for backup file (add ! to override)" -msgstr "E507: 关闭备份文件出错 (请加 ! 强制执行)" - -#: ../fileio.c:2901 -msgid "E508: Can't read file for backup (add ! to override)" -msgstr "E508: 无法读取文件以供备份 (请加 ! 强制执行)" +#: ../fileio.c:2701 ../fileio.c:2852 +#, c-format +msgid "E303: Unable to create directory \"%s\" for backup file: %s" +msgstr "E303: 无法为备份文件创建目录 \"%s\":%s" -#: ../fileio.c:2923 +#: ../fileio.c:2792 ../fileio.c:2813 msgid "E509: Cannot create backup file (add ! to override)" msgstr "E509: 无法创建备份文件 (请加 ! 强制执行)" -#: ../fileio.c:3008 +#: ../fileio.c:2912 msgid "E510: Can't make backup file (add ! to override)" msgstr "E510: 无法生成备份文件 (请加 ! 强制执行)" #. Can't write without a tempfile! -#: ../fileio.c:3121 +#: ../fileio.c:3015 msgid "E214: Can't find temp file for writing" msgstr "E214: 找不到用于写入的临时文件" -#: ../fileio.c:3134 +#: ../fileio.c:3031 msgid "E213: Cannot convert (add ! to write without conversion)" msgstr "E213: 无法转换 (请加 ! 强制不转换写入)" -#: ../fileio.c:3169 +#: ../fileio.c:3081 msgid "E166: Can't open linked file for writing" msgstr "E166: 无法打开并写入链接文件" -#: ../fileio.c:3173 -msgid "E212: Can't open file for writing" -msgstr "E212: 无法打开并写入文件" - -#: ../fileio.c:3363 -msgid "E667: Fsync failed" -msgstr "E667: 同步失败" +#: ../fileio.c:3084 +#, c-format +msgid "E212: Can't open file for writing: %s" +msgstr "E212: 无法打开并写入文件:%s" -#: ../fileio.c:3398 -msgid "E512: Close failed" -msgstr "E512: 关闭失败" +#: ../fileio.c:3324 +#, c-format +msgid "E512: Close failed: %s" +msgstr "E512: 关闭失败:%s" -#: ../fileio.c:3436 +#: ../fileio.c:3363 msgid "E513: write error, conversion failed (make 'fenc' empty to override)" msgstr "E513: 写入错误,转换失败 (请将 'fenc' 置空以强制执行)" -#: ../fileio.c:3441 -#, fuzzy, c-format -msgid "" -"E513: write error, conversion failed in line %<PRId64> (make 'fenc' empty to " -"override)" -msgstr "E513: 写入错误,转换失败 (请将 'fenc' 置空以强制执行)" +#. NOLINT(runtime/printf) +#: ../fileio.c:3369 +msgid "E513: write error, conversion failed in line %" +msgstr "" -#: ../fileio.c:3448 +#: ../fileio.c:3376 msgid "E514: write error (file system full?)" msgstr "E514: 写入错误 (文件系统已满?)" -#: ../fileio.c:3506 +#: ../fileio.c:3420 msgid " CONVERSION ERROR" msgstr " 转换错误" -#: ../fileio.c:3509 +#: ../fileio.c:3423 #, fuzzy, c-format msgid " in line %<PRId64>;" msgstr "第 %<PRId64> 行" -#: ../fileio.c:3519 +#: ../fileio.c:3434 msgid "[Device]" msgstr "[设备]" -#: ../fileio.c:3522 -msgid "[New]" -msgstr "[新]" - -#: ../fileio.c:3535 +#: ../fileio.c:3451 msgid " [a]" msgstr " [a]" -#: ../fileio.c:3535 +#: ../fileio.c:3451 msgid " appended" msgstr " 已追加" -#: ../fileio.c:3537 +#: ../fileio.c:3453 msgid " [w]" msgstr " [w]" -#: ../fileio.c:3537 +#: ../fileio.c:3453 msgid " written" msgstr " 已写入" -#: ../fileio.c:3579 +#: ../fileio.c:3496 msgid "E205: Patchmode: can't save original file" msgstr "E205: Patchmode: 无法保存原始文件" -#: ../fileio.c:3602 +#: ../fileio.c:3515 msgid "E206: patchmode: can't touch empty original file" msgstr "E206: Patchmode: 无法生成空的原始文件" -#: ../fileio.c:3616 +#: ../fileio.c:3530 msgid "E207: Can't delete backup file" msgstr "E207: 无法删除备份文件" -#: ../fileio.c:3672 +#. Set highlight for error messages. +#: ../fileio.c:3584 msgid "" "\n" "WARNING: Original file may be lost or damaged\n" @@ -2279,1309 +2715,1511 @@ msgstr "" "\n" "警告: 原始文件可能已丢失或损坏\n" -#: ../fileio.c:3675 +#: ../fileio.c:3586 msgid "don't quit the editor until the file is successfully written!" msgstr "在文件正确写入前请勿退出编辑器!" -#: ../fileio.c:3795 +#: ../fileio.c:3721 msgid "[dos]" msgstr "[dos]" -#: ../fileio.c:3795 +#: ../fileio.c:3721 msgid "[dos format]" msgstr "[dos 格式]" -#: ../fileio.c:3801 +#: ../fileio.c:3726 msgid "[mac]" msgstr "[mac]" -#: ../fileio.c:3801 +#: ../fileio.c:3726 msgid "[mac format]" msgstr "[mac 格式]" -#: ../fileio.c:3807 +#: ../fileio.c:3731 msgid "[unix]" msgstr "[unix]" -#: ../fileio.c:3807 +#: ../fileio.c:3731 msgid "[unix format]" msgstr "[unix 格式]" -#: ../fileio.c:3831 -msgid "1 line, " -msgstr "1 行," - -#: ../fileio.c:3833 +#: ../fileio.c:3753 #, c-format -msgid "%<PRId64> lines, " -msgstr "%<PRId64> 行," - -#: ../fileio.c:3836 -msgid "1 character" -msgstr "1 个字符" +msgid "%<PRId64> line, " +msgid_plural "%<PRId64> lines, " +msgstr[0] "%<PRId64> 行," -#: ../fileio.c:3838 +#: ../fileio.c:3757 #, c-format -msgid "%<PRId64> characters" -msgstr "%<PRId64> 个字符" +msgid "%<PRId64> byte" +msgid_plural "%<PRId64> bytes" +msgstr[0] "%<PRId64> 字节" -#: ../fileio.c:3849 +#: ../fileio.c:3766 msgid "[noeol]" msgstr "[noeol]" -#: ../fileio.c:3849 +#: ../fileio.c:3766 msgid "[Incomplete last line]" msgstr "[最后一行不完整]" -#. don't overwrite messages here -#. must give this prompt -#. don't use emsg() here, don't want to flush the buffers -#: ../fileio.c:3865 +#. Don't overwrite messages here. +#. Must give this prompt. +#. Don't use emsg() here, don't want to flush the buffers. +#: ../fileio.c:3779 msgid "WARNING: The file has been changed since reading it!!!" msgstr "警告: 此文件自读入后已发生变动!!!" -#: ../fileio.c:3867 +#: ../fileio.c:3781 msgid "Do you really want to write to it" msgstr "确实要写入吗" -#: ../fileio.c:4648 +#: ../fileio.c:4629 #, c-format msgid "E208: Error writing to \"%s\"" msgstr "E208: 写入文件 \"%s\" 出错" -#: ../fileio.c:4655 +#: ../fileio.c:4637 #, c-format msgid "E209: Error closing \"%s\"" msgstr "E209: 关闭文件 \"%s\" 出错" -#: ../fileio.c:4657 +#: ../fileio.c:4640 #, c-format msgid "E210: Error reading \"%s\"" msgstr "E210: 读取文件 \"%s\" 出错" -#: ../fileio.c:4883 +#: ../fileio.c:4859 msgid "E246: FileChangedShell autocommand deleted buffer" msgstr "E246: FileChangedShell 自动命令删除了缓冲区" -#: ../fileio.c:4894 +#: ../fileio.c:4876 #, c-format msgid "E211: File \"%s\" no longer available" msgstr "E211: 文件 \"%s\" 已经不存在" -#: ../fileio.c:4906 +#: ../fileio.c:4888 #, c-format msgid "" "W12: Warning: File \"%s\" has changed and the buffer was changed in Vim as " "well" msgstr "W12: 警告: 文件 \"%s\" 已变动,并且在 Vim 中的缓冲区也已变动" -#: ../fileio.c:4907 +#: ../fileio.c:4889 msgid "See \":help W12\" for more info." msgstr "进一步说明请见 \":help W12\"" -#: ../fileio.c:4910 +#: ../fileio.c:4891 #, c-format msgid "W11: Warning: File \"%s\" has changed since editing started" msgstr "W11: 警告: 编辑开始后,文件 \"%s\" 已变动" -#: ../fileio.c:4911 +#: ../fileio.c:4892 msgid "See \":help W11\" for more info." msgstr "进一步说明请见 \":help W11\"" -#: ../fileio.c:4914 +#: ../fileio.c:4894 #, c-format msgid "W16: Warning: Mode of file \"%s\" has changed since editing started" msgstr "W16: 警告: 编辑开始后,文件 \"%s\" 的模式已变动" -#: ../fileio.c:4915 +#: ../fileio.c:4895 msgid "See \":help W16\" for more info." msgstr "进一步说明请见 \":help W16\"" -#: ../fileio.c:4927 +#: ../fileio.c:4908 #, c-format msgid "W13: Warning: File \"%s\" has been created after editing started" msgstr "W13: 警告: 编辑开始后,文件 \"%s\" 已被创建" -#: ../fileio.c:4947 +#: ../fileio.c:4929 msgid "Warning" msgstr "警告" -#: ../fileio.c:4948 +#: ../fileio.c:4930 msgid "" "&OK\n" -"&Load File" +"&Load File\n" +"Load File &and Options" msgstr "" "确定(&O)\n" -"加载文件(&L)" +"加载文件(&L)\n" +"加载文件和选项(&A)" -#: ../fileio.c:5065 +#: ../fileio.c:5050 #, c-format msgid "E462: Could not prepare for reloading \"%s\"" msgstr "E462: 无法为重新加载 \"%s\" 做准备" -#: ../fileio.c:5078 +#: ../fileio.c:5062 #, c-format msgid "E321: Could not reload \"%s\"" msgstr "E321: 无法重新加载 \"%s\"" -#: ../fileio.c:5601 -msgid "--Deleted--" -msgstr "--已删除--" - -#: ../fileio.c:5732 -#, c-format -msgid "auto-removing autocommand: %s <buffer=%d>" -msgstr "自动删除自动命令: %s <buffer=%d>" - -#. the group doesn't exist -#: ../fileio.c:5772 -#, c-format -msgid "E367: No such group: \"%s\"" -msgstr "E367: 无此组: \"%s\"" - -#: ../fileio.c:5897 -#, c-format -msgid "E215: Illegal character after *: %s" -msgstr "E215: * 后面有无效字符: %s" - -#: ../fileio.c:5905 -#, c-format -msgid "E216: No such event: %s" -msgstr "E216: 无此事件: %s" - -#: ../fileio.c:5907 -#, c-format -msgid "E216: No such group or event: %s" -msgstr "E216: 无此组或事件: %s" - -#. Highlight title -#: ../fileio.c:6090 -msgid "" -"\n" -"--- Autocommands ---" -msgstr "" -"\n" -"--- 自动命令 ---" - -#: ../fileio.c:6293 -#, c-format -msgid "E680: <buffer=%d>: invalid buffer number " -msgstr "E680: <buffer=%d>: 无效的缓冲区号 " - -#: ../fileio.c:6370 -msgid "E217: Can't execute autocommands for ALL events" -msgstr "E217: 不能对所有事件执行自动命令" - -#: ../fileio.c:6393 -msgid "No matching autocommands" -msgstr "没有匹配的自动命令" - -#: ../fileio.c:6831 -msgid "E218: autocommand nesting too deep" -msgstr "E218: 自动命令嵌套层数过深" - -#: ../fileio.c:7143 -#, c-format -msgid "%s Autocommands for \"%s\"" -msgstr "%s 自动命令 \"%s\"" - -#: ../fileio.c:7149 -#, c-format -msgid "Executing %s" -msgstr "执行 %s" - -#: ../fileio.c:7211 -#, c-format -msgid "autocommand %s" -msgstr "自动命令 %s" - -#: ../fileio.c:7795 +#: ../fileio.c:5680 msgid "E219: Missing {." msgstr "E219: 缺少 {。" -#: ../fileio.c:7797 +#: ../fileio.c:5682 msgid "E220: Missing }." msgstr "E220: 缺少 }。" -#: ../fold.c:93 +#: ../fold.c:104 msgid "E490: No fold found" msgstr "E490: 找不到折叠" -#: ../fold.c:544 +#: ../fold.c:520 msgid "E350: Cannot create fold with current 'foldmethod'" msgstr "E350: 不能在当前的 'foldmethod' 下创建折叠" -#: ../fold.c:546 +#: ../fold.c:522 msgid "E351: Cannot delete fold with current 'foldmethod'" msgstr "E351: 不能在当前的 'foldmethod' 下删除折叠" -#: ../fold.c:1784 +#: ../fold.c:1789 #, c-format -msgid "+--%3ld lines folded " -msgstr "+--已折叠 %3ld 行" +msgid "+--%3ld line folded" +msgid_plural "+--%3ld lines folded " +msgstr[0] "+--已折叠 %3ld 行" + +#: ../fold.c:3275 +#, c-format +msgid "+-%s%3ld line: " +msgid_plural "+-%s%3ld lines: " +msgstr[0] "+-%s%3ld 行: " #. buffer has already been read -#: ../getchar.c:273 +#: ../getchar.c:235 msgid "E222: Add to read buffer" msgstr "E222: 添加到已读缓冲区中" -#: ../getchar.c:2040 +#: ../getchar.c:2172 msgid "E223: recursive mapping" msgstr "E223: 递归映射" -#: ../getchar.c:2849 -#, c-format -msgid "E224: global abbreviation already exists for %s" -msgstr "E224: 全局缩写 %s 已存在" - -#: ../getchar.c:2852 -#, c-format -msgid "E225: global mapping already exists for %s" -msgstr "E225: 全局映射 %s 已存在" - -#: ../getchar.c:2952 -#, c-format -msgid "E226: abbreviation already exists for %s" -msgstr "E226: 缩写 %s 已存在" - -#: ../getchar.c:2955 -#, c-format -msgid "E227: mapping already exists for %s" -msgstr "E227: 映射 %s 已存在" - -#: ../getchar.c:3008 -msgid "No abbreviation found" -msgstr "找不到缩写" - -#: ../getchar.c:3010 -msgid "No mapping found" -msgstr "找不到映射" - -#: ../getchar.c:3974 -msgid "E228: makemap: Illegal mode" -msgstr "E228: makemap: 无效的模式" - -#. key value of 'cedit' option -#. type of cmdline window or 0 -#. result of cmdline window or 0 -#: ../globals.h:924 +#. /< type of cmdline window or 0 +#. /< result of cmdline window or 0 +#. /< cmdline recursion level +#: ../globals.h:815 msgid "--No lines in buffer--" msgstr "--缓冲区无内容--" -#. -#. * The error messages that can be shared are included here. -#. * Excluded are errors that are only used once and debugging messages. -#. -#: ../globals.h:996 +#. uncrustify:off +#. The error messages that can be shared are included here. +#. Excluded are errors that are only used once and debugging messages. +#: ../globals.h:861 msgid "E470: Command aborted" msgstr "E470: 命令被中止" -#: ../globals.h:997 +#: ../globals.h:862 +msgid "E905: Cannot set this option after startup" +msgstr "E905: 启动后不能设置这个选项" + +#: ../globals.h:863 +msgid "E903: Could not spawn API job" +msgstr "E903: 无法生成 API 任务" + +#: ../globals.h:864 msgid "E471: Argument required" msgstr "E471: 需要参数" -#: ../globals.h:998 +#: ../globals.h:865 msgid "E10: \\ should be followed by /, ? or &" msgstr "E10: \\ 后面应该跟有 /、? 或 &" -#: ../globals.h:1000 +#: ../globals.h:866 msgid "E11: Invalid in command-line window; <CR> executes, CTRL-C quits" msgstr "E11: 在命令行窗口中无效;<CR> 执行,CTRL-C 退出" -#: ../globals.h:1002 +#: ../globals.h:867 msgid "E12: Command not allowed from exrc/vimrc in current dir or tag search" msgstr "E12: 当前目录中的 exrc/vimrc 或 tag 查找中不允许此命令" -#: ../globals.h:1003 +#: ../globals.h:868 +msgid "E169: Command too recursive" +msgstr "E169: 命令递归层数过多" + +#: ../globals.h:869 msgid "E171: Missing :endif" msgstr "E171: 缺少 :endif" -#: ../globals.h:1004 +#: ../globals.h:870 msgid "E600: Missing :endtry" msgstr "E600: 缺少 :endtry" -#: ../globals.h:1005 +#: ../globals.h:871 msgid "E170: Missing :endwhile" msgstr "E170: 缺少 :endwhile" -#: ../globals.h:1006 +#: ../globals.h:872 msgid "E170: Missing :endfor" msgstr "E170: 缺少 :endfor" -#: ../globals.h:1007 +#: ../globals.h:873 msgid "E588: :endwhile without :while" msgstr "E588: :endwhile 缺少对应的 :while" -#: ../globals.h:1008 +#: ../globals.h:874 msgid "E588: :endfor without :for" msgstr "E588: :endfor 缺少对应的 :for" -#: ../globals.h:1009 +#: ../globals.h:875 msgid "E13: File exists (add ! to override)" msgstr "E13: 文件已存在 (请加 ! 强制执行)" -#: ../globals.h:1010 +#: ../globals.h:876 msgid "E472: Command failed" msgstr "E472: 命令执行失败" -#: ../globals.h:1011 +#: ../globals.h:877 msgid "E473: Internal error" msgstr "E473: 内部错误" -#: ../globals.h:1012 +#: ../globals.h:878 +#, c-format +msgid "E685: Internal error: %s" +msgstr "E685: 内部错误: %s" + +#: ../globals.h:879 msgid "Interrupted" msgstr "已中断" -#: ../globals.h:1013 -msgid "E14: Invalid address" -msgstr "E14: 无效的地址" - -#: ../globals.h:1014 +#: ../globals.h:880 msgid "E474: Invalid argument" msgstr "E474: 无效的参数" -#: ../globals.h:1015 +#: ../globals.h:881 #, c-format msgid "E475: Invalid argument: %s" msgstr "E475: 无效的参数: %s" -#: ../globals.h:1016 +#: ../globals.h:882 +#, c-format +msgid "E475: Invalid value for argument %s" +msgstr "E475: 参数 %s 的值无效" + +#: ../globals.h:883 +#, c-format +msgid "E475: Invalid value for argument %s: %s" +msgstr "E475: 参数 %s 的值无效:%s" + +#: ../globals.h:884 +#, c-format +msgid "E983: Duplicate argument: %s" +msgstr "E983: 重复的参数:%s" + +#: ../globals.h:885 #, c-format msgid "E15: Invalid expression: %s" msgstr "E15: 无效的表达式: %s" -#: ../globals.h:1017 +#: ../globals.h:886 msgid "E16: Invalid range" msgstr "E16: 无效的范围" -#: ../globals.h:1018 +#: ../globals.h:887 msgid "E476: Invalid command" msgstr "E476: 无效的命令" -#: ../globals.h:1019 +#: ../globals.h:888 #, c-format msgid "E17: \"%s\" is a directory" msgstr "E17: \"%s\" 是目录" -#: ../globals.h:1020 -#, fuzzy -msgid "E900: Invalid job id" -msgstr "E49: 无效的滚动大小" +#: ../globals.h:889 +msgid "E756: Spell checking is not possible" +msgstr "E756: 拼写检查不可能" -#: ../globals.h:1021 +#: ../globals.h:890 +msgid "E900: Invalid channel id" +msgstr "E900: channel id 无效" + +#: ../globals.h:891 +msgid "E900: Invalid channel id: not a job" +msgstr "E900: channel id 无效:不是任务" + +#: ../globals.h:892 msgid "E901: Job table is full" -msgstr "E901: 任务表已经满" +msgstr "E901: 任务表已满" -#: ../globals.h:1024 +#: ../globals.h:893 +#, c-format +msgid "E903: Process failed to start: %s: \"%s\"" +msgstr "E903: 进程启动失败:%s:\"%s\"" + +#: ../globals.h:894 +msgid "E904: channel is not a pty" +msgstr "E664: channel 不是 pty" + +#: ../globals.h:895 +#, c-format +msgid "E905: Couldn't open stdio channel: %s" +msgstr "E905: 无法打开 stdio channel:%s" + +#: ../globals.h:896 +msgid "E906: invalid stream for channel" +msgstr "E906: stream 对 channel 无效" + +#: ../globals.h:897 +msgid "E906: invalid stream for rpc channel, use 'rpc'" +msgstr "E906: stream 对 rpc channel 无效,请使用 'rpc'" + +#: ../globals.h:898 +#, c-format +msgid "" +"E5210: dict key '%s' already set for buffered stream in channel %<PRIu64>" +msgstr "" + +#: ../globals.h:899 #, c-format msgid "E364: Library call failed for \"%s()\"" msgstr "E364: 调用函数库 \"%s()\" 失败" -#: ../globals.h:1026 +#: ../globals.h:900 +#, c-format +msgid "E667: Fsync failed: %s" +msgstr "E667: Fsync 失败:%s" + +#: ../globals.h:901 +#, c-format +msgid "E739: Cannot create directory %s: %s" +msgstr "E739: 无法创建目录 %s:%s" + +#: ../globals.h:902 msgid "E19: Mark has invalid line number" msgstr "E19: 标记的行号无效" -#: ../globals.h:1027 +#: ../globals.h:903 msgid "E20: Mark not set" msgstr "E20: 没有设定标记" -#: ../globals.h:1029 +#: ../globals.h:904 msgid "E21: Cannot make changes, 'modifiable' is off" msgstr "E21: 不能修改,因为选项 'modifiable' 是关的" -#: ../globals.h:1030 +#: ../globals.h:905 msgid "E22: Scripts nested too deep" msgstr "E22: 脚本嵌套过深" -#: ../globals.h:1031 +#: ../globals.h:906 msgid "E23: No alternate file" msgstr "E23: 没有交替文件" -#: ../globals.h:1032 +#: ../globals.h:907 msgid "E24: No such abbreviation" msgstr "E24: 没有这个缩写" -#: ../globals.h:1033 +#: ../globals.h:908 msgid "E477: No ! allowed" msgstr "E477: 不能使用 \"!\"" -#: ../globals.h:1035 -msgid "E25: Nvim does not have a built-in GUI" -msgstr "E25: 无法使用图形界面: 编译时没有启用" - -#: ../globals.h:1036 +#: ../globals.h:909 #, c-format msgid "E28: No such highlight group name: %s" msgstr "E28: 没有这个高亮群组名: %s" -#: ../globals.h:1037 +#: ../globals.h:910 msgid "E29: No inserted text yet" msgstr "E29: 没有插入过文字" -#: ../globals.h:1038 +#: ../globals.h:911 msgid "E30: No previous command line" msgstr "E30: 没有前一个命令行" -#: ../globals.h:1039 +#: ../globals.h:912 msgid "E31: No such mapping" msgstr "E31: 没有这个映射" -#: ../globals.h:1040 +#: ../globals.h:913 msgid "E479: No match" msgstr "E479: 没有匹配" -#: ../globals.h:1041 +#: ../globals.h:914 #, c-format msgid "E480: No match: %s" msgstr "E480: 没有匹配: %s" -#: ../globals.h:1042 +#: ../globals.h:915 msgid "E32: No file name" msgstr "E32: 没有文件名" -#: ../globals.h:1044 +#: ../globals.h:916 msgid "E33: No previous substitute regular expression" msgstr "E33: 没有前一个替换正则表达式" -#: ../globals.h:1045 +#: ../globals.h:917 msgid "E34: No previous command" msgstr "E34: 没有前一个命令" -#: ../globals.h:1046 +#: ../globals.h:918 msgid "E35: No previous regular expression" msgstr "E35: 没有前一个正则表达式" -#: ../globals.h:1047 +#: ../globals.h:919 msgid "E481: No range allowed" msgstr "E481: 不能使用范围" -#: ../globals.h:1048 +#: ../globals.h:920 msgid "E36: Not enough room" msgstr "E36: 没有足够的空间" -#: ../globals.h:1049 -#, c-format -msgid "E482: Can't create file %s" -msgstr "E482: 无法创建文件 %s" - -#: ../globals.h:1050 +#: ../globals.h:921 msgid "E483: Can't get temp file name" msgstr "E483: 无法获取临时文件名" -#: ../globals.h:1051 +#: ../globals.h:922 #, c-format msgid "E484: Can't open file %s" msgstr "E484: 无法打开文件 %s" -#: ../globals.h:1052 +#: ../globals.h:923 +#, c-format +msgid "E484: Can't open file %s: %s" +msgstr "E484: 无法打开文件 %s:%s" + +#: ../globals.h:924 #, c-format msgid "E485: Can't read file %s" msgstr "E485: 无法读取文件 %s" -#: ../globals.h:1054 -msgid "E37: No write since last change (add ! to override)" -msgstr "E37: 已修改但尚未保存 (可用 ! 强制执行)" - -#: ../globals.h:1055 -#, fuzzy -msgid "E37: No write since last change" -msgstr "[已修改但尚未保存]\n" - -#: ../globals.h:1056 +#: ../globals.h:925 msgid "E38: Null argument" msgstr "E38: 空的参数" -#: ../globals.h:1057 +#: ../globals.h:926 msgid "E39: Number expected" msgstr "E39: 此处需要数字" -#: ../globals.h:1058 +#: ../globals.h:927 #, c-format msgid "E40: Can't open errorfile %s" msgstr "E40: 无法打开错误文件 %s" -#: ../globals.h:1059 +#: ../globals.h:928 msgid "E41: Out of memory!" msgstr "E41: 内存不足!" -#: ../globals.h:1060 +#: ../globals.h:929 msgid "Pattern not found" msgstr "找不到模式" -#: ../globals.h:1061 +#: ../globals.h:930 #, c-format msgid "E486: Pattern not found: %s" msgstr "E486: 找不到模式: %s" -#: ../globals.h:1062 +#: ../globals.h:931 msgid "E487: Argument must be positive" msgstr "E487: 参数必须是正数" -#: ../globals.h:1064 +#: ../globals.h:932 msgid "E459: Cannot go back to previous directory" msgstr "E459: 无法回到前一个目录" -#: ../globals.h:1066 +#: ../globals.h:934 msgid "E42: No Errors" msgstr "E42: 没有错误" -#: ../globals.h:1067 +#: ../globals.h:935 msgid "E776: No location list" -msgstr "E776: 没有 location 列表" +msgstr "E776: 没有位置列表" -#: ../globals.h:1068 +#: ../globals.h:936 msgid "E43: Damaged match string" msgstr "E43: 已损坏的匹配字符串" -#: ../globals.h:1069 +#: ../globals.h:937 msgid "E44: Corrupted regexp program" msgstr "E44: 已损坏的正则表达式程序" -#: ../globals.h:1071 +#: ../globals.h:938 msgid "E45: 'readonly' option is set (add ! to override)" msgstr "E45: 已设定选项 'readonly' (请加 ! 强制执行)" -#: ../globals.h:1073 +#: ../globals.h:939 #, c-format -msgid "E46: Cannot change read-only variable \"%s\"" -msgstr "E46: 不能改变只读变量 \"%s\"" +msgid "E734: Wrong variable type for %s=" +msgstr "E734: %s= 的变量类型不正确" -#: ../globals.h:1075 -#, fuzzy, c-format -msgid "E794: Cannot set variable in the sandbox: \"%s\"" -msgstr "E46: 不能在 sandbox 中设定变量: \"%s\"" +#: ../globals.h:940 +#, c-format +msgid "E461: Illegal variable name: %s" +msgstr "E461: 变量名无效: %s" -#: ../globals.h:1076 +#: ../globals.h:941 +msgid "E995: Cannot modify existing variable" +msgstr "E995: 不能修改已有变量" + +#: ../globals.h:942 +#, c-format +msgid "E46: Cannot change read-only variable \"%.*s\"" +msgstr "E46: 不能改变只读变量 \"%.*s\"" + +#: ../globals.h:943 +msgid "E928: String required" +msgstr "E928: 需要字符串" + +#: ../globals.h:944 +msgid "E715: Dictionary required" +msgstr "E715: 需要字典" + +#: ../globals.h:945 +#, c-format +msgid "E979: Blob index out of range: %<PRId64>" +msgstr "E979: Blob 索引超出范围:%<PRId64>" + +#: ../globals.h:946 +msgid "E978: Invalid operation for Blob" +msgstr "E978: 操作对 Blob 无效" + +#: ../globals.h:947 +#, c-format +msgid "E118: Too many arguments for function: %s" +msgstr "E118: 函数 %s 的参数过多" + +#: ../globals.h:948 +#, c-format +msgid "E716: Key not present in Dictionary: \"%s\"" +msgstr "E716: 字典中不存在键:\"%s\"" + +#: ../globals.h:949 +msgid "E714: List required" +msgstr "E714: 需要列表" + +#: ../globals.h:950 +msgid "E897: List or Blob required" +msgstr "E897: 需要列表或 Blob" + +#: ../globals.h:951 +#, c-format +msgid "E712: Argument of %s must be a List or Dictionary" +msgstr "E712: %s 的参数必须是 List 或者 Dictionary" + +#: ../globals.h:952 +#, c-format +msgid "E896: Argument of %s must be a List, Dictionary or Blob" +msgstr "E896: %s 的参数必须是列表、字典或 Blob" + +#: ../globals.h:953 msgid "E47: Error while reading errorfile" msgstr "E47: 读取错误文件失败" -#: ../globals.h:1078 +#: ../globals.h:954 msgid "E48: Not allowed in sandbox" msgstr "E48: 不允许在 sandbox 中使用" -#: ../globals.h:1080 +#: ../globals.h:955 msgid "E523: Not allowed here" msgstr "E523: 不允许在此使用" -#: ../globals.h:1082 +#: ../globals.h:956 +msgid "E565: Not allowed to change text or change window" +msgstr "E565: 不允许改变文本或窗口" + +#: ../globals.h:957 msgid "E359: Screen mode setting not supported" msgstr "E359: 不支持设定屏幕模式" -#: ../globals.h:1083 +#: ../globals.h:958 msgid "E49: Invalid scroll size" -msgstr "E49: 无效的滚动大小" +msgstr "E49: 滚动大小无效" -#: ../globals.h:1084 +#: ../globals.h:959 msgid "E91: 'shell' option is empty" -msgstr "E91: 选项 'shell' 为空" +msgstr "E91: 'shell' 选项为空" -#: ../globals.h:1085 +#: ../globals.h:960 msgid "E255: Couldn't read in sign data!" msgstr "E255: 无法读取 sign 数据!" -#: ../globals.h:1086 +#: ../globals.h:961 msgid "E72: Close error on swap file" msgstr "E72: 交换文件关闭错误" -#: ../globals.h:1087 +#: ../globals.h:962 msgid "E73: tag stack empty" msgstr "E73: tag 堆栈为空" -#: ../globals.h:1088 +#: ../globals.h:963 msgid "E74: Command too complex" -msgstr "E74: 命令过复杂" +msgstr "E74: 命令过于复杂" -#: ../globals.h:1089 +#: ../globals.h:964 msgid "E75: Name too long" msgstr "E75: 名字过长" -#: ../globals.h:1090 +#: ../globals.h:965 msgid "E76: Too many [" msgstr "E76: [ 过多" -#: ../globals.h:1091 +#: ../globals.h:966 msgid "E77: Too many file names" msgstr "E77: 文件名过多" -#: ../globals.h:1092 +#: ../globals.h:967 msgid "E488: Trailing characters" msgstr "E488: 多余的尾部字符" -#: ../globals.h:1093 +#: ../globals.h:968 +#, c-format +msgid "E488: Trailing characters: %s" +msgstr "E488: 多余的尾部字符:%s" + +#: ../globals.h:969 msgid "E78: Unknown mark" msgstr "E78: 未知的标记" -#: ../globals.h:1094 +#: ../globals.h:970 msgid "E79: Cannot expand wildcards" msgstr "E79: 无法扩展通配符" -#: ../globals.h:1096 +#: ../globals.h:971 msgid "E591: 'winheight' cannot be smaller than 'winminheight'" msgstr "E591: 'winheight' 不能小于 'winminheight'" -#: ../globals.h:1098 +#: ../globals.h:972 msgid "E592: 'winwidth' cannot be smaller than 'winminwidth'" msgstr "E592: 'winwidth' 不能小于 'winminwidth'" -#: ../globals.h:1099 +#: ../globals.h:973 msgid "E80: Error while writing" msgstr "E80: 写入出错" -#: ../globals.h:1100 -msgid "Zero count" -msgstr "计数为零" +#: ../globals.h:974 +msgid "E939: Positive count required" +msgstr "E939: 需要正的计数" -#: ../globals.h:1101 +#: ../globals.h:975 msgid "E81: Using <SID> not in a script context" msgstr "E81: 在脚本环境外使用了 <SID>" -#: ../globals.h:1102 +#: ../globals.h:976 #, c-format -msgid "E685: Internal error: %s" -msgstr "E685: 内部错误: %s" +msgid "E107: Missing parentheses: %s" +msgstr "E107: 缺少括号:%s" -#: ../globals.h:1104 +#: ../globals.h:977 msgid "E363: pattern uses more memory than 'maxmempattern'" msgstr "E363: 表达式的内存使用超出 'maxmempattern'" -#: ../globals.h:1105 +#: ../globals.h:978 msgid "E749: empty buffer" msgstr "E749: 空的缓冲区" -#: ../globals.h:1108 +#: ../globals.h:979 +#, c-format +msgid "E86: Buffer %<PRId64> does not exist" +msgstr "E86: 缓冲区 %<PRId64> 不存在" + +#: ../globals.h:981 msgid "E682: Invalid search pattern or delimiter" msgstr "E682: 无效的搜索表达式或分隔符" -#: ../globals.h:1109 +#: ../globals.h:982 msgid "E139: File is loaded in another buffer" msgstr "E139: 文件已在另一个缓冲区中被加载" -#: ../globals.h:1110 +#: ../globals.h:983 #, c-format msgid "E764: Option '%s' is not set" msgstr "E764: 没有设定选项 '%s'" -#: ../globals.h:1111 -#, fuzzy +#: ../globals.h:984 msgid "E850: Invalid register name" -msgstr "E354: 无效的寄存器名: '%s'" +msgstr "E850: 寄存器名无效" + +#: ../globals.h:985 +#, c-format +msgid "E919: Directory not found in '%s': \"%s\"" +msgstr "E919: '%s' 中未找到目录:\"%s\"" + +#: ../globals.h:986 +msgid "E952: Autocommand caused recursive behavior" +msgstr "E952: 自动命令导致了递归行为" + +#: ../globals.h:987 +msgid "E328: Menu only exists in another mode" +msgstr "E328: 菜单只在其它模式中存在" + +#: ../globals.h:988 +msgid "E813: Cannot close autocmd window" +msgstr "E813: 不能关闭自动命令窗口" + +#: ../globals.h:989 +#, c-format +msgid "E686: Argument of %s must be a List" +msgstr "E686: %s 的参数必须是列表" + +#: ../globals.h:990 +msgid "E519: Option not supported" +msgstr "E519: 不支持该选项" + +#: ../globals.h:991 +msgid "E856: Filename too long" +msgstr "E856: 文件名过长" + +#: ../globals.h:992 +msgid "E806: using Float as a String" +msgstr "E806: 将浮点数当作字符串使用" + +#: ../globals.h:993 +msgid "E788: Not allowed to edit another buffer now" +msgstr "E788: 目前不允许编辑别的缓冲区" + +#: ../globals.h:995 +#, c-format +msgid "E5500: autocmd has thrown an exception: %s" +msgstr "E5500: 自动命令抛出了异常:%s" + +#: ../globals.h:996 +msgid "E5520: <Cmd> mapping must end with <CR>" +msgstr "E5520: <Cmd> 映射必须以 <CR> 结束" + +#: ../globals.h:998 +msgid "E5521: <Cmd> mapping must end with <CR> before second <Cmd>" +msgstr "" + +#: ../globals.h:1000 +#, c-format +msgid "E5555: API call: %s" +msgstr "" -#: ../globals.h:1114 +#: ../globals.h:1002 +#, c-format +msgid "E5560: %s must not be called in a lua loop callback" +msgstr "E5560: Lua 循环回调中不能调用 %s" + +#: ../globals.h:1004 +msgid "E5601: Cannot close window, only floating window would remain" +msgstr "E5601: 无法关闭窗口,不然就只剩下浮动窗口了" + +#: ../globals.h:1005 +msgid "E5602: Cannot exchange or rotate float" +msgstr "" + +#: ../globals.h:1007 +msgid "E1155: Cannot define autocommands for ALL events" +msgstr "E1155: 不能对所有事件定义自动命令" + +#: ../globals.h:1009 +msgid "E1240: Resulting text too long" +msgstr "E1240: 得到的文本过长" + +#: ../globals.h:1011 +msgid "E1247: Line number out of range" +msgstr "E1247: 行号超出范围" + +#: ../globals.h:1013 +msgid "E5248: Invalid character in group name" +msgstr "E5248: 组名中含有无效字符" + +#: ../globals.h:1015 +msgid "E1249: Highlight group name too long" +msgstr "E1249: 高亮组名称过长" + +#: ../globals.h:1018 +msgid "E5767: Cannot use :undo! to redo or move to a different undo branch" +msgstr "" + +#: ../globals.h:1020 msgid "search hit TOP, continuing at BOTTOM" msgstr "已查找到文件开头,再从结尾继续查找" -#: ../globals.h:1115 +#: ../globals.h:1021 msgid "search hit BOTTOM, continuing at TOP" msgstr "已查找到文件结尾,再从开头继续查找" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: 缺少冒号" +#: ../globals.h:1023 +msgid " line " +msgstr " 行 " -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: 无效的部分" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: 应该要有数字" +#: ../help.c:79 +msgid "E478: Don't panic!" +msgstr "E478: 不要慌!" -#: ../hardcopy.c:473 +#: ../help.c:120 #, c-format -msgid "Page %d" -msgstr "第 %d 页" +msgid "E661: Sorry, no '%s' help for %s" +msgstr "E661: 抱歉,没有 '%s' 的 %s 的说明" -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "没有要打印的文字" +#: ../help.c:122 +#, c-format +msgid "E149: Sorry, no help for %s" +msgstr "E149: 抱歉,没有 %s 的说明" -#: ../hardcopy.c:668 +#: ../help.c:154 #, c-format -msgid "Printing page %d (%d%%)" -msgstr "正在打印第 %d 页 (%d%%)" +msgid "Sorry, help file \"%s\" not found" +msgstr "抱歉,找不到帮助文件 \"%s\"" -#: ../hardcopy.c:680 +#: ../help.c:900 ../help.c:1094 #, c-format -msgid " Copy %d of %d" -msgstr "复制 %d / %d" +msgid "E151: No match: %s" +msgstr "E151: 没有匹配:%s" -#: ../hardcopy.c:733 +#: ../help.c:920 #, c-format -msgid "Printed: %s" -msgstr "已打印: %s" +msgid "E152: Cannot open %s for writing" +msgstr "E152: 无法打开并写入 %s" -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "打印中止" +#: ../help.c:941 +#, c-format +msgid "E153: Unable to open %s for reading" +msgstr "E153: 无法打开并读取 %s" -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: 写入 PostScript 输出文件出错" +#: ../help.c:969 +#, c-format +msgid "E670: Mix of help file encodings within a language: %s" +msgstr "E670: 在一种语言中混合了多种帮助文件编码: %s" -#: ../hardcopy.c:1747 +#: ../help.c:1026 #, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: 无法打开文件 \"%s\"" +msgid "E154: Duplicate tag \"%s\" in file %s/%s" +msgstr "E154: Tag \"%s\" 在文件 %s/%s 中重复出现" -#: ../hardcopy.c:1756 ../hardcopy.c:2470 +#: ../help.c:1185 #, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: 无法读取 PostScript 资源文件 \"%s\"" +msgid "E150: Not a directory: %s" +msgstr "E150: 不是目录: %s" -#: ../hardcopy.c:1772 +#: ../highlight.c:95 +msgid "E424: Too many different highlighting attributes in use" +msgstr "E424: 使用了太多不同的高亮度属性" + +#: ../highlight_group.c:910 #, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: 文件 \"%s\" 不是 PostScript 资源文件" +msgid "E411: highlight group not found: %s" +msgstr "E411: 找不到 highlight group: %s" -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 +#: ../highlight_group.c:933 #, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: 文件 \"%s\" 不是已支持的 PostScript 资源文件" +msgid "E412: Not enough arguments: \":highlight link %s\"" +msgstr "E412: 参数太少: \":highlight link %s\"" -#: ../hardcopy.c:1856 +#: ../highlight_group.c:939 #, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" 资源文件版本不正确" +msgid "E413: Too many arguments: \":highlight link %s\"" +msgstr "E413: 参数过多: \":highlight link %s\"" -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: 不兼容的多字节编码和字符集。" +#: ../highlight_group.c:966 +msgid "E414: group has settings, highlight link ignored" +msgstr "E414: 已设定组, 忽略 highlight link" -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset 在多字节编码下不能为空" +#: ../highlight_group.c:1039 +#, c-format +msgid "E415: unexpected equal sign: %s" +msgstr "E415: 不该有的等号: %s" -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: 没有指定多字节打印的默认字体" +#: ../highlight_group.c:1051 ../highlight_group.c:1099 +msgid "E423: Illegal argument" +msgstr "E423: 无效的参数" -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: 无法打开 PostScript 输出文件" +#: ../highlight_group.c:1072 +#, c-format +msgid "E416: missing equal sign: %s" +msgstr "E416: 缺少等号: %s" + +#: ../highlight_group.c:1093 +#, c-format +msgid "E417: missing argument: %s" +msgstr "E417: 缺少参数: %s" -#: ../hardcopy.c:2458 +#: ../highlight_group.c:1127 #, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: 无法打开文件 \"%s\"" +msgid "E418: Illegal value: %s" +msgstr "E418: 不合法的值: %s" -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: 找不到 PostScript 资源文件 \"prolog.ps\"" +#: ../highlight_group.c:1175 +msgid "E419: FG color unknown" +msgstr "E419: 错误的前景颜色" -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: 找不到 PostScript 资源文件 \"cidfont.ps\"" +#: ../highlight_group.c:1183 +msgid "E420: BG color unknown" +msgstr "E420: 错误的背景颜色" -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 +#: ../highlight_group.c:1198 #, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: 找不到 PostScript 资源文件 \"%s.ps\"" +msgid "E421: Color name or number not recognized: %s" +msgstr "E421: 错误的颜色名称或数值: %s" -#: ../hardcopy.c:2654 +#: ../highlight_group.c:1330 #, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: 无法转换至打印编码 \"%s\"" +msgid "E423: Illegal argument: %s" +msgstr "E423: 无效的参数: %s" + +#: ../highlight_group.c:1845 +msgid "E669: Unprintable character in group name" +msgstr "E669: 组名中存在不可显示字符" -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "发送到打印机……" +#: ../highlight_group.c:1872 +msgid "E849: Too many highlight and syntax groups" +msgstr "E849: 高亮和语法组过多" -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: 无法打印 PostScript 文件" +#: ../input.c:232 +msgid "Type number and <Enter> or click with the mouse (q or empty cancels): " +msgstr "输入数字并按 <Enter> 或点击鼠标(q 或空取消):" -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "打印任务已被发送。" +#: ../input.c:235 +msgid "Type number and <Enter> (q or empty cancels): " +msgstr "输入数字并按 <Enter>(q 或空取消):" -#: ../if_cscope.c:85 -msgid "Add a new database" -msgstr "添加一个新的数据库" +#: ../insexpand.c:99 +msgid " Keyword completion (^N^P)" +msgstr " 关键字补全 (^N^P)" -#: ../if_cscope.c:87 -msgid "Query for a pattern" -msgstr "查询一个模式" +#. CTRL_X_NORMAL, ^P/^N compl. +#: ../insexpand.c:100 +msgid " ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)" +msgstr " ^X 模式 (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)" -#: ../if_cscope.c:89 -msgid "Show this message" -msgstr "显示此信息" +#. CTRL_X_SCROLL: depends on state +#: ../insexpand.c:102 +msgid " Whole line completion (^L^N^P)" +msgstr " 整行补全 (^L^N^P)" -#: ../if_cscope.c:91 -msgid "Kill a connection" -msgstr "结束一个连接" +#: ../insexpand.c:103 +msgid " File name completion (^F^N^P)" +msgstr " 文件名补全 (^F^N^P)" -#: ../if_cscope.c:93 -msgid "Reinit all connections" -msgstr "重置所有连接" +#: ../insexpand.c:104 +msgid " Tag completion (^]^N^P)" +msgstr " Tag 补全 (^]^N^P)" -#: ../if_cscope.c:95 -msgid "Show connections" -msgstr "显示连接" +#: ../insexpand.c:105 +msgid " Path pattern completion (^N^P)" +msgstr " 头文件模式补全 (^N^P)" -#: ../if_cscope.c:101 -#, c-format -msgid "E560: Usage: cs[cope] %s" -msgstr "E560: 用法: cs[cope] %s" +#: ../insexpand.c:106 +msgid " Definition completion (^D^N^P)" +msgstr " 定义补全 (^D^N^P)" -#: ../if_cscope.c:225 -msgid "This cscope command does not support splitting the window.\n" -msgstr "这个 cscope 命令不支持分割窗口。\n" +#. CTRL_X_FINISHED +#: ../insexpand.c:108 +msgid " Dictionary completion (^K^N^P)" +msgstr " Dictionary 补全 (^K^N^P)" -#: ../if_cscope.c:266 -msgid "E562: Usage: cstag <ident>" -msgstr "E562: 用法: cstag <ident>" +#: ../insexpand.c:109 +msgid " Thesaurus completion (^T^N^P)" +msgstr " Thesaurus 补全 (^T^N^P)" -#: ../if_cscope.c:313 -msgid "E257: cstag: tag not found" -msgstr "E257: cstag: 找不到 tag" +#. CTRL_X_EVAL doesn't use msg. +#: ../insexpand.c:110 ../insexpand.c:116 +msgid " Command-line completion (^V^N^P)" +msgstr " 命令行补全 (^V^N^P)" -#: ../if_cscope.c:461 -#, c-format -msgid "E563: stat(%s) error: %d" -msgstr "E563: stat(%s) 错误: %d" +#: ../insexpand.c:111 +msgid " User defined completion (^U^N^P)" +msgstr " 用户自定义补全 (^U^N^P)" -#: ../if_cscope.c:551 -#, c-format -msgid "E564: %s is not a directory or a valid cscope database" -msgstr "E564: %s 不是目录或有效的 cscope 数据库" +#: ../insexpand.c:112 +msgid " Omni completion (^O^N^P)" +msgstr " 全能补全 (^O^N^P)" -#: ../if_cscope.c:566 -#, c-format -msgid "Added cscope database %s" -msgstr "添加了 cscope 数据库 %s" +#: ../insexpand.c:113 +msgid " Spelling suggestion (s^N^P)" +msgstr " 拼写建议 (s^N^P)" + +#: ../insexpand.c:114 +msgid " Keyword Local completion (^N^P)" +msgstr " 关键字局部补全 (^N^P)" + +#: ../insexpand.c:190 +msgid "Hit end of paragraph" +msgstr "已到段落结尾" + +#: ../insexpand.c:191 +msgid "E840: Completion function deleted text" +msgstr "E840: 补全函数删除了文本" -#: ../if_cscope.c:616 +#: ../insexpand.c:469 +msgid "'dictionary' option is empty" +msgstr "选项 'dictionary' 为空" + +#: ../insexpand.c:470 +msgid "'thesaurus' option is empty" +msgstr "选项 'thesaurus' 为空" + +#: ../insexpand.c:1456 #, c-format -msgid "E262: error reading cscope connection %<PRId64>" -msgstr "E262: 读取 cscope 连接 %<PRId64> 出错" +msgid "Scanning dictionary: %s" +msgstr "正在扫描 dictionary: %s" -#: ../if_cscope.c:711 -msgid "E561: unknown cscope search type" -msgstr "E561: 未知的 cscope 查找类型" +#: ../insexpand.c:1854 +msgid " (insert) Scroll (^E/^Y)" +msgstr " (插入) Scroll (^E/^Y)" -#: ../if_cscope.c:752 ../if_cscope.c:789 -msgid "E566: Could not create cscope pipes" -msgstr "E566: 无法创建 cscope 管道" +#: ../insexpand.c:1856 +msgid " (replace) Scroll (^E/^Y)" +msgstr " (替换) Scroll (^E/^Y)" -#: ../if_cscope.c:767 -msgid "E622: Could not fork for cscope" -msgstr "E622: 无法对 cscope 进行 fork" +#: ../insexpand.c:2600 +msgid "E785: complete() can only be used in Insert mode" +msgstr "E785: complete() 只能在插入模式中使用" -#: ../if_cscope.c:849 -#, fuzzy -msgid "cs_create_connection setpgid failed" -msgstr "cs_create_connection 执行失败" +#. reset in msg_trunc_attr() +#: ../insexpand.c:2881 +#, c-format +msgid "Scanning: %s" +msgstr "正在扫描: %s" -#: ../if_cscope.c:853 ../if_cscope.c:889 -msgid "cs_create_connection exec failed" -msgstr "cs_create_connection 执行失败" +#. reset in msg_trunc_attr() +#: ../insexpand.c:2912 +msgid "Scanning tags." +msgstr "扫描标签." -#: ../if_cscope.c:863 ../if_cscope.c:902 -msgid "cs_create_connection: fdopen for to_fp failed" -msgstr "cs_create_connection: fdopen to_fp 失败" +#: ../insexpand.c:3465 +msgid "match in file" +msgstr "在文件中匹配" -#: ../if_cscope.c:865 ../if_cscope.c:906 -msgid "cs_create_connection: fdopen for fr_fp failed" -msgstr "cs_create_connection: fdopen fr_fp 失败" +#: ../insexpand.c:4198 +msgid " Adding" +msgstr " 增加" + +#. showmode might reset the internal line pointers, so it must +#. be called before line = ml_get(), or when this address is no +#. longer needed. -- Acevedo. +#: ../insexpand.c:4243 +msgid "-- Searching..." +msgstr "-- 查找中..." -#: ../if_cscope.c:890 -msgid "E623: Could not spawn cscope process" -msgstr "E623: 无法生成 cscope 进程" +#: ../insexpand.c:4263 +msgid "Back at original" +msgstr "回到起点" + +#: ../insexpand.c:4266 +msgid "Word from other line" +msgstr "另一行的词" -#: ../if_cscope.c:932 -msgid "E567: no cscope connections" -msgstr "E567: 没有 cscope 连接" +#: ../insexpand.c:4269 +msgid "The only match" +msgstr "唯一匹配" -#: ../if_cscope.c:1009 +#: ../insexpand.c:4287 #, c-format -msgid "E469: invalid cscopequickfix flag %c for %c" -msgstr "E469: cscopequickfix 标志 %c 对 %c 无效" +msgid "match %d of %d" +msgstr "匹配 %d / %d" -#: ../if_cscope.c:1058 +#: ../insexpand.c:4291 #, c-format -msgid "E259: no matches found for cscope query %s of %s" -msgstr "E259: cscope 查询 %s %s 没有找到匹配的结果" +msgid "match %d" +msgstr "匹配 %d" -#: ../if_cscope.c:1142 -msgid "cscope commands:\n" -msgstr "cscope 命令:\n" +#: ../locale.c:227 +#, c-format +msgid "Current %slanguage: \"%s\"" +msgstr "当前的 %s语言: \"%s\"" -#: ../if_cscope.c:1150 -#, fuzzy, c-format -msgid "%-5s: %s%*s (Usage: %s)" -msgstr "%-5s: %-30s (用法: %s)" +#: ../locale.c:243 +#, c-format +msgid "E197: Cannot set language to \"%s\"" +msgstr "E197: 不能设定语言为 \"%s\"" + +#: ../lua/converter.c:68 ../lua/converter.c:204 ../lua/converter.c:621 +#, c-format +msgid "E1502: Lua failed to grow stack to %i" +msgstr "" -#: ../if_cscope.c:1155 +#: ../lua/converter.c:380 msgid "" -"\n" -" a: Find assignments to this symbol\n" -" c: Find functions calling this function\n" -" d: Find functions called by this function\n" -" e: Find this egrep pattern\n" -" f: Find this file\n" -" g: Find this definition\n" -" i: Find files #including this file\n" -" s: Find this C symbol\n" -" t: Find this text string\n" +"E5100: Cannot convert given lua table: table should either have a sequence " +"of positive integer keys or contain only string keys" msgstr "" -"\n" -" a: 搜索对此符号的赋值\n" -" c: 搜索调用此函数的函数\n" -" d: 搜索此函数调用的函数\n" -" e: 搜索此 egrep 模式\n" -" f: 搜索此文件\n" -" g: 搜索此定义\n" -" i: 搜索包含此文件的文件\n" -" s: 搜索此 C 符号\n" -" t: 搜索此文本字符串\n" -#: ../if_cscope.c:1226 -msgid "E568: duplicate cscope database not added" -msgstr "E568: 重复的 cscope 数据库未被加入" +#: ../lua/converter.c:409 ../lua/converter.c:415 +msgid "E5101: Cannot convert given lua type" +msgstr "" -#: ../if_cscope.c:1335 +#: ../lua/converter.c:509 ../lua/converter.c:532 #, c-format -msgid "E261: cscope connection %s not found" -msgstr "E261: 找不到 cscope 连接 %s" +msgid "E5102: Lua failed to grow stack to %i" +msgstr "" -#: ../if_cscope.c:1364 +#: ../lua/executor.c:333 #, c-format -msgid "cscope connection %s closed" -msgstr "cscope 连接 %s 已关闭" +msgid "Error executing vim.schedule lua callback: %.*s" +msgstr "" -#. should not reach here -#: ../if_cscope.c:1486 -msgid "E570: fatal error in cs_manage_matches" -msgstr "E570: cs_manage_matches 严重错误" +#: ../lua/executor.c:782 +msgid "E970: Failed to initialize lua interpreter\n" +msgstr "E970: 无法初始化 Lua 解释器\n" -#: ../if_cscope.c:1693 -#, c-format -msgid "Cscope tag: %s" -msgstr "Cscope tag: %s" +#: ../lua/executor.c:787 +msgid "E970: Failed to initialize builtin lua modules\n" +msgstr "E970: 无法初始化内置 Lua 模块\n" -#: ../if_cscope.c:1711 -msgid "" -"\n" -" # line" +#: ../lua/executor.c:972 +#, c-format +msgid "E5114: Error while converting print argument #%i: %.*s" msgstr "" -"\n" -" # 行 " -#: ../if_cscope.c:1713 -msgid "filename / context / line\n" -msgstr "文件名 / 上下文 / 行\n" +#: ../lua/executor.c:1066 +#, fuzzy, c-format +msgid "E5115: Error while loading debug string: %.*s" +msgstr "E782: 当读取.sug 文件时错误" + +#: ../lua/executor.c:1068 +#, fuzzy, c-format +msgid "E5116: Error while calling debug string: %.*s" +msgstr "E782: 当读取.sug 文件时错误" + +#: ../lua/executor.c:1370 +#, fuzzy, c-format +msgid "E5108: Error executing Lua function: %.*s" +msgstr "E208: 写入文件 \"%s\" 出错" + +#: ../lua/executor.c:1390 +#, fuzzy, c-format +msgid "E5107: Error loading lua %.*s" +msgstr "E210: 读取文件 \"%s\" 出错" + +#: ../lua/executor.c:1397 +#, fuzzy, c-format +msgid "E5108: Error executing lua %.*s" +msgstr "E208: 写入文件 \"%s\" 出错" -#: ../if_cscope.c:1809 +#: ../lua/executor.c:1539 #, c-format -msgid "E609: Cscope error: %s" -msgstr "E609: Cscope 错误: %s" +msgid "Error executing lua callback: %.*s" +msgstr "" -#: ../if_cscope.c:2053 -msgid "All cscope databases reset" -msgstr "所有 cscope 数据库已被重置" +#: ../lua/executor.c:1605 +msgid "cannot save undo information" +msgstr "无法保存撤销信息" -#: ../if_cscope.c:2123 -msgid "no cscope connections\n" -msgstr "没有 cscope 连接\n" +#: ../lua/executor.c:1631 +#, fuzzy, c-format +msgid "E5109: Error loading lua: %.*s" +msgstr "E209: 关闭文件 \"%s\" 出错" -#: ../if_cscope.c:2126 -msgid " # pid database name prepend path\n" -msgstr " # pid 数据库名 prepend path\n" +#: ../lua/executor.c:1641 +#, fuzzy, c-format +msgid "E5110: Error executing lua: %.*s" +msgstr "E210: 读取文件 \"%s\" 出错" -#: ../main.c:144 -msgid "Unknown option argument" -msgstr "未知的选项参数" +#: ../lua/executor.c:1653 ../lua/executor.c:1705 +#, fuzzy, c-format +msgid "E5111: Error calling lua: %.*s" +msgstr "E209: 关闭文件 \"%s\" 出错" -#: ../main.c:146 -msgid "Too many edit arguments" -msgstr "编辑参数过多" +#. 1 +#: ../lua/executor.c:1715 +#, fuzzy, c-format +msgid "E5112: Error while creating lua chunk: %.*s" +msgstr "E782: 当读取.sug 文件时错误" + +#: ../lua/executor.c:1726 +#, fuzzy, c-format +msgid "E5113: Error while calling lua chunk: %.*s" +msgstr "E782: 当读取.sug 文件时错误" -#: ../main.c:148 +#: ../lua/executor.c:1791 +#, c-format +msgid "Error executing vim._expand_pat: %.*s" +msgstr "" + +#: ../lua/executor.c:1928 +#, c-format +msgid "Error executing vim.on_key Lua callback: %.*s" +msgstr "" + +#: ../lua/executor.c:2149 +#, c-format +msgid "Error executing Lua callback: %.*s" +msgstr "" + +#. Error messages +#: ../main.c:126 msgid "Argument missing after" msgstr "缺少必要的参数" -#: ../main.c:150 +#: ../main.c:127 msgid "Garbage after option argument" msgstr "选项参数后的内容无效" -#: ../main.c:152 +#: ../main.c:128 +msgid "Unknown option argument" +msgstr "未知的选项参数" + +#: ../main.c:129 +msgid "Too many edit arguments" +msgstr "编辑参数过多" + +#: ../main.c:131 msgid "Too many \"+command\", \"-c command\" or \"--cmd command\" arguments" msgstr "\"+command\"、\"-c command\" 或 \"--cmd command\" 参数过多" -#: ../main.c:154 -msgid "Invalid argument for" -msgstr "无效的参数" - -#: ../main.c:294 +#: ../main.c:1004 #, c-format -msgid "%d files to edit\n" -msgstr "还有 %d 个文件等待编辑\n" +msgid "E5421: Failed to open stdin: %s" +msgstr "E5421: 无法打开标准输入:%s" -#: ../main.c:1342 -msgid "Attempt to open script file again: \"" -msgstr "试图再次打开脚本文件: \"" +#: ../main.c:1284 +#, c-format +msgid "Attempt to open script file again: \"%s %s\"\n" +msgstr "试图再次打开脚本文件:\"%s %s\"\n" -#: ../main.c:1350 -msgid "Cannot open for reading: \"" -msgstr "无法打开并读取: \"" +#: ../main.c:1303 +#, c-format +msgid "Cannot open for reading: \"%s\": %s\n" +msgstr "无法打开并读取:\"%s\":%s\n" -#: ../main.c:1393 +#: ../main.c:1337 msgid "Cannot open for script output: \"" msgstr "无法打开并输出脚本: \"" -#: ../main.c:1622 -msgid "Vim: Warning: Output is not to a terminal\n" -msgstr "Vim: 警告: 输出不是到终端(屏幕)\n" - -#: ../main.c:1624 -msgid "Vim: Warning: Input is not from a terminal\n" -msgstr "Vim: 警告: 输入不是来自终端(键盘)\n" +#: ../main.c:1388 +msgid "--embed conflicts with -es/-Es" +msgstr "" #. just in case.. -#: ../main.c:1891 +#: ../main.c:1795 msgid "pre-vimrc command line" msgstr "pre-vimrc 命令行" -#: ../main.c:1964 +#: ../main.c:1916 +#, c-format +msgid "E5422: Conflicting configs: \"%s\" \"%s\"" +msgstr "" + +#: ../main.c:1985 #, c-format msgid "E282: Cannot read from \"%s\"" msgstr "E282: 无法读取 \"%s\"" -#: ../main.c:2149 +#: ../main.c:2071 +#, fuzzy 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)" -msgstr "[文件 ..] 编辑指定的文件" +#. kill us with CTRL-C here, if you like +#: ../main.c:2094 +msgid "Usage:\n" +msgstr "用法:\n" -#: ../main.c:2179 -msgid "- read text from stdin" -msgstr "- 从标准输入(stdin)读取文本" +#: ../main.c:2095 +msgid " nvim [options] [file ...] Edit file(s)\n" +msgstr " nvim [options] [文件 ..] 编辑指定的文件\n" -#: ../main.c:2180 -msgid "-t tag edit file where tag is defined" -msgstr "-t tag 编辑 tag 定义处的文件" +#: ../main.c:2096 +msgid " nvim [options] -t <tag> Edit file where tag is defined\n" +msgstr " nvim [options] -t tag 编辑 tag 定义处的文件\n" -#: ../main.c:2181 -msgid "-q [errorfile] edit file with first error" -msgstr "-q [errorfile] 编辑第一个出错处的文件" +#: ../main.c:2097 +msgid " nvim [options] -q [errorfile] Edit file with first error\n" +msgstr " nvim [options] -q [errorfile] 编辑第一个出错处的文件\n" -#: ../main.c:2187 +#: ../main.c:2098 msgid "" "\n" -"\n" -"Usage:" +"Options:\n" msgstr "" "\n" -"\n" -"用法:" - -#: ../main.c:2189 -msgid " vim [arguments] " -msgstr " vim [参数] " +"选项:\n" -#: ../main.c:2193 -msgid "" -"\n" -" or:" -msgstr "" -"\n" -" 或:" +#: ../main.c:2099 +msgid " -- Only file names after this\n" +msgstr " -- 在这以后只有文件名\n" -#: ../main.c:2196 -msgid "" -"\n" -"\n" -"Arguments:\n" -msgstr "" -"\n" -"\n" -"参数:\n" +#: ../main.c:2100 +msgid " + Start at end of file\n" +msgstr " + 从文件末尾开始\n" -#: ../main.c:2197 -msgid "--\t\t\tOnly file names after this" -msgstr "--\t\t\t在这以后只有文件名" +#: ../main.c:2101 +msgid " --cmd <cmd> Execute <cmd> before any config\n" +msgstr " --cmd <cmd> 加载任何配置前执行 <cmd>\n" -#: ../main.c:2199 -msgid "--literal\t\tDon't expand wildcards" -msgstr "--literal\t\t不扩展通配符" +#: ../main.c:2102 +msgid " +<cmd>, -c <cmd> Execute <cmd> after config and first file\n" +msgstr " +<cmd>, -c <cmd> 加载第一个配置和第一个文件后执行 <cmd>\n" -#: ../main.c:2201 -msgid "-v\t\t\tVi mode (like \"vi\")" -msgstr "-v\t\t\tVi 模式 (同 \"vi\")" +#: ../main.c:2104 +msgid " -b Binary mode\n" +msgstr " -b 二进制模式\n" -#: ../main.c:2202 -msgid "-e\t\t\tEx mode (like \"ex\")" -msgstr "-e\t\t\tEx 模式 (同 \"ex\")" +#: ../main.c:2105 +msgid " -d Diff mode\n" +msgstr " -d 差异模式\n" -#: ../main.c:2203 -msgid "-E\t\t\tImproved Ex mode" -msgstr "" +#: ../main.c:2106 +msgid " -e, -E Ex mode\n" +msgstr " -e, -E Ex 模式\n" -#: ../main.c:2204 -msgid "-s\t\t\tSilent (batch) mode (only for \"ex\")" -msgstr "-s\t\t\t安静(批处理)模式 (只能与 \"ex\" 一起使用)" +#: ../main.c:2107 +msgid " -es, -Es Silent (batch) mode\n" +msgstr " -es, -Es 安静(批处理)模式\n" -#: ../main.c:2205 -msgid "-d\t\t\tDiff mode (like \"vimdiff\")" -msgstr "-d\t\t\tDiff 模式 (同 \"vimdiff\")" +#: ../main.c:2108 +msgid " -h, --help Print this help message\n" +msgstr " -h, --help 打印此帮助信息\n" -#: ../main.c:2206 -msgid "-y\t\t\tEasy mode (like \"evim\", modeless)" -msgstr "-y\t\t\t容易模式 (同 \"evim\",无模式)" +#: ../main.c:2109 +msgid " -i <shada> Use this shada file\n" +msgstr " -i <shada> 使用这个 shada 文件\n" -#: ../main.c:2207 -msgid "-R\t\t\tReadonly mode (like \"view\")" -msgstr "-R\t\t\t只读模式 (同 \"view\")" +#: ../main.c:2110 +msgid " -m Modifications (writing files) not allowed\n" +msgstr " -m 不可修改(写入文件)\n" -#: ../main.c:2209 -msgid "-m\t\t\tModifications (writing files) not allowed" -msgstr "-m\t\t\t不可修改(写入文件)" +#: ../main.c:2111 +msgid " -M Modifications in text not allowed\n" +msgstr " -M 文本不可修改\n" -#: ../main.c:2210 -msgid "-M\t\t\tModifications in text not allowed" -msgstr "-M\t\t\t文本不可修改" +#: ../main.c:2112 +msgid " -n No swap file, use memory only\n" +msgstr " -n 不使用交换文件,只使用内存\n" -#: ../main.c:2211 -msgid "-b\t\t\tBinary mode" -msgstr "-b\t\t\t二进制模式" +#: ../main.c:2113 +msgid " -o[N] Open N windows (default: one per file)\n" +msgstr " -o[N] 打开 N 个窗口(默认:每个文件一个)\n" -#: ../main.c:2212 -msgid "-l\t\t\tLisp mode" -msgstr "-l\t\t\tLisp 模式" +#: ../main.c:2114 +#, fuzzy +msgid "" +" -O[N] Open N vertical windows (default: one per file)\n" +msgstr "-o[N]\t\t打开 N 个窗口 (默认值: 每个文件一个)" -#: ../main.c:2213 -msgid "-C\t\t\tCompatible with Vi: 'compatible'" -msgstr "-C\t\t\t兼容传统的 Vi: 'compatible'" +#: ../main.c:2115 +msgid " -p[N] Open N tab pages (default: one per file)\n" +msgstr " -p[N] 打开 N 个标签页(默认:每个文件一个)\n" -#: ../main.c:2214 -msgid "-N\t\t\tNot fully Vi compatible: 'nocompatible'" -msgstr "-N\t\t\t不完全兼容传统的 Vi: 'nocompatible'" +#: ../main.c:2116 +msgid " -r, -L List swap files\n" +msgstr " -r, -L 列出交换文件\n" -#: ../main.c:2215 -msgid "-V[N][fname]\t\tBe verbose [level N] [log messages to fname]" -msgstr "-V[N][fname]\t\t详细 [level N] [log messages to fname]" +#: ../main.c:2117 +msgid " -r <file> Recover edit state for this file\n" +msgstr " -r <file> 恢复这个文件的编辑状态\n" -#: ../main.c:2216 -msgid "-D\t\t\tDebugging mode" -msgstr "-D\t\t\t调试模式" +#: ../main.c:2118 +msgid " -R Read-only mode\n" +msgstr " -R 只读模式\n" -#: ../main.c:2217 -msgid "-n\t\t\tNo swap file, use memory only" -msgstr "-n\t\t\t不使用交换文件,只使用内存" +#: ../main.c:2119 +msgid " -S <session> Source <session> after loading the first file\n" +msgstr " -S <session> 加载第一个文件后执行文件 <session>\n" -#: ../main.c:2218 -msgid "-r\t\t\tList swap files and exit" -msgstr "-r\t\t\t列出交换文件并退出" +#: ../main.c:2120 +msgid " -s <scriptin> Read Normal mode commands from <scriptin>\n" +msgstr " -s <scriptin> 从文件 <scriptin> 读入正常模式的命令\n" -#: ../main.c:2219 -msgid "-r (with file name)\tRecover crashed session" -msgstr "-r (跟文件名)\t\t恢复崩溃的会话" +#: ../main.c:2121 +msgid " -u <config> Use this config file\n" +msgstr " -u <config> 使用此配置文件\n" -#: ../main.c:2220 -msgid "-L\t\t\tSame as -r" -msgstr "-L\t\t\t同 -r" +#: ../main.c:2122 +msgid " -v, --version Print version information\n" +msgstr " -v, --version 打印版本信息\n" -#: ../main.c:2221 -msgid "-A\t\t\tstart in Arabic mode" -msgstr "-A\t\t\t以 Arabic 模式启动" +#: ../main.c:2123 +msgid " -V[N][file] Verbose [level][file]\n" +msgstr " -V[N][file] 啰嗦 [等级][文件]\n" -#: ../main.c:2222 -msgid "-H\t\t\tStart in Hebrew mode" -msgstr "-H\t\t\t以 Hebrew 模式启动" +#: ../main.c:2125 +msgid " --api-info Write msgpack-encoded API metadata to stdout\n" +msgstr " --api-info 将 msgpack 编码的 API 元数据写入标准输出\n" -#: ../main.c:2223 -msgid "-F\t\t\tStart in Farsi mode" -msgstr "-F\t\t\t以 Farsi 模式启动" +#: ../main.c:2126 +msgid "" +" --clean \"Factory defaults\" (skip user config and plugins, " +"shada)\n" +msgstr "" +" --clean \"出厂设置\"(跳过用户配置、plugin 和 shada)\n" -#: ../main.c:2224 -msgid "-T <terminal>\tSet terminal type to <terminal>" -msgstr "-T <terminal>\t设定终端类型为 <terminal>" +#: ../main.c:2127 +msgid " --embed Use stdin/stdout as a msgpack-rpc channel\n" +msgstr "" -#: ../main.c:2225 -msgid "-u <vimrc>\t\tUse <vimrc> instead of any .vimrc" -msgstr "-u <vimrc>\t\t使用 <vimrc> 替代任何 .vimrc" +#: ../main.c:2128 +msgid " --headless Don't start a user interface\n" +msgstr " --headless 不要启动用户界面\n" -#: ../main.c:2226 -msgid "--noplugin\t\tDon't load plugin scripts" -msgstr "--noplugin\t\t不加载 plugin 脚本" +#: ../main.c:2129 +msgid " --listen <address> Serve RPC API from this address\n" +msgstr " --listen <address> 在这个地址提供 RPC API\n" -#: ../main.c:2227 -msgid "-p[N]\t\tOpen N tab pages (default: one for each file)" -msgstr "-P[N]\t\t打开 N 个标签页 (默认值: 每个文件一个)" +#: ../main.c:2130 +msgid " --noplugin Don't load plugins\n" +msgstr " --noplugin 不加载 plugin 脚本\n" -#: ../main.c:2228 -msgid "-o[N]\t\tOpen N windows (default: one for each file)" -msgstr "-o[N]\t\t打开 N 个窗口 (默认值: 每个文件一个)" +#: ../main.c:2131 +msgid " --remote[-subcommand] Execute commands remotely on a server\n" +msgstr "" -#: ../main.c:2229 -msgid "-O[N]\t\tLike -o but split vertically" -msgstr "-O[N]\t\t同 -o 但垂直分割" +#: ../main.c:2132 +msgid " --server <address> Specify RPC server to send commands to\n" +msgstr "" -#: ../main.c:2230 -msgid "+\t\t\tStart at end of file" -msgstr "+\t\t\t启动后跳到文件末尾" +#: ../main.c:2133 +msgid " --startuptime <file> Write startup timing messages to <file>\n" +msgstr " --startuptime <file> 将启动时间信息写入到文件 <file>\n" -#: ../main.c:2231 -msgid "+<lnum>\t\tStart at line <lnum>" -msgstr "+<lnum>\t\t启动后跳到第 <lnum> 行" +#: ../main.c:2134 +msgid "" +"\n" +"See \":help startup-options\" for all options.\n" +msgstr "" -#: ../main.c:2232 -msgid "--cmd <command>\tExecute <command> before loading any vimrc file" -msgstr "--cmd <command>\t加载任何 vimrc 文件前执行 <command>" +#: ../mapping.c:645 +#, c-format +msgid "E224: global abbreviation already exists for %s" +msgstr "E224: 全局缩写 %s 已存在" -#: ../main.c:2233 -msgid "-c <command>\t\tExecute <command> after loading the first file" -msgstr "-c <command>\t\t加载第一个文件后执行 <command>" +#: ../mapping.c:648 +#, c-format +msgid "E225: global mapping already exists for %s" +msgstr "E225: 全局映射 %s 已存在" -#: ../main.c:2235 -msgid "-S <session>\t\tSource file <session> after loading the first file" -msgstr "-S <session>\t\t加载第一个文件后执行文件 <session>" +#: ../mapping.c:762 +#, c-format +msgid "E226: abbreviation already exists for %s" +msgstr "E226: 缩写 %s 已存在" -#: ../main.c:2236 -msgid "-s <scriptin>\tRead Normal mode commands from file <scriptin>" -msgstr "-s <scriptin>\t从文件 <scriptin> 读入正常模式的命令" +#: ../mapping.c:764 +#, c-format +msgid "E227: mapping already exists for %s" +msgstr "E227: 映射 %s 已存在" -#: ../main.c:2237 -msgid "-w <scriptout>\tAppend all typed commands to file <scriptout>" -msgstr "-w <scriptout>\t将所有输入的命令追加到文件 <scriptout>" +#: ../mapping.c:844 +msgid "No abbreviation found" +msgstr "找不到缩写" -#: ../main.c:2238 -msgid "-W <scriptout>\tWrite all typed commands to file <scriptout>" -msgstr "-W <scriptout>\t将所有输入的命令写入到文件 <scriptout>" +#: ../mapping.c:846 +msgid "No mapping found" +msgstr "找不到映射" -#: ../main.c:2240 -msgid "--startuptime <file>\tWrite startup timing messages to <file>" -msgstr "--startuptime <file>\t将启动时间信息写入到文件 <file>" +#: ../mapping.c:1751 +msgid "E228: makemap: Illegal mode" +msgstr "E228: makemap: 无效的模式" -#: ../main.c:2242 -msgid "-i <viminfo>\t\tUse <viminfo> instead of .viminfo" -msgstr "-i <viminfo>\t\t使用 <viminfo> 取代 .viminfo" +#: ../mapping.c:2181 +msgid "E460: entries missing in mapset() dict argument" +msgstr "E460: mapset() dict 参数缺少项" -#: ../main.c:2243 -msgid "-h or --help\tPrint Help (this message) and exit" -msgstr "-h 或 --help\t打印帮助(本信息)并退出" +#: ../mapping.c:2389 +#, c-format +msgid "E357: 'langmap': Matching character missing for %s" +msgstr "E357: 'langmap': 找不到 %s 对应的字符" -#: ../main.c:2244 -msgid "--version\t\tPrint version information and exit" -msgstr "--version\t\t打印版本信息并退出" +#: ../mapping.c:2409 +#, c-format +msgid "E358: 'langmap': Extra characters after semicolon: %s" +msgstr "E358: 'langmap': 分号后有多余的字符: %s" -#: ../mark.c:676 +#: ../mark.c:879 msgid "No marks set" msgstr "没有设定标记" -#: ../mark.c:678 +#: ../mark.c:881 #, c-format msgid "E283: No marks matching \"%s\"" msgstr "E283: 没有匹配 \"%s\" 的标记" #. Highlight title -#: ../mark.c:687 +#: ../mark.c:895 msgid "" "\n" "mark line col file/text" @@ -3590,7 +4228,7 @@ msgstr "" "标记 行 列 文件/文本" #. Highlight title -#: ../mark.c:789 +#: ../mark.c:1002 msgid "" "\n" " jump line col file/text" @@ -3599,7 +4237,7 @@ msgstr "" " 跳转 行 列 文件/文本" #. Highlight title -#: ../mark.c:831 +#: ../mark.c:1054 msgid "" "\n" "change line col text" @@ -3607,109 +4245,159 @@ msgstr "" "\n" " 改变 行 列 文本" -#: ../mark.c:1238 -msgid "" -"\n" -"# File marks:\n" +#: ../match.c:73 +#, c-format +msgid "E799: Invalid ID: %<PRId64> (must be greater than or equal to 1)" +msgstr "E799: ID 无效:%<PRId64>(必须大于等于 1)" + +#: ../match.c:85 +#, c-format +msgid "E801: ID already taken: %<PRId64>" +msgstr "E801: ID 已被使用:%<PRId64>" + +#: ../match.c:140 +#, c-format +msgid "E5030: Empty list at position %d" msgstr "" -"\n" -"# 文件标记:\n" -#. Write the jumplist with -' -#: ../mark.c:1271 -msgid "" -"\n" -"# Jumplist (newest first):\n" +#: ../match.c:182 +#, c-format +msgid "E5031: List or number required at position %d" +msgstr "E5031: 位置 %d 需要列表或整数" + +#: ../match.c:252 +#, c-format +msgid "E802: Invalid ID: %<PRId64> (must be greater than or equal to 1)" +msgstr "E802: 无效的 ID:%<PRId64>(必须大于等于 1)" + +#: ../match.c:263 +#, c-format +msgid "E803: ID not found: %<PRId64>" +msgstr "E803: 找不到 ID:%<PRId64>" + +#: ../match.c:978 +#, c-format +msgid "E474: List item %d is either not a dictionary or an empty one" msgstr "" -"\n" -"# 跳转列表 (从新到旧):\n" -#: ../mark.c:1352 -msgid "" -"\n" -"# History of marks within files (newest to oldest):\n" +#: ../match.c:987 +#, c-format +msgid "E474: List item %d is missing one of the required keys" msgstr "" -"\n" -"# 文件内的标记历史记录 (从新到旧):\n" -#: ../mark.c:1431 -msgid "Missing '>'" -msgstr "缺少 '>'" +#: ../match.c:1093 +#, c-format +msgid "E798: ID is reserved for \":match\": %<PRId64>" +msgstr "E798: ID 为 \":match\" 保留:%<PRId64>" -#: ../memfile.c:426 +#: ../match.c:1144 +#, c-format +msgid "E798: ID is reserved for \"match\": %<PRId64>" +msgstr "E798: ID 为 \"match\" 保留:%<PRId64>" + +#: ../mbyte.c:98 +#, c-format +msgid "E1109: List item %d is not a List" +msgstr "E1109: 列表项 %d 不是列表" + +#: ../mbyte.c:100 +#, c-format +msgid "E1110: List item %d does not contain 3 numbers" +msgstr "E1110: 列表项 %d 不包含三个整数" + +#: ../mbyte.c:102 +#, c-format +msgid "E1111: List item %d range invalid" +msgstr "E1111: 列表项 %d 范围无效" + +#: ../mbyte.c:104 +#, c-format +msgid "E1112: List item %d cell width invalid" +msgstr "E1112: 列表项 %d 单元格宽度无效" + +#: ../mbyte.c:106 +#, c-format +msgid "E1113: Overlapping ranges for 0x%lx" +msgstr "E1113: 重叠范围为 0x%lx" + +#: ../mbyte.c:108 +msgid "E1114: Only values of 0x100 and higher supported" +msgstr "E1114: 只支持 0x100 或更高的值" + +#: ../memfile.c:334 msgid "E293: block was not locked" msgstr "E293: 块未被锁定" -#: ../memfile.c:799 +#: ../memfile.c:582 msgid "E294: Seek error in swap file read" msgstr "E294: 交换文件读取定位错误" -#: ../memfile.c:803 +#: ../memfile.c:589 msgid "E295: Read error in swap file" msgstr "E295: 交换文件读取错误" -#: ../memfile.c:849 +#: ../memfile.c:641 msgid "E296: Seek error in swap file write" msgstr "E296: 交换文件写入定位错误" -#: ../memfile.c:865 +#: ../memfile.c:657 msgid "E297: Write error in swap file" msgstr "E297: 交换文件写入错误" -#: ../memfile.c:1036 +#: ../memfile.c:801 msgid "E300: Swap file already exists (symlink attack?)" msgstr "E300: 交换文件已存在 (符号连接攻击?)" -#: ../memline.c:318 +#: ../memline.c:288 msgid "E298: Didn't get block nr 0?" msgstr "E298: 找不到块 0?" -#: ../memline.c:361 +#: ../memline.c:328 msgid "E298: Didn't get block nr 1?" msgstr "E298: 找不到块 1?" -#: ../memline.c:377 +#: ../memline.c:342 msgid "E298: Didn't get block nr 2?" msgstr "E298: 找不到块 2?" #. could not (re)open the swap file, what can we do???? -#: ../memline.c:465 +#: ../memline.c:424 msgid "E301: Oops, lost the swap file!!!" msgstr "E301: 噢,交换文件不见了!!!" -#: ../memline.c:477 +#: ../memline.c:430 msgid "E302: Could not rename swap file" msgstr "E302: 无法重命名交换文件" -#: ../memline.c:554 +#: ../memline.c:504 #, c-format msgid "E303: Unable to open swap file for \"%s\", recovery impossible" msgstr "E303: 无法打开 \"%s\" 的交换文件,恢复将不可能" -#: ../memline.c:666 +#: ../memline.c:609 msgid "E304: ml_upd_block0(): Didn't get block 0??" msgstr "E304: ml_upd_block0(): 找不到块 0?" #. no swap files found -#: ../memline.c:830 +#: ../memline.c:745 #, c-format msgid "E305: No swap file found for %s" msgstr "E305: 找不到 %s 的交换文件" -#: ../memline.c:839 +#: ../memline.c:755 msgid "Enter number of swap file to use (0 to quit): " msgstr "请输入要使用的交换文件编号 (0 退出): " -#: ../memline.c:879 +#: ../memline.c:791 #, c-format msgid "E306: Cannot open %s" msgstr "E306: 无法打开 %s" -#: ../memline.c:897 +#: ../memline.c:805 msgid "Unable to read block 0 from " msgstr "无法读取块 0: " -#: ../memline.c:900 +#: ../memline.c:807 msgid "" "\n" "Maybe no changes were made or Vim did not update the swap file." @@ -3717,28 +4405,28 @@ msgstr "" "\n" "可能你没做过任何修改或是 Vim 还来不及更新交换文件。" -#: ../memline.c:909 +#: ../memline.c:816 msgid " cannot be used with this version of Vim.\n" msgstr " 不能在该版本的 Vim 中使用。\n" -#: ../memline.c:911 +#: ../memline.c:818 msgid "Use Vim version 3.0.\n" msgstr "使用 Vim 3.0。\n" -#: ../memline.c:916 +#: ../memline.c:823 #, c-format msgid "E307: %s does not look like a Vim swap file" msgstr "E307: %s 看起来不像是 Vim 交换文件" -#: ../memline.c:922 +#: ../memline.c:829 msgid " cannot be used on this computer.\n" msgstr " 不能在这台电脑上使用。\n" -#: ../memline.c:924 +#: ../memline.c:831 msgid "The file was created on " msgstr "此文件创建于 " -#: ../memline.c:928 +#: ../memline.c:835 msgid "" ",\n" "or the file has been damaged." @@ -3746,100 +4434,92 @@ msgstr "" ",\n" "或是此文件已损坏。" -#: ../memline.c:945 +#: ../memline.c:849 msgid " has been damaged (page size is smaller than minimum value).\n" msgstr "已损坏(页面大小小于最小值)。\n" -#: ../memline.c:974 +#: ../memline.c:879 #, c-format msgid "Using swap file \"%s\"" msgstr "使用交换文件 \"%s\"" -#: ../memline.c:980 +#: ../memline.c:886 #, c-format msgid "Original file \"%s\"" msgstr "原始文件 \"%s\"" -#: ../memline.c:995 +#: ../memline.c:899 msgid "E308: Warning: Original file may have been changed" msgstr "E308: 警告: 原始文件可能已被修改" -#: ../memline.c:1061 +#: ../memline.c:958 #, c-format msgid "E309: Unable to read block 1 from %s" msgstr "E309: 无法从 %s 读取块 1" # do not translate to avoid writing Chinese in files -#: ../memline.c:1065 -#, fuzzy +#: ../memline.c:962 msgid "???MANY LINES MISSING" -msgstr "???缺少了太多行" +msgstr "" # do not translate to avoid writing Chinese in files -#: ../memline.c:1076 -#, fuzzy +#: ../memline.c:974 msgid "???LINE COUNT WRONG" -msgstr "???行数错误" +msgstr "" # do not translate to avoid writing Chinese in files -#: ../memline.c:1082 -#, fuzzy +#: ../memline.c:980 msgid "???EMPTY BLOCK" -msgstr "???空的块" +msgstr "" # do not translate to avoid writing Chinese in files -#: ../memline.c:1103 -#, fuzzy +#: ../memline.c:1000 msgid "???LINES MISSING" -msgstr "???缺少了一些行" +msgstr "" -#: ../memline.c:1128 +#: ../memline.c:1023 #, c-format msgid "E310: Block 1 ID wrong (%s not a .swp file?)" msgstr "E310: 块 1 ID 错误 (%s 不是交换文件?)" # do not translate to avoid writing Chinese in files -#: ../memline.c:1133 -#, fuzzy +#: ../memline.c:1028 msgid "???BLOCK MISSING" -msgstr "???缺少块" +msgstr "" # do not translate to avoid writing Chinese in files -#: ../memline.c:1147 -#, fuzzy +#: ../memline.c:1038 msgid "??? from here until ???END lines may be messed up" -msgstr "??? 从这里到 ???END 的行可能已混乱" +msgstr "" # do not translate to avoid writing Chinese in files -#: ../memline.c:1164 -#, fuzzy +#: ../memline.c:1052 msgid "??? from here until ???END lines may have been inserted/deleted" -msgstr "??? 从这里到 ???END 的行可能已被插入/删除过" +msgstr "" # do not translate to avoid writing Chinese in files -#: ../memline.c:1181 -#, fuzzy +#: ../memline.c:1071 msgid "???END" -msgstr "???END" +msgstr "" -#: ../memline.c:1238 +#: ../memline.c:1125 msgid "E311: Recovery Interrupted" msgstr "E311: 恢复已被中断" -#: ../memline.c:1243 +#: ../memline.c:1129 msgid "" "E312: Errors detected while recovering; look for lines starting with ???" msgstr "E312: 恢复时发生错误;请注意开头为 ??? 的行" -#: ../memline.c:1245 +#: ../memline.c:1131 msgid "See \":help E312\" for more information." msgstr "更多信息请见 \":help E312\"" -#: ../memline.c:1249 +#: ../memline.c:1135 msgid "Recovery completed. You should check if everything is OK." msgstr "恢复完毕。请确定一切正常。" -#: ../memline.c:1251 +#: ../memline.c:1136 msgid "" "\n" "(You might want to write out this file under another name\n" @@ -3847,71 +4527,74 @@ msgstr "" "\n" "(你可能想要将这个文件另存为别的文件名\n" -#: ../memline.c:1252 -#, fuzzy +#: ../memline.c:1137 msgid "and run diff with the original file to check for changes)" -msgstr "再运行 diff 与原文件比较以检查是否有改变)\n" +msgstr "再运行 diff 与原文件比较以检查是否有改变)" -#: ../memline.c:1254 +#: ../memline.c:1139 msgid "Recovery completed. Buffer contents equals file contents." msgstr "恢复完成。缓冲区内容与文件内容相同。" -#: ../memline.c:1255 -#, fuzzy +#: ../memline.c:1141 msgid "" "\n" "You may want to delete the .swp file now.\n" "\n" msgstr "" -"然后把 .swp 文件删掉。\n" +"\n" +"你现在可以删除 .swp 文件了。\n" "\n" #. use msg() to start the scrolling properly -#: ../memline.c:1327 +#: ../memline.c:1206 msgid "Swap files found:" msgstr "找到交换文件:" -#: ../memline.c:1446 +#: ../memline.c:1311 msgid " In current directory:\n" msgstr " 位于当前目录:\n" -#: ../memline.c:1448 +#: ../memline.c:1313 msgid " Using specified name:\n" msgstr " 使用指定的名字:\n" -#: ../memline.c:1450 +#: ../memline.c:1316 msgid " In directory " msgstr " 位于目录 " -#: ../memline.c:1465 +#: ../memline.c:1331 msgid " -- none --\n" msgstr " -- 无 --\n" -#: ../memline.c:1527 +#: ../memline.c:1429 msgid " owned by: " msgstr " 所有者: " -#: ../memline.c:1529 +#: ../memline.c:1431 msgid " dated: " msgstr " 日期: " -#: ../memline.c:1532 ../memline.c:3231 +#: ../memline.c:1433 ../memline.c:1436 ../memline.c:3084 msgid " dated: " msgstr " 日期: " -#: ../memline.c:1548 +#: ../memline.c:1448 msgid " [from Vim version 3.0]" msgstr " [来自 Vim 版本 3.0]" -#: ../memline.c:1550 +#: ../memline.c:1450 msgid " [does not look like a Vim swap file]" msgstr " [不像是 Vim 交换文件]" -#: ../memline.c:1552 +#: ../memline.c:1452 +msgid " [garbled strings (not nul terminated)]" +msgstr "" + +#: ../memline.c:1454 msgid " file name: " msgstr " 文件名: " -#: ../memline.c:1558 +#: ../memline.c:1461 msgid "" "\n" " modified: " @@ -3919,15 +4602,15 @@ msgstr "" "\n" " 修改过: " -#: ../memline.c:1559 +#: ../memline.c:1462 msgid "YES" msgstr "是" -#: ../memline.c:1559 +#: ../memline.c:1462 msgid "no" msgstr "否" -#: ../memline.c:1562 +#: ../memline.c:1465 msgid "" "\n" " user name: " @@ -3935,11 +4618,11 @@ msgstr "" "\n" " 用户名: " -#: ../memline.c:1568 +#: ../memline.c:1471 msgid " host name: " msgstr " 主机名: " -#: ../memline.c:1570 +#: ../memline.c:1473 msgid "" "\n" " host name: " @@ -3947,7 +4630,7 @@ msgstr "" "\n" " 主机名: " -#: ../memline.c:1575 +#: ../memline.c:1479 msgid "" "\n" " process ID: " @@ -3955,11 +4638,11 @@ msgstr "" "\n" " 进程 ID: " -#: ../memline.c:1579 -msgid " (still running)" -msgstr " (仍在运行)" +#: ../memline.c:1482 +msgid " (STILL RUNNING)" +msgstr " (还!在!运!行!)" -#: ../memline.c:1586 +#: ../memline.c:1488 msgid "" "\n" " [not usable on this computer]" @@ -3967,97 +4650,97 @@ msgstr "" "\n" " [不能在本机上使用]" -#: ../memline.c:1590 +#: ../memline.c:1492 msgid " [cannot be read]" msgstr " [无法读取]" -#: ../memline.c:1593 +#: ../memline.c:1496 msgid " [cannot be opened]" msgstr " [无法打开]" -#: ../memline.c:1698 +#: ../memline.c:1638 msgid "E313: Cannot preserve, there is no swap file" msgstr "E313: 无法保留,没有交换文件" -#: ../memline.c:1747 +#: ../memline.c:1687 msgid "File preserved" msgstr "文件已保留" -#: ../memline.c:1749 +#: ../memline.c:1689 msgid "E314: Preserve failed" msgstr "E314: 保留失败" -#: ../memline.c:1819 +#: ../memline.c:1741 #, c-format msgid "E315: ml_get: invalid lnum: %<PRId64>" msgstr "E315: ml_get: 无效的 lnum: %<PRId64>" -#: ../memline.c:1851 -#, c-format -msgid "E316: ml_get: cannot find line %<PRId64>" +#: ../memline.c:1777 +#, fuzzy, c-format +msgid "E316: ml_get: cannot find line %<PRId64> in buffer %d %s" msgstr "E316: ml_get: 找不到第 %<PRId64> 行" -#: ../memline.c:2236 +#: ../memline.c:2128 msgid "E317: pointer block id wrong 3" msgstr "E317: 指针块 id 错误 3" -#: ../memline.c:2311 +#: ../memline.c:2202 msgid "stack_idx should be 0" msgstr "stack_idx 应该是 0" -#: ../memline.c:2369 +#: ../memline.c:2257 msgid "E318: Updated too many blocks?" msgstr "E318: 更新了太多的块?" -#: ../memline.c:2511 +#: ../memline.c:2432 msgid "E317: pointer block id wrong 4" msgstr "E317: 指针块 id 错误 4" -#: ../memline.c:2536 +#: ../memline.c:2458 msgid "deleted block 1?" msgstr "删除了块 1?" -#: ../memline.c:2707 +#: ../memline.c:2603 #, c-format msgid "E320: Cannot find line %<PRId64>" msgstr "E320: 找不到第 %<PRId64> 行" -#: ../memline.c:2916 +#: ../memline.c:2795 msgid "E317: pointer block id wrong" msgstr "E317: 指针块 id 错误" -#: ../memline.c:2930 +#: ../memline.c:2810 msgid "pe_line_count is zero" msgstr "pe_line_count 为零" -#: ../memline.c:2955 +#: ../memline.c:2833 #, c-format msgid "E322: line number out of range: %<PRId64> past the end" msgstr "E322: 行号超出范围: %<PRId64> 超出结尾" -#: ../memline.c:2959 +#: ../memline.c:2836 #, c-format msgid "E323: line count wrong in block %<PRId64>" msgstr "E323: 块 %<PRId64> 行数错误" -#: ../memline.c:2999 +#: ../memline.c:2874 msgid "Stack size increases" msgstr "堆栈大小增加" -#: ../memline.c:3038 +#: ../memline.c:2906 msgid "E317: pointer block id wrong 2" msgstr "E317: 指针块 id 错误 2" -#: ../memline.c:3070 +#: ../memline.c:2938 #, c-format msgid "E773: Symlink loop for \"%s\"" msgstr "E773: \"%s\" 符号连接出现循环" -#: ../memline.c:3221 +#: ../memline.c:3072 msgid "E325: ATTENTION" msgstr "E325: 注意" -#: ../memline.c:3222 +#: ../memline.c:3073 msgid "" "\n" "Found a swap file by the name \"" @@ -4065,44 +4748,42 @@ msgstr "" "\n" "发现交换文件 \"" -#: ../memline.c:3226 +#: ../memline.c:3077 msgid "While opening file \"" msgstr "正在打开文件 \"" -#: ../memline.c:3239 +#: ../memline.c:3082 +#, fuzzy +msgid " CANNOT BE FOUND" +msgstr " 找不到" + +#: ../memline.c:3089 msgid " NEWER than swap file!\n" msgstr " 比交换文件新!\n" -#: ../memline.c:3244 -#, fuzzy +#. Some of these messages are long to allow translation to +#. other languages. +#: ../memline.c:3094 msgid "" "\n" "(1) Another program may be editing the same file. If this is the case,\n" " be careful not to end up with two different instances of the same\n" -" file when making changes." +" file when making changes. Quit, or continue with caution.\n" msgstr "" "\n" "(1) 另一个程序可能也在编辑同一个文件。\n" " 如果是这样,修改时请注意避免同一个文件产生两个不同的版本。\n" -"\n" - -#: ../memline.c:3245 -#, fuzzy -msgid " Quit, or continue with caution.\n" -msgstr " 退出,或小心地继续。\n" +" 退出,或者小心地继续。\n" -#: ../memline.c:3246 -#, fuzzy +#: ../memline.c:3098 msgid "(2) An edit session for this file crashed.\n" -msgstr "" -"\n" -"(2) 上次编辑此文件时崩溃。\n" +msgstr "(2) 上次编辑此文件时崩溃。\n" -#: ../memline.c:3247 +#: ../memline.c:3099 msgid " If this is the case, use \":recover\" or \"vim -r " msgstr " 如果是这样,请用 \":recover\" 或 \"vim -r " -#: ../memline.c:3249 +#: ../memline.c:3101 msgid "" "\"\n" " to recover the changes (see \":help recovery\").\n" @@ -4110,11 +4791,11 @@ msgstr "" "\"\n" " 恢复修改的内容 (请见 \":help recovery\")。\n" -#: ../memline.c:3250 +#: ../memline.c:3102 msgid " If you did this already, delete the swap file \"" msgstr " 如果你已经进行了恢复,请删除交换文件 \"" -#: ../memline.c:3252 +#: ../memline.c:3104 msgid "" "\"\n" " to avoid this message.\n" @@ -4122,23 +4803,23 @@ msgstr "" "\"\n" " 以避免再看到此消息。\n" -#: ../memline.c:3450 ../memline.c:3452 +#: ../memline.c:3270 +msgid "Found a swap file that is not useful, deleting it" +msgstr "找到一个没用的交换文件,删了" + +#: ../memline.c:3296 msgid "Swap file \"" msgstr "交换文件 \"" -#: ../memline.c:3451 ../memline.c:3455 +#: ../memline.c:3297 msgid "\" already exists!" msgstr "\" 已存在!" -#: ../memline.c:3457 +#: ../memline.c:3309 msgid "VIM - ATTENTION" msgstr "VIM - 注意" -#: ../memline.c:3459 -msgid "Swap file already exists!" -msgstr "交换文件已存在!" - -#: ../memline.c:3464 +#: ../memline.c:3312 msgid "" "&Open Read-Only\n" "&Edit anyway\n" @@ -4152,7 +4833,7 @@ msgstr "" "退出(&Q)\n" "中止(&A)" -#: ../memline.c:3467 +#: ../memline.c:3314 msgid "" "&Open Read-Only\n" "&Edit anyway\n" @@ -4168,56 +4849,62 @@ msgstr "" "退出(&Q)\n" "中止(&A)" -#. -#. * Change the ".swp" extension to find another file that can be used. -#. * First decrement the last char: ".swo", ".swn", etc. -#. * If that still isn't enough decrement the last but one char: ".svz" -#. * Can happen when editing many "No Name" buffers. -#. +#. Change the ".swp" extension to find another file that can be used. +#. First decrement the last char: ".swo", ".swn", etc. +#. If that still isn't enough decrement the last but one char: ".svz" +#. Can happen when editing many "No Name" buffers. #. ".s?a" #. ".saa": tried enough, give up -#: ../memline.c:3528 +#: ../memline.c:3370 msgid "E326: Too many swap files found" msgstr "E326: 找到太多交换文件" -#: ../memory.c:227 +#: ../memline.c:3386 +#, c-format +msgid "" +"E303: Unable to create directory \"%s\" for swap file, recovery impossible: " +"%s" +msgstr "E303: 无法为交换文件创建目录 \"%s\",恢复将不可能:%s" + +#: ../memory.c:197 +msgid "Vim: Data too large to fit into virtual memory space\n" +msgstr "Vim:数据太大无法放入虚拟内存空间\n" + +#: ../memory.c:527 #, c-format msgid "E342: Out of memory! (allocating %<PRIu64> bytes)" msgstr "E342: 内存不足!(分配 %<PRIu64> 字节)" -#: ../menu.c:62 +#: ../menu.c:52 msgid "E327: Part of menu-item path is not sub-menu" msgstr "E327: 菜单项的某部分路径不是子菜单" -#: ../menu.c:63 -msgid "E328: Menu only exists in another mode" -msgstr "E328: 菜单只在其它模式中存在" - -#: ../menu.c:64 +#: ../menu.c:53 #, c-format msgid "E329: No menu \"%s\"" msgstr "E329: 没有菜单 \"%s\"" #. Only a mnemonic or accelerator is not valid. -#: ../menu.c:329 +#: ../menu.c:310 msgid "E792: Empty menu name" msgstr "E792: 空的菜单名称" -#: ../menu.c:340 +#: ../menu.c:321 msgid "E330: Menu path must not lead to a sub-menu" msgstr "E330: 菜单路径不能指向子菜单" -#: ../menu.c:365 +#: ../menu.c:347 msgid "E331: Must not add menu items directly to menu bar" msgstr "E331: 不能把菜单项直接加到菜单栏中" -#: ../menu.c:370 +#: ../menu.c:352 msgid "E332: Separator cannot be part of a menu path" msgstr "E332: 分隔线不能是菜单路径的一部分" +#. When there are no menus at all, the title still needs to be shown. #. Now we have found the matching menu, and we list the mappings #. Highlight title -#: ../menu.c:762 +#: ../menu.c:805 msgid "" "\n" "--- Menus ---" @@ -4225,69 +4912,85 @@ msgstr "" "\n" "--- 菜单 ---" -#: ../menu.c:1313 +#: ../menu.c:1568 +#, c-format +msgid "E335: Menu not defined for %s mode" +msgstr "E335: %s 模式中菜单未定义" + +#: ../menu.c:1588 msgid "E333: Menu path must lead to a menu item" msgstr "E333: 菜单路径必须指向菜单项" -#: ../menu.c:1330 +#: ../menu.c:1608 #, c-format msgid "E334: Menu not found: %s" msgstr "E334: 找不到菜单: %s" -#: ../menu.c:1396 -#, c-format -msgid "E335: Menu not defined for %s mode" -msgstr "E335: %s 模式中菜单未定义" - -#: ../menu.c:1426 +#: ../menu.c:1670 msgid "E336: Menu path must lead to a sub-menu" msgstr "E336: 菜单路径必须指向子菜单" -#: ../menu.c:1447 +#: ../menu.c:1694 msgid "E337: Menu not found - check menu names" msgstr "E337: 找不到菜单 - 请检查菜单名称" -#: ../message.c:423 +#: ../message.c:562 #, c-format msgid "Error detected while processing %s:" msgstr "处理 %s 时发生错误:" -#: ../message.c:445 +#: ../message.c:584 #, c-format msgid "line %4ld:" msgstr "第 %4ld 行:" -#: ../message.c:617 +#: ../message.c:777 #, c-format msgid "E354: Invalid register name: '%s'" msgstr "E354: 无效的寄存器名: '%s'" -#: ../message.c:986 +#: ../message.c:1319 msgid "Interrupt: " msgstr "已中断: " -#: ../message.c:988 +#: ../message.c:1322 msgid "Press ENTER or type command to continue" msgstr "请按 ENTER 或其它命令继续" -#: ../message.c:1843 +#: ../message.c:1367 +#, c-format +msgid "%ld more line" +msgid_plural "%ld more lines" +msgstr[0] "多了 %ld 行" + +#: ../message.c:1371 +#, c-format +msgid "%ld line less" +msgid_plural "%ld fewer lines" +msgstr[0] "少了 %ld 行" + +#: ../message.c:1375 +msgid " (Interrupted)" +msgstr " (已中断)" + +#: ../message.c:2473 #, c-format msgid "%s line %<PRId64>" msgstr "%s 第 %<PRId64> 行" -#: ../message.c:2392 +#: ../message.c:3057 msgid "-- More --" msgstr "-- 更多 --" -#: ../message.c:2398 +#: ../message.c:3062 msgid " SPACE/d/j: screen/page/line down, b/u/k: up, q: quit " msgstr " 空格/d/j: 屏幕/页/行 下翻,b/u/k: 上翻,q: 退出 " -#: ../message.c:3021 ../message.c:3031 +#: ../message.c:3768 ../message.c:3779 msgid "Question" msgstr "问题" -#: ../message.c:3023 +#: ../message.c:3770 msgid "" "&Yes\n" "&No" @@ -4295,7 +4998,7 @@ msgstr "" "是(&Y)\n" "否(&N)" -#: ../message.c:3033 +#: ../message.c:3781 msgid "" "&Yes\n" "&No\n" @@ -4305,7 +5008,7 @@ msgstr "" "否(&N)\n" "取消(&C)" -#: ../message.c:3045 +#: ../message.c:3795 msgid "" "&Yes\n" "&No\n" @@ -4319,206 +5022,123 @@ msgstr "" "全部丢弃(&D)\n" "取消(&C)" -#: ../message.c:3058 -msgid "E766: Insufficient arguments for printf()" -msgstr "E766: printf() 的参数不足" - -#: ../message.c:3119 -msgid "E807: Expected Float argument for printf()" -msgstr "E807: 期盼浮点数作为printf()参数" - -#: ../message.c:3873 -msgid "E767: Too many arguments to printf()" -msgstr "E767: printf() 的参数过多" - -#: ../misc1.c:2256 -msgid "W10: Warning: Changing a readonly file" -msgstr "W10: 警告: 正在修改一个只读文件" - -#: ../misc1.c:2537 -#, fuzzy -msgid "Type number and <Enter> or click with mouse (empty cancels): " -msgstr "请输入数字或点击鼠标 (<Enter> 取消): " - -#: ../misc1.c:2539 -#, fuzzy -msgid "Type number and <Enter> (empty cancels): " -msgstr "请选择数字 (<Enter> 取消): " - -#: ../misc1.c:2585 -msgid "1 more line" -msgstr "多了 1 行" - -#: ../misc1.c:2588 -msgid "1 line less" -msgstr "少了 1 行" - -#: ../misc1.c:2593 -#, c-format -msgid "%<PRId64> more lines" -msgstr "多了 %<PRId64> 行" - -#: ../misc1.c:2596 -#, c-format -msgid "%<PRId64> fewer lines" -msgstr "少了 %<PRId64> 行" - -#: ../misc1.c:2599 -msgid " (Interrupted)" -msgstr " (已中断)" - -#: ../misc1.c:2635 -msgid "Beep!" -msgstr "Beep!" - -#: ../misc2.c:738 -#, c-format -msgid "Calling shell to execute: \"%s\"" -msgstr "调用 shell 执行: \"%s\"" - -#: ../normal.c:183 +#. nv_*(): functions called to handle Normal and Visual mode commands. +#. n_*(): functions called to handle Normal mode commands. +#. v_*(): functions called to handle Visual mode commands. +#: ../normal.c:120 msgid "E349: No identifier under cursor" msgstr "E349: 光标处没有识别字" -#: ../normal.c:1866 -msgid "E774: 'operatorfunc' is empty" -msgstr "E774: 'operatorfunc' 为空" - -#: ../normal.c:2637 -msgid "Warning: terminal cannot highlight" -msgstr "警告: 你的终端不能显示高亮" - -#: ../normal.c:2807 +#: ../normal.c:1633 msgid "E348: No string under cursor" msgstr "E348: 光标处没有字符串" -#: ../normal.c:3937 +#: ../normal.c:2983 msgid "E352: Cannot erase folds with current 'foldmethod'" msgstr "E352: 不能在当前的 'foldmethod' 下删除 fold" -#: ../normal.c:5897 +#: ../normal.c:4915 msgid "E664: changelist is empty" msgstr "E664: 改变列表为空" -#: ../normal.c:5899 +#: ../normal.c:4917 msgid "E662: At start of changelist" msgstr "E662: 已在改变列表的开始处" -#: ../normal.c:5901 +#: ../normal.c:4919 msgid "E663: At end of changelist" msgstr "E663: 已在改变列表的末尾处" -#: ../normal.c:7053 -msgid "Type :quit<Enter> to exit Nvim" -msgstr "输入 :quit<Enter> 退出 Vim" - -#: ../ops.c:248 -#, c-format -msgid "1 line %sed 1 time" -msgstr "1 行 %s 了 1 次" +#: ../normal.c:6081 +msgid "Type :qa! and press <Enter> to abandon all changes and exit Nvim" +msgstr "输入 :qa! 并按 <Enter> 来放弃所有更改并退出 Nvim" -#: ../ops.c:250 -#, c-format -msgid "1 line %sed %d times" -msgstr "1 行 %s 了 %d 次" +#: ../normal.c:6084 +msgid "Type :qa and press <Enter> to exit Nvim" +msgstr "输入 :qa 并按 <Enter> 退出 Nvim" -#: ../ops.c:253 -#, c-format -msgid "%<PRId64> lines %sed 1 time" -msgstr "%<PRId64> 行 %s 了 1 次" +#: ../ops.c:263 +#, fuzzy, c-format +msgid "%<PRId64> line %sed %d time" +msgid_plural "%<PRId64> line %sed %d times" +msgstr[0] "%<PRId64> 行 %s 了 %d 次" -#: ../ops.c:256 -#, c-format -msgid "%<PRId64> lines %sed %d times" -msgstr "%<PRId64> 行 %s 了 %d 次" +#: ../ops.c:265 +#, fuzzy, c-format +msgid "%<PRId64> lines %sed %d time" +msgid_plural "%<PRId64> lines %sed %d times" +msgstr[0] "%<PRId64> 行 %s 了 %d 次" -#: ../ops.c:592 +#: ../ops.c:662 #, c-format msgid "%<PRId64> lines to indent... " msgstr "缩进 %<PRId64> 行…… " -#: ../ops.c:634 -msgid "1 line indented " -msgstr "缩进了 1 行 " - -#: ../ops.c:636 +#: ../ops.c:705 #, c-format -msgid "%<PRId64> lines indented " -msgstr "缩进了 %<PRId64> 行 " +msgid "%<PRId64> line indented " +msgid_plural "%<PRId64> lines indented " +msgstr[0] "缩进了 %<PRId64> 行 " -#: ../ops.c:938 +#: ../ops.c:1088 msgid "E748: No previously used register" msgstr "E748: 没有前一个使用的寄存器" -#. must display the prompt -#: ../ops.c:1433 -msgid "cannot yank; delete anyway" -msgstr "无法复制;改为删除" - -#: ../ops.c:1929 -msgid "1 line changed" -msgstr "改变了 1 行" - -#: ../ops.c:1931 +#: ../ops.c:2122 #, c-format -msgid "%<PRId64> lines changed" -msgstr "改变了 %<PRId64> 行" +msgid "%<PRId64> line changed" +msgid_plural "%<PRId64> lines changed" +msgstr[0] "改变了 %<PRId64> 行" -#: ../ops.c:2521 -msgid "block of 1 line yanked" -msgstr "复制了 1 行的块" - -#: ../ops.c:2523 -msgid "1 line yanked" -msgstr "复制了 1 行" - -#: ../ops.c:2525 +#: ../ops.c:2786 #, c-format -msgid "block of %<PRId64> lines yanked" -msgstr "复制了 %<PRId64> 行的块" +msgid " into \"%c" +msgstr "进 \"%c " -#: ../ops.c:2528 -#, c-format -msgid "%<PRId64> lines yanked" -msgstr "复制了 %<PRId64> 行" +#: ../ops.c:2795 +#, fuzzy, c-format +msgid "block of %<PRId64> line yanked%s" +msgid_plural "block of %<PRId64> lines yanked%s" +msgstr[0] "复制了 %<PRId64> 行的块" + +#: ../ops.c:2799 +#, fuzzy, c-format +msgid "%<PRId64> line yanked%s" +msgid_plural "%<PRId64> lines yanked%s" +msgstr[0] "复制了 %<PRId64> 行" -#: ../ops.c:2710 +#: ../ops.c:3148 #, c-format msgid "E353: Nothing in register %s" msgstr "E353: 寄存器 %s 里没有东西" #. Highlight title -#: ../ops.c:3185 +#: ../ops.c:3779 msgid "" "\n" -"--- Registers ---" +"Type Name Content" msgstr "" "\n" -"--- 寄存器 ---" +"类型 名称 内容" -#: ../ops.c:4455 -msgid "Illegal register name" -msgstr "无效的寄存器名" +#: ../ops.c:4453 +#, c-format +msgid "%<PRId64> lines changed" +msgid_plural "%<PRId64> lines changed" +msgstr[0] "改变了 %<PRId64> 行" -#: ../ops.c:4533 +#: ../ops.c:5034 msgid "" -"\n" -"# Registers:\n" -msgstr "" -"\n" -"# 寄存器:\n" - -#: ../ops.c:4575 -#, c-format -msgid "E574: Unknown register type %d" -msgstr "E574: 未知的寄存器类型 %d" +"E883: search pattern and expression register may not contain two or more " +"lines" +msgstr "E883: 搜索模式和表达式寄存器只能包含一行文本" -#: ../ops.c:5089 +#: ../ops.c:5462 #, c-format msgid "%<PRId64> Cols; " msgstr "%<PRId64> 列; " -#: ../ops.c:5097 +#: ../ops.c:5471 #, c-format msgid "" "Selected %s%<PRId64> of %<PRId64> Lines; %<PRId64> of %<PRId64> Words; " @@ -4527,7 +5147,7 @@ msgstr "" "选择了 %s%<PRId64>/%<PRId64> 行; %<PRId64>/%<PRId64> 个词; %<PRId64>/" "%<PRId64> 个字节" -#: ../ops.c:5105 +#: ../ops.c:5480 #, c-format msgid "" "Selected %s%<PRId64> of %<PRId64> Lines; %<PRId64> of %<PRId64> Words; " @@ -4536,7 +5156,7 @@ msgstr "" "选择了 %s%<PRId64>/%<PRId64> 行; %<PRId64>/%<PRId64> 个词; %<PRId64>/" "%<PRId64> 个字符; %<PRId64>/%<PRId64> 个字节" -#: ../ops.c:5123 +#: ../ops.c:5500 #, c-format msgid "" "Col %s of %s; Line %<PRId64> of %<PRId64>; Word %<PRId64> of %<PRId64>; Byte " @@ -4545,7 +5165,7 @@ msgstr "" "第 %s/%s 列; 第 %<PRId64>/%<PRId64> 行; 第 %<PRId64>/%<PRId64> 个词; 第 " "%<PRId64>/%<PRId64> 个字节" -#: ../ops.c:5133 +#: ../ops.c:5510 #, c-format msgid "" "Col %s of %s; Line %<PRId64> of %<PRId64>; Word %<PRId64> of %<PRId64>; Char " @@ -4554,156 +5174,67 @@ msgstr "" "第 %s/%s 列; 第 %<PRId64>/%<PRId64> 行; 第 %<PRId64>/%<PRId64> 个词; 第 " "%<PRId64>/%<PRId64> 个字符; 第 %<PRId64>/%<PRId64> 个字节" -#: ../ops.c:5146 +#: ../ops.c:5528 #, c-format msgid "(+%<PRId64> for BOM)" msgstr "" -#: ../option.c:1238 -msgid "%<%f%h%m%=Page %N" -msgstr "%<%f%h%m%=页 %N" - -#: ../option.c:1574 -msgid "Thanks for flying Vim" -msgstr "感谢您选择 Vim" +#: ../ops.c:5648 +msgid "E774: 'operatorfunc' is empty" +msgstr "E774: 'operatorfunc' 为空" -#. found a mismatch: skip -#: ../option.c:2698 +#: ../option.c:107 msgid "E518: Unknown option" msgstr "E518: 未知的选项" -#: ../option.c:2709 -msgid "E519: Option not supported" -msgstr "E519: 不支持该选项" - -#: ../option.c:2740 +#: ../option.c:109 msgid "E520: Not allowed in a modeline" msgstr "E520: 不允许在 modeline 中使用" -#: ../option.c:2815 +#: ../option.c:111 +msgid "E992: Not allowed in a modeline when 'modelineexpr' is off" +msgstr "E992: 当 'modelineexpr' 关闭时不允许在 modeline 中使用" + +#: ../option.c:113 msgid "E846: Key code not set" msgstr "E846: 未设置键位代码" -#: ../option.c:2924 +#: ../option.c:115 msgid "E521: Number required after =" msgstr "E521: = 后面需要数字" -#: ../option.c:3226 ../option.c:3864 -msgid "E522: Not found in termcap" -msgstr "E522: Termcap 里面找不到" - -#: ../option.c:3335 -#, c-format -msgid "E539: Illegal character <%s>" -msgstr "E539: 无效的字符 <%s>" - -#: ../option.c:3862 -msgid "E529: Cannot set 'term' to empty string" -msgstr "E529: 不能设定 'term' 为空字符串" - -#: ../option.c:3885 -msgid "E589: 'backupext' and 'patchmode' are equal" -msgstr "E589: 'backupext' 和 'patchmode' 相等" - -#: ../option.c:3964 -msgid "E834: Conflicts with value of 'listchars'" -msgstr "E834: 与'listchars'中的值发生冲突" - -#: ../option.c:3966 -msgid "E835: Conflicts with value of 'fillchars'" -msgstr "E835: 与'fillchars'中的值冲突" - -#: ../option.c:4163 -msgid "E524: Missing colon" -msgstr "E524: 缺少冒号" - -#: ../option.c:4165 -msgid "E525: Zero length string" -msgstr "E525: 字符串长度为零" - -#: ../option.c:4220 -#, c-format -msgid "E526: Missing number after <%s>" -msgstr "E526: <%s> 后面缺少数字" - -#: ../option.c:4232 -msgid "E527: Missing comma" -msgstr "E527: 缺少逗号" - -#: ../option.c:4239 -msgid "E528: Must specify a ' value" -msgstr "E528: 必须指定一个 ' 值" - -#: ../option.c:4271 -msgid "E595: contains unprintable or wide character" -msgstr "E595: 包含不可显示字符或宽字符" - -#: ../option.c:4469 -#, c-format -msgid "E535: Illegal character after <%c>" -msgstr "E535: <%c> 后面有无效的字符" - -#: ../option.c:4534 -msgid "E536: comma required" -msgstr "E536: 需要逗号" - -#: ../option.c:4543 -#, c-format -msgid "E537: 'commentstring' must be empty or contain %s" -msgstr "E537: 'commentstring' 必须为空或包含 %s" - -#: ../option.c:4928 -msgid "E540: Unclosed expression sequence" -msgstr "E540: 没有结束的表达式序列" - -#: ../option.c:4932 -msgid "E541: too many items" -msgstr "E541: 项目过多" - -#: ../option.c:4934 -msgid "E542: unbalanced groups" -msgstr "E542: 错乱的组" - -#: ../option.c:5148 +#: ../option.c:117 msgid "E590: A preview window already exists" msgstr "E590: 预览窗口已存在" -#: ../option.c:5311 +#: ../option.c:2116 msgid "W17: Arabic requires UTF-8, do ':set encoding=utf-8'" msgstr "W17: Arabic 需要 UTF-8,请执行 ':set encoding=utf-8'" -#: ../option.c:5623 +#: ../option.c:2523 #, c-format msgid "E593: Need at least %d lines" msgstr "E593: 至少需要 %d 行" -#: ../option.c:5631 +#: ../option.c:2531 #, c-format msgid "E594: Need at least %d columns" msgstr "E594: 至少需要 %d 列" -#: ../option.c:6011 +#: ../option.c:3064 ../option.c:3616 #, c-format msgid "E355: Unknown option: %s" msgstr "E355: 未知的选项: %s" #. 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. -#: ../option.c:6037 -#, fuzzy, c-format +#. is empty. In both cases, we are trying to set a +#. num option using a string. +#: ../option.c:3092 +#, c-format msgid "E521: Number required: &%s = '%s'" -msgstr "E521: = 后面需要数字" - -#: ../option.c:6149 -msgid "" -"\n" -"--- Terminal codes ---" -msgstr "" -"\n" -"--- 终端编码 ---" +msgstr "E521: 需要整数:&%s = '%s'" -#: ../option.c:6151 +#: ../option.c:3198 msgid "" "\n" "--- Global option values ---" @@ -4711,7 +5242,7 @@ msgstr "" "\n" "--- 全局选项值 ---" -#: ../option.c:6153 +#: ../option.c:3200 msgid "" "\n" "--- Local option values ---" @@ -4719,7 +5250,7 @@ msgstr "" "\n" "--- 局部选项值 ---" -#: ../option.c:6155 +#: ../option.c:3202 msgid "" "\n" "--- Options ---" @@ -4727,653 +5258,1070 @@ msgstr "" "\n" "--- 选项 ---" -#: ../option.c:6816 +#: ../option.c:4096 msgid "E356: get_varp ERROR" msgstr "E356: get_varp 错误" -#: ../option.c:7696 +#: ../optionstr.c:62 +msgid "E540: Unclosed expression sequence" +msgstr "E540: 没有结束的表达式序列" + +#: ../optionstr.c:64 +msgid "E542: unbalanced groups" +msgstr "E542: 错乱的组" + +#: ../optionstr.c:66 +msgid "E589: 'backupext' and 'patchmode' are equal" +msgstr "E589: 'backupext' 和 'patchmode' 相等" + +#: ../optionstr.c:68 +msgid "E595: 'showbreak' contains unprintable or wide character" +msgstr "E595: 'showbreak' 包含不可显示字符或宽字符" + +#: ../optionstr.c:205 #, c-format -msgid "E357: 'langmap': Matching character missing for %s" -msgstr "E357: 'langmap': 找不到 %s 对应的字符" +msgid "E539: Illegal character <%s>" +msgstr "E539: 无效的字符 <%s>" -#: ../option.c:7715 +#: ../optionstr.c:337 #, c-format -msgid "E358: 'langmap': Extra characters after semicolon: %s" -msgstr "E358: 'langmap': 分号后有多余的字符: %s" +msgid "For option %s" +msgstr "对选项 %s" -#: ../os/shell.c:194 -msgid "" -"\n" -"Cannot execute shell " +#: ../optionstr.c:959 +msgid "E524: Missing colon" +msgstr "E524: 缺少冒号" + +#: ../optionstr.c:961 +msgid "E525: Zero length string" +msgstr "E525: 字符串长度为零" + +#: ../optionstr.c:1040 +#, c-format +msgid "E526: Missing number after <%s>" +msgstr "E526: <%s> 后面缺少数字" + +#: ../optionstr.c:1053 +msgid "E527: Missing comma" +msgstr "E527: 缺少逗号" + +#: ../optionstr.c:1061 +msgid "E528: Must specify a ' value" +msgstr "E528: 必须指定一个 ' 值" + +#: ../optionstr.c:1243 +#, c-format +msgid "E535: Illegal character after <%c>" +msgstr "E535: <%c> 后面有无效的字符" + +#: ../optionstr.c:1351 +msgid "E536: comma required" +msgstr "E536: 需要逗号" + +#: ../optionstr.c:1359 +#, c-format +msgid "E537: 'commentstring' must be empty or contain %s" +msgstr "E537: 'commentstring' 必须为空或包含 %s" + +# do not translate +#: ../os/dl.c:53 ../os/dl.c:61 +#, c-format +msgid "dlerror = \"%s\"" msgstr "" -"\n" -"无法执行 shell" -#: ../os/shell.c:439 +#: ../os/fileio.c:434 +#, c-format +msgid "E5420: Failed to write to file: %s" +msgstr "E5420: 无法写入文件:%s" + +#: ../os/input.c:564 +msgid "Vim: Error reading input, exiting...\n" +msgstr "Vim:读取输入时出错,正在退出……\n" + +#: ../os/shell.c:696 msgid "" "\n" "shell returned " msgstr "" "\n" -"Shell 已返回" +"Shell 返回了 " -#: ../os_unix.c:465 ../os_unix.c:471 +#: ../os/shell.c:878 msgid "" "\n" -"Could not get security context for " +"shell failed to start: " msgstr "" - -#: ../os_unix.c:479 -msgid "" "\n" -"Could not set security context for " -msgstr "" +"Shell 无法启动:" -# do not translate -#: ../os_unix.c:1558 ../os_unix.c:1647 +#. Can happen if system() tries to send input to a shell command that was +#. backgrounded (:call system("cat - &", "foo")). #3529 #5241 +#: ../os/shell.c:1316 #, c-format -msgid "dlerror = \"%s\"" -msgstr "dlerror = \"%s\"" +msgid "E5677: Error writing input to shell-command: %s" +msgstr "E5677: 向 Shell 命令输入时出错:%s" + +#: ../os/time.c:191 +msgid "%a %b %d %H:%M:%S %Y" +msgstr "%Y-%m-%d %H:%M:%S" -#: ../path.c:1449 +#: ../path.c:1711 #, c-format msgid "E447: Can't find file \"%s\" in path" msgstr "E447: 在路径中找不到文件 \"%s\"" -#: ../quickfix.c:359 +#: ../profile.c:307 +msgid "E750: First use \":profile start {fname}\"" +msgstr "E750: 请先使用 \":profile start {fname}\"" + +#: ../quickfix.c:246 +msgid "E553: No more items" +msgstr "E553: 没有更多的项" + +#: ../quickfix.c:278 +msgid "E925: Current quickfix list was changed" +msgstr "E925: 当前 quickfix 列表已改变" + +#: ../quickfix.c:280 +msgid "E926: Current location list was changed" +msgstr "E926: 当前位置列表已改变" + +#. Each errorformat pattern can occur only once +#: ../quickfix.c:382 #, c-format msgid "E372: Too many %%%c in format string" msgstr "E372: 格式化字符串里有太多 %%%c " -#: ../quickfix.c:371 +#: ../quickfix.c:389 #, c-format msgid "E373: Unexpected %%%c in format string" msgstr "E373: 格式化字符串不应该出现 %%%c " -#: ../quickfix.c:420 +#: ../quickfix.c:447 msgid "E374: Missing ] in format string" -msgstr "E374: 格式化字符串里少了 ]" +msgstr "E374: 格式化字符串中缺少 ]" -#: ../quickfix.c:431 +#: ../quickfix.c:457 #, c-format msgid "E375: Unsupported %%%c in format string" msgstr "E375: 格式化字符串里有不支持的 %%%c " -#: ../quickfix.c:448 +#: ../quickfix.c:476 #, c-format msgid "E376: Invalid %%%c in format string prefix" msgstr "E376: 格式化字符串开头里有不正确的 %%%c " -#: ../quickfix.c:454 +#: ../quickfix.c:526 #, c-format msgid "E377: Invalid %%%c in format string" msgstr "E377: 格式化字符串里有不正确的 %%%c " #. nothing found -#: ../quickfix.c:477 +#: ../quickfix.c:629 msgid "E378: 'errorformat' contains no pattern" msgstr "E378: 'errorformat' 未设定" -#: ../quickfix.c:695 +#: ../quickfix.c:1564 msgid "E379: Missing or empty directory name" msgstr "E379: 找不到目录名称或是空的目录名称" -#: ../quickfix.c:1305 -msgid "E553: No more items" -msgstr "E553: 没有更多的项" +#: ../quickfix.c:2737 +msgid "E924: Current window was closed" +msgstr "E924: 当前窗口已关闭" -#: ../quickfix.c:1674 +#: ../quickfix.c:2809 #, c-format msgid "(%d of %d)%s%s: " msgstr "(%d / %d)%s%s: " -#: ../quickfix.c:1676 +#: ../quickfix.c:2811 msgid " (line deleted)" msgstr " (行已删除)" -#: ../quickfix.c:1863 +#: ../quickfix.c:3252 +#, c-format +msgid "%serror list %d of %d; %d errors " +msgstr "%s 错误列表 %d / %d;共 %d 个错误" + +#: ../quickfix.c:3287 msgid "E380: At bottom of quickfix stack" msgstr "E380: Quickfix 堆栈底端" -#: ../quickfix.c:1869 +#: ../quickfix.c:3293 msgid "E381: At top of quickfix stack" msgstr "E381: Quickfix 堆栈顶端" -#: ../quickfix.c:1880 -#, c-format -msgid "error list %d of %d; %d errors" -msgstr "错误列表 %d / %d;共 %d 个错误" +#: ../quickfix.c:3327 +msgid "No entries" +msgstr "没有项目" -#: ../quickfix.c:2427 -msgid "E382: Cannot write, 'buftype' option is set" -msgstr "E382: 无法写入,已设定选项 'buftype'" - -#: ../quickfix.c:2812 +#: ../quickfix.c:5335 msgid "E683: File name missing or invalid pattern" msgstr "E683: 缺少文件名或模式无效" -#: ../quickfix.c:2911 +#: ../quickfix.c:5398 #, c-format msgid "Cannot open file \"%s\"" msgstr "无法打开文件 \"%s\"" -#: ../quickfix.c:3429 +#: ../quickfix.c:6680 +msgid "cannot have both a list and a \"what\" argument" +msgstr "不能同时有列表和 \"what\" 参数" + +#: ../quickfix.c:6796 msgid "E681: Buffer is not loaded" msgstr "E681: 缓冲区未加载" -#: ../quickfix.c:3487 +#: ../quickfix.c:6959 msgid "E777: String or List expected" -msgstr "E777: 此处需要 String 或者 List" +msgstr "E777: 此处需要字符串或列表" -#: ../regexp.c:359 +#: ../quickfix.c:7246 +#, c-format +msgid "E927: Invalid action: '%s'" +msgstr "E927: 动作无效:'%s'" + +#: ../regexp.c:103 #, c-format msgid "E369: invalid item in %s%%[]" msgstr "E369: %s%%[] 中有无效的项" -#: ../regexp.c:374 +#: ../regexp.c:107 #, c-format msgid "E769: Missing ] after %s[" msgstr "E769: %s[ 后缺少 ]" -#: ../regexp.c:375 +#: ../regexp.c:108 +msgid "E944: Reverse range in character class" +msgstr "E944: 字符类中有逆向范围" + +#: ../regexp.c:109 +msgid "E945: Range too large in character class" +msgstr "E945: 字符类中的范围太大了" + +#: ../regexp.c:110 #, c-format msgid "E53: Unmatched %s%%(" msgstr "E53: 不匹配的 %s%%(" -#: ../regexp.c:376 +#: ../regexp.c:111 #, c-format msgid "E54: Unmatched %s(" msgstr "E54: 不匹配的 %s(" -#: ../regexp.c:377 +#: ../regexp.c:112 #, c-format msgid "E55: Unmatched %s)" msgstr "E55: 不匹配的 %s)" -#: ../regexp.c:378 +#: ../regexp.c:113 msgid "E66: \\z( not allowed here" msgstr "E66: 此处不允许 \\z(" -#: ../regexp.c:379 -msgid "E67: \\z1 et al. not allowed here" -msgstr "E67: 此处不允许 \\z1 等" +#: ../regexp.c:114 +msgid "E67: \\z1 - \\z9 not allowed here" +msgstr "E67: 此处不允许 \\z1 - \\z9" -#: ../regexp.c:380 +#: ../regexp.c:115 #, c-format msgid "E69: Missing ] after %s%%[" msgstr "E69: %s%%[ 后缺少 ]" -#: ../regexp.c:381 +#: ../regexp.c:116 #, c-format msgid "E70: Empty %s%%[]" msgstr "E70: 空的 %s%%[]" -#: ../regexp.c:1209 ../regexp.c:1224 -msgid "E339: Pattern too long" -msgstr "E339: 模式太长" - -#: ../regexp.c:1371 -msgid "E50: Too many \\z(" -msgstr "E50: 太多 \\z(" +#: ../regexp.c:117 +msgid "E956: Cannot use pattern recursively" +msgstr "E956: 不能递归地使用模式" -#: ../regexp.c:1378 +#: ../regexp.c:119 #, c-format -msgid "E51: Too many %s(" -msgstr "E51: 太多 %s(" +msgid "E1204: No Number allowed after .: '\\%%%c'" +msgstr "E1204: . 后不能加整数: '\\%%%c'" -#: ../regexp.c:1427 -msgid "E52: Unmatched \\z(" -msgstr "E52: 不匹配的 \\z(" - -#: ../regexp.c:1637 +#: ../regexp.c:121 #, c-format -msgid "E59: invalid character after %s@" -msgstr "E59: %s@ 后面有无效的字符" +msgid "E1273: (NFA regexp) missing value in '\\%%%c'" +msgstr "E1273: (NFA 正则) '\\%%%c' 中缺少值" -#: ../regexp.c:1672 +#: ../regexp.c:123 #, c-format -msgid "E60: Too many complex %s{...}s" -msgstr "E60: 太多复杂的 %s{...}s" +msgid "E1281: Atom '\\%%#=%c' must be at the start of the pattern" +msgstr "E1281: 原子 '\\%%#=%c' 必须位于模式的开头" -#: ../regexp.c:1687 -#, c-format -msgid "E61: Nested %s*" -msgstr "E61: 嵌套的 %s*" +#: ../regexp.c:124 +msgid "E1290: substitute nesting too deep" +msgstr "E1290: 替换嵌套层数过深" -#: ../regexp.c:1690 +#: ../regexp.c:498 #, c-format -msgid "E62: Nested %s%c" -msgstr "E62: 嵌套的 %s%c" +msgid "E654: missing delimiter after search pattern: %s" +msgstr "E654: 搜索模式后缺少分割符:%s" -#: ../regexp.c:1800 -msgid "E63: invalid use of \\_" -msgstr "E63: 不正确地使用 \\_" +#: ../regexp.c:919 +#, c-format +msgid "E554: Syntax error in %s{...}" +msgstr "E554: %s{...} 中语法错误" -#: ../regexp.c:1850 +#: ../regexp.c:1294 #, c-format -msgid "E64: %s%c follows nothing" -msgstr "E64: %s%c 前面无内容" +msgid "E888: (NFA regexp) cannot repeat %s" +msgstr "E888: (NFA regexp) 不能重复 %s" -#: ../regexp.c:1902 -msgid "E65: Illegal back reference" -msgstr "E65: 无效的回引" +#: ../regexp.c:2302 +msgid "" +"E864: \\%#= can only be followed by 0, 1, or 2. The automatic engine will be " +"used " +msgstr "E864: \\%#= 后面只能是0,1,或者2。自动引擎将会被使用" -#: ../regexp.c:1943 -msgid "E68: Invalid character after \\z" -msgstr "E68: \\z 后面有无效的字符" +#: ../regexp.c:2383 +msgid "Switching to backtracking RE engine for pattern: " +msgstr "为此模式切换到回溯正则引擎:" -#: ../regexp.c:2049 ../regexp_nfa.c:1296 +#: ../runtime.c:266 #, c-format -msgid "E678: Invalid character after %s%%[dxouU]" -msgstr "E678: %s%%[dxouU] 后面有无效的字符" +msgid "Searching for \"%s\" in \"%s\"" +msgstr "正在查找 \"%s\",在 \"%s\" 中" -#: ../regexp.c:2107 +#: ../runtime.c:303 ../runtime.c:436 #, c-format -msgid "E71: Invalid character after %s%%" -msgstr "E71: %s%% 后面有无效的字符" +msgid "Searching for \"%s\"" +msgstr "正在查找 \"%s\"" -#: ../regexp.c:3017 +#: ../runtime.c:334 #, c-format -msgid "E554: Syntax error in %s{...}" -msgstr "E554: %s{...} 中语法错误" +msgid "not found in '%s': \"%s\"" +msgstr "在 '%s' 中找不到:\"%s\"" -#: ../regexp.c:3805 -msgid "External submatches:\n" -msgstr "外部符合:\n" +#: ../runtime.c:400 +#, c-format +msgid "Searching for \"%s\" in runtime path" +msgstr "正在查找 \"%s\",在 runtime path 中" -#: ../regexp.c:7022 -msgid "" -"E864: \\%#= can only be followed by 0, 1, or 2. The automatic engine will be " -"used " -msgstr "" -"E864: \\%#= 后面只能是0,1,或者2。自动引擎将会被使用" +#: ../runtime.c:464 +#, c-format +msgid "not found in runtime path: \"%s\"" +msgstr "在 runtime path 中找不到:\"%s\"" -#: ../regexp_nfa.c:239 -msgid "E865: (NFA) Regexp end encountered prematurely" -msgstr "E865: (NFA) 过早地遇到了正则表达式的结尾" +#: ../runtime.c:1912 +#, c-format +msgid "Cannot source a directory: \"%s\"" +msgstr "不能执行目录: \"%s\"" -#: ../regexp_nfa.c:240 +#: ../runtime.c:1947 #, c-format -msgid "E866: (NFA regexp) Misplaced %c" -msgstr "E866: (NFA regexp) %c 放错了位置" +msgid "could not source \"%s\"" +msgstr "不能执行 \"%s\"" -#: ../regexp_nfa.c:242 +#: ../runtime.c:1949 #, c-format -msgid "E877: (NFA regexp) Invalid character class: %<PRId64>" -msgstr "E877: (NFA regexp) 不可用的字符类: %<PRId64>" +msgid "line %<PRId64>: could not source \"%s\"" +msgstr "第 %<PRId64> 行: 不能执行 \"%s\"" -#: ../regexp_nfa.c:1261 +#: ../runtime.c:1963 #, c-format -msgid "E867: (NFA) Unknown operator '\\z%c'" -msgstr "E867: (NFA) 未知的操作符 '\\z%c'" +msgid "sourcing \"%s\"" +msgstr "执行 \"%s\"" -#: ../regexp_nfa.c:1387 +#: ../runtime.c:1965 #, c-format -msgid "E867: (NFA) Unknown operator '\\%%%c'" -msgstr "E867: (NFA) 未知的操作符 '\\%%%c'" +msgid "line %<PRId64>: sourcing \"%s\"" +msgstr "第 %<PRId64> 行: 执行 \"%s\"" -#: ../regexp_nfa.c:1802 +#: ../runtime.c:2081 #, c-format -msgid "E869: (NFA) Unknown operator '\\@%c'" -msgstr "E869: (NFA) 未知的操作符 '\\@%c'" +msgid "finished sourcing %s" +msgstr "结束执行 %s" -#: ../regexp_nfa.c:1831 -msgid "E870: (NFA regexp) Error reading repetition limits" -msgstr "E870: (NFA regexp) 读取重复限制时出错" +#: ../runtime.c:2210 +msgid "modeline" +msgstr "modeline" -#. Can't have a multi follow a multi. -#: ../regexp_nfa.c:1895 -msgid "E871: (NFA regexp) Can't have a multi follow a multi !" -msgstr "E871: (NFA regexp) 不能多个跟多个!" +#: ../runtime.c:2212 +msgid "--cmd argument" +msgstr "--cmd 参数" -#. Too many `(' -#: ../regexp_nfa.c:2037 -msgid "E872: (NFA regexp) Too many '('" -msgstr "E872: (NFA regexp) 太多 '('" +#: ../runtime.c:2214 +msgid "-c argument" +msgstr "-c 参数" -#: ../regexp_nfa.c:2042 -#, fuzzy -msgid "E879: (NFA regexp) Too many \\z(" -msgstr "E50: 太多 \\z(" +#: ../runtime.c:2216 +msgid "environment variable" +msgstr "环境变量" -#: ../regexp_nfa.c:2066 -msgid "E873: (NFA regexp) proper termination error" -msgstr "E873: (NFA regexp) 未适当终止" +#: ../runtime.c:2218 +msgid "error handler" +msgstr "错误的处理程序" -#: ../regexp_nfa.c:2599 -msgid "E874: (NFA) Could not pop the stack !" -msgstr "E874: (NFA) 无法出栈!" +#: ../runtime.c:2220 +msgid "changed window size" +msgstr "改变了窗口大小" -#: ../regexp_nfa.c:3298 -msgid "" -"E875: (NFA regexp) (While converting from postfix to NFA), too many states " -"left on stack" -msgstr "E875: (NFA regexp) (从后缀转换到 NFA 时),栈上遗留了太多状态" +#: ../runtime.c:2222 +msgid "Lua" +msgstr "Lua" -#: ../regexp_nfa.c:3302 -msgid "E876: (NFA regexp) Not enough space to store the whole NFA " -msgstr "E876: (NFA regexp) 没有足够的空间存储整个NFA " +#: ../runtime.c:2224 +#, c-format +msgid "API client (channel id %<PRIu64>)" +msgstr "" -#: ../regexp_nfa.c:4571 ../regexp_nfa.c:4869 -msgid "" -"Could not open temporary log file for writing, displaying on stderr ... " +#: ../runtime.c:2227 +msgid "anonymous :source" msgstr "" -"无法打开临时日志文件进行写入,显示在 stderr 中..." -#: ../regexp_nfa.c:4840 +#: ../runtime.c:2231 #, c-format -msgid "(NFA) COULD NOT OPEN %s !" -msgstr "(NFA) 不能打开 %s !" +msgid "anonymous :source (script id %d)" +msgstr "" -#: ../regexp_nfa.c:6049 -#, fuzzy -msgid "Could not open temporary log file for writing " -msgstr "E214: 找不到用于写入的临时文件" +#: ../runtime.c:2417 +msgid "W15: Warning: Wrong line separator, ^M may be missing" +msgstr "W15: 警告: 错误的行分隔符,可能是少了 ^M" + +#: ../runtime.c:2457 +msgid "E167: :scriptencoding used outside of a sourced file" +msgstr "E167: 在脚本文件外使用了 :scriptencoding" -#: ../screen.c:7435 +#: ../runtime.c:2482 +msgid "E168: :finish used outside of a sourced file" +msgstr "E168: 在脚本文件外使用了 :finish" + +#: ../screen.c:57 +msgid "E834: Conflicts with value of 'listchars'" +msgstr "E834: 与'listchars'中的值发生冲突" + +#: ../screen.c:58 +msgid "E835: Conflicts with value of 'fillchars'" +msgstr "E835: 与'fillchars'中的值冲突" + +#: ../screen.c:521 +msgid " TERMINAL" +msgstr " 终端" + +#: ../screen.c:523 msgid " VREPLACE" msgstr " V-替换" -#: ../screen.c:7437 +#: ../screen.c:525 msgid " REPLACE" msgstr " 替换" -#: ../screen.c:7440 +#: ../screen.c:528 msgid " REVERSE" msgstr " 反向" -#: ../screen.c:7441 +#: ../screen.c:530 msgid " INSERT" msgstr " 插入" -#: ../screen.c:7443 +#: ../screen.c:534 +msgid " (terminal)" +msgstr " (终端)" + +#: ../screen.c:536 msgid " (insert)" msgstr " (插入)" -#: ../screen.c:7445 +#: ../screen.c:539 msgid " (replace)" msgstr " (替换)" -#: ../screen.c:7447 +#: ../screen.c:541 msgid " (vreplace)" msgstr " (V-替换)" -#: ../screen.c:7449 +#: ../screen.c:544 msgid " Hebrew" -msgstr " Hebrew" +msgstr " 希伯来文" -#: ../screen.c:7454 +#: ../screen.c:548 msgid " Arabic" -msgstr " Arabic" - -#: ../screen.c:7456 -msgid " (lang)" -msgstr " (语言)" +msgstr " 阿拉伯文" -#: ../screen.c:7459 +#: ../screen.c:555 msgid " (paste)" msgstr " (粘帖)" -#: ../screen.c:7469 +#: ../screen.c:567 msgid " VISUAL" msgstr " 可视" -#: ../screen.c:7470 +#: ../screen.c:569 msgid " VISUAL LINE" msgstr " 可视 行" -#: ../screen.c:7471 +#: ../screen.c:571 msgid " VISUAL BLOCK" msgstr " 可视 块" -#: ../screen.c:7472 +#: ../screen.c:573 msgid " SELECT" msgstr " 选择" -#: ../screen.c:7473 +#: ../screen.c:575 msgid " SELECT LINE" msgstr " 选择 行" -#: ../screen.c:7474 +#: ../screen.c:577 msgid " SELECT BLOCK" msgstr " 选择 块" -#: ../screen.c:7486 ../screen.c:7541 +#: ../screen.c:673 msgid "recording" msgstr "记录中" -#: ../search.c:487 +#: ../search.c:563 #, c-format msgid "E383: Invalid search string: %s" msgstr "E383: 无效的查找字符串: %s" -#: ../search.c:832 +#: ../search.c:928 #, c-format msgid "E384: search hit TOP without match for: %s" msgstr "E384: 已查找到文件开头仍找不到 %s" -#: ../search.c:835 +#: ../search.c:931 #, c-format msgid "E385: search hit BOTTOM without match for: %s" msgstr "E385: 已查找到文件结尾仍找不到 %s" -#: ../search.c:1200 +#: ../search.c:1382 msgid "E386: Expected '?' or '/' after ';'" msgstr "E386: 在 ';' 后面应该有 '?' 或 '/'" -#: ../search.c:4085 +#: ../search.c:3540 msgid " (includes previously listed match)" msgstr " (包括上次列出符合项)" #. cursor at status line -#: ../search.c:4104 +#: ../search.c:3557 msgid "--- Included files " msgstr "--- 包含文件 " -#: ../search.c:4106 +#: ../search.c:3559 msgid "not found " msgstr "找不到 " -#: ../search.c:4107 +#: ../search.c:3561 msgid "in path ---\n" msgstr "在路径 ---\n" -#: ../search.c:4168 +#: ../search.c:3620 msgid " (Already listed)" msgstr " (已列出)" -#: ../search.c:4170 +#: ../search.c:3622 msgid " NOT FOUND" msgstr " 找不到" -#: ../search.c:4211 +#: ../search.c:3664 #, c-format msgid "Scanning included file: %s" msgstr "查找包含文件: %s" -#: ../search.c:4216 +#: ../search.c:3669 #, c-format msgid "Searching included file %s" msgstr "查找包含的文件 %s" -#: ../search.c:4405 +#: ../search.c:3862 msgid "E387: Match is on current line" msgstr "E387: 当前行匹配" -#: ../search.c:4517 +#: ../search.c:3988 msgid "All included files were found" msgstr "所有包含文件都已找到" -#: ../search.c:4519 +#: ../search.c:3990 msgid "No included files" msgstr "没有包含文件" -#: ../search.c:4527 +#: ../search.c:3998 msgid "E388: Couldn't find definition" msgstr "E388: 找不到定义" -#: ../search.c:4529 +#: ../search.c:4000 msgid "E389: Couldn't find pattern" msgstr "E389: 找不到 pattern" -#: ../search.c:4668 -#, fuzzy -msgid "Substitute " -msgstr "1 次替换," +#: ../shada.c:699 +msgid "too few bytes read" +msgstr "" + +#: ../shada.c:721 +#, c-format +msgid "System error while skipping in ShaDa file: %s" +msgstr "" -#: ../search.c:4681 +#: ../shada.c:725 ../shada.c:3271 #, c-format msgid "" +"Error while reading ShaDa file: last entry specified that it occupies " +"%<PRIu64> bytes, but file ended earlier" +msgstr "" + +#: ../shada.c:770 +#, c-format +msgid "System error while closing ShaDa file: %s" +msgstr "" + +#: ../shada.c:805 +#, c-format +msgid "System error while writing ShaDa file: %s" +msgstr "" + +#: ../shada.c:841 +#, c-format +msgid "Reading ShaDa file \"%s\"%s%s%s%s" +msgstr "读取 ShaDa 文件 \"%s\"%s%s%s%s" + +#: ../shada.c:843 +msgid " info" +msgstr " 信息" + +#: ../shada.c:844 +msgid " marks" +msgstr " 标记" + +#: ../shada.c:845 +msgid " oldfiles" +msgstr " 旧文件" + +#: ../shada.c:846 +msgid " FAILED" +msgstr " 失败" + +#: ../shada.c:852 +#, c-format +msgid "System error while opening ShaDa file %s for reading: %s" +msgstr "" + +#: ../shada.c:1536 +msgid "additional elements of ShaDa " +msgstr "" + +#: ../shada.c:1556 +msgid "additional data of ShaDa " +msgstr "" + +#: ../shada.c:1625 +#, c-format +msgid "Failed to write variable %s" +msgstr "" + +#: ../shada.c:1914 +#, c-format +msgid "" +"Failed to parse ShaDa file due to a msgpack parser error at position " +"%<PRIu64>" +msgstr "" + +#: ../shada.c:1929 +#, c-format +msgid "" +"Failed to parse ShaDa file: incomplete msgpack string at position %<PRIu64>" +msgstr "" + +#: ../shada.c:1936 +#, c-format +msgid "" +"Failed to parse ShaDa file: extra bytes in msgpack string at position " +"%<PRIu64>" +msgstr "" + +#: ../shada.c:2989 +#, c-format +msgid "" +"System error while opening ShaDa file %s for reading to merge before writing " +"it: %s" +msgstr "" + +#. Tried names from .tmp.a to .tmp.z, all failed. Something must be +#. wrong then. +#: ../shada.c:3021 +#, c-format +msgid "E138: All %s.tmp.X files exist, cannot write ShaDa file!" +msgstr "" + +#: ../shada.c:3032 +#, c-format +msgid "System error while opening temporary ShaDa file %s for writing: %s" +msgstr "" + +#: ../shada.c:3047 +#, c-format +msgid "Failed to create directory %s for writing ShaDa file: %s" +msgstr "" + +#: ../shada.c:3061 +#, c-format +msgid "System error while opening ShaDa file %s for writing: %s" +msgstr "" + +#: ../shada.c:3077 +#, c-format +msgid "Writing ShaDa file \"%s\"" +msgstr "写入 ShaDa 文件 \"%s\"" + +#: ../shada.c:3104 +#, c-format +msgid "E137: ShaDa file is not writable: %s" +msgstr "E137: ShaDa 文件不可写入:%s" + +#: ../shada.c:3116 +#, c-format +msgid "Failed setting uid and gid for file %s: %s" +msgstr "无法为文件 %s 设置 uid 和 gid:%s" + +#: ../shada.c:3124 +#, c-format +msgid "Can't rename ShaDa file from %s to %s!" +msgstr "" + +#: ../shada.c:3132 +#, c-format +msgid "Did not rename %s because %s does not look like a ShaDa file" +msgstr "" + +#: ../shada.c:3135 +#, c-format +msgid "Did not rename %s to %s because there were errors during writing it" +msgstr "" + +#: ../shada.c:3141 +#, c-format +msgid "Do not forget to remove %s or rename it manually to %s." +msgstr "" + +#: ../shada.c:3267 +#, c-format +msgid "System error while reading ShaDa file: %s" +msgstr "" + +#: ../shada.c:3304 +#, c-format +msgid "System error while reading integer from ShaDa file: %s" +msgstr "" + +#: ../shada.c:3308 +#, c-format +msgid "" +"Error while reading ShaDa file: expected positive integer at position " +"%<PRIu64>, but got nothing" +msgstr "" + +#: ../shada.c:3335 +#, c-format +msgid "" +"Error while reading ShaDa file: expected positive integer at position " +"%<PRIu64>" +msgstr "" + +#: ../shada.c:3530 +#, c-format +msgid "" +"Error while reading ShaDa file: there is an item at position %<PRIu64> that " +"is stated to be too long" +msgstr "" + +#. kSDItemUnknown cannot possibly pass that far because it is -1 and that +#. will fail in msgpack_read_uint64. But kSDItemMissing may and it will +#. otherwise be skipped because (1 << 0) will never appear in flags. +#: ../shada.c:3544 +#, c-format +msgid "" +"Error while reading ShaDa file: there is an item at position %<PRIu64> that " +"must not be there: Missing items are for internal uses only" +msgstr "" + +#: ../shada.c:3914 +#, c-format +msgid "" +"Error while reading ShaDa file: buffer list at position %<PRIu64> contains " +"entry that is not a dictionary" +msgstr "" + +#: ../shada.c:3941 +#, c-format +msgid "" +"Error while reading ShaDa file: buffer list at position %<PRIu64> contains " +"entry with invalid line number" +msgstr "" + +#: ../shada.c:3948 +#, c-format +msgid "" +"Error while reading ShaDa file: buffer list at position %<PRIu64> contains " +"entry with invalid column number" +msgstr "" + +#: ../shada.c:3955 +#, c-format +msgid "" +"Error while reading ShaDa file: buffer list at position %<PRIu64> contains " +"entry that does not have a file name" +msgstr "" + +#: ../sign.c:292 +msgid "[Deleted]" +msgstr "[已删除]" + +#: ../sign.c:709 +msgid "" "\n" -"# Last %sSearch Pattern:\n" -"~" +"--- Signs ---" msgstr "" "\n" -"# 最后 %s搜索模式:\n" -"~" +"--- Signs ---" + +#: ../sign.c:718 +#, c-format +msgid "Signs for %s:" +msgstr "%s 的 Signs:" + +#: ../sign.c:730 +#, c-format +msgid " group=%s" +msgstr " 组=%s" + +#: ../sign.c:736 +#, c-format +msgid " line=%ld id=%d%s name=%s priority=%d" +msgstr " 行=%ld id=%d%s 名称=%s 优先级=%d" + +#: ../sign.c:879 +msgid "E612: Too many signs defined" +msgstr "E612: Signs 定义过多" + +#: ../sign.c:933 +#, c-format +msgid "E239: Invalid sign text: %s" +msgstr "E239: 无效的 sign 文字: %s" + +#: ../sign.c:1036 ../sign.c:1053 ../sign.c:1085 +#, c-format +msgid "E155: Unknown sign: %s" +msgstr "E155: 未知的 sign: %s" + +#: ../sign.c:1114 +#, c-format +msgid "E885: Not possible to change sign %s" +msgstr "E885: 不可能改变标号 %s" + +#: ../sign.c:1161 +msgid "E159: Missing sign number" +msgstr "E159: 缺少 sign 号" + +#: ../sign.c:1171 +#, c-format +msgid "E157: Invalid sign ID: %<PRId64>" +msgstr "E157: 无效的 sign ID: %<PRId64>" + +#: ../sign.c:1182 +msgid "E934: Cannot jump to a buffer that does not have a name" +msgstr "E934: 无法跳转到没有名字的缓冲区" -#: ../spell.c:951 +#: ../sign.c:1476 +#, c-format +msgid "E160: Unknown sign command: %s" +msgstr "E160: 未知的 sign 命令: %s" + +#: ../sign.c:1489 +msgid "E156: Missing sign name" +msgstr "E156: 缺少 sign 名称" + +#: ../sign.c:1676 +msgid " (not supported)" +msgstr " (不支持)" + +#. mode values for find_word +#. find word case-folded +#. find keep-case word +#. find word after prefix +#. find case-folded compound word +#. find keep-case compound word +#: ../spell.c:194 msgid "E759: Format error in spell file" msgstr "E759: 拼写文件格式错误" -#: ../spell.c:952 +#: ../spell.c:1553 +#, c-format +msgid "Warning: Cannot find word list \"%s.%s.spl\" or \"%s.ascii.spl\"" +msgstr "警告: 找不到单词列表 \"%s.%s.spl\" or \"%s.ascii.spl\"" + +#: ../spell.c:1968 +msgid "E797: SpellFileMissing autocommand deleted buffer" +msgstr "E797: SpellFileMissing 自动命令删除了缓冲区" + +#. This is probably an error. Give a warning and +#. accept the words anyway. +#: ../spell.c:1992 +#, c-format +msgid "Warning: region %s not supported" +msgstr "警告: 区域 %s 不支持" + +#: ../spell.c:2579 +msgid "E752: No previous spell replacement" +msgstr "E752: 之前没有拼写替换" + +#: ../spell.c:2624 +#, c-format +msgid "E753: Not found: %s" +msgstr "E753: 找不到: %s" + +#: ../spellfile.c:329 msgid "E758: Truncated spell file" msgstr "E758: 已截断的拼写文件" -#: ../spell.c:953 +#: ../spellfile.c:330 +msgid "E1280: Illegal character in word" +msgstr "E1280: 词中有非法字符" + +#: ../spellfile.c:331 #, c-format msgid "Trailing text in %s line %d: %s" msgstr "%s 第 %d 行,多余的后续字符: %s" -#: ../spell.c:954 +#: ../spellfile.c:332 #, c-format msgid "Affix name too long in %s line %d: %s" msgstr "%s 第 %d 行,附加项名字太长: %s" -#: ../spell.c:955 -msgid "E761: Format error in affix file FOL, LOW or UPP" -msgstr "E761: 附加文件 FOL、LOW 或 UPP 中格式错误" - -#: ../spell.c:957 -msgid "E762: Character in FOL, LOW or UPP is out of range" -msgstr "E762: FOL、LOW 或 UPP 中字符超出范围" - -#: ../spell.c:958 +#: ../spellfile.c:333 msgid "Compressing word tree..." msgstr "压缩单词树……" -#: ../spell.c:1951 -msgid "E756: Spell checking is not enabled" -msgstr "E756: 拼写检查未启用" - -#: ../spell.c:2249 -#, c-format -msgid "Warning: Cannot find word list \"%s.%s.spl\" or \"%s.ascii.spl\"" -msgstr "警告: 找不到单词列表 \"%s.%s.spl\" or \"%s.ascii.spl\"" - -#: ../spell.c:2473 +#: ../spellfile.c:622 #, c-format msgid "Reading spell file \"%s\"" msgstr "读取拼写文件 \"%s\"" -#: ../spell.c:2496 +#: ../spellfile.c:647 msgid "E757: This does not look like a spell file" msgstr "E757: 这看起来不像是拼写文件" -#: ../spell.c:2501 +#: ../spellfile.c:650 +#, c-format +msgid "E5042: Failed to read spell file %s: %s" +msgstr "E5042: 无法读取拼写文件 %s:%s" + +#: ../spellfile.c:658 msgid "E771: Old spell file, needs to be updated" msgstr "E771: 旧版本的拼写文件,需要更新" -#: ../spell.c:2504 +#: ../spellfile.c:661 msgid "E772: Spell file is for newer version of Vim" msgstr "E772: 为更高版本的 Vim 所用的拼写文件" -#: ../spell.c:2602 +#: ../spellfile.c:769 msgid "E770: Unsupported section in spell file" msgstr "E770: 拼写文件中存在不支持的节" -#: ../spell.c:3762 +#: ../spellfile.c:935 #, c-format -msgid "Warning: region %s not supported" -msgstr "警告: 区域 %s 不支持" +msgid "E778: This does not look like a .sug file: %s" +msgstr "E778: 看起来不像是 .sug 文件: %s" -#: ../spell.c:4550 +#: ../spellfile.c:941 +#, c-format +msgid "E779: Old .sug file, needs to be updated: %s" +msgstr "E779: 旧的.sug 文件,需要更新: %s" + +#: ../spellfile.c:945 +#, c-format +msgid "E780: .sug file is for newer version of Vim: %s" +msgstr "E780: .sug 文件适用于较新的vim 版本: %s" + +#: ../spellfile.c:954 +#, c-format +msgid "E781: .sug file doesn't match .spl file: %s" +msgstr "E781: .sug 文件不能匹配 .spl 文件: %s" + +#: ../spellfile.c:964 +#, c-format +msgid "E782: error while reading .sug file: %s" +msgstr "E782: 读取 .sug 文件时出错:%s" + +#: ../spellfile.c:2067 #, c-format msgid "Reading affix file %s..." msgstr "读取附加文件 %s ……" -#: ../spell.c:4589 ../spell.c:5635 ../spell.c:6140 +#: ../spellfile.c:2103 ../spellfile.c:3177 #, c-format msgid "Conversion failure for word in %s line %d: %s" msgstr "单词 %s 转换失败,第 %d 行: %s" -#: ../spell.c:4630 ../spell.c:6170 +#: ../spellfile.c:2150 ../spellfile.c:3750 #, c-format msgid "Conversion in %s not supported: from %s to %s" msgstr "不支持 %s 中的转换: 从 %s 到 %s" -#: ../spell.c:4642 +#: ../spellfile.c:2163 #, c-format msgid "Invalid value for FLAG in %s line %d: %s" msgstr "%s 第 %d 行,FLAG 的值无效: %s" -#: ../spell.c:4655 +#: ../spellfile.c:2177 #, c-format msgid "FLAG after using flags in %s line %d: %s" msgstr "%s 第 %d 行,在使用标志后出现 FLAG: %s" -#: ../spell.c:4723 +#: ../spellfile.c:2238 #, c-format msgid "" "Defining COMPOUNDFORBIDFLAG after PFX item may give wrong results in %s line " "%d" -msgstr "在 PFX 项之后定义 COMPOUNDFORBIDFLAG (%s 第%d行)可能会给出错误的结果" -"%d" +msgstr "" +"在 PFX 项之后定义 COMPOUNDFORBIDFLAG (%s 第 %d 行)可能会给出错误的结果" -#: ../spell.c:4731 +#: ../spellfile.c:2246 #, c-format msgid "" "Defining COMPOUNDPERMITFLAG after PFX item may give wrong results in %s line " "%d" -msgstr "在 PFX 项之后定义 COMPOUNDPERMITFLAG (%s 第%d行)可能会给出错误的结果" -"%d" +msgstr "" +"在 PFX 项之后定义 COMPOUNDPERMITFLAG (%s 第 %d 行)可能会给出错误的结果" -#: ../spell.c:4747 -#, fuzzy, c-format +#: ../spellfile.c:2261 +#, c-format msgid "Wrong COMPOUNDRULES value in %s line %d: %s" -msgstr "%s 第 %d 行,错误的 COMPOUNDMIN 值: %s" +msgstr "%s 第 %d 行,错误的 COMPOUNDRULES 值: %s" -#: ../spell.c:4771 +#: ../spellfile.c:2285 #, c-format msgid "Wrong COMPOUNDWORDMAX value in %s line %d: %s" msgstr "%s 第 %d 行,错误的 COMPOUNDWORDMAX 值: %s" -#: ../spell.c:4777 +#: ../spellfile.c:2292 #, c-format msgid "Wrong COMPOUNDMIN value in %s line %d: %s" msgstr "%s 第 %d 行,错误的 COMPOUNDMIN 值: %s" -#: ../spell.c:4783 +#: ../spellfile.c:2299 #, c-format msgid "Wrong COMPOUNDSYLMAX value in %s line %d: %s" msgstr "%s 第 %d 行,错误的 COMPOUNDSYLMAX 值: %s" -#: ../spell.c:4795 +#: ../spellfile.c:2312 #, c-format msgid "Wrong CHECKCOMPOUNDPATTERN value in %s line %d: %s" msgstr "%s 第 %d 行,错误的 CHECKCOMPOUNDPATTERN 值: %s" -#: ../spell.c:4847 +#: ../spellfile.c:2368 #, c-format msgid "Different combining flag in continued affix block in %s line %d: %s" msgstr "%s 第 %d 行,在连续的附加块中出现不同的组合标志: %s" -#: ../spell.c:4850 +#: ../spellfile.c:2372 #, c-format msgid "Duplicate affix in %s line %d: %s" msgstr "%s 第 %d 行,重复的附加项: %s" -#: ../spell.c:4871 +#: ../spellfile.c:2391 #, c-format msgid "" "Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST in %s " @@ -5382,334 +6330,337 @@ msgstr "" "%s 第 %d 行,附加项被 BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST 使" "用: %s" -#: ../spell.c:4893 +#: ../spellfile.c:2421 #, c-format msgid "Expected Y or N in %s line %d: %s" msgstr "%s 第 %d 行,此处需要 Y 或 N: %s" -#: ../spell.c:4968 +#: ../spellfile.c:2497 #, c-format msgid "Broken condition in %s line %d: %s" msgstr "%s 第 %d 行,错误的条件: %s" -#: ../spell.c:5091 +#: ../spellfile.c:2613 #, c-format msgid "Expected REP(SAL) count in %s line %d" msgstr "%s 第 %d 行,此处需要 REP(SAL) 计数" -#: ../spell.c:5120 +#: ../spellfile.c:2648 #, c-format msgid "Expected MAP count in %s line %d" msgstr "%s 第 %d 行,此处需要 MAP 计数" -#: ../spell.c:5132 +#: ../spellfile.c:2661 #, c-format msgid "Duplicate character in MAP in %s line %d" msgstr "%s 第 %d 行,MAP 中存在重复的字符" -#: ../spell.c:5176 +#: ../spellfile.c:2706 #, c-format msgid "Unrecognized or duplicate item in %s line %d: %s" msgstr "%s 第 %d 行,无法识别或重复的项: %s" -#: ../spell.c:5197 -#, c-format -msgid "Missing FOL/LOW/UPP line in %s" -msgstr "%s 中缺少 FOL/LOW/UPP 行" - -#: ../spell.c:5220 +#: ../spellfile.c:2738 msgid "COMPOUNDSYLMAX used without SYLLABLE" msgstr "在没有 SYLLABLE 的情况下使用了 COMPOUNDSYLMAX" -#: ../spell.c:5236 +#: ../spellfile.c:2756 msgid "Too many postponed prefixes" msgstr "太多延迟前缀" -#: ../spell.c:5238 +#: ../spellfile.c:2758 msgid "Too many compound flags" msgstr "太多组合标志" -#: ../spell.c:5240 +#: ../spellfile.c:2760 msgid "Too many postponed prefixes and/or compound flags" msgstr "太多延迟前缀和/或组合标志" -#: ../spell.c:5250 +#: ../spellfile.c:2771 #, c-format msgid "Missing SOFO%s line in %s" msgstr "%s 中缺少 SOFO%s 行" -#: ../spell.c:5253 +#: ../spellfile.c:2774 #, c-format msgid "Both SAL and SOFO lines in %s" msgstr "%s 同时出现 SQL 和 SOFO 行" -#: ../spell.c:5331 +#: ../spellfile.c:2855 #, c-format msgid "Flag is not a number in %s line %d: %s" msgstr "%s 第 %d 行,标志不是数字: %s" -#: ../spell.c:5334 +#: ../spellfile.c:2858 #, c-format msgid "Illegal flag in %s line %d: %s" msgstr "%s 第 %d 行,无效的标志: %s" -#: ../spell.c:5493 ../spell.c:5501 +#: ../spellfile.c:3029 ../spellfile.c:3038 #, c-format msgid "%s value differs from what is used in another .aff file" msgstr "%s 的值与另一个 .aff 文件中使用的值不相同" -#: ../spell.c:5602 +#: ../spellfile.c:3142 #, c-format msgid "Reading dictionary file %s..." msgstr "读取字典文件 %s ……" -#: ../spell.c:5611 +#: ../spellfile.c:3150 #, c-format msgid "E760: No word count in %s" msgstr "E760: %s 中没有单词计数" -#: ../spell.c:5669 +#: ../spellfile.c:3214 #, c-format -msgid "line %6d, word %6d - %s" -msgstr "第 %6d 行,第 %6d 个单词 - %s" +msgid "line %6d, word %6ld - %s" +msgstr "第 %6d 行,第 %6ld 个单词 - %s" -#: ../spell.c:5691 +#: ../spellfile.c:3237 #, c-format msgid "Duplicate word in %s line %d: %s" msgstr "%s 第 %d 行,重复的单词: %s" -#: ../spell.c:5694 +#: ../spellfile.c:3240 #, c-format msgid "First duplicate word in %s line %d: %s" msgstr "%s 第 %d 行,首次重复的单词: %s" -#: ../spell.c:5746 +#: ../spellfile.c:3301 #, c-format msgid "%d duplicate word(s) in %s" msgstr "存在 %d 个重复的单词,在 %s 中" -#: ../spell.c:5748 +#: ../spellfile.c:3304 #, c-format msgid "Ignored %d word(s) with non-ASCII characters in %s" msgstr "忽略了含有非 ASCII 字符的 %d 个单词,在 %s 中" -#: ../spell.c:6115 +#: ../spellfile.c:3695 #, c-format msgid "Reading word file %s..." msgstr "读取单词文件 %s ……" -#: ../spell.c:6155 +#: ../spellfile.c:3723 +#, c-format +msgid "Conversion failure for word in %s line %ld: %s" +msgstr "单词 %s 转换失败,第 %ld 行: %s" + +#: ../spellfile.c:3737 #, c-format -msgid "Duplicate /encoding= line ignored in %s line %d: %s" +msgid "Duplicate /encoding= line ignored in %s line %ld: %s" msgstr "%s 第 %ld 行,重复的 /encoding= 行已被忽略: %s" -#: ../spell.c:6159 +#: ../spellfile.c:3740 #, c-format -msgid "/encoding= line after word ignored in %s line %d: %s" -msgstr "%s 第 %d 行,单词后的 /encoding= 行已被忽略: %s" +msgid "/encoding= line after word ignored in %s line %ld: %s" +msgstr "%s 第 %ld 行,单词后的 /encoding= 行已被忽略: %s" -#: ../spell.c:6180 +#: ../spellfile.c:3761 #, c-format -msgid "Duplicate /regions= line ignored in %s line %d: %s" -msgstr "%s 第 %d 行,重复的 /regions= 行已被忽略: %s" +msgid "Duplicate /regions= line ignored in %s line %ld: %s" +msgstr "%s 第 %ld 行,重复的 /regions= 行已被忽略: %s" -#: ../spell.c:6185 +#: ../spellfile.c:3766 #, c-format -msgid "Too many regions in %s line %d: %s" -msgstr "%s 第 %d 行,太多区域: %s" +msgid "Too many regions in %s line %ld: %s" +msgstr "%s 第 %ld 行,太多区域: %s" -#: ../spell.c:6198 +#: ../spellfile.c:3779 #, c-format -msgid "/ line ignored in %s line %d: %s" -msgstr "%s 第 %d 行,/ 行已被忽略: %s" +msgid "/ line ignored in %s line %ld: %s" +msgstr "%s 第 %ld 行,/ 行已被忽略: %s" -#: ../spell.c:6224 +#: ../spellfile.c:3806 #, c-format -msgid "Invalid region nr in %s line %d: %s" -msgstr "%s 第 %d 行,无效的区域号: %s" +msgid "Invalid region nr in %s line %ld: %s" +msgstr "%s 第 %ld 行,区域号无效:%s" -#: ../spell.c:6230 +#: ../spellfile.c:3812 #, c-format -msgid "Unrecognized flags in %s line %d: %s" -msgstr "%s 第 %d 行,不可识别的标志: %s" +msgid "Unrecognized flags in %s line %ld: %s" +msgstr "%s 第 %ld 行,不可识别的标志: %s" -#: ../spell.c:6257 +#: ../spellfile.c:3839 #, c-format msgid "Ignored %d words with non-ASCII characters" msgstr "忽略了含有非 ASCII 字符的 %d 个单词" -#: ../spell.c:6656 +#: ../spellfile.c:4231 #, c-format -msgid "Compressed %d of %d nodes; %d (%d%%) remaining" -msgstr "压缩了 %d/%d 个节点;剩余 %d (%d%%)" +msgid "Compressed %s of %ld nodes; %ld (%ld%%) remaining" +msgstr "压缩了 %s / %ld 个节点;剩余 %ld (%ld%%)" -#: ../spell.c:7340 +#: ../spellfile.c:4933 msgid "Reading back spell file..." msgstr "读取拼写文件……" #. Go through the trie of good words, soundfold each word and add it to #. the soundfold trie. -#: ../spell.c:7357 +#: ../spellfile.c:4951 msgid "Performing soundfolding..." msgstr "正在 soundfolding……" -#: ../spell.c:7368 +#: ../spellfile.c:4964 #, c-format msgid "Number of words after soundfolding: %<PRId64>" msgstr "soundfolding 后的单词数: %<PRId64>" -#: ../spell.c:7476 +#: ../spellfile.c:5072 #, c-format msgid "Total number of words: %d" msgstr "单词总数: %d" -#: ../spell.c:7655 +#: ../spellfile.c:5213 #, c-format msgid "Writing suggestion file %s..." msgstr "写入建议文件 %s ……" -#: ../spell.c:7707 ../spell.c:7927 +#: ../spellfile.c:5269 ../spellfile.c:5457 #, c-format msgid "Estimated runtime memory use: %d bytes" msgstr "估计运行时内存用量: %d 字节" -#: ../spell.c:7820 +#: ../spellfile.c:5353 msgid "E751: Output file name must not have region name" msgstr "E751: 输出文件名不能含有区域名" -#: ../spell.c:7822 -msgid "E754: Only up to 8 regions supported" -msgstr "E754: 最多只支持 8 个区域" +#: ../spellfile.c:5355 +#, c-format +msgid "E754: Only up to %d regions supported" +msgstr "E754: 最多只支持 %d 个区域" -#: ../spell.c:7846 +#: ../spellfile.c:5379 #, c-format msgid "E755: Invalid region in %s" msgstr "E755: %s 出现无效的范围" -#: ../spell.c:7907 +#: ../spellfile.c:5436 msgid "Warning: both compounding and NOBREAK specified" msgstr "警告: 同时指定了 compounding 和 NOBREAK" -#: ../spell.c:7920 +#: ../spellfile.c:5450 #, c-format msgid "Writing spell file %s..." msgstr "写入拼写文件 %s ……" -#: ../spell.c:7925 +#: ../spellfile.c:5455 msgid "Done!" msgstr "完成!" -#: ../spell.c:8034 +#: ../spellfile.c:5576 #, c-format msgid "E765: 'spellfile' does not have %<PRId64> entries" msgstr "E765: 'spellfile' 没有 %<PRId64> 项" -#: ../spell.c:8074 -#, fuzzy, c-format +#: ../spellfile.c:5621 +#, c-format msgid "Word '%.*s' removed from %s" -msgstr "从 %s 中删除了单词" +msgstr "单词 '%.*s' 从 %s 中删除了" -#: ../spell.c:8117 -#, fuzzy, c-format +#: ../spellfile.c:5625 +msgid "Seek error in spellfile" +msgstr "拼写文件中定位错误" + +#: ../spellfile.c:5671 +#, c-format msgid "Word '%.*s' added to %s" -msgstr "向 %s 中添加了单词" +msgstr "单词 '%.*s' 添加到了 %s" -#: ../spell.c:8381 +#: ../spellfile.c:5803 msgid "E763: Word characters differ between spell files" msgstr "E763: 拼写文件之间的字符不相同" -#: ../spell.c:8684 +#. This should have been checked when generating the .spl +#. file. +#: ../spellfile.c:5899 +msgid "E783: duplicate char in MAP entry" +msgstr "E783: MAP 条目中有重复的字符" + +#: ../spellsuggest.c:530 msgid "Sorry, no suggestions" msgstr "抱歉,没有建议" -#: ../spell.c:8687 +#: ../spellsuggest.c:533 #, c-format msgid "Sorry, only %<PRId64> suggestions" msgstr "抱歉,只有 %<PRId64> 条建议" #. for when 'cmdheight' > 1 #. avoid more prompt -#: ../spell.c:8704 +#: ../spellsuggest.c:547 #, c-format msgid "Change \"%.*s\" to:" msgstr "将 \"%.*s\" 改为:" -#: ../spell.c:8737 +#: ../spellsuggest.c:581 #, c-format msgid " < \"%.*s\"" msgstr " < \"%.*s\"" -#: ../spell.c:8882 -msgid "E752: No previous spell replacement" -msgstr "E752: 之前没有拼写替换" +#: ../statusline.c:110 ../statusline.c:1553 +msgid "[Help]" +msgstr "[帮助]" -#: ../spell.c:8925 -#, c-format -msgid "E753: Not found: %s" -msgstr "E753: 找不到: %s" +#: ../statusline.c:114 ../statusline.c:1588 +msgid "[Preview]" +msgstr "[预览]" -#: ../spell.c:9276 -#, c-format -msgid "E778: This does not look like a .sug file: %s" -msgstr "E778: 看起来不像是 .sug 文件: %s" +#: ../strings.c:496 +msgid "E766: Insufficient arguments for printf()" +msgstr "E766: printf() 的参数不足" -#: ../spell.c:9282 -#, c-format -msgid "E779: Old .sug file, needs to be updated: %s" -msgstr "E779: 旧的.sug 文件,需要更新: %s" +#: ../strings.c:618 +msgid "E807: Expected Float argument for printf()" +msgstr "E807: 期盼浮点数作为printf()参数" -#: ../spell.c:9286 -#, c-format -msgid "E780: .sug file is for newer version of Vim: %s" -msgstr "E780: .sug 文件适用于较新的vim 版本: %s" +#: ../strings.c:1400 +msgid "E767: Too many arguments to printf()" +msgstr "E767: printf() 的参数过多" -#: ../spell.c:9295 +#: ../syntax.c:61 #, c-format -msgid "E781: .sug file doesn't match .spl file: %s" -msgstr "E781: .sug 文件不能匹配 .spl 文件: %s" - -#: ../spell.c:9305 -#, c-format -msgid "E782: error while reading .sug file: %s" -msgstr "E782: 当读取.sug 文件时错误" - -#. This should have been checked when generating the .spl -#. file. -#: ../spell.c:11575 -msgid "E783: duplicate char in MAP entry" -msgstr "E783: MAP 条目中有重复的字符" +msgid "E390: Illegal argument: %s" +msgstr "E390: 无效的参数: %s" -#: ../syntax.c:266 +#: ../syntax.c:241 msgid "No Syntax items defined for this buffer" msgstr "这个缓冲区没有定义任何语法项" -#: ../syntax.c:3083 ../syntax.c:3104 ../syntax.c:3127 -#, c-format -msgid "E390: Illegal argument: %s" -msgstr "E390: 无效的参数: %s" +#: ../syntax.c:2706 +msgid "'redrawtime' exceeded, syntax highlighting disabled" +msgstr "'redrawtime' 已过,语法高亮被禁用" + +#: ../syntax.c:2941 +msgid "syntax iskeyword not set" +msgstr "syntax iskeyword 未设置" -#: ../syntax.c:3299 +#: ../syntax.c:3118 #, c-format msgid "E391: No such syntax cluster: %s" msgstr "E391: 无此语法 cluster: \"%s\"" -#: ../syntax.c:3433 +#: ../syntax.c:3234 msgid "syncing on C-style comments" msgstr "C风格注释同步中" -#: ../syntax.c:3439 +#: ../syntax.c:3240 msgid "no syncing" msgstr "没有同步" -#: ../syntax.c:3441 +#: ../syntax.c:3243 +msgid "syncing starts at the first line" +msgstr "同步开始于第一行" + +#: ../syntax.c:3245 msgid "syncing starts " msgstr "同步开始" -#: ../syntax.c:3443 ../syntax.c:3506 +#: ../syntax.c:3247 ../syntax.c:3316 msgid " lines before top line" msgstr "行号超出范围" -#: ../syntax.c:3448 +#: ../syntax.c:3253 msgid "" "\n" "--- Syntax sync items ---" @@ -5717,7 +6668,7 @@ msgstr "" "\n" "--- 语法同步项目 (Syntax sync items) ---" -#: ../syntax.c:3452 +#: ../syntax.c:3257 msgid "" "\n" "syncing on items" @@ -5725,7 +6676,7 @@ msgstr "" "\n" "同步中:" -#: ../syntax.c:3457 +#: ../syntax.c:3262 msgid "" "\n" "--- Syntax items ---" @@ -5733,277 +6684,211 @@ msgstr "" "\n" "--- 语法项目 ---" -#: ../syntax.c:3475 +#: ../syntax.c:3279 #, c-format msgid "E392: No such syntax cluster: %s" msgstr "E392: 无此语法 cluster: \"%s\"" -#: ../syntax.c:3497 +#: ../syntax.c:3303 +msgid "from the first line" +msgstr "从第一行" + +#: ../syntax.c:3306 msgid "minimal " msgstr "最小" -#: ../syntax.c:3503 +#: ../syntax.c:3313 msgid "maximal " msgstr "最大" -#: ../syntax.c:3513 -#, fuzzy +#: ../syntax.c:3324 msgid "; match " -msgstr "匹配 %d" +msgstr ";匹配 " -#: ../syntax.c:3515 -#, fuzzy +#: ../syntax.c:3326 msgid " line breaks" -msgstr "少于一行" +msgstr " 个换行" -#: ../syntax.c:4076 +#: ../syntax.c:3875 msgid "E395: contains argument not accepted here" msgstr "E395: 使用了不正确的参数" -#: ../syntax.c:4096 -#, fuzzy +#: ../syntax.c:3894 msgid "E844: invalid cchar value" -msgstr "E474: 无效的参数" +msgstr "E844: cchar 值无效" -#: ../syntax.c:4107 +#: ../syntax.c:3905 msgid "E393: group[t]here not accepted here" msgstr "E393: 使用了不正确的参数" -#: ../syntax.c:4126 +#: ../syntax.c:3927 #, c-format msgid "E394: Didn't find region item for %s" msgstr "E394: 找不到 %s 的 region item" -#: ../syntax.c:4188 +#: ../syntax.c:3988 msgid "E397: Filename required" msgstr "E397: 需要文件名称" -#: ../syntax.c:4221 -#, fuzzy +#: ../syntax.c:4019 msgid "E847: Too many syntax includes" -msgstr "E77: 文件名过多" +msgstr "E847: syntax include 过多" -#: ../syntax.c:4303 -#, fuzzy, c-format +#: ../syntax.c:4107 +#, c-format msgid "E789: Missing ']': %s" -msgstr "E747: 缺少 ']': %s" +msgstr "E789: 缺少 ']':%s" -#: ../syntax.c:4531 +#: ../syntax.c:4112 +#, c-format +msgid "E890: trailing char after ']': %s]%s" +msgstr "E890: ']' 后有尾随的字符:%s]%s" + +#: ../syntax.c:4320 #, c-format msgid "E398: Missing '=': %s" msgstr "E398: 缺少 '=': %s" -#: ../syntax.c:4666 +#: ../syntax.c:4450 #, c-format msgid "E399: Not enough arguments: syntax region %s" msgstr "E399: syntax region %s 的参数太少" -#: ../syntax.c:4870 -#, fuzzy +#: ../syntax.c:4629 msgid "E848: Too many syntax clusters" -msgstr "E391: 无此语法 cluster: \"%s\"" +msgstr "E848: syntax cluster 过多" -#: ../syntax.c:4954 +#: ../syntax.c:4714 msgid "E400: No cluster specified" msgstr "E400: 没有指定的属性" #. end delimiter not found -#: ../syntax.c:4986 +#: ../syntax.c:4746 #, c-format msgid "E401: Pattern delimiter not found: %s" msgstr "E401: 找不到分隔符号: %s" -#: ../syntax.c:5049 +#: ../syntax.c:4816 #, c-format msgid "E402: Garbage after pattern: %s" msgstr "E402: '%s' 后面的东西不能识别" -#: ../syntax.c:5120 +#: ../syntax.c:4893 msgid "E403: syntax sync: line continuations pattern specified twice" msgstr "E403: 语法同步: 连接行符号指定了两次" -#: ../syntax.c:5169 +#: ../syntax.c:4942 #, c-format msgid "E404: Illegal arguments: %s" msgstr "E404: 无效的参数: %s" -#: ../syntax.c:5217 +#: ../syntax.c:4978 #, c-format msgid "E405: Missing equal sign: %s" msgstr "E405: 缺少等号: %s" -#: ../syntax.c:5222 +#: ../syntax.c:4983 #, c-format msgid "E406: Empty argument: %s" msgstr "E406: 空的参数: %s" -#: ../syntax.c:5240 +#: ../syntax.c:4998 #, c-format msgid "E407: %s not allowed here" msgstr "E407: %s 不能在此出现" -#: ../syntax.c:5246 +#: ../syntax.c:5004 #, c-format msgid "E408: %s must be first in contains list" msgstr "E408: %s 必须是列表里的第一个" -#: ../syntax.c:5304 +#: ../syntax.c:5067 #, c-format msgid "E409: Unknown group name: %s" msgstr "E409: 不正确的组名: %s" -#: ../syntax.c:5512 +#: ../syntax.c:5272 #, c-format msgid "E410: Invalid :syntax subcommand: %s" msgstr "E410: 不正确的 :syntax 子命令: %s" -#: ../syntax.c:5854 +#: ../syntax.c:5660 msgid "" " TOTAL COUNT MATCH SLOWEST AVERAGE NAME PATTERN" msgstr "" " 总 计 计 数 匹 配 最 慢 的 平 均 名 字 模 式" -#: ../syntax.c:6146 -msgid "E679: recursive loop loading syncolor.vim" -msgstr "E679: 加载 syncolor.vim 时出现嵌套循环" - -#: ../syntax.c:6256 -#, c-format -msgid "E411: highlight group not found: %s" -msgstr "E411: 找不到 highlight group: %s" - -#: ../syntax.c:6278 -#, c-format -msgid "E412: Not enough arguments: \":highlight link %s\"" -msgstr "E412: 参数太少: \":highlight link %s\"" - -#: ../syntax.c:6284 -#, c-format -msgid "E413: Too many arguments: \":highlight link %s\"" -msgstr "E413: 参数过多: \":highlight link %s\"" - -#: ../syntax.c:6302 -msgid "E414: group has settings, highlight link ignored" -msgstr "E414: 已设定组, 忽略 highlight link" - -#: ../syntax.c:6367 -#, c-format -msgid "E415: unexpected equal sign: %s" -msgstr "E415: 不该有的等号: %s" - -#: ../syntax.c:6395 -#, c-format -msgid "E416: missing equal sign: %s" -msgstr "E416: 缺少等号: %s" - -#: ../syntax.c:6418 -#, c-format -msgid "E417: missing argument: %s" -msgstr "E417: 缺少参数: %s" - -#: ../syntax.c:6446 -#, c-format -msgid "E418: Illegal value: %s" -msgstr "E418: 不合法的值: %s" - -#: ../syntax.c:6496 -msgid "E419: FG color unknown" -msgstr "E419: 错误的前景颜色" - -#: ../syntax.c:6504 -msgid "E420: BG color unknown" -msgstr "E420: 错误的背景颜色" - -#: ../syntax.c:6564 -#, c-format -msgid "E421: Color name or number not recognized: %s" -msgstr "E421: 错误的颜色名称或数值: %s" - -#: ../syntax.c:6714 -#, c-format -msgid "E422: terminal code too long: %s" -msgstr "E422: 终端编码太长: %s" - -#: ../syntax.c:6753 -#, c-format -msgid "E423: Illegal argument: %s" -msgstr "E423: 无效的参数: %s" - -#: ../syntax.c:6925 -msgid "E424: Too many different highlighting attributes in use" -msgstr "E424: 使用了太多不同的高亮度属性" - -#: ../syntax.c:7427 -msgid "E669: Unprintable character in group name" -msgstr "E669: 组名中存在不可显示字符" - -#: ../highlight_group.c:1756 -msgid "E5248: Invalid character in group name" -msgstr "E5248: 组名中含有无效字符" - -#: ../syntax.c:7448 -msgid "E849: Too many highlight and syntax groups" -msgstr "E849: 高亮和语法组过多" - -#: ../tag.c:104 +#: ../tag.c:117 msgid "E555: at bottom of tag stack" msgstr "E555: 已在 tag 堆栈底部" -#: ../tag.c:105 +#: ../tag.c:118 msgid "E556: at top of tag stack" msgstr "E556: 已在 tag 堆栈顶部" -#: ../tag.c:380 +#: ../tag.c:120 +msgid "E986: cannot modify the tag stack within tagfunc" +msgstr "E986: 在 tagfunc 中不能修改 tag 堆栈" + +#: ../tag.c:122 +msgid "E987: invalid return value from tagfunc" +msgstr "E987: tagfunc 返回了无效的值" + +#: ../tag.c:124 +msgid "E1299: Window unexpectedly closed while searching for tags" +msgstr "E1299: 搜索 tag 时窗口意外关闭" + +#: ../tag.c:427 msgid "E425: Cannot go before first matching tag" msgstr "E425: 已到第一个匹配的 tag" -#: ../tag.c:504 +#: ../tag.c:568 #, c-format msgid "E426: tag not found: %s" msgstr "E426: 找不到 tag: %s" -#: ../tag.c:528 -msgid " # pri kind tag" -msgstr " # pri kind tag" - -#: ../tag.c:531 -msgid "file\n" -msgstr "文件\n" - -#: ../tag.c:829 +#: ../tag.c:610 msgid "E427: There is only one matching tag" msgstr "E427: 只有一个匹配的 tag" -#: ../tag.c:831 +#: ../tag.c:612 msgid "E428: Cannot go beyond last matching tag" msgstr "E428: 己到最后一个匹配的 tag" -#: ../tag.c:850 +#: ../tag.c:641 #, c-format msgid "File \"%s\" does not exist" msgstr "文件 \"%s\" 不存在" #. Give an indication of the number of matching tags -#: ../tag.c:859 +#: ../tag.c:649 #, c-format msgid "tag %d of %d%s" msgstr "找到 tag: %d / %d%s" -#: ../tag.c:862 +#: ../tag.c:652 msgid " or more" msgstr " 或更多" -#: ../tag.c:864 +#: ../tag.c:654 msgid " Using tag with different case!" msgstr " 以不同大小写来使用 tag!" -#: ../tag.c:909 +#: ../tag.c:701 #, c-format msgid "E429: File \"%s\" does not exist" msgstr "E429: 文件 \"%s\" 不存在" +#: ../tag.c:749 +msgid " # pri kind tag" +msgstr " # pri kind tag" + +#: ../tag.c:752 +msgid "file\n" +msgstr "文件\n" + #. Highlight title -#: ../tag.c:960 +#: ../tag.c:1064 msgid "" "\n" " # TO tag FROM line in file/text" @@ -6011,1619 +6896,2495 @@ msgstr "" "\n" " # 到 tag 从 行 在 文件/文本" -#: ../tag.c:1303 +#: ../tag.c:1635 #, c-format msgid "Searching tags file %s" msgstr "查找 tag 文件 %s" -#: ../tag.c:1545 -msgid "Ignoring long line in tags file" -msgstr "忽略较长的行在 tags 文件中" - -#: ../tag.c:1915 +#: ../tag.c:2155 #, c-format msgid "E431: Format error in tags file \"%s\"" msgstr "E431: Tag 文件 \"%s\" 格式错误" -#: ../tag.c:1917 +#: ../tag.c:2156 #, c-format msgid "Before byte %<PRId64>" msgstr "在第 %<PRId64> 字节之前" -#: ../tag.c:1929 +#: ../tag.c:2168 #, c-format msgid "E432: Tags file not sorted: %s" msgstr "E432: Tag 文件未排序: %s" #. never opened any tags file -#: ../tag.c:1960 +#: ../tag.c:2195 msgid "E433: No tags file" msgstr "E433: 没有 tag 文件" -#: ../tag.c:2536 +#: ../tag.c:2794 msgid "E434: Can't find tag pattern" msgstr "E434: 找不到 tag 模式" -#: ../tag.c:2544 +#: ../tag.c:2800 msgid "E435: Couldn't find tag, just guessing!" msgstr "E435: 找不到 tag,试着猜!" -#: ../tag.c:2797 -#, fuzzy, c-format +#: ../tag.c:3070 +#, c-format msgid "Duplicate field name: %s" -msgstr "%s 第 %d 行,重复的附加项: %s" - -#: ../term.c:1442 -msgid "' not known. Available builtin terminals are:" -msgstr "' 未知。可用的内建终端有:" +msgstr "字段名重复:%s" -#: ../term.c:1463 -msgid "defaulting to '" -msgstr "默认值为: '" - -#: ../term.c:1731 -msgid "E557: Cannot open termcap file" -msgstr "E557: 无法打开 termcap 文件" - -#: ../term.c:1735 -msgid "E558: Terminal entry not found in terminfo" -msgstr "E558: 在 terminfo 中找不到终端项" +#: ../testing.c:38 +msgid "" +"E856: assert_fails() second argument must be a string or a list with one or " +"two strings" +msgstr "" +"E856: assert_fails() 的第二个参数必须是字符串或包含一或两个字符串的列表" -#: ../term.c:1737 -msgid "E559: Terminal entry not found in termcap" -msgstr "E559: 在 termcap 中找不到终端项" +#: ../testing.c:40 +msgid "E1115: assert_fails() fourth argument must be a number" +msgstr "E1115: assert_fails() 的第四个参数必须是整数" -#: ../term.c:1878 -#, c-format -msgid "E436: No \"%s\" entry in termcap" -msgstr "E436: termcap 中没有 \"%s\" 项" +#: ../testing.c:42 +msgid "E1116: assert_fails() fifth argument must be a string" +msgstr "E1116: assert_fails() 的第五个参数必须是字符串" -#: ../term.c:2249 -msgid "E437: terminal capability \"cm\" required" -msgstr "E437: 终端需要能力 \"cm\"" +#: ../testing.c:44 +msgid "E1142: Calling test_garbagecollect_now() while v:testing is not set" +msgstr "E1142: 调用 test_garbagecollect_now(),但 v:testing 未设置" -#. Highlight title -#: ../term.c:4376 -msgid "" -"\n" -"--- Terminal keys ---" -msgstr "" -"\n" -"--- 终端按键 ---" - -#: ../ui.c:481 -msgid "Vim: Error reading input, exiting...\n" -msgstr "Vim: 读错误,退出中...\n" +#: ../ui.c:342 +msgid "Beep!" +msgstr "Beep!" #. This happens when the FileChangedRO autocommand changes the -#. * file in a way it becomes shorter. -#: ../undo.c:379 +#. file in a way it becomes shorter. +#: ../undo.c:357 msgid "E881: Line count changed unexpectedly" msgstr "E881: 行数意外地改变了" -#: ../undo.c:627 +#: ../undo.c:628 #, c-format msgid "E828: Cannot open undo file for writing: %s" -msgstr "E828: 无法打开撤销文件去写入" +msgstr "E828: 无法打开并写入撤销文件:%s" + +#: ../undo.c:710 +#, c-format +msgid "E5003: Unable to create directory \"%s\" for undo file: %s" +msgstr "E303: 无法为交换文件创建目录 \"%s\":%s" -#: ../undo.c:717 +#: ../undo.c:749 #, c-format msgid "E825: Corrupted undo file (%s): %s" msgstr "E825: 已损坏的撤销文件 (%s): %s" -#: ../undo.c:1039 +#: ../undo.c:1169 msgid "Cannot write undo file in any directory in 'undodir'" msgstr "不能写入撤销文件到 'undodir' 中的任何文件夹" -#: ../undo.c:1074 +#: ../undo.c:1205 #, c-format msgid "Will not overwrite with undo file, cannot read: %s" msgstr "不能重写撤销文件, 不可读取: %s" -#: ../undo.c:1092 +#: ../undo.c:1222 #, c-format msgid "Will not overwrite, this is not an undo file: %s" msgstr "这个文件: %s 不是撤销文件,不可以重写" -#: ../undo.c:1108 +#: ../undo.c:1239 msgid "Skipping undo file write, nothing to undo" msgstr "跳过写入撤销文件,没有任何撤销" -#: ../undo.c:1121 -#, fuzzy, c-format +#: ../undo.c:1252 +#, c-format msgid "Writing undo file: %s" -msgstr "写入 viminfo 文件 \"%s\"" +msgstr "写入撤销文件:%s" -#: ../undo.c:1213 -#, fuzzy, c-format +#: ../undo.c:1340 +#, c-format msgid "E829: write error in undo file: %s" -msgstr "E297: 交换文件写入错误" +msgstr "E829: 撤销文件写入错误:%s" -#: ../undo.c:1280 +#: ../undo.c:1387 #, c-format msgid "Not reading undo file, owner differs: %s" -msgstr "不能读取撤销文件, 所有者不同: %s" +msgstr "不能读取撤销文件,所有者不同:%s" -#: ../undo.c:1292 -#, fuzzy, c-format +#: ../undo.c:1400 +#, c-format msgid "Reading undo file: %s" -msgstr "读取单词文件 %s ……" +msgstr "读取撤销文件:%s" -#: ../undo.c:1299 -#, fuzzy, c-format +#: ../undo.c:1407 +#, c-format msgid "E822: Cannot open undo file for reading: %s" -msgstr "E195: 无法打开并读取 viminfo 文件" +msgstr "E822: 无法打开并读取撤销文件:%s" -#: ../undo.c:1308 -#, fuzzy, c-format +#: ../undo.c:1421 +#, c-format msgid "E823: Not an undo file: %s" -msgstr "E753: 找不到: %s" +msgstr "E823: 不是撤销文件:%s" -#: ../undo.c:1313 -#, fuzzy, c-format +#: ../undo.c:1426 +#, c-format msgid "E824: Incompatible undo file: %s" -msgstr "E484: 无法打开文件 %s" +msgstr "E824: 不兼容的撤销文件:%s" -#: ../undo.c:1328 +#: ../undo.c:1442 msgid "File contents changed, cannot use undo info" msgstr "文件内容已改变,不能使用撤销信息" -#: ../undo.c:1497 -#, fuzzy, c-format +#: ../undo.c:1636 +#, c-format msgid "Finished reading undo file %s" -msgstr "结束执行 %s" +msgstr "读取撤销文件 %s 结束" -#: ../undo.c:1586 ../undo.c:1812 +#: ../undo.c:1871 ../undo.c:2106 msgid "Already at oldest change" msgstr "已位于最旧的改变" -#: ../undo.c:1597 ../undo.c:1814 +#: ../undo.c:1882 ../undo.c:2108 msgid "Already at newest change" msgstr "已位于最新的改变" -#: ../undo.c:1806 -#, fuzzy, c-format +#: ../undo.c:2100 +#, c-format msgid "E830: Undo number %<PRId64> not found" -msgstr "找不到撤销号 %<PRId64>" +msgstr "E830: 找不到撤销号 %<PRId64>" -#: ../undo.c:1979 +#: ../undo.c:2280 msgid "E438: u_undo: line numbers wrong" -msgstr "E438: u_undo: 行号错误" +msgstr "E438: u_undo:行号错误" -#: ../undo.c:2183 +#: ../undo.c:2538 msgid "more line" msgstr "行被加入" -#: ../undo.c:2185 +#: ../undo.c:2540 msgid "more lines" msgstr "行被加入" -#: ../undo.c:2187 +#: ../undo.c:2542 msgid "line less" msgstr "行被去掉" -#: ../undo.c:2189 +#: ../undo.c:2544 msgid "fewer lines" msgstr "行被去掉" -#: ../undo.c:2193 +#: ../undo.c:2548 msgid "change" msgstr "行发生改变" -#: ../undo.c:2195 +#: ../undo.c:2550 msgid "changes" msgstr "行发生改变" -#: ../undo.c:2225 +#: ../undo.c:2589 #, c-format msgid "%<PRId64> %s; %s #%<PRId64> %s" msgstr "%<PRId64> %s;%s #%<PRId64> %s" -#: ../undo.c:2228 +#: ../undo.c:2592 msgid "before" msgstr "before" -#: ../undo.c:2228 +#: ../undo.c:2592 msgid "after" msgstr "after" -#: ../undo.c:2325 +#: ../undo.c:2613 +#, c-format +msgid "%<PRId64> second ago" +msgid_plural "%<PRId64> seconds ago" +msgstr[0] "%<PRId64> 秒前" + +#: ../undo.c:2697 msgid "Nothing to undo" msgstr "无可撤销" -#: ../undo.c:2330 +#: ../undo.c:2702 msgid "number changes when saved" msgstr " 编号 变更 时间 保存" -#: ../undo.c:2360 -#, fuzzy, c-format -msgid "%<PRId64> seconds ago" -msgstr "%<PRId64> 列; " - -#: ../undo.c:2372 -#, fuzzy +#: ../undo.c:2724 msgid "E790: undojoin is not allowed after undo" -msgstr "E407: %s 不能在此出现" +msgstr "E790: undo 后不允许 undojoin" -#: ../undo.c:2466 +#: ../undo.c:2807 msgid "E439: undo list corrupt" msgstr "E439: 撤销列表损坏" -#: ../undo.c:2495 +#: ../undo.c:2830 msgid "E440: undo line missing" msgstr "E440: 找不到要撤销的行" -#: ../version.c:600 +#: ../usercmd.c:46 +msgid "E1208: -complete used without allowing arguments" +msgstr "E1208: 用了 -complete 却不允许使用参数" + +#: ../usercmd.c:48 +#, c-format +msgid "E184: No such user-defined command: %s" +msgstr "E184: 没有这个用户自定义命令: %s" + +#: ../usercmd.c:50 +#, c-format +msgid "E1237: No such user-defined command in current buffer: %s" +msgstr "E1237: 当前缓冲区中无此用户定义命令:%s" + +#: ../usercmd.c:416 msgid "" "\n" -"Included patches: " +" Name Args Address Complete Definition" msgstr "" "\n" -"包含补丁: " +" 名称 参数 范围 补全 定义 " -#: ../version.c:627 -#, fuzzy -msgid "" -"\n" -"Extra patches: " -msgstr "外部符合:\n" +#: ../usercmd.c:561 +msgid "No user-defined commands found" +msgstr "找不到用户自定义命令" + +#: ../usercmd.c:585 +#, c-format +msgid "E180: Invalid address type value: %s" +msgstr "E180: 无效的地址类型:%s" + +#: ../usercmd.c:633 +#, c-format +msgid "E180: Invalid complete value: %s" +msgstr "E180: 无效的补全类型:%s" + +#: ../usercmd.c:639 +msgid "E468: Completion argument only allowed for custom completion" +msgstr "E468: 只有 custom 补全才允许参数" + +#: ../usercmd.c:645 +msgid "E467: Custom completion requires a function argument" +msgstr "E467: Custom 补全需要一个函数参数" + +#: ../usercmd.c:662 +msgid "E175: No attribute specified" +msgstr "E175: 没有指定属性" + +#: ../usercmd.c:710 +msgid "E176: Invalid number of arguments" +msgstr "E176: 无效的参数个数" -#: ../version.c:639 ../version.c:864 -msgid "Modified by " -msgstr "修改者 " +#: ../usercmd.c:721 +msgid "E177: Count cannot be specified twice" +msgstr "E177: 不能指定两次计数" -#: ../version.c:646 +#: ../usercmd.c:730 +msgid "E178: Invalid default value for count" +msgstr "E178: 无效的计数默认值" + +#: ../usercmd.c:763 +msgid "E179: argument required for -complete" +msgstr "E179: -complete 需要参数" + +#: ../usercmd.c:774 +msgid "E179: argument required for -addr" +msgstr "E179: -addr 需要参数" + +#: ../usercmd.c:786 +#, c-format +msgid "E181: Invalid attribute: %s" +msgstr "E181: 无效的属性: %s" + +#: ../usercmd.c:866 +#, c-format +msgid "E174: Command already exists: add ! to replace it: %s" +msgstr "E174: 命令已存在:请加 ! 强制替换:%s" + +#: ../usercmd.c:955 +msgid "E182: Invalid command name" +msgstr "E182: 无效的命令名" + +#: ../usercmd.c:966 +msgid "E183: User defined commands must start with an uppercase letter" +msgstr "E183: 用户自定义命令必须以大写字母开头" + +#: ../usercmd.c:968 +msgid "E841: Reserved name, cannot be used for user defined command" +msgstr "E841: 名称已被保留,不能用于用户自定义命令" + +#: ../version.c:2636 msgid "" "\n" -"Compiled " +"\n" +"Features: " msgstr "" "\n" -"编译" - -#: ../version.c:649 -msgid "by " -msgstr "者 " +"\n" +"功能: " -#: ../version.c:660 +#: ../version.c:2740 msgid "" "\n" -"Huge version " +"Compiled " msgstr "" "\n" -"巨型版本 " - -#: ../version.c:661 -msgid "without GUI." -msgstr "无图形界面。" +"编译" -#: ../version.c:662 -msgid " Features included (+) or not (-):\n" -msgstr " 可使用(+)与不可使用(-)的功能:\n" +#: ../version.c:2743 +msgid "by " +msgstr "者 " -#: ../version.c:667 +#: ../version.c:2757 msgid " system vimrc file: \"" msgstr " 系统 vimrc 文件: \"" -#: ../version.c:672 -msgid " user vimrc file: \"" -msgstr " 用户 vimrc 文件: \"" - -#: ../version.c:677 -msgid " 2nd user vimrc file: \"" -msgstr " 第二用户 vimrc 文件: \"" - -#: ../version.c:682 -msgid " 3rd user vimrc file: \"" -msgstr " 第三用户 vimrc 文件: \"" - -#: ../version.c:687 -msgid " user exrc file: \"" -msgstr " 用户 exrc 文件: \"" - -#: ../version.c:692 -msgid " 2nd user exrc file: \"" -msgstr " 第二用户 exrc 文件: \"" - -#: ../version.c:699 +#: ../version.c:2764 msgid " fall-back for $VIM: \"" msgstr " $VIM 预设值: \"" -#: ../version.c:705 +#: ../version.c:2770 msgid " f-b for $VIMRUNTIME: \"" msgstr " $VIMRUNTIME 预设值: \"" -#: ../version.c:709 -msgid "Compilation: " -msgstr "编译方式: " +#: ../version.c:2805 +msgid "Nvim is open source and freely distributable" +msgstr "Nvim 是可自由分发的开放源代码软件" -#: ../version.c:712 -msgid "Linking: " -msgstr "链接方式: " +#: ../version.c:2808 +msgid "type :help nvim<Enter> if you are new! " +msgstr "输入 :help nvim<Enter> 了解 Neovim! " -#: ../version.c:717 -msgid " DEBUG BUILD" -msgstr " 调试版本" +#: ../version.c:2809 +msgid "type :checkhealth<Enter> to optimize Nvim" +msgstr "输入 :checkhealth<Enter> 优化 Neovim " -#: ../version.c:767 -msgid "VIM - Vi IMproved" -msgstr "VIM - Vi IMproved" - -#: ../version.c:769 -msgid "version " -msgstr "版本 " +#: ../version.c:2810 +msgid "type :q<Enter> to exit " +msgstr "输入 :q<Enter> 退出 " -#: ../version.c:770 -msgid "by Bram Moolenaar et al." -msgstr "维护人 Bram Moolenaar 等" +#: ../version.c:2811 +msgid "type :help<Enter> for help " +msgstr "输入 :help<Enter> 查看帮助 " -#: ../version.c:774 -msgid "Vim is open source and freely distributable" -msgstr "Vim 是可自由分发的开放源代码软件" +#: ../version.c:2813 +#, c-format +msgid "type :help news<Enter> to see changes in v%s.%s" +msgstr "输入 :help news<Enter> 查看 v%s.%s 的变化" -#: ../version.c:776 +#: ../version.c:2816 msgid "Help poor children in Uganda!" msgstr "帮助乌干达的可怜儿童!" -#: ../version.c:777 +#: ../version.c:2817 msgid "type :help iccf<Enter> for information " msgstr "输入 :help iccf<Enter> 查看说明 " -#: ../version.c:779 -msgid "type :q<Enter> to exit " -msgstr "输入 :q<Enter> 退出 " - -#: ../version.c:780 -msgid "type :help<Enter> or <F1> for on-line help" -msgstr "输入 :help<Enter> 或 <F1> 查看在线帮助 " - -#: ../version.c:781 -msgid "type :help version7<Enter> for version info" -msgstr "输入 :help version7<Enter> 查看版本信息 " - -#: ../version.c:784 -msgid "Running in Vi compatible mode" -msgstr "运行于 Vi 兼容模式" - -#: ../version.c:785 -msgid "type :set nocp<Enter> for Vim defaults" -msgstr "输入 :set nocp<Enter> 恢复默认的 Vim " - -#: ../version.c:786 -msgid "type :help cp-default<Enter> for info on this" -msgstr "输入 :help cp-default<Enter> 查看相关说明 " - -#: ../version.c:827 +#: ../version.c:2850 msgid "Sponsor Vim development!" msgstr "赞助 Vim 的开发!" -#: ../version.c:828 +#: ../version.c:2851 msgid "Become a registered Vim user!" msgstr "成为 Vim 的注册用户!" -#: ../version.c:831 +#: ../version.c:2854 msgid "type :help sponsor<Enter> for information " msgstr "输入 :help sponsor<Enter> 查看说明 " -#: ../version.c:832 +#: ../version.c:2855 msgid "type :help register<Enter> for information " msgstr "输入 :help register<Enter> 查看说明 " -#: ../version.c:834 +#: ../version.c:2857 msgid "menu Help->Sponsor/Register for information " msgstr "菜单 Help->Sponsor/Register 查看说明 " -#: ../window.c:119 +#: ../viml/parser/expressions.c:292 +#, c-format +msgid "E15: Invalid control character present in input: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:519 +#, c-format +msgid "E112: Option name missing: %.*s" +msgstr "E112: 缺少选项名称:%.*s" + +#: ../viml/parser/expressions.c:678 ../viml/parser/expressions.c:694 +#, c-format +msgid "E15: Unexpected EOC character: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:706 +#, c-format +msgid "E15: Unidentified character: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:1335 +#, c-format +msgid "E15: Operator is not associative: %.*s" +msgstr "" + +#. / Record missing operator: for things like +#. / +#. / :echo @a @a +#. / +#. / (allowed) or +#. / +#. / :echo (@a @a) +#. / +#. / (parsed as OpMissing(@a, @a)). +#. Multiple expressions allowed, return without calling +#. viml_parser_advance(). +#: ../viml/parser/expressions.c:1440 +#, c-format +msgid "E15: Missing operator: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2081 +#, c-format +msgid "E15: Expected lambda arguments list or arrow: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2101 +#, c-format +msgid "E15: Expected value part of assignment lvalue: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2120 +#, c-format +msgid "E15: Expected assignment operator or subscript: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2171 +msgid "E15: Unexpected " +msgstr "" + +#: ../viml/parser/expressions.c:2181 +#, c-format +msgid "E15: Unexpected multiplication-like operator: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2241 +msgid "E15: Environment variable name missing" +msgstr "E15: 缺少环境变量名称" + +#: ../viml/parser/expressions.c:2259 +#, c-format +msgid "E15: Expected value, got comparison operator: %.*s" +msgstr "" + +#. Value level: comma appearing here is not valid. +#. Note: in Vim string(,x) will give E116, this is not the case here. +#: ../viml/parser/expressions.c:2286 +#, c-format +msgid "E15: Expected value, got comma: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2320 +#, c-format +msgid "E15: Comma outside of call, lambda or literal: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2404 +#, c-format +msgid "E15: Colon outside of dictionary or ternary operator: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2451 +#, c-format +msgid "E15: Expected value, got closing bracket: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2464 +#, c-format +msgid "E475: Unable to assign to empty list: %.*s" +msgstr "E475: 无法赋值给空列表:%.*s" + +#: ../viml/parser/expressions.c:2474 ../viml/parser/expressions.c:2609 +#, c-format +msgid "E15: Unexpected closing figure brace: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2507 +#, c-format +msgid "E475: Nested lists not allowed when assigning: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2557 +#, c-format +msgid "E15: Expected value, got closing figure brace: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2587 +#, c-format +msgid "E15: Don't know what figure brace means: %.*s" +msgstr "" + +#. Only first branch is valid. +#: ../viml/parser/expressions.c:2706 +#, c-format +msgid "E15: Unexpected arrow: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2707 +#, c-format +msgid "E15: Arrow outside of lambda: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2788 +#, c-format +msgid "E15: Unexpected dot: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2791 +#, c-format +msgid "E15: Cannot concatenate in assignments: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2813 +#, c-format +msgid "E15: Expected value, got parenthesis: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2846 +#, c-format +msgid "E15: Unexpected closing parenthesis: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2889 +#, c-format +msgid "E15: Expected value, got question mark: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2911 +#, c-format +msgid "E114: Missing double quote: %.*s" +msgstr "E114: 缺少双引号:%.*s" + +#: ../viml/parser/expressions.c:2912 +#, c-format +msgid "E115: Missing single quote: %.*s" +msgstr "E115: 缺少单引号:%.*s" + +#: ../viml/parser/expressions.c:2931 +#, c-format +msgid "E475: Expected closing bracket to end list assignment lvalue: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2934 +#, c-format +msgid "E15: Misplaced assignment: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2938 +#, c-format +msgid "E15: Unexpected assignment: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2965 +#, c-format +msgid "E15: Expected value, got EOC: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2988 +#, c-format +msgid "E116: Missing closing parenthesis for function call: %.*s" +msgstr "E116: 函数调用缺少结束括号:%.*s" + +#: ../viml/parser/expressions.c:2993 +#, c-format +msgid "E110: Missing closing parenthesis for nested expression: %.*s" +msgstr "E110: 嵌套表达式缺少结束括号:%.*s" + +#: ../viml/parser/expressions.c:3001 +#, c-format +msgid "E697: Missing end of List ']': %.*s" +msgstr "E697: List 缺少结束符 ']': %.*s" + +#: ../viml/parser/expressions.c:3008 +#, c-format +msgid "E723: Missing end of Dictionary '}': %.*s" +msgstr "E723: 字典缺少结束符 '}':%.*s" + +#: ../viml/parser/expressions.c:3013 +#, c-format +msgid "E15: Missing closing figure brace: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:3018 +#, c-format +msgid "E15: Missing closing figure brace for lambda: %.*s" +msgstr "" + +#. Actually Vim throws E109 in more cases. +#: ../viml/parser/expressions.c:3068 +#, c-format +msgid "E109: Missing ':' after '?': %.*s" +msgstr "E109: '?' 后缺少 ':':%.*s" + +#: ../window.c:97 +msgid "E1159: Cannot split a window when closing the buffer" +msgstr "E1159: 关闭缓冲区时不能分割窗口" + +#: ../window.c:99 msgid "Already only one window" msgstr "已经只剩一个窗口了" -#: ../window.c:224 +#: ../window.c:259 msgid "E441: There is no preview window" msgstr "E441: 没有预览窗口" -#: ../window.c:559 +#: ../window.c:988 +msgid "E242: Can't split a window while closing another" +msgstr "E242: 在关闭窗口时不能分割另一个窗口" + +#: ../window.c:1025 msgid "E442: Can't split topleft and botright at the same time" msgstr "E442: 不能同时进行 topleft 和 botright 分割" -#: ../window.c:1228 +#: ../window.c:1868 msgid "E443: Cannot rotate when another window is split" msgstr "E443: 有其它分割窗口时不能旋转" -#: ../window.c:1803 +#: ../window.c:2657 msgid "E444: Cannot close last window" msgstr "E444: 不能关闭最后一个窗口" -#: ../window.c:1810 -#, fuzzy -msgid "E813: Cannot close autocmd window" -msgstr "E444: 不能关闭最后一个窗口" - -#: ../window.c:1814 -#, fuzzy +#: ../window.c:2671 msgid "E814: Cannot close window, only autocmd window would remain" -msgstr "E444: 不能关闭最后一个窗口" +msgstr "E814: 无法关闭窗口,不然就只剩下自动命令窗口了" -#: ../window.c:2717 +#: ../window.c:3867 msgid "E445: Other window contains changes" msgstr "E445: 其它窗口有改变的内容" -#: ../window.c:4805 +#: ../window.c:6498 msgid "E446: No file name under cursor" msgstr "E446: 光标处没有文件名" -#~ msgid "Patch file" -#~ msgstr "Patch 文件" +#: ../../../runtime/optwin.vim:72 +msgid "(local to window)" +msgstr "(局部于窗口)" -#~ msgid "" -#~ "&OK\n" -#~ "&Cancel" -#~ msgstr "" -#~ "确定(&O)\n" -#~ "取消(&C)" +#: ../../../runtime/optwin.vim:73 +msgid "(local to buffer)" +msgstr "(局部于缓冲区)" -#~ msgid "E240: No connection to Vim server" -#~ msgstr "E240: 没有到 Vim 服务器的连接" +#: ../../../runtime/optwin.vim:74 +msgid "(global or local to buffer)" +msgstr "(全局或局部于缓冲区)" -#~ msgid "E241: Unable to send to %s" -#~ msgstr "E241: 无法发送到 %s" +#: ../../../runtime/optwin.vim:153 +msgid "" +"\" Each \"set\" line shows the current value of an option (on the left)." +msgstr "\" 每个 \"set\" 行显示左侧选项的当前值" -#~ msgid "E277: Unable to read a server reply" -#~ msgstr "E277: 无法读取服务器响应" +#: ../../../runtime/optwin.vim:154 +msgid "\" Hit <Enter> on a \"set\" line to execute it." +msgstr "\" 在 \"set\" 行上按 <回车> 来执行。" -#~ msgid "E258: Unable to send to client" -#~ msgstr "E258: 无法发送到客户端" +#: ../../../runtime/optwin.vim:155 +msgid "\" A boolean option will be toggled." +msgstr "\" 布尔选项将被切换。" -#~ msgid "Save As" -#~ msgstr "另存为" +#: ../../../runtime/optwin.vim:156 +msgid "" +"\" For other options you can edit the value before hitting " +"<Enter>." +msgstr "\" 对于其他选项,您可以在按 <回车> 之前编辑该值。" -#~ msgid "Edit File" -#~ msgstr "编辑文件" +#: ../../../runtime/optwin.vim:157 +msgid "\" Hit <Enter> on a help line to open a help window on this option." +msgstr "\" 在帮助行上按 <回车> 来打开关于此选项的帮助窗口。" -#~ msgid " (NOT FOUND)" -#~ msgstr " (找不到)" +#: ../../../runtime/optwin.vim:158 +msgid "\" Hit <Enter> on an index line to jump there." +msgstr "\" 在索引行上按 <回车> 来跳转到那里。" -#~ msgid "Source Vim script" -#~ msgstr "执行 Vim 脚本" +#: ../../../runtime/optwin.vim:159 +msgid "\" Hit <Space> on a \"set\" line to refresh it." +msgstr "\" 在 \"set\" 行上按 <空格> 来刷新。 " -#~ msgid "Edit File in new window" -#~ msgstr "在新窗口编辑文件" +#: ../../../runtime/optwin.vim:228 +msgid "important" +msgstr "重要选项" -#~ msgid "Append File" -#~ msgstr "追加文件" +#: ../../../runtime/optwin.vim:229 +msgid "behave very Vi compatible (not advisable)" +msgstr "非常兼容 Vi(不建议)" -#~ msgid "Window position: X %d, Y %d" -#~ msgstr "窗口位置: X %d, Y %d" +#: ../../../runtime/optwin.vim:231 +msgid "list of flags to specify Vi compatibility" +msgstr "指定 Vi 兼容性的标志列表" -#~ msgid "Save Redirection" -#~ msgstr "保存重定向" +#: ../../../runtime/optwin.vim:233 +msgid "paste mode, insert typed text literally" +msgstr "粘贴模式,按本义插入输入的文本" -#~ msgid "Save View" -#~ msgstr "保存视图" +#: ../../../runtime/optwin.vim:235 +msgid "key sequence to toggle paste mode" +msgstr "切换粘贴模式的按键序列" -#~ msgid "Save Session" -#~ msgstr "保存会话" +#: ../../../runtime/optwin.vim:241 +msgid "list of directories used for runtime files and plugins" +msgstr "运行时文件和插件使用的目录列表" -#~ msgid "Save Setup" -#~ msgstr "保存设定" +#: ../../../runtime/optwin.vim:243 +msgid "list of directories used for plugin packages" +msgstr "插件包使用的目录列表" -#~ msgid "E196: No digraphs in this version" -#~ msgstr "E196: 此版本无复合字符(digraph)" +#: ../../../runtime/optwin.vim:245 +msgid "name of the main help file" +msgstr "主帮助文件的名称" -#~ msgid "Reading from stdin..." -#~ msgstr "从标准输入读取..." +#: ../../../runtime/optwin.vim:249 +msgid "moving around, searching and patterns" +msgstr "移动、搜索以及正则表达式" -#~ msgid "[NL found]" -#~ msgstr "[找到 NL]" +#: ../../../runtime/optwin.vim:250 +msgid "list of flags specifying which commands wrap to another line" +msgstr "指定哪些命令折行的标志列表" -#~ msgid "[crypted]" -#~ msgstr "[已加密]" +#: ../../../runtime/optwin.vim:252 +msgid "" +"many jump commands move the cursor to the first non-blank\n" +"character of a line" +msgstr "许多跳转命令将光标移动到第一个非空的位置行中的字符" -#~ msgid "NetBeans disallows writes of unmodified buffers" -#~ msgstr "NetBeans 不允许未修改的缓冲区写入" +#: ../../../runtime/optwin.vim:254 +msgid "nroff macro names that separate paragraphs" +msgstr "用于分隔段落的 nroff 宏名" -#~ msgid "Partial writes disallowed for NetBeans buffers" -#~ msgstr "NetBeans 不允许缓冲区部分写入" +#: ../../../runtime/optwin.vim:256 +msgid "nroff macro names that separate sections" +msgstr "用于分隔小节的 nroff 宏名" -#~ msgid "E460: The resource fork would be lost (add ! to override)" -#~ msgstr "E460: Resource fork 会丢失 (请加 ! 强制执行)" +#: ../../../runtime/optwin.vim:258 +msgid "list of directory names used for file searching" +msgstr "用于文件搜索的目录名称列表" -#~ msgid "E229: Cannot start the GUI" -#~ msgstr "E229: 无法启动图形界面" +#: ../../../runtime/optwin.vim:261 +msgid ":cd without argument goes to the home directory" +msgstr "不带参数的 :cd 进入主目录" -#~ msgid "E230: Cannot read from \"%s\"" -#~ msgstr "E230: 无法读取文件 \"%s\"" +#: ../../../runtime/optwin.vim:263 +msgid "list of directory names used for :cd" +msgstr "目录名称列表用于 :cd" -#~ msgid "E665: Cannot start GUI, no valid font found" -#~ msgstr "E665: 无法启动图形界面,找不到有效的字体" +#: ../../../runtime/optwin.vim:266 +msgid "change to directory of file in buffer" +msgstr "切换到缓冲区的文件所在的目录" -#~ msgid "E231: 'guifontwide' invalid" -#~ msgstr "E231: 无效的 'guifontwide'" +#: ../../../runtime/optwin.vim:269 +msgid "search commands wrap around the end of the buffer" +msgstr "搜索在缓冲区折行的命令" -#~ msgid "E599: Value of 'imactivatekey' is invalid" -#~ msgstr "E599: 'imactivatekey' 的值无效" +#: ../../../runtime/optwin.vim:271 +msgid "show match for partly typed search command" +msgstr "显示匹配部分键入的搜索命令" -#~ msgid "E254: Cannot allocate color %s" -#~ msgstr "E254: 无法分配颜色 %s" +#: ../../../runtime/optwin.vim:273 +msgid "change the way backslashes are used in search patterns" +msgstr "改变反斜杠在搜索模式中的使用方式" -#~ msgid "No match at cursor, finding next" -#~ msgstr "在光标处没有匹配,查找下一个" +#: ../../../runtime/optwin.vim:275 +msgid "select the default regexp engine used" +msgstr "选择默认的正则表达式引擎" -#~ msgid "<cannot open> " -#~ msgstr "<无法打开>" +#: ../../../runtime/optwin.vim:277 +msgid "ignore case when using a search pattern" +msgstr "使用搜索模式时忽略大小写" -#~ msgid "E616: vim_SelFile: can't get font %s" -#~ msgstr "E616: vim_SelFile: 无法获取字体 %s" +#: ../../../runtime/optwin.vim:279 +msgid "override 'ignorecase' when pattern has upper case characters" +msgstr "当模式包含大写字符时,覆盖 'ignorecase'" -#~ msgid "E614: vim_SelFile: can't return to current directory" -#~ msgstr "E614: vim_SelFile: 无法返回当前目录" +#: ../../../runtime/optwin.vim:281 +msgid "what method to use for changing case of letters" +msgstr "用什么方法来改变字母的大小写" -#~ msgid "Pathname:" -#~ msgstr "路径:" +#: ../../../runtime/optwin.vim:283 +msgid "maximum amount of memory in Kbyte used for pattern matching" +msgstr "模式匹配使用的最大内存(以千字节为单位)" -#~ msgid "E615: vim_SelFile: can't get current directory" -#~ msgstr "E615: vim_SelFile: 无法获取当前目录" +#: ../../../runtime/optwin.vim:285 +msgid "pattern for a macro definition line" +msgstr "宏定义行的模式" -#~ msgid "OK" -#~ msgstr "确定" +#: ../../../runtime/optwin.vim:289 +msgid "pattern for an include-file line" +msgstr "包含文件行的模式" -#~ msgid "Cancel" -#~ msgstr "取消" +#: ../../../runtime/optwin.vim:292 +msgid "expression used to transform an include line to a file name" +msgstr "用于将包含行转换为文件名的表达式" -#~ msgid "Scrollbar Widget: Could not get geometry of thumb pixmap." -#~ msgstr "滚动条部件: 无法获取滑块图像的几何大小" +#: ../../../runtime/optwin.vim:298 +msgid "tags" +msgstr "标签" -#~ msgid "Vim dialog" -#~ msgstr "Vim 对话框" +#: ../../../runtime/optwin.vim:299 +msgid "use binary searching in tags files" +msgstr "在标签文件中使用二分法查找" -#~ msgid "E232: Cannot create BalloonEval with both message and callback" -#~ msgstr "E232: 不能同时使用消息和回调函数来创建 BalloonEval" +#: ../../../runtime/optwin.vim:301 +msgid "number of significant characters in a tag name or zero" +msgstr "标签名称中的有效字符数,默认为零" -#~ msgid "Vim dialog..." -#~ msgstr "Vim 对话框..." +#: ../../../runtime/optwin.vim:303 +msgid "list of file names to search for tags" +msgstr "用于搜索标签的文件名列表" -#~ msgid "Input _Methods" -#~ msgstr "输入法(_M)" +#: ../../../runtime/optwin.vim:306 +msgid "" +"how to handle case when searching in tags files:\n" +"\"followic\" to follow 'ignorecase', \"ignore\" or \"match\"" +msgstr "" +"在标签文件中搜索如何处理大小写: \"followic\" 跟随 'ignorecase', \"ignore\" 或" +"者 \"match\"" -#~ msgid "VIM - Search and Replace..." -#~ msgstr "VIM - 查找和替换..." +#: ../../../runtime/optwin.vim:309 +msgid "file names in a tags file are relative to the tags file" +msgstr "标签文件里的文件名是相对于标签文件的路径" -#~ msgid "VIM - Search..." -#~ msgstr "VIM - 查找..." +#: ../../../runtime/optwin.vim:311 +msgid "a :tag command will use the tagstack" +msgstr ":tag 命令将使用 tagstack" -#~ msgid "Find what:" -#~ msgstr "查找内容:" +#: ../../../runtime/optwin.vim:313 +msgid "when completing tags in Insert mode show more info" +msgstr "在插入模式补全标签时显示更多信息" -#~ msgid "Replace with:" -#~ msgstr "替换为:" +#: ../../../runtime/optwin.vim:316 +msgid "a function to be used to perform tag searches" +msgstr "用于执行标签搜索的函数" -#~ msgid "Match whole word only" -#~ msgstr "匹配完整的词" +#: ../../../runtime/optwin.vim:322 +msgid "displaying text" +msgstr "显示文本" -#~ msgid "Match case" -#~ msgstr "匹配大小写" +#: ../../../runtime/optwin.vim:323 +msgid "number of lines to scroll for CTRL-U and CTRL-D" +msgstr "按 CTRL-U 和 CTRL-D 滚动的行数" -#~ msgid "Direction" -#~ msgstr "方向" +#: ../../../runtime/optwin.vim:326 +msgid "number of screen lines to show around the cursor" +msgstr "在光标周围显示的屏幕行数" -#~ msgid "Up" -#~ msgstr "向上" +#: ../../../runtime/optwin.vim:328 +msgid "long lines wrap" +msgstr "长行自动换行" -#~ msgid "Down" -#~ msgstr "向下" +#: ../../../runtime/optwin.vim:331 +msgid "wrap long lines at a character in 'breakat'" +msgstr "在 'breakat' 中的字符处对长行折行" -#~ msgid "Find Next" -#~ msgstr "查找下一个" +#: ../../../runtime/optwin.vim:334 +msgid "preserve indentation in wrapped text" +msgstr "在折行文本中保持缩进" -#~ msgid "Replace" -#~ msgstr "替换" +#: ../../../runtime/optwin.vim:337 +msgid "adjust breakindent behaviour" +msgstr "调整 breakindent 的行为" -#~ msgid "Replace All" -#~ msgstr "全部替换" +#: ../../../runtime/optwin.vim:340 +msgid "which characters might cause a line break" +msgstr "哪些字符可能导致换行" -#~ msgid "Vim: Received \"die\" request from session manager\n" -#~ msgstr "Vim: 从会话管理器收到 \"die\" 请求\n" +#: ../../../runtime/optwin.vim:342 +msgid "string to put before wrapped screen lines" +msgstr "放在折回的屏幕行之前的字符串" -#~ msgid "Close" -#~ msgstr "关闭" +#: ../../../runtime/optwin.vim:344 +msgid "minimal number of columns to scroll horizontally" +msgstr "水平滚动的最小列数" -#~ msgid "New tab" -#~ msgstr "新建标签" +#: ../../../runtime/optwin.vim:346 +msgid "minimal number of columns to keep left and right of the cursor" +msgstr "保留在光标左右的最小列数" -#~ msgid "Open Tab..." -#~ msgstr "打开标签..." +#: ../../../runtime/optwin.vim:348 +msgid "" +"include \"lastline\" to show the last line even if it doesn't fit\n" +"include \"uhex\" to show unprintable characters as a hex number" +msgstr "" +"包含 \"lastline\" 来显示最后一行,即使它显示不下\n" +"包含 \"uhex\" 以十六进制显示不可打印字符" -#~ msgid "Vim: Main window unexpectedly destroyed\n" -#~ msgstr "Vim: 主窗口被意外地摧毁\n" +#: ../../../runtime/optwin.vim:350 +msgid "characters to use for the status line, folds and filler lines" +msgstr "用于状态行、折叠和填充行的字符" -#~ msgid "Font Selection" -#~ msgstr "选择字体" +#: ../../../runtime/optwin.vim:352 +msgid "number of lines used for the command-line" +msgstr "用于命令行的行数" -#~ msgid "Used CUT_BUFFER0 instead of empty selection" -#~ msgstr "使用 CUT_BUFFER0 来取代空选择" +#: ../../../runtime/optwin.vim:354 +msgid "width of the display" +msgstr "显示的宽度" -#~ msgid "&Filter" -#~ msgstr "过滤(&F)" +#: ../../../runtime/optwin.vim:356 +msgid "number of lines in the display" +msgstr "显示的行数" -#~ msgid "&Cancel" -#~ msgstr "取消(&C)" +#: ../../../runtime/optwin.vim:358 +msgid "number of lines to scroll for CTRL-F and CTRL-B" +msgstr "按 CTRL-F 和 CTRL-B 滚动的行数" -#~ msgid "Directories" -#~ msgstr "目录" +#: ../../../runtime/optwin.vim:360 +msgid "don't redraw while executing macros" +msgstr "在执行宏时不要重新绘制" -#~ msgid "Filter" -#~ msgstr "过滤器" +#: ../../../runtime/optwin.vim:363 +msgid "timeout for 'hlsearch' and :match highlighting in msec" +msgstr "'hlsearch' 和 :match 高亮的超时时间(以毫秒计)" -#~ msgid "&Help" -#~ msgstr "帮助(&H)" +#: ../../../runtime/optwin.vim:366 +msgid "" +"delay in msec for each char written to the display\n" +"(for debugging)" +msgstr "每个字符写到显示的延时(以毫秒计;用于调试)" -#~ msgid "Files" -#~ msgstr "文件" +#: ../../../runtime/optwin.vim:368 +msgid "show <Tab> as ^I and end-of-line as $" +msgstr "以 ^I 显示 <Tab>, 以 $ 显示行尾" -#~ msgid "&OK" -#~ msgstr "确定(&O)" +#: ../../../runtime/optwin.vim:371 +msgid "list of strings used for list mode" +msgstr "用于 list 模式的字符串列表" -#~ msgid "Selection" -#~ msgstr "选择" +#: ../../../runtime/optwin.vim:373 +msgid "show the line number for each line" +msgstr "对每一行显示行号" -#~ msgid "Find &Next" -#~ msgstr "查找下一个(&N)" +#: ../../../runtime/optwin.vim:376 +msgid "show the relative line number for each line" +msgstr "显示每行的相对行号" -#~ msgid "&Replace" -#~ msgstr "替换(&R)" +#: ../../../runtime/optwin.vim:380 +msgid "number of columns to use for the line number" +msgstr "用于行号的列数" -#~ msgid "Replace &All" -#~ msgstr "全部替换(&A)" +#: ../../../runtime/optwin.vim:385 +msgid "controls whether concealable text is hidden" +msgstr "控制是否隐藏可隐藏文本" -#~ msgid "&Undo" -#~ msgstr "撤销(&U)" +#: ../../../runtime/optwin.vim:388 +msgid "modes in which text in the cursor line can be concealed" +msgstr "可隐藏光标行的文本的模式" -#~ msgid "E671: Cannot find window title \"%s\"" -#~ msgstr "E671: 找不到窗口标题 \"%s\"" +#: ../../../runtime/optwin.vim:394 +msgid "syntax, highlighting and spelling" +msgstr "语法、高亮和拼写" -#~ msgid "E243: Argument not supported: \"-%s\"; Use the OLE version." -#~ msgstr "E243: 不支持的参数: \"-%s\";请使用 OLE 版本。" +#: ../../../runtime/optwin.vim:395 +msgid "\"dark\" or \"light\"; the background color brightness" +msgstr "\"dark\" 或者 \"light\";背景色亮度" -#~ msgid "E672: Unable to open window inside MDI application" -#~ msgstr "E672: 无法在 MDI 应用程序中打开窗口" +#: ../../../runtime/optwin.vim:397 +msgid "type of file; triggers the FileType event when set" +msgstr "文件类型; 在设置时触发 FileType 事件" -#~ msgid "Close tab" -#~ msgstr "关闭标签" +#: ../../../runtime/optwin.vim:401 +msgid "name of syntax highlighting used" +msgstr "使用中的语法高亮显示的名称" -#~ msgid "Open tab..." -#~ msgstr "打开标签..." +#: ../../../runtime/optwin.vim:404 +msgid "maximum column to look for syntax items" +msgstr "寻找语法项的最大列" -#~ msgid "Find string (use '\\\\' to find a '\\')" -#~ msgstr "查找字符串 (使用 '\\\\' 来查找 '\\')" +#: ../../../runtime/optwin.vim:408 +msgid "which highlighting to use for various occasions" +msgstr "在不同场合使用哪些高亮提示" -#~ msgid "Find & Replace (use '\\\\' to find a '\\')" -#~ msgstr "查找和替换字符串 (使用 '\\\\' 来查找 '\\')" +#: ../../../runtime/optwin.vim:410 +msgid "highlight all matches for the last used search pattern" +msgstr "高亮显示最后使用的搜索模式的所有匹配项" -#~ msgid "Not Used" -#~ msgstr "未使用" +#: ../../../runtime/optwin.vim:413 +msgid "use GUI colors for the terminal" +msgstr "为终端使用 GUI 颜色" -#~ msgid "Directory\t*.nothing\n" -#~ msgstr "目录\t*.nothing\n" +#: ../../../runtime/optwin.vim:417 +msgid "highlight the screen column of the cursor" +msgstr "突出显示光标的屏幕列" -#~ msgid "" -#~ "Vim E458: Cannot allocate colormap entry, some colors may be incorrect" -#~ msgstr "Vim E458: 无法分配颜色表项,某些颜色可能不正确" +#: ../../../runtime/optwin.vim:420 +msgid "highlight the screen line of the cursor" +msgstr "突出显示光标的屏幕行" -#~ msgid "E250: Fonts for the following charsets are missing in fontset %s:" -#~ msgstr "E250: Fontset %s 缺少下列字符集的字体:" +#: ../../../runtime/optwin.vim:423 +msgid "specifies which area 'cursorline' highlights" +msgstr "指定 'cursorline' 高亮显示的区域" -#~ msgid "E252: Fontset name: %s" -#~ msgstr "E252: Fontset 名称: %s" +#: ../../../runtime/optwin.vim:426 +msgid "columns to highlight" +msgstr "要高亮的列" -#~ msgid "Font '%s' is not fixed-width" -#~ msgstr "'%s' 不是固定宽度的字体" +#: ../../../runtime/optwin.vim:429 +msgid "highlight spelling mistakes" +msgstr "高亮拼写错误" -#~ msgid "E253: Fontset name: %s\n" -#~ msgstr "E253: Fontset 名称: %s\n" +#: ../../../runtime/optwin.vim:432 +msgid "list of accepted languages" +msgstr "接受的语言列表" -#~ msgid "Font0: %s\n" -#~ msgstr "字体0: %s\n" +#: ../../../runtime/optwin.vim:435 +msgid "file that \"zg\" adds good words to" +msgstr "\"zg\" 添加正确单词的文件" -#~ msgid "Font1: %s\n" -#~ msgstr "字体1: %s\n" +#: ../../../runtime/optwin.vim:438 +msgid "pattern to locate the end of a sentence" +msgstr "定位句子尾部的模式" -#~ msgid "Font%<PRId64> width is not twice that of font0\n" -#~ msgstr "字体%<PRId64>的宽度不是字体0的两倍\n" +#: ../../../runtime/optwin.vim:441 +msgid "flags to change how spell checking works" +msgstr "更改拼写检查工作方式的标志" -#~ msgid "Font0 width: %<PRId64>\n" -#~ msgstr "字体0的宽度:%<PRId64>\n" +#: ../../../runtime/optwin.vim:444 +msgid "methods used to suggest corrections" +msgstr "用于建议修正的方法" -#~ msgid "" -#~ "Font1 width: %<PRId64>\n" -#~ "\n" -#~ msgstr "" -#~ "字体1的宽度: %<PRId64>\n" -#~ "\n" +#: ../../../runtime/optwin.vim:446 +msgid "amount of memory used by :mkspell before compressing" +msgstr ":mkspell 在压缩前使用的内存量" -#~ msgid "Invalid font specification" -#~ msgstr "指定了无效的字体" +#: ../../../runtime/optwin.vim:451 +msgid "multiple windows" +msgstr "多个窗口" -#~ msgid "&Dismiss" -#~ msgstr "取消(&D)" +#: ../../../runtime/optwin.vim:452 +msgid "0, 1, 2 or 3; when to use a status line for the last window" +msgstr "0、1、2 或 3;何时为最后一个窗口使用状态行" -#~ msgid "no specific match" -#~ msgstr "找不到匹配的项" +#: ../../../runtime/optwin.vim:455 +msgid "alternate format to be used for a status line" +msgstr "用于状态行的替代格式" -#~ msgid "Vim - Font Selector" -#~ msgstr "Vim - 字体选择器" +#: ../../../runtime/optwin.vim:458 +msgid "make all windows the same size when adding/removing windows" +msgstr "当添加/删除窗口时,使所有窗口的大小相同" -#~ msgid "Name:" -#~ msgstr "名称:" +#: ../../../runtime/optwin.vim:460 +msgid "in which direction 'equalalways' works: \"ver\", \"hor\" or \"both\"" +msgstr "'equalalways' 的工作方向:\"ver\", \"hor\" 或者 \"both\"" -#~ msgid "Encoding:" -#~ msgstr "编码:" +#: ../../../runtime/optwin.vim:462 +msgid "minimal number of lines used for the current window" +msgstr "当前窗口的最小行数" -#~ msgid "Font:" -#~ msgstr "字体:" +#: ../../../runtime/optwin.vim:464 +msgid "minimal number of lines used for any window" +msgstr "任何窗口的最小行数" -#~ msgid "Style:" -#~ msgstr "风格:" +#: ../../../runtime/optwin.vim:466 +msgid "keep the height of the window" +msgstr "保持窗口的高度" -#~ msgid "Size:" -#~ msgstr "尺寸:" +#: ../../../runtime/optwin.vim:469 +msgid "keep the width of the window" +msgstr "保持窗口的宽度" -#~ msgid "E256: Hangul automata ERROR" -#~ msgstr "E256: Hangul automata 错误" +#: ../../../runtime/optwin.vim:472 +msgid "minimal number of columns used for the current window" +msgstr "当前窗口的最小列数" -#~ msgid "E563: stat error" -#~ msgstr "E563: stat 错误" +#: ../../../runtime/optwin.vim:474 +msgid "minimal number of columns used for any window" +msgstr "任何窗口的最小列数" -#~ msgid "E625: cannot open cscope database: %s" -#~ msgstr "E625: 无法打开 cscope 数据库: %s" +#: ../../../runtime/optwin.vim:476 +msgid "initial height of the help window" +msgstr "帮助窗口的初始高度" -#~ msgid "E626: cannot get cscope database information" -#~ msgstr "E626: 无法获取 cscope 数据库信息" +#: ../../../runtime/optwin.vim:481 +msgid "default height for the preview window" +msgstr "预览窗口的默认高度" -#~ msgid "E569: maximum number of cscope connections reached" -#~ msgstr "E569: 已达到 cscope 的最大连接数" +#: ../../../runtime/optwin.vim:483 +msgid "identifies the preview window" +msgstr "标识预览窗口" -#~ msgid "" -#~ "???: Sorry, this command is disabled, the MzScheme library could not be " -#~ "loaded." -#~ msgstr "???: 抱歉,此命令不可用,无法加载 MzScheme 库" +#: ../../../runtime/optwin.vim:487 +msgid "don't unload a buffer when no longer shown in a window" +msgstr "当缓冲区不再显示在窗口中时,不要卸载它" -#~ msgid "invalid expression" -#~ msgstr "无效的表达式" +#: ../../../runtime/optwin.vim:489 +msgid "" +"\"useopen\" and/or \"split\"; which window to use when jumping\n" +"to a buffer" +msgstr "当向缓冲区跳转时可使用窗口: \"useopen\" 和/或 \"split\"" -#~ msgid "expressions disabled at compile time" -#~ msgstr "编译时没有启用表达式" +#: ../../../runtime/optwin.vim:491 +msgid "a new window is put below the current one" +msgstr "新窗口放在当前窗口的下面" -#~ msgid "hidden option" -#~ msgstr "隐藏的选项" +#: ../../../runtime/optwin.vim:493 +msgid "determines scroll behavior for split windows" +msgstr "" -#~ msgid "unknown option" -#~ msgstr "未知的选项" +#: ../../../runtime/optwin.vim:495 +msgid "a new window is put right of the current one" +msgstr "新窗口放在当前窗口的右边" -#~ msgid "window index is out of range" -#~ msgstr "窗口索引超出范围" +#: ../../../runtime/optwin.vim:497 +msgid "this window scrolls together with other bound windows" +msgstr "此窗口与其他已绑定的窗口一起滚动" -#~ msgid "couldn't open buffer" -#~ msgstr "无法打开缓冲区" +#: ../../../runtime/optwin.vim:500 +msgid "\"ver\", \"hor\" and/or \"jump\"; list of options for 'scrollbind'" +msgstr "'scrollbind' 的选项列表: \"ver\", \"hor\" 和/或 \"jump\"" -#~ msgid "cannot save undo information" -#~ msgstr "无法保存撤销信息" +#: ../../../runtime/optwin.vim:502 +msgid "this window's cursor moves together with other bound windows" +msgstr "此窗口的光标与其他已绑定的窗口一起移动" -#~ msgid "cannot delete line" -#~ msgstr "无法删除行" +#: ../../../runtime/optwin.vim:506 +msgid "size of a terminal window" +msgstr "终端窗口的大小" -#~ msgid "cannot replace line" -#~ msgstr "无法替换行" +#: ../../../runtime/optwin.vim:509 +msgid "key that precedes Vim commands in a terminal window" +msgstr "终端窗口中 Vim 命令的前导键" -#~ msgid "cannot insert line" -#~ msgstr "无法插入行" +#: ../../../runtime/optwin.vim:515 +msgid "multiple tab pages" +msgstr "多个标签页" -#~ msgid "string cannot contain newlines" -#~ msgstr "字符串不能包含换行(NL)" +#: ../../../runtime/optwin.vim:516 +msgid "0, 1 or 2; when to use a tab pages line" +msgstr "0, 1 或 2; 何时使用标签页行" -#~ msgid "Vim error: ~a" -#~ msgstr "Vim 错误: ~a" +#: ../../../runtime/optwin.vim:518 +msgid "maximum number of tab pages to open for -p and \"tab all\"" +msgstr "-p 和 \"tab all\" 打开的最大标签页数" -#~ msgid "Vim error" -#~ msgstr "Vim 错误" +#: ../../../runtime/optwin.vim:520 +msgid "custom tab pages line" +msgstr "自定义标签页行" -#~ msgid "buffer is invalid" -#~ msgstr "缓冲区无效" +#: ../../../runtime/optwin.vim:523 +msgid "custom tab page label for the GUI" +msgstr "为 GUI 自定义标签页的标签" -#~ msgid "window is invalid" -#~ msgstr "窗口无效" +#: ../../../runtime/optwin.vim:525 +msgid "custom tab page tooltip for the GUI" +msgstr "为 GUI 自定义标签页的工具提示" -#~ msgid "linenr out of range" -#~ msgstr "行号超出范围" +#: ../../../runtime/optwin.vim:530 +msgid "terminal" +msgstr "终端" -#~ msgid "not allowed in the Vim sandbox" -#~ msgstr "不允许在 sandbox 中使用" +#: ../../../runtime/optwin.vim:531 +msgid "minimal number of lines to scroll at a time" +msgstr "一次滚动的最少行数" -#~ msgid "" -#~ "E263: Sorry, this command is disabled, the Python library could not be " -#~ "loaded." -#~ msgstr "E263: 抱歉,此命令不可用,无法加载 Python 库。" +#: ../../../runtime/optwin.vim:534 +msgid "specifies what the cursor looks like in different modes" +msgstr "指定光标在不同模式下的样子" -#~ msgid "E659: Cannot invoke Python recursively" -#~ msgstr "E659: 不能递归调用 Python" +#: ../../../runtime/optwin.vim:539 +msgid "show info in the window title" +msgstr "在窗口标题中显示信息" -#~ msgid "can't delete OutputObject attributes" -#~ msgstr "不能删除 OutputObject 属性" +#: ../../../runtime/optwin.vim:542 +msgid "percentage of 'columns' used for the window title" +msgstr "窗口标题的 'columns' 的百分比" -#~ msgid "softspace must be an integer" -#~ msgstr "softspace 必须是整数" +#: ../../../runtime/optwin.vim:544 +msgid "when not empty, string to be used for the window title" +msgstr "非空时,用于窗口标题的字符串" -#~ msgid "invalid attribute" -#~ msgstr "无效的属性" +#: ../../../runtime/optwin.vim:546 +msgid "string to restore the title to when exiting Vim" +msgstr "退出 Vim 时用于恢复标题的字符串" -#~ msgid "writelines() requires list of strings" -#~ msgstr "writelines() 需要字符串列表作参数" +#: ../../../runtime/optwin.vim:549 +msgid "set the text of the icon for this window" +msgstr "设置此窗口图标的文本" -#~ msgid "E264: Python: Error initialising I/O objects" -#~ msgstr "E264: Python: 初始化 I/O 对象出错" +#: ../../../runtime/optwin.vim:552 +msgid "when not empty, text for the icon of this window" +msgstr "非空时,设置此窗口图标的文本" -#~ msgid "attempt to refer to deleted buffer" -#~ msgstr "试图引用已被删除的缓冲区" +#: ../../../runtime/optwin.vim:557 +msgid "using the mouse" +msgstr "使用鼠标" -#~ msgid "line number out of range" -#~ msgstr "行号超出范围" +#: ../../../runtime/optwin.vim:558 +msgid "list of flags for using the mouse" +msgstr "使用鼠标时的标志列表" -#~ msgid "<buffer object (deleted) at %8lX>" -#~ msgstr "<缓冲区对象(已删除): %8lX>" +#: ../../../runtime/optwin.vim:561 +msgid "the window with the mouse pointer becomes the current one" +msgstr "带有鼠标指针的窗口成为当前窗口" -#~ msgid "invalid mark name" -#~ msgstr "无效的标记名称" +#: ../../../runtime/optwin.vim:563 +msgid "hide the mouse pointer while typing" +msgstr "在输入时隐藏鼠标指针" -#~ msgid "no such buffer" -#~ msgstr "无此缓冲区" +#: ../../../runtime/optwin.vim:566 +msgid "" +"\"extend\", \"popup\" or \"popup_setpos\"; what the right\n" +"mouse button is used for" +msgstr "\"extend\", \"popup\" 或 \"popup_setpos\"; 鼠标右键用于什么" -#~ msgid "attempt to refer to deleted window" -#~ msgstr "试图引用已被删除的窗口" +#: ../../../runtime/optwin.vim:568 +msgid "maximum time in msec to recognize a double-click" +msgstr "识别双击的最大时间(以毫秒计)" -#~ msgid "readonly attribute" -#~ msgstr "只读属性" +#: ../../../runtime/optwin.vim:571 +msgid "what the mouse pointer looks like in different modes" +msgstr "鼠标指针在不同模式下的样子" -#~ msgid "cursor position outside buffer" -#~ msgstr "光标位置在缓冲区外" +#: ../../../runtime/optwin.vim:577 +msgid "GUI" +msgstr "图形用户界面" -#~ msgid "<window object (deleted) at %.8lX>" -#~ msgstr "<窗口对象(已删除): %.8lX>" +#: ../../../runtime/optwin.vim:578 +msgid "list of font names to be used in the GUI" +msgstr "在 GUI 中使用的字体名称列表" -#~ msgid "<window object (unknown) at %.8lX>" -#~ msgstr "<窗口对象(未知): %.8lX>" +#: ../../../runtime/optwin.vim:581 +msgid "pair of fonts to be used, for multibyte editing" +msgstr "用于多字节编辑的字体对" -#~ msgid "<window %d>" -#~ msgstr "<窗口 %d>" +#: ../../../runtime/optwin.vim:584 +msgid "list of font names to be used for double-wide characters" +msgstr "用于双宽度字符的字体名称列表" -#~ msgid "no such window" -#~ msgstr "无此窗口" +#: ../../../runtime/optwin.vim:586 +msgid "list of flags that specify how the GUI works" +msgstr "指定 GUI 工作方式的标志列表" -#~ msgid "" -#~ "E266: Sorry, this command is disabled, the Ruby library could not be " -#~ "loaded." -#~ msgstr "E266: 抱歉,此命令不可用,无法加载 Ruby 库" +#: ../../../runtime/optwin.vim:589 +msgid "\"icons\", \"text\" and/or \"tooltips\"; how to show the toolbar" +msgstr "\"icons\", \"text\" 和/或 \"tooltips\"; 如何显示工具栏" -#~ msgid "E273: unknown longjmp status %d" -#~ msgstr "E273: 未知的 longjmp 状态 %d" +#: ../../../runtime/optwin.vim:592 +msgid "size of toolbar icons" +msgstr "工具栏图标大小" -#~ msgid "Toggle implementation/definition" -#~ msgstr "切换实现/定义" +#: ../../../runtime/optwin.vim:597 +msgid "" +"\"last\", \"buffer\" or \"current\": which directory used for the file " +"browser" +msgstr "\"last\", \"buffer\" 或 \"current\"; 文件浏览器使用哪个目录" -#~ msgid "Show base class of" -#~ msgstr "显示 base class of:" +#: ../../../runtime/optwin.vim:601 +msgid "language to be used for the menus" +msgstr "菜单使用的语言" -#~ msgid "Show overridden member function" -#~ msgstr "显示被覆盖的成员函数" +#: ../../../runtime/optwin.vim:604 +msgid "maximum number of items in one menu" +msgstr "单个菜单中的最大项目数量" -#~ msgid "Retrieve from file" -#~ msgstr "恢复: 从文件" +#: ../../../runtime/optwin.vim:607 +msgid "\"no\", \"yes\" or \"menu\"; how to use the ALT key" +msgstr "\"no\", \"yes\" 或 \"menu\"; 如何使用 ALT 键" -#~ msgid "Retrieve from project" -#~ msgstr "恢复: 从对象" +#: ../../../runtime/optwin.vim:610 +msgid "number of pixel lines to use between characters" +msgstr "字符之间使用的像素行数" -#~ msgid "Retrieve from all projects" -#~ msgstr "恢复: 从所有项目" +#: ../../../runtime/optwin.vim:613 +msgid "delay in milliseconds before a balloon may pop up" +msgstr "延迟指定毫秒后,气泡可能会弹出" -#~ msgid "Retrieve" -#~ msgstr "恢复" +#: ../../../runtime/optwin.vim:616 +msgid "use balloon evaluation in the GUI" +msgstr "在 GUI 中使用气泡求值" -#~ msgid "Show source of" -#~ msgstr "显示源代码: " +#: ../../../runtime/optwin.vim:620 +msgid "use balloon evaluation in the terminal" +msgstr "在终端中使用气泡求值" -#~ msgid "Find symbol" -#~ msgstr "查找 symbol" +#: ../../../runtime/optwin.vim:624 +msgid "expression to show in balloon eval" +msgstr "在气泡求值中显示的表达式" -#~ msgid "Browse class" -#~ msgstr "浏览 class" +#: ../../../runtime/optwin.vim:631 +msgid "printing" +msgstr "打印" -#~ msgid "Show class in hierarchy" -#~ msgstr "显示层次关系的类" +#: ../../../runtime/optwin.vim:654 +msgid "messages and info" +msgstr "消息和信息" -#~ msgid "Show class in restricted hierarchy" -#~ msgstr "显示 restricted 层次关系的 class" +#: ../../../runtime/optwin.vim:655 +msgid "add 's' flag in 'shortmess' (don't show search message)" +msgstr "在 'shortmess' 中添加 's' 标志(不显示搜索消息)" -#~ msgid "Xref refers to" -#~ msgstr "Xref 参考到" +#: ../../../runtime/optwin.vim:657 +msgid "list of flags to make messages shorter" +msgstr "使消息更短的标志列表" -#~ msgid "Xref referred by" -#~ msgstr "Xref 被谁参考:" +#: ../../../runtime/optwin.vim:659 +msgid "show (partial) command keys in the status line" +msgstr "在状态行中显示(不完整的)命令键" -#~ msgid "Xref has a" -#~ msgstr "Xref 有" +#: ../../../runtime/optwin.vim:663 +msgid "display the current mode in the status line" +msgstr "在状态行中显示当前模式" -#~ msgid "Xref used by" -#~ msgstr "Xref 被谁使用:" +#: ../../../runtime/optwin.vim:665 +msgid "show cursor position below each window" +msgstr "在每个窗口下方显示光标的位置" -#~ msgid "Show docu of" -#~ msgstr "显示文件: " +#: ../../../runtime/optwin.vim:670 +msgid "alternate format to be used for the ruler" +msgstr "ruler 的替代格式" -#~ msgid "Generate docu for" -#~ msgstr "产生文件: " +#: ../../../runtime/optwin.vim:673 +msgid "threshold for reporting number of changed lines" +msgstr "报告已更改行数的阈值" -#~ msgid "" -#~ "Cannot connect to SNiFF+. Check environment (sniffemacs must be found in " -#~ "$PATH).\n" -#~ msgstr "" -#~ "不能连接到 SNiFF+。请检查环境变量 ($PATH 里必需可以找到 sniffemacs)\n" +#: ../../../runtime/optwin.vim:675 +msgid "the higher the more messages are given" +msgstr "等级越高,给出的信息越多" -#~ msgid "E274: Sniff: Error during read. Disconnected" -#~ msgstr "E274: Sniff: 读取错误. 取消连接" +#: ../../../runtime/optwin.vim:677 +msgid "file to write messages in" +msgstr "用于写入消息的文件" -#~ msgid "SNiFF+ is currently " -#~ msgstr "SNiFF+ 目前" +#: ../../../runtime/optwin.vim:679 +msgid "pause listings when the screen is full" +msgstr "当屏幕满时暂停显示清单" -#~ msgid "not " -#~ msgstr "未" +#: ../../../runtime/optwin.vim:682 +msgid "start a dialog when a command fails" +msgstr "当命令失败时开启对话框" -#~ msgid "connected" -#~ msgstr "连接中" +#: ../../../runtime/optwin.vim:685 +msgid "ring the bell for error messages" +msgstr "错误信息响铃" -#~ msgid "E275: Unknown SNiFF+ request: %s" -#~ msgstr "E275: 不正确的 SNiff+ 调用: %s" +#: ../../../runtime/optwin.vim:687 +msgid "use a visual bell instead of beeping" +msgstr "使用视觉铃声代替响铃" -#~ msgid "E276: Error connecting to SNiFF+" -#~ msgstr "E276: 连接到 SNiFF+ 失败" +#: ../../../runtime/optwin.vim:689 +msgid "do not ring the bell for these reasons" +msgstr "不要为这些原因响铃" -#~ msgid "E278: SNiFF+ not connected" -#~ msgstr "E278: 未连接到 SNiFF+" +#: ../../../runtime/optwin.vim:692 +msgid "list of preferred languages for finding help" +msgstr "查找帮助的首选语言列表" -#~ msgid "E279: Not a SNiFF+ buffer" -#~ msgstr "E279: 不是 SNiFF+ 的缓冲区" +#: ../../../runtime/optwin.vim:697 +msgid "selecting text" +msgstr "选择文本" -#~ msgid "Sniff: Error during write. Disconnected" -#~ msgstr "Sniff: 写入错误。结束连接" +#: ../../../runtime/optwin.vim:698 +msgid "\"old\", \"inclusive\" or \"exclusive\"; how selecting text behaves" +msgstr "\"old\", \"inclusive\" 或 \"exclusive\"; 如何选择文本" -#~ msgid "invalid buffer number" -#~ msgstr "无效的缓冲区号" +#: ../../../runtime/optwin.vim:700 +msgid "" +"\"mouse\", \"key\" and/or \"cmd\"; when to start Select mode\n" +"instead of Visual mode" +msgstr "\"mouse\", \"key\" 和/或 \"cmd\"; 何时启动选择模式而不是可视模式" -#~ msgid "not implemented yet" -#~ msgstr "尚未实现" +#: ../../../runtime/optwin.vim:703 +msgid "" +"\"unnamed\" to use the * register like unnamed register\n" +"\"autoselect\" to always put selected text on the clipboard" +msgstr "" +"\"unnamed\": 像无名寄存器一样使用 * 寄存器\n" +"\"autoselect\": 将选择的文本始终放在剪贴板上" -#~ msgid "cannot set line(s)" -#~ msgstr "无法设定行" +#: ../../../runtime/optwin.vim:706 +msgid "\"startsel\" and/or \"stopsel\"; what special keys can do" +msgstr "\"startsel\" 和/或 \"stopsel\"; 特殊键可以做" -#~ msgid "mark not set" -#~ msgstr "没有设定标记" +#: ../../../runtime/optwin.vim:710 +msgid "editing text" +msgstr "编辑文本" -#~ msgid "row %d column %d" -#~ msgstr "第 %d 行 第 %d 列" +#: ../../../runtime/optwin.vim:711 +msgid "maximum number of changes that can be undone" +msgstr "可以撤销的最大更改数" -#~ msgid "cannot insert/append line" -#~ msgstr "无法插入/追加行" +#: ../../../runtime/optwin.vim:714 +msgid "automatically save and restore undo history" +msgstr "自动保存和恢复撤销历史" -#~ msgid "unknown flag: " -#~ msgstr "未知的标志: " +#: ../../../runtime/optwin.vim:716 +msgid "list of directories for undo files" +msgstr "撤销文件的目录列表" -#~ msgid "unknown vimOption" -#~ msgstr "未知的 vim 选项" +#: ../../../runtime/optwin.vim:718 +msgid "maximum number lines to save for undo on a buffer reload" +msgstr "缓冲区重新加载时为撤销保存的最大行数" -#~ msgid "keyboard interrupt" -#~ msgstr "键盘中断" +#: ../../../runtime/optwin.vim:720 +msgid "changes have been made and not written to a file" +msgstr "已经做出了修改,但没有被写入文件" -#~ msgid "vim error" -#~ msgstr "vim 错误" +#: ../../../runtime/optwin.vim:723 +msgid "buffer is not to be written" +msgstr "缓冲区不会被写入" -#~ msgid "cannot create buffer/window command: object is being deleted" -#~ msgstr "无法创建缓冲区/窗口命令: 对象将被删除" +#: ../../../runtime/optwin.vim:726 +msgid "changes to the text are possible" +msgstr "可以对文本进行更改" -#~ msgid "" -#~ "cannot register callback command: buffer/window is already being deleted" -#~ msgstr "无法注册回调命令: 缓冲区/窗口已被删除" +#: ../../../runtime/optwin.vim:729 +msgid "line length above which to break a line" +msgstr "超过行长度就断行" -#~ msgid "" -#~ "E280: TCL FATAL ERROR: reflist corrupt!? Please report this to vim-" -#~ "dev@vim.org" -#~ msgstr "E280: TCL 严重错误: reflist 损坏!?请报告给 vim-dev@vim.org" +#: ../../../runtime/optwin.vim:732 +msgid "margin from the right in which to break a line" +msgstr "断行开始的右边距" -#~ msgid "cannot register callback command: buffer/window reference not found" -#~ msgstr "无法注册回调命令: 找不到缓冲区/窗口引用" +#: ../../../runtime/optwin.vim:735 +msgid "specifies what <BS>, CTRL-W, etc. can do in Insert mode" +msgstr "指定 <BS>, CTRL-W 等在插入模式下可以做什么" -#~ msgid "" -#~ "E571: Sorry, this command is disabled: the Tcl library could not be " -#~ "loaded." -#~ msgstr "E571: 抱歉,此命令不可用,无法加载 Tcl 库" +#: ../../../runtime/optwin.vim:737 +msgid "definition of what comment lines look like" +msgstr "定义注释行长什么样" -#~ msgid "" -#~ "E281: TCL ERROR: exit code is not int!? Please report this to vim-dev@vim." -#~ "org" -#~ msgstr "E281: TCL 错误: 退出返回值不是整数!?请报告给 vim-dev@vim.org" +#: ../../../runtime/optwin.vim:740 +msgid "list of flags that tell how automatic formatting works" +msgstr "控制自动格式化如何工作的标志列表" -#~ msgid "E572: exit code %d" -#~ msgstr "E572: 退出返回值 %d" +#: ../../../runtime/optwin.vim:743 +msgid "pattern to recognize a numbered list" +msgstr "识别编号列表的模式" -#~ msgid "cannot get line" -#~ msgstr "无法获取行" +#: ../../../runtime/optwin.vim:747 +msgid "expression used for \"gq\" to format lines" +msgstr "用 \"gq\" 格式化行时使用的表达式" -#~ msgid "Unable to register a command server name" -#~ msgstr "无法注册命令服务器名" +#: ../../../runtime/optwin.vim:752 +msgid "specifies how Insert mode completion works for CTRL-N and CTRL-P" +msgstr "指定使用 CTRL-N 和 CTRL-P 进行插入模式补全的工作方式" -#~ msgid "E248: Failed to send command to the destination program" -#~ msgstr "E248: 无法发送命令到目的程序" +#: ../../../runtime/optwin.vim:755 +msgid "whether to use a popup menu for Insert mode completion" +msgstr "是否在插入模式补全时使用弹出菜单" -#~ msgid "E573: Invalid server id used: %s" -#~ msgstr "E573: 使用了无效的服务器 id: %s" +#: ../../../runtime/optwin.vim:757 +msgid "maximum height of the popup menu" +msgstr "弹出菜单的最大高度" -#~ msgid "E251: VIM instance registry property is badly formed. Deleted!" -#~ msgstr "E251: VIM 实例注册属性有误。已删除!" +#: ../../../runtime/optwin.vim:759 +msgid "minimum width of the popup menu" +msgstr "弹出菜单的最小宽度" -#~ msgid "This Vim was not compiled with the diff feature." -#~ msgstr "此 Vim 编译时没有加入 diff 功能" +#: ../../../runtime/optwin.vim:761 +msgid "user defined function for Insert mode completion" +msgstr "用于插入模式补全的用户定义函数" -#~ msgid "Vim: Error: Failure to start gvim from NetBeans\n" -#~ msgstr "Vim: 错误: 无法从 NetBeans 中启动 gvim\n" +#: ../../../runtime/optwin.vim:764 +msgid "function for filetype-specific Insert mode completion" +msgstr "用于特定文件类型的插入模式补全的函数" -#~ msgid "-register\t\tRegister this gvim for OLE" -#~ msgstr "-register\t\t注册此 gvim 到 OLE" +#: ../../../runtime/optwin.vim:767 +msgid "list of dictionary files for keyword completion" +msgstr "用于关键字补全的字典文件列表" -#~ msgid "-unregister\t\tUnregister gvim for OLE" -#~ msgstr "-unregister\t\t取消 OLE 中的 gvim 注册" +#: ../../../runtime/optwin.vim:770 +msgid "list of thesaurus files for keyword completion" +msgstr "用于关键字补全的同义词字典文件列表" -#~ msgid "-g\t\t\tRun using GUI (like \"gvim\")" -#~ msgstr "-g\t\t\t使用图形界面 (同 \"gvim\")" +#: ../../../runtime/optwin.vim:773 +msgid "function used for thesaurus completion" +msgstr "用于同义词字典补全的函数" -#~ msgid "-f or --nofork\tForeground: Don't fork when starting GUI" -#~ msgstr "-f 或 --nofork\t前台: 启动图形界面时不 fork" +#: ../../../runtime/optwin.vim:777 +msgid "adjust case of a keyword completion match" +msgstr "调整关键字补全匹配的大小写" -#~ msgid "-V[N]\t\tVerbose level" -#~ msgstr "-V[N]\t\tVerbose 等级" +#: ../../../runtime/optwin.vim:781 +msgid "enable entering digraphs with c1 <BS> c2" +msgstr "支持使用 c1 <BS> c2 输入二合字符" -#~ msgid "-f\t\t\tDon't use newcli to open window" -#~ msgstr "-f\t\t\t不使用 newcli 来打开窗口" +#: ../../../runtime/optwin.vim:784 +msgid "the \"~\" command behaves like an operator" +msgstr "\"~\"命令表现得像操作符" -#~ msgid "-dev <device>\t\tUse <device> for I/O" -#~ msgstr "-dev <device>\t\t使用 <device> 进行输入输出" +#: ../../../runtime/optwin.vim:786 +msgid "function called for the \"g@\" operator" +msgstr "\"g@\" 操作符调用的函数" -#~ msgid "-U <gvimrc>\t\tUse <gvimrc> instead of any .gvimrc" -#~ msgstr "-U <gvimrc>\t\t使用 <gvimrc> 替代任何 .gvimrc" +#: ../../../runtime/optwin.vim:788 +msgid "when inserting a bracket, briefly jump to its match" +msgstr "当插入括号时,短暂地跳转到匹配它的括号" -#~ msgid "-x\t\t\tEdit encrypted files" -#~ msgstr "-x\t\t\t编辑加密的文件" +#: ../../../runtime/optwin.vim:790 +msgid "tenth of a second to show a match for 'showmatch'" +msgstr "显示 'showmatch' 的匹配的时长(以十分之一秒计)" -#~ msgid "-display <display>\tConnect vim to this particular X-server" -#~ msgstr "-display <display>\t将 vim 与指定的 X-server 连接" +#: ../../../runtime/optwin.vim:792 +msgid "list of pairs that match for the \"%\" command" +msgstr "\"%\" 命令匹配的对列表" -#~ msgid "-X\t\t\tDo not connect to X server" -#~ msgstr "-X\t\t\t不连接到 X Server" +#: ../../../runtime/optwin.vim:795 +msgid "use two spaces after '.' when joining a line" +msgstr "连接行时,在 '.' 后面添加两个空格" -#~ msgid "--remote <files>\tEdit <files> in a Vim server if possible" -#~ msgstr "--remote <files>\t如有可能,在 Vim 服务器上编辑文件 <files>" +#: ../../../runtime/optwin.vim:797 +msgid "" +"\"alpha\", \"octal\", \"hex\", \"bin\" and/or \"unsigned\"; number formats\n" +"recognized for CTRL-A and CTRL-X commands" +msgstr "" +"\"alpha\", \"octal\", \"hex\", \"bin\" 和/或 \"unsigned\"\n" +"CTRL-A 和 CTRL-X 命令识别的数字的格式" -#~ msgid "--remote-silent <files> Same, don't complain if there is no server" -#~ msgstr "--remote-silent <files> 同上,找不到服务器时不抱怨" +#: ../../../runtime/optwin.vim:802 +msgid "tabs and indenting" +msgstr "Tab 和缩进" -#~ msgid "" -#~ "--remote-wait <files> As --remote but wait for files to have been edited" -#~ msgstr "--remote-wait <files> 同 --remote 但会等待文件完成编辑" +#: ../../../runtime/optwin.vim:803 +msgid "number of spaces a <Tab> in the text stands for" +msgstr "<Tab> 在文本中代表的空格数" -#~ msgid "" -#~ "--remote-wait-silent <files> Same, don't complain if there is no server" -#~ msgstr "--remote-wait-silent <files> 同上,找不到服务器时不抱怨" +#: ../../../runtime/optwin.vim:806 +msgid "number of spaces used for each step of (auto)indent" +msgstr "每步(自动)缩进所使用的空格数" -#~ msgid "--remote-tab <files> As --remote but open tab page for each file" -#~ msgstr "--remote-tab <files> 同 --remote 但对每个文件打开一个标签页" +#: ../../../runtime/optwin.vim:810 +msgid "list of number of spaces a tab counts for" +msgstr "tab 代表的空格数的列表" -#~ msgid "--remote-send <keys>\tSend <keys> to a Vim server and exit" -#~ msgstr "--remote-send <keys>\t送出 <keys> 到 Vim 服务器并退出" +#: ../../../runtime/optwin.vim:813 +msgid "list of number of spaces a soft tabsstop counts for" +msgstr "软制表符代表的空格数的列表" -#~ msgid "" -#~ "--remote-expr <expr>\tEvaluate <expr> in a Vim server and print result" -#~ msgstr "--remote-expr <expr>\t在 Vim 服务器上求 <expr> 的值并打印结果" +#: ../../../runtime/optwin.vim:817 +msgid "a <Tab> in an indent inserts 'shiftwidth' spaces" +msgstr "用 <Tab> 键缩进时插入 'shiftwidth' 个空格" -#~ msgid "--serverlist\t\tList available Vim server names and exit" -#~ msgstr "--serverlist\t\t列出可用的 Vim 服务器名称并退出" +#: ../../../runtime/optwin.vim:819 +msgid "if non-zero, number of spaces to insert for a <Tab>" +msgstr "如果非零,为 <Tab> 插入的空格数" -#~ msgid "--servername <name>\tSend to/become the Vim server <name>" -#~ msgstr "--servername <name>\t发送到或成为 Vim 服务器 <name>" +#: ../../../runtime/optwin.vim:822 +msgid "round to 'shiftwidth' for \"<<\" and \">>\"" +msgstr "用 \"<<\" 和 \">>\" 缩进时,插入 'shiftwidth' 整数倍个空格" -#~ msgid "" -#~ "\n" -#~ "Arguments recognised by gvim (Motif version):\n" -#~ msgstr "" -#~ "\n" -#~ "gvim (Motif 版本) 可识别的参数:\n" +#: ../../../runtime/optwin.vim:824 +msgid "expand <Tab> to spaces in Insert mode" +msgstr "在插入模式下将 <Tab> 展开为空格" -#~ msgid "" -#~ "\n" -#~ "Arguments recognised by gvim (neXtaw version):\n" -#~ msgstr "" -#~ "\n" -#~ "gvim (neXtaw 版本) 可识别的参数:\n" +#: ../../../runtime/optwin.vim:827 +msgid "automatically set the indent of a new line" +msgstr "自动设置新行缩进" -#~ msgid "" -#~ "\n" -#~ "Arguments recognised by gvim (Athena version):\n" -#~ msgstr "" -#~ "\n" -#~ "gvim (Athena 版本) 可识别的参数:\n" +#: ../../../runtime/optwin.vim:831 +msgid "do clever autoindenting" +msgstr "智能自动缩进" -#~ msgid "-display <display>\tRun vim on <display>" -#~ msgstr "-display <display>\t在 <display> 上运行 vim" +#: ../../../runtime/optwin.vim:836 +msgid "enable specific indenting for C code" +msgstr "为 C 代码启用特定的缩进" -#~ msgid "-iconic\t\tStart vim iconified" -#~ msgstr "-iconic\t\t启动后最小化" +#: ../../../runtime/optwin.vim:839 +msgid "options for C-indenting" +msgstr "C 风格缩进的选项" -#~ msgid "-name <name>\t\tUse resource as if vim was <name>" -#~ msgstr "-name <name>\t\t读取 Resource 时把 vim 视为 <name>" +#: ../../../runtime/optwin.vim:842 +msgid "keys that trigger C-indenting in Insert mode" +msgstr "在插入模式下触发 C 风格缩进的键" -#~ msgid "\t\t\t (Unimplemented)\n" -#~ msgstr "\t\t\t (尚未实现)\n" +#: ../../../runtime/optwin.vim:845 +msgid "list of words that cause more C-indent" +msgstr "导致更多 C 风格缩进的单词列表" -#~ msgid "-background <color>\tUse <color> for the background (also: -bg)" -#~ msgstr "-background <color>\t使用 <color> 作为背景色 (也可用 -bg)" +#: ../../../runtime/optwin.vim:848 +msgid "list of scope declaration names used by cino-g" +msgstr "cino-g 使用的作用域声明名称列表" -#~ msgid "-foreground <color>\tUse <color> for normal text (also: -fg)" -#~ msgstr "-foreground <color>\t使用 <color> 作为一般文字颜色 (也可用 -fg)" +#: ../../../runtime/optwin.vim:851 +msgid "expression used to obtain the indent of a line" +msgstr "用于获取一行缩进的表达式" -#~ msgid "-font <font>\t\tUse <font> for normal text (also: -fn)" -#~ msgstr "-font <font>\t使用 <font> 作为一般字体 (也可用 -fn)" +#: ../../../runtime/optwin.vim:854 +msgid "keys that trigger indenting with 'indentexpr' in Insert mode" +msgstr "在插入模式下使用 'indentexpr' 触发缩进的键" -#~ msgid "-boldfont <font>\tUse <font> for bold text" -#~ msgstr "-boldfont <font>\t使用 <font> 作为粗体字体" +#: ../../../runtime/optwin.vim:858 +msgid "copy whitespace for indenting from previous line" +msgstr "为缩进从上一行复制空白字符" -#~ msgid "-italicfont <font>\tUse <font> for italic text" -#~ msgstr "-italicfont <font>\t使用 <font> 作为斜体字体" +#: ../../../runtime/optwin.vim:861 +msgid "preserve kind of whitespace when changing indent" +msgstr "在更改缩进时保留空白类型" -#~ msgid "-geometry <geom>\tUse <geom> for initial geometry (also: -geom)" -#~ msgstr "-geometry <geom>\t使用 <geom> 作为初始位置 (也可用 -geom)" +#: ../../../runtime/optwin.vim:865 +msgid "enable lisp mode" +msgstr "启用 lisp 模式" -#~ msgid "-borderwidth <width>\tUse a border width of <width> (also: -bw)" -#~ msgstr "-borderwidth <width>\t设定边框宽度为 <width> (也可用 -bw)" +#: ../../../runtime/optwin.vim:868 +msgid "words that change how lisp indenting works" +msgstr "改变 lisp 如何缩进的单词" -#~ msgid "" -#~ "-scrollbarwidth <width> Use a scrollbar width of <width> (also: -sw)" -#~ msgstr "-scrollbarwidth <width> 设定滚动条宽度为 <width> (也可用 -sw)" +#: ../../../runtime/optwin.vim:870 +msgid "options for Lisp indenting" +msgstr "Lisp 缩进选项" -#~ msgid "-menuheight <height>\tUse a menu bar height of <height> (also: -mh)" -#~ msgstr "-menuheight <height>\t设定菜单栏高度为 <height> (也可用 -mh)" +#: ../../../runtime/optwin.vim:876 +msgid "folding" +msgstr "折叠" -#~ msgid "-reverse\t\tUse reverse video (also: -rv)" -#~ msgstr "-reverse\t\t使用反显 (也可用 -rv)" +#: ../../../runtime/optwin.vim:877 +msgid "unset to display all folds open" +msgstr "取消设置以打开所有折叠" -#~ msgid "+reverse\t\tDon't use reverse video (also: +rv)" -#~ msgstr "+reverse\t\t不使用反显 (也可用 +rv)" +#: ../../../runtime/optwin.vim:880 +msgid "folds with a level higher than this number will be closed" +msgstr "等级比这个数字高的折叠将被关闭" -#~ msgid "-xrm <resource>\tSet the specified resource" -#~ msgstr "-xrm <resource>\t设定指定的资源" +#: ../../../runtime/optwin.vim:883 +msgid "value for 'foldlevel' when starting to edit a file" +msgstr "开始编辑文件时,'foldlevel' 的值" -#~ msgid "" -#~ "\n" -#~ "Arguments recognised by gvim (RISC OS version):\n" -#~ msgstr "" -#~ "\n" -#~ "gvim (RISC OS 版本) 可识别的参数:\n" +#: ../../../runtime/optwin.vim:885 +msgid "width of the column used to indicate folds" +msgstr "用来指示折叠的列的宽度" -#~ msgid "--columns <number>\tInitial width of window in columns" -#~ msgstr "--columns <number>\t窗口初始宽度" +#: ../../../runtime/optwin.vim:888 +msgid "expression used to display the text of a closed fold" +msgstr "用于显示已关闭折叠的表达式" -#~ msgid "--rows <number>\tInitial height of window in rows" -#~ msgstr "--rows <number>\t窗口初始高度" +#: ../../../runtime/optwin.vim:891 +msgid "set to \"all\" to close a fold when the cursor leaves it" +msgstr "设置为 \"all\" 来在光标离开时关闭折叠" -#~ msgid "" -#~ "\n" -#~ "Arguments recognised by gvim (GTK+ version):\n" -#~ msgstr "" -#~ "\n" -#~ "gvim (GTK+ 版本) 可识别的参数:\n" +#: ../../../runtime/optwin.vim:893 +msgid "specifies for which commands a fold will be opened" +msgstr "指定会打开折叠的命令" -#~ msgid "-display <display>\tRun vim on <display> (also: --display)" -#~ msgstr "-display <display>\t在 <display> 上运行 vim (也可用 --display)" +#: ../../../runtime/optwin.vim:895 +msgid "minimum number of screen lines for a fold to be closed" +msgstr "可关闭折叠所需的最小屏幕行数" -#~ msgid "--role <role>\tSet a unique role to identify the main window" -#~ msgstr "--role <role>\t设置用于区分主窗口的窗口角色名" +#: ../../../runtime/optwin.vim:898 +msgid "template for comments; used to put the marker in" +msgstr "注释的模板,用于放置折叠标记" -#~ msgid "--socketid <xid>\tOpen Vim inside another GTK widget" -#~ msgstr "--socketid <xid>\t在另一个 GTK 部件中打开 Vim" +#: ../../../runtime/optwin.vim:900 +msgid "" +"folding type: \"manual\", \"indent\", \"expr\", \"marker\",\n" +"\"syntax\" or \"diff\"" +msgstr "" +"折叠类型:\"manual\", \"indent\", \"expr\", \"marker\", \n" +"\"syntax\" 或者 \"diff\"" -#~ msgid "-P <parent title>\tOpen Vim inside parent application" -#~ msgstr "-P <parent title>\t在父应用程序中打开 Vim" +#: ../../../runtime/optwin.vim:903 +msgid "expression used when 'foldmethod' is \"expr\"" +msgstr "当 'foldmethod' 为 \"expr\" 时使用的表达式" -#~ msgid "No display" -#~ msgstr "没有 display" +#: ../../../runtime/optwin.vim:906 +msgid "used to ignore lines when 'foldmethod' is \"indent\"" +msgstr "当 'foldmethod' 为 \"indent\" 时用于忽略行" -#~ msgid ": Send failed.\n" -#~ msgstr ": 发送失败。\n" +#: ../../../runtime/optwin.vim:909 +msgid "markers used when 'foldmethod' is \"marker\"" +msgstr "当 'foldmethod' 为 \"marker\" 时所使用的标记" -#~ msgid ": Send failed. Trying to execute locally\n" -#~ msgstr ": 发送失败。尝试本地执行\n" +#: ../../../runtime/optwin.vim:912 +msgid "maximum fold depth for when 'foldmethod' is \"indent\" or \"syntax\"" +msgstr "当 'foldmethod' 为 \"indent\" 或 \"syntax\" 时的最大折叠深度" -#~ msgid "%d of %d edited" -#~ msgstr "%d 中 %d 已编辑" +#: ../../../runtime/optwin.vim:919 +msgid "diff mode" +msgstr "差异模式" -#~ msgid "No display: Send expression failed.\n" -#~ msgstr "没有 display: 发送表达式失败。\n" +#: ../../../runtime/optwin.vim:920 +msgid "use diff mode for the current window" +msgstr "对当前窗口使用差异模式" -#~ msgid ": Send expression failed.\n" -#~ msgstr ": 发送表达式失败。\n" +#: ../../../runtime/optwin.vim:923 +msgid "options for using diff mode" +msgstr "使用差异模式的选项" -#~ msgid "E543: Not a valid codepage" -#~ msgstr "E543: 无效的代码页" +#: ../../../runtime/optwin.vim:925 +msgid "expression used to obtain a diff file" +msgstr "用于获取差异文件的表达式" -#~ msgid "E285: Failed to create input context" -#~ msgstr "E285: 无法创建输入上下文" +#: ../../../runtime/optwin.vim:927 +msgid "expression used to patch a file" +msgstr "用于给文件打补丁的表达式" -#~ msgid "E286: Failed to open input method" -#~ msgstr "E286: 无法打开输入法" +#: ../../../runtime/optwin.vim:932 +msgid "mapping" +msgstr "映射" -#~ msgid "E287: Warning: Could not set destroy callback to IM" -#~ msgstr "E287: 警告: 无法设定输入法的释放回调函数" +#: ../../../runtime/optwin.vim:933 +msgid "maximum depth of mapping" +msgstr "最大映射深度" -#~ msgid "E288: input method doesn't support any style" -#~ msgstr "E288: 输入法不支持任何风格" +#: ../../../runtime/optwin.vim:935 +msgid "allow timing out halfway into a mapping" +msgstr "允许在映射中途超时" -#~ msgid "E289: input method doesn't support my preedit type" -#~ msgstr "E289: 输入法不支持我的预编辑类型" +#: ../../../runtime/optwin.vim:937 +msgid "allow timing out halfway into a key code" +msgstr "允许在键码中途超时" -#~ msgid "E290: over-the-spot style requires fontset" -#~ msgstr "E290: over-the-spot 风格需要 Fontset" +#: ../../../runtime/optwin.vim:939 +msgid "time in msec for 'timeout'" +msgstr "'timeout' 的时间(以毫秒计)" -#~ msgid "E291: Your GTK+ is older than 1.2.3. Status area disabled" -#~ msgstr "E291: 你的 GTK+ 比 1.2.3 旧。状态区不可用。" +#: ../../../runtime/optwin.vim:941 +msgid "time in msec for 'ttimeout'" +msgstr "'ttimeout' 的时间(以毫秒计)" -#~ msgid "E292: Input Method Server is not running" -#~ msgstr "E292: 输入法服务器未运行" +#: ../../../runtime/optwin.vim:945 +msgid "reading and writing files" +msgstr "读写文件" -#~ msgid "" -#~ "\n" -#~ " [not usable with this version of Vim]" -#~ msgstr "" -#~ "\n" -#~ " [不能在该版本的 Vim 上使用]" +#: ../../../runtime/optwin.vim:946 +msgid "enable using settings from modelines when reading a file" +msgstr "读取文件时是否使用 modeline 里的设置" -#~ msgid "Tear off this menu" -#~ msgstr "撕下此菜单" +#: ../../../runtime/optwin.vim:949 +msgid "allow setting expression options from a modeline" +msgstr "允许从 modeline 中设置表达式选项" -#~ msgid "Select Directory dialog" -#~ msgstr "选择目录对话框" +#: ../../../runtime/optwin.vim:951 +msgid "number of lines to check for modelines" +msgstr "为 modeline 而检查的行数" -#~ msgid "Save File dialog" -#~ msgstr "保存文件对话框" +#: ../../../runtime/optwin.vim:953 +msgid "binary file editing" +msgstr "二进制文件编辑" -#~ msgid "Open File dialog" -#~ msgstr "打开文件对话框" +#: ../../../runtime/optwin.vim:956 +msgid "last line in the file has an end-of-line" +msgstr "文件的最后一行有换行符" -#~ msgid "E338: Sorry, no file browser in console mode" -#~ msgstr "E338: 抱歉,控制台模式下没有文件浏览器" +#: ../../../runtime/optwin.vim:959 +msgid "last line in the file followed by CTRL-Z" +msgstr "" -#~ msgid "Vim: preserving files...\n" -#~ msgstr "Vim: 正在保留文件……\n" +#: ../../../runtime/optwin.vim:962 +msgid "fixes missing end-of-line at end of text file" +msgstr "修复文本文件末尾缺少换行符的问题" -#~ msgid "Vim: Finished.\n" -#~ msgstr "Vim: 结束。\n" +#: ../../../runtime/optwin.vim:965 +msgid "prepend a Byte Order Mark to the file" +msgstr "在文件前加上字节顺序标记" -#~ msgid "ERROR: " -#~ msgstr "错误: " +#: ../../../runtime/optwin.vim:968 +msgid "end-of-line format: \"dos\", \"unix\" or \"mac\"" +msgstr "换行符格式: \"dos\", \"unix\" 或 \"mac\"" -#~ msgid "" -#~ "\n" -#~ "[bytes] total alloc-freed %<PRIu64>-%<PRIu64>, in use %<PRIu64>, peak use " -#~ "%<PRIu64>\n" -#~ msgstr "" -#~ "\n" -#~ "[字节] 总共 alloc-free %<PRIu64>-%<PRIu64>,使用中 %<PRIu64>,高峰使用 " -#~ "%<PRIu64>\n" +#: ../../../runtime/optwin.vim:971 +msgid "list of file formats to look for when editing a file" +msgstr "编辑文件时要检查的文件格式列表" -#~ msgid "" -#~ "[calls] total re/malloc()'s %<PRIu64>, total free()'s %<PRIu64>\n" -#~ "\n" -#~ msgstr "" -#~ "[调用] 总共 re/malloc(): %<PRIu64>,总共 free()': %<PRIu64>\n" -#~ "\n" +#: ../../../runtime/optwin.vim:973 +msgid "writing files is allowed" +msgstr "允许写入文件" -#~ msgid "E340: Line is becoming too long" -#~ msgstr "E340: 此行过长" +#: ../../../runtime/optwin.vim:975 +msgid "write a backup file before overwriting a file" +msgstr "覆盖文件前先写入备份文件" -#~ msgid "E341: Internal error: lalloc(%<PRId64>, )" -#~ msgstr "E341: 内部错误: lalloc(%<PRId64>, )" +#: ../../../runtime/optwin.vim:977 +msgid "keep a backup after overwriting a file" +msgstr "覆盖文件后保留备份" -#~ msgid "E547: Illegal mouseshape" -#~ msgstr "E547: 无效的鼠标形状" +#: ../../../runtime/optwin.vim:979 +msgid "patterns that specify for which files a backup is not made" +msgstr "不备份的文件的模式" -#~ msgid "Enter encryption key: " -#~ msgstr "输入密码: " +#: ../../../runtime/optwin.vim:981 +msgid "whether to make the backup as a copy or rename the existing file" +msgstr "通过复制还是重命名已有文件进行备份" -#~ msgid "Enter same key again: " -#~ msgstr "请再输入一次: " +#: ../../../runtime/optwin.vim:984 +msgid "list of directories to put backup files in" +msgstr "存放备份文件的目录列表" -#~ msgid "Keys don't match!" -#~ msgstr "两次密码不匹配!" +#: ../../../runtime/optwin.vim:986 +msgid "file name extension for the backup file" +msgstr "备份文件使用的扩展名" -#~ msgid "Cannot connect to Netbeans #2" -#~ msgstr "无法连接到 Netbeans #2" +#: ../../../runtime/optwin.vim:988 +msgid "automatically write a file when leaving a modified buffer" +msgstr "在离开修改过的缓冲区时自动写入文件" -#~ msgid "Cannot connect to Netbeans" -#~ msgstr "无法连接到 Netbeans" +#: ../../../runtime/optwin.vim:990 +msgid "as 'autowrite', but works with more commands" +msgstr "类似于 'autowrite',但适用于更多命令" -#~ msgid "E668: Wrong access mode for NetBeans connection info file: \"%s\"" -#~ msgstr "E668: NetBeans 连接信息文件中错误的访问模式: \"%s\"" +#: ../../../runtime/optwin.vim:992 +msgid "always write without asking for confirmation" +msgstr "写文件时总是不需要确认" -#~ msgid "read from Netbeans socket" -#~ msgstr "从 Netbeans 套接字读取" +#: ../../../runtime/optwin.vim:994 +msgid "automatically read a file when it was modified outside of Vim" +msgstr "在 Vim 之外修改了文件时,自动读取文件" -#~ msgid "E658: NetBeans connection lost for buffer %<PRId64>" -#~ msgstr "E658: 缓冲区 %<PRId64> 丢失 NetBeans 连接" +#: ../../../runtime/optwin.vim:997 +msgid "keep oldest version of a file; specifies file name extension" +msgstr "保存最旧版本的文件;指定文件扩展名" -#~ msgid "E505: " -#~ msgstr "E505: " +#: ../../../runtime/optwin.vim:999 +msgid "forcibly sync the file to disk after writing it" +msgstr "文件写入后强制同步到磁盘" -#~ msgid "E775: Eval feature not available" -#~ msgstr "E775: 求值功能不可用" +#: ../../../runtime/optwin.vim:1003 +msgid "the swap file" +msgstr "交换文件" -#~ msgid "freeing %<PRId64> lines" -#~ msgstr "释放了 %<PRId64> 行" +#: ../../../runtime/optwin.vim:1004 +msgid "list of directories for the swap file" +msgstr "交换文件的目录列表" -#~ msgid "E530: Cannot change term in GUI" -#~ msgstr "E530: 在图形界面中不能改变终端" +#: ../../../runtime/optwin.vim:1006 +msgid "use a swap file for this buffer" +msgstr "对这个缓冲区使用交换文件" -#~ msgid "E531: Use \":gui\" to start the GUI" -#~ msgstr "E531: 请用 \":gui\" 启动图形界面" +#: ../../../runtime/optwin.vim:1009 +msgid "number of characters typed to cause a swap file update" +msgstr "导致交换文件更新的字符数" -#~ msgid "E617: Cannot be changed in the GTK+ 2 GUI" -#~ msgstr "E617: 在 GTK+ 2 图形界面中不能更改" +#: ../../../runtime/optwin.vim:1011 +msgid "time in msec after which the swap file will be updated" +msgstr "更新交换文件前所需的毫秒数" -#~ msgid "E596: Invalid font(s)" -#~ msgstr "E596: 无效的字体" +# do not translate to avoid writing Chinese in files +#: ../../../runtime/optwin.vim:1015 +msgid "command line editing" +msgstr "" -#~ msgid "E597: can't select fontset" -#~ msgstr "E597: 无法选择 Fontset" +#: ../../../runtime/optwin.vim:1016 +msgid "how many command lines are remembered" +msgstr "记住的命令行数" -#~ msgid "E598: Invalid fontset" -#~ msgstr "E598: 无效的 Fontset" +#: ../../../runtime/optwin.vim:1018 +msgid "key that triggers command-line expansion" +msgstr "触发命令行扩展的键" -#~ msgid "E533: can't select wide font" -#~ msgstr "E533: 无法选择宽字体" +#: ../../../runtime/optwin.vim:1020 +msgid "like 'wildchar' but can also be used in a mapping" +msgstr "类似 'wildchar',但也可以在映射中使用" -#~ msgid "E534: Invalid wide font" -#~ msgstr "E534: 无效的宽字体" +#: ../../../runtime/optwin.vim:1022 +msgid "specifies how command line completion works" +msgstr "指定命令行如何补全" -#~ msgid "E538: No mouse support" -#~ msgstr "E538: 不支持鼠标" +#: ../../../runtime/optwin.vim:1025 +msgid "empty or \"tagfile\" to list file name of matching tags" +msgstr "空或 \"tagfile\" 来列出匹配标签的文件名" -#~ msgid "cannot open " -#~ msgstr "不能打开" +#: ../../../runtime/optwin.vim:1028 +msgid "list of file name extensions that have a lower priority" +msgstr "优先级低的文件扩展名列表" -#~ msgid "VIM: Can't open window!\n" -#~ msgstr "VIM: 不能打开窗口!\n" +#: ../../../runtime/optwin.vim:1031 +msgid "list of file name extensions added when searching for a file" +msgstr "搜索文件时添加的文件扩展名列表" -#~ msgid "Need Amigados version 2.04 or later\n" -#~ msgstr "需要 Amigados 版本 2.04 以上\n" +#: ../../../runtime/optwin.vim:1036 +msgid "list of patterns to ignore files for file name completion" +msgstr "文件名补全所忽略文件的模式列表" -#~ msgid "Need %s version %<PRId64>\n" -#~ msgstr "需要 %s 版本 %<PRId64>\n" +#: ../../../runtime/optwin.vim:1039 +msgid "ignore case when using file names" +msgstr "使用文件名时忽略大小写" -#~ msgid "Cannot open NIL:\n" -#~ msgstr "不能打开 NIL:\n" +#: ../../../runtime/optwin.vim:1041 +msgid "ignore case when completing file names" +msgstr "补全文件名时忽略大小写" -#~ msgid "Cannot create " -#~ msgstr "不能创建 " +#: ../../../runtime/optwin.vim:1044 +msgid "command-line completion shows a list of matches" +msgstr "命令行补全时显示匹配列表" -#~ msgid "Vim exiting with %d\n" -#~ msgstr "Vim 返回值: %d\n" +#: ../../../runtime/optwin.vim:1047 +msgid "key used to open the command-line window" +msgstr "用于打开命令行窗口的键" -#~ msgid "cannot change console mode ?!\n" -#~ msgstr "不能切换主控台(console)模式 !?\n" +#: ../../../runtime/optwin.vim:1049 +msgid "height of the command-line window" +msgstr "命令行窗口的高度" -#~ msgid "mch_get_shellsize: not a console??\n" -#~ msgstr "mch_get_shellsize: 不是主控台(console)??\n" +#: ../../../runtime/optwin.vim:1053 +msgid "executing external commands" +msgstr "执行外部命令" -#~ msgid "E360: Cannot execute shell with -f option" -#~ msgstr "E360: 不能用 -f 选项执行 shell" +#: ../../../runtime/optwin.vim:1054 +msgid "name of the shell program used for external commands" +msgstr "用于外部命令的 shell 程序的名称" -#~ msgid "Cannot execute " -#~ msgstr "不能执行 " +#: ../../../runtime/optwin.vim:1056 +msgid "character(s) to enclose a shell command in" +msgstr "用于包围 shell 命令的字符" -#~ msgid "shell " -#~ msgstr "shell " +#: ../../../runtime/optwin.vim:1058 +msgid "like 'shellquote' but include the redirection" +msgstr "类似 'shellquote',但包含重定向" -#~ msgid " returned\n" -#~ msgstr " 已返回\n" +#: ../../../runtime/optwin.vim:1060 +msgid "characters to escape when 'shellxquote' is (" +msgstr "'shellxquote' 为 ( 时需要转义的字符" -#~ msgid "ANCHOR_BUF_SIZE too small." -#~ msgstr "ANCHOR_BUF_SIZE 太小" +#: ../../../runtime/optwin.vim:1062 +msgid "argument for 'shell' to execute a command" +msgstr "'shell' 执行命令的参数" -#~ msgid "I/O ERROR" -#~ msgstr "I/O 错误" +#: ../../../runtime/optwin.vim:1064 +msgid "used to redirect command output to a file" +msgstr "用于将命令输出重定向到文件" -#~ msgid "Message" -#~ msgstr "消息" +#: ../../../runtime/optwin.vim:1066 +msgid "use a temp file for shell commands instead of using a pipe" +msgstr "对 shell 命令使用临时文件而不是管道" -#~ msgid "'columns' is not 80, cannot execute external commands" -#~ msgstr "'columns' 不是 80, 不能执行外部命令" +#: ../../../runtime/optwin.vim:1068 +msgid "program used for \"=\" command" +msgstr "用于 \"=\" 命令的程序" -#~ msgid "E237: Printer selection failed" -#~ msgstr "E237: 选择打印机失败" +#: ../../../runtime/optwin.vim:1071 +msgid "program used to format lines with \"gq\" command" +msgstr "用 \"gq\" 命令格式化代码时使用的程序" -#~ msgid "to %s on %s" -#~ msgstr "从 %s 到 %s" +#: ../../../runtime/optwin.vim:1073 +msgid "program used for the \"K\" command" +msgstr "用于 \"K\" 命令的程序" -#~ msgid "E613: Unknown printer font: %s" -#~ msgstr "E613: 未知的打印机字体: %s" +#: ../../../runtime/optwin.vim:1075 +msgid "warn when using a shell command and a buffer has changes" +msgstr "当使用 shell 命令并且缓冲区有修改时发出警告" -#~ msgid "E238: Print error: %s" -#~ msgstr "E238: 打印错误: %s" +#: ../../../runtime/optwin.vim:1080 +msgid "running make and jumping to errors (quickfix)" +msgstr "运行 make 并跳到错误(快速修复)" -#~ msgid "Printing '%s'" -#~ msgstr "打印 '%s'" +#: ../../../runtime/optwin.vim:1081 +msgid "name of the file that contains error messages" +msgstr "包含错误消息的文件的名称" -#~ msgid "E244: Illegal charset name \"%s\" in font name \"%s\"" -#~ msgstr "E244: 字符集 \"%s\" 不能对应字体\"%s\"" +#: ../../../runtime/optwin.vim:1083 +msgid "list of formats for error messages" +msgstr "错误消息的格式列表" -#~ msgid "E245: Illegal char '%c' in font name \"%s\"" -#~ msgstr "E245: 不正确的字符 '%c' 出现在字体名称 \"%s\" 内" +#: ../../../runtime/optwin.vim:1086 +msgid "program used for the \":make\" command" +msgstr "用于 \":make\" 命令的程序" -#~ msgid "Vim: Double signal, exiting\n" -#~ msgstr "Vim: 双重信号,退出中\n" +#: ../../../runtime/optwin.vim:1089 +msgid "string used to put the output of \":make\" in the error file" +msgstr "用于将 \":make\" 的输出放在错误文件中的字符串" -#~ msgid "Vim: Caught deadly signal %s\n" -#~ msgstr "Vim: 拦截到致命信号(deadly signal) %s\n" +#: ../../../runtime/optwin.vim:1091 +msgid "name of the errorfile for the 'makeprg' command" +msgstr "'makeprg' 命令的错误文件的名称" -#~ msgid "Vim: Caught deadly signal\n" -#~ msgstr "Vim: 拦截到致命信号(deadly signal)\n" +#: ../../../runtime/optwin.vim:1093 +msgid "program used for the \":grep\" command" +msgstr "用于 \":grep\" 命令的程序" -#~ msgid "Opening the X display took %<PRId64> msec" -#~ msgstr "打开 X display 用时 %<PRId64> 秒" +#: ../../../runtime/optwin.vim:1096 +msgid "list of formats for output of 'grepprg'" +msgstr "'grepprg' 的输出格式列表" + +#: ../../../runtime/optwin.vim:1098 +msgid "encoding of the \":make\" and \":grep\" output" +msgstr "\":make\" 和 \":grep\" 输出的编码" + +#: ../../../runtime/optwin.vim:1105 +msgid "system specific" +msgstr "系统特定" + +#: ../../../runtime/optwin.vim:1106 +msgid "use forward slashes in file names; for Unix-like shells" +msgstr "在文件名中使用正斜杠;用于类 Unix shell" + +#: ../../../runtime/optwin.vim:1108 +msgid "specifies slash/backslash used for completion" +msgstr "指定补全时使用的斜杠/反斜杠" + +#: ../../../runtime/optwin.vim:1113 +msgid "language specific" +msgstr "语言特定" + +#: ../../../runtime/optwin.vim:1114 +msgid "specifies the characters in a file name" +msgstr "指定文件名中的字符" + +#: ../../../runtime/optwin.vim:1116 +msgid "specifies the characters in an identifier" +msgstr "指定标识符中的字符" + +#: ../../../runtime/optwin.vim:1118 +msgid "specifies the characters in a keyword" +msgstr "指定关键字中的字符" + +#: ../../../runtime/optwin.vim:1121 +msgid "specifies printable characters" +msgstr "指定可打印字符" + +#: ../../../runtime/optwin.vim:1124 +msgid "specifies escape characters in a string" +msgstr "指定字符串中的转义字符" + +#: ../../../runtime/optwin.vim:1129 +msgid "display the buffer right-to-left" +msgstr "从右到左显示缓冲区" + +#: ../../../runtime/optwin.vim:1132 +msgid "when to edit the command-line right-to-left" +msgstr "何时从右到左编辑命令行" + +#: ../../../runtime/optwin.vim:1135 +msgid "insert characters backwards" +msgstr "倒序插入字符" + +#: ../../../runtime/optwin.vim:1137 +msgid "allow CTRL-_ in Insert and Command-line mode to toggle 'revins'" +msgstr "在插入和命令行模式下允许 CTRL-_ 切换 'revins'" + +#: ../../../runtime/optwin.vim:1139 +msgid "the ASCII code for the first letter of the Hebrew alphabet" +msgstr "希伯来字母表第一个字母的 ASCII 码" + +#: ../../../runtime/optwin.vim:1141 +msgid "use Hebrew keyboard mapping" +msgstr "使用希伯来语键盘映射" + +#: ../../../runtime/optwin.vim:1143 +msgid "use phonetic Hebrew keyboard mapping" +msgstr "使用希伯来语的语音键盘映射" + +#: ../../../runtime/optwin.vim:1147 +msgid "prepare for editing Arabic text" +msgstr "准备编辑阿拉伯语文本" + +#: ../../../runtime/optwin.vim:1150 +msgid "perform shaping of Arabic characters" +msgstr "阿拉伯语的字形重整" + +#: ../../../runtime/optwin.vim:1152 +msgid "terminal will perform bidi handling" +msgstr "终端支持双向文本" + +#: ../../../runtime/optwin.vim:1156 +msgid "name of a keyboard mapping" +msgstr "键盘映射名称" + +#: ../../../runtime/optwin.vim:1160 +msgid "list of characters that are translated in Normal mode" +msgstr "在普通模式下转换的字符列表" + +#: ../../../runtime/optwin.vim:1162 +msgid "apply 'langmap' to mapped characters" +msgstr "对映射的字符应用 'langmap'" + +#: ../../../runtime/optwin.vim:1166 +msgid "when set never use IM; overrules following IM options" +msgstr "设置时总不使用输入法;覆盖以下输入法选项" + +#: ../../../runtime/optwin.vim:1169 +msgid "in Insert mode: 1: use :lmap; 2: use IM; 0: neither" +msgstr "插入模式:1:使用 :lamp;2:使用输入法;0:都不用" + +#: ../../../runtime/optwin.vim:1172 +msgid "entering a search pattern: 1: use :lmap; 2: use IM; 0: neither" +msgstr "输入搜索模式时:1:使用 :lamp;2:使用输入法;0:都不用" + +#: ../../../runtime/optwin.vim:1176 +msgid "when set always use IM when starting to edit a command line" +msgstr "如果设置,开始编辑命令行时总是使用输入法" + +#: ../../../runtime/optwin.vim:1178 +msgid "function to obtain IME status" +msgstr "获取输入法状态的函数" + +#: ../../../runtime/optwin.vim:1180 +msgid "function to enable/disable IME" +msgstr "启用/禁用输入法的函数" + +#: ../../../runtime/optwin.vim:1185 +msgid "multi-byte characters" +msgstr "多字节字符" + +#: ../../../runtime/optwin.vim:1186 +msgid "character encoding used in Nvim: \"utf-8\"" +msgstr "Nvim 中使用的字符编码:\"utf-8\"" + +#: ../../../runtime/optwin.vim:1188 +msgid "character encoding for the current file" +msgstr "当前文件的字符编码" + +#: ../../../runtime/optwin.vim:1191 +msgid "automatically detected character encodings" +msgstr "自动检测字符编码" + +#: ../../../runtime/optwin.vim:1193 +msgid "expression used for character encoding conversion" +msgstr "用于字符编码转换的表达式" + +#: ../../../runtime/optwin.vim:1195 +msgid "delete combining (composing) characters on their own" +msgstr "删除组合字符本身" + +#: ../../../runtime/optwin.vim:1197 +msgid "maximum number of combining (composing) characters displayed" +msgstr "显示的最大字符组合数" + +#: ../../../runtime/optwin.vim:1200 +msgid "key that activates the X input method" +msgstr "激活 X 输入方法的键" + +#: ../../../runtime/optwin.vim:1203 +msgid "width of ambiguous width characters" +msgstr "宽度有歧义字符的宽度" + +#: ../../../runtime/optwin.vim:1205 +msgid "emoji characters are full width" +msgstr "表情字符视作全宽" + +#: ../../../runtime/optwin.vim:1209 +msgid "various" +msgstr "杂项" + +#: ../../../runtime/optwin.vim:1210 +msgid "" +"when to use virtual editing: \"block\", \"insert\", \"all\"\n" +"and/or \"onemore\"" +msgstr "何时使用虚拟编辑:\"block\", \"insert\", \"all\" 和/或 \"onemore\"" + +#: ../../../runtime/optwin.vim:1212 +msgid "list of autocommand events which are to be ignored" +msgstr "要忽略的自动命令事件列表" + +#: ../../../runtime/optwin.vim:1214 +msgid "load plugin scripts when starting up" +msgstr "启动时加载插件脚本" + +#: ../../../runtime/optwin.vim:1216 +msgid "enable reading .vimrc/.exrc/.gvimrc in the current directory" +msgstr "启用读取在当前目录下的 .vimrc/.exrc/.gvimrc" + +#: ../../../runtime/optwin.vim:1218 +msgid "safer working with script files in the current directory" +msgstr "在当前目录下使用脚本文件时更安全" + +#: ../../../runtime/optwin.vim:1220 +msgid "use the 'g' flag for \":substitute\"" +msgstr "打开 \":substitute\" 的 'g' 标志" + +#: ../../../runtime/optwin.vim:1223 +msgid "allow reading/writing devices" +msgstr "允许读写设备" + +#: ../../../runtime/optwin.vim:1227 +msgid "maximum depth of function calls" +msgstr "函数调用的最大深度" + +#: ../../../runtime/optwin.vim:1231 +msgid "list of words that specifies what to put in a session file" +msgstr "指定放入会话文件的内容的单词列表" + +#: ../../../runtime/optwin.vim:1233 +msgid "list of words that specifies what to save for :mkview" +msgstr "指定 :mkview 保存的内容的单词列表" + +#: ../../../runtime/optwin.vim:1235 +msgid "directory where to store files with :mkview" +msgstr ":mkview 存放文件的目录" + +#: ../../../runtime/optwin.vim:1239 +msgid "list that specifies what to write in the ShaDa file" +msgstr "指定在 ShaDa 文件中写入的内容的列表" + +#: ../../../runtime/optwin.vim:1243 +msgid "what happens with a buffer when it's no longer in a window" +msgstr "当缓冲区不再位于窗口中时,会发生什么" + +#: ../../../runtime/optwin.vim:1246 +msgid "empty, \"nofile\", \"nowrite\", \"quickfix\", etc.: type of buffer" +msgstr "空, \"nofile\", \"nowrite\", \"quickfix\" 等; 缓冲区类型" + +#: ../../../runtime/optwin.vim:1250 +msgid "whether the buffer shows up in the buffer list" +msgstr "缓冲区是否显示在缓冲区列表中" + +#: ../../../runtime/optwin.vim:1253 +msgid "set to \"msg\" to see all error messages" +msgstr "设置为 \"msg\" 以查看所有错误消息" + +#: ../../../runtime/optwin.vim:1255 +msgid "whether to show the signcolumn" +msgstr "是否显示标号列" + +#: ../../../runtime/optwin.vim:1282 +msgid "name of the MzScheme dynamic library" +msgstr "MzScheme 动态库的名字" + +#: ../../../runtime/optwin.vim:1284 +msgid "name of the MzScheme GC dynamic library" +msgstr "MzScheme GC 动态库的名字" + +#: ../../../runtime/optwin.vim:1288 +msgid "whether to use Python 2 or 3" +msgstr "是否使用 Python 2 和 3" + +#~ msgid "\t\t\t (Unimplemented)\n" +#~ msgstr "\t\t\t (尚未实现)\n" #~ msgid "" #~ "\n" -#~ "Vim: Got X error\n" +#~ "\n" +#~ "Arguments:\n" #~ msgstr "" #~ "\n" -#~ "Vim: X 错误\n" - -#~ msgid "Testing the X display failed" -#~ msgstr "测试 X display 失败" - -#~ msgid "Opening the X display timed out" -#~ msgstr "打开 X display 超时" +#~ "\n" +#~ "参数:\n" #~ msgid "" #~ "\n" -#~ "Cannot execute shell sh\n" +#~ " [not usable with this version of Vim]" #~ msgstr "" #~ "\n" -#~ "无法执行 shell sh\n" +#~ " [不能在该版本的 Vim 上使用]" #~ msgid "" #~ "\n" -#~ "Cannot create pipes\n" +#~ " a: Find assignments to this symbol\n" +#~ " c: Find functions calling this function\n" +#~ " d: Find functions called by this function\n" +#~ " e: Find this egrep pattern\n" +#~ " f: Find this file\n" +#~ " g: Find this definition\n" +#~ " i: Find files #including this file\n" +#~ " s: Find this C symbol\n" +#~ " t: Find this text string\n" #~ msgstr "" #~ "\n" -#~ "无法建立管道\n" +#~ " a: 搜索对此符号的赋值\n" +#~ " c: 搜索调用此函数的函数\n" +#~ " d: 搜索此函数调用的函数\n" +#~ " e: 搜索此 egrep 模式\n" +#~ " f: 搜索此文件\n" +#~ " g: 搜索此定义\n" +#~ " i: 搜索包含此文件的文件\n" +#~ " s: 搜索此 C 符号\n" +#~ " t: 搜索此文本字符串\n" #~ msgid "" #~ "\n" -#~ "Cannot fork\n" +#~ " # line" #~ msgstr "" #~ "\n" -#~ "无法 fork\n" +#~ " # 行 " #~ msgid "" #~ "\n" -#~ "Command terminated\n" +#~ " or:" #~ msgstr "" #~ "\n" -#~ "命令已结束\n" +#~ " 或:" -#~ msgid "XSMP lost ICE connection" -#~ msgstr "XSMP 丢失了到 ICE 的连接" - -#~ msgid "Opening the X display failed" -#~ msgstr "打开 X display 失败" +# do not translate to avoid writing Chinese in files +#, fuzzy, c-format +#~ msgid "" +#~ "\n" +#~ "# %s History (newest to oldest):\n" +#~ msgstr "" +#~ "\n" +#~ "# %s 历史记录 (从新到旧):\n" -#~ msgid "XSMP handling save-yourself request" -#~ msgstr "XSMP 处理 save-yourself 请求" +#~ msgid "" +#~ "\n" +#~ "# Buffer list:\n" +#~ msgstr "" +#~ "\n" +#~ "# 缓冲区列表:\n" -#~ msgid "XSMP opening connection" -#~ msgstr "XSMP 打开连接" +#~ msgid "" +#~ "\n" +#~ "# File marks:\n" +#~ msgstr "" +#~ "\n" +#~ "# 文件标记:\n" -#~ msgid "XSMP ICE connection watch failed" -#~ msgstr "XSMP ICE 连接监视失败" +#~ msgid "" +#~ "\n" +#~ "# History of marks within files (newest to oldest):\n" +#~ msgstr "" +#~ "\n" +#~ "# 文件内的标记历史记录 (从新到旧):\n" -#~ msgid "XSMP SmcOpenConnection failed: %s" -#~ msgstr "XSMP SmcOpenConnection 调用失败: %s" +#~ msgid "" +#~ "\n" +#~ "# Jumplist (newest first):\n" +#~ msgstr "" +#~ "\n" +#~ "# 跳转列表 (从新到旧):\n" -#~ msgid "At line" -#~ msgstr "在行号 " +#, c-format +#~ msgid "" +#~ "\n" +#~ "# Last %sSearch Pattern:\n" +#~ "~" +#~ msgstr "" +#~ "\n" +#~ "# 最后 %s搜索模式:\n" +#~ "~" -#~ msgid "Could not load vim32.dll!" -#~ msgstr "无法加载 vim32.dll!" +# do not translate to avoid writing Chinese in files +#, fuzzy +#~ msgid "" +#~ "\n" +#~ "# Last Substitute String:\n" +#~ "$" +#~ msgstr "" +#~ "\n" +#~ "# 最近的替换字符串:\n" +#~ "$" -#~ msgid "VIM Error" -#~ msgstr "VIM 错误" +#~ msgid "" +#~ "\n" +#~ "# Registers:\n" +#~ msgstr "" +#~ "\n" +#~ "# 寄存器:\n" -#~ msgid "Could not fix up function pointers to the DLL!" -#~ msgstr "无法修正到 DLL 的函数指针!" +#~ msgid "" +#~ "\n" +#~ "# global variables:\n" +#~ msgstr "" +#~ "\n" +#~ "# 全局变量:\n" -#~ msgid "shell returned %d" -#~ msgstr "Shell 返回 %d" +#~ msgid "" +#~ "\n" +#~ "--- Registers ---" +#~ msgstr "" +#~ "\n" +#~ "--- 寄存器 ---" -#~ msgid "Vim: Caught %s event\n" -#~ msgstr "Vim: 拦截到 %s 事件\n" +#~ msgid "" +#~ "\n" +#~ "--- Terminal codes ---" +#~ msgstr "" +#~ "\n" +#~ "--- 终端编码 ---" -#~ msgid "close" -#~ msgstr "关闭" +#~ msgid "" +#~ "\n" +#~ "--- Terminal keys ---" +#~ msgstr "" +#~ "\n" +#~ "--- 终端按键 ---" -#~ msgid "logoff" -#~ msgstr "注消" +#~ msgid "" +#~ "\n" +#~ "16-bit MS-DOS version" +#~ msgstr "" +#~ "\n" +#~ "16 位 MS-DOS 版本" -#~ msgid "shutdown" -#~ msgstr "关机" +#~ msgid "" +#~ "\n" +#~ "32-bit MS-DOS version" +#~ msgstr "" +#~ "\n" +#~ "32 位 MS-DOS 版本" -#~ msgid "E371: Command not found" -#~ msgstr "E371: 找不到命令" +#~ msgid "" +#~ "\n" +#~ "Arguments recognised by gvim (Athena version):\n" +#~ msgstr "" +#~ "\n" +#~ "gvim (Athena 版本) 可识别的参数:\n" #~ msgid "" -#~ "VIMRUN.EXE not found in your $PATH.\n" -#~ "External commands will not pause after completion.\n" -#~ "See :help win32-vimrun for more information." +#~ "\n" +#~ "Arguments recognised by gvim (GTK+ version):\n" #~ msgstr "" -#~ "在你的 $PATH 中找不到 VIMRUN.EXE。\n" -#~ "外部命令执行完毕后将不会暂停。\n" -#~ "进一步说明请见 :help win32-vimrun" +#~ "\n" +#~ "gvim (GTK+ 版本) 可识别的参数:\n" -#~ msgid "Vim Warning" -#~ msgstr "Vim 警告" +#~ msgid "" +#~ "\n" +#~ "Arguments recognised by gvim (Motif version):\n" +#~ msgstr "" +#~ "\n" +#~ "gvim (Motif 版本) 可识别的参数:\n" -#~ msgid "Conversion in %s not supported" -#~ msgstr "不支持 %s 中的转换" +#~ msgid "" +#~ "\n" +#~ "Arguments recognised by gvim (RISC OS version):\n" +#~ msgstr "" +#~ "\n" +#~ "gvim (RISC OS 版本) 可识别的参数:\n" -#~ msgid "E396: containedin argument not accepted here" -#~ msgstr "E396: 使用了不正确的参数" +#~ msgid "" +#~ "\n" +#~ "Arguments recognised by gvim (neXtaw version):\n" +#~ msgstr "" +#~ "\n" +#~ "gvim (neXtaw 版本) 可识别的参数:\n" -#~ msgid "E430: Tag file path truncated for %s\n" -#~ msgstr "E430: Tag 文件路径被截断为 %s\n" +#~ msgid "" +#~ "\n" +#~ "Big version " +#~ msgstr "" +#~ "\n" +#~ "大型版本 " -#~ msgid "new shell started\n" -#~ msgstr "启动新 shell\n" +#~ msgid "" +#~ "\n" +#~ "Cannot create pipes\n" +#~ msgstr "" +#~ "\n" +#~ "无法建立管道\n" -#~ msgid "No undo possible; continue anyway" -#~ msgstr "无法撤销;仍然继续" +#~ msgid "" +#~ "\n" +#~ "Cannot execute shell " +#~ msgstr "" +#~ "\n" +#~ "无法执行 shell" -#~ msgid "number changes time" -#~ msgstr " 编号 改变 时间" +#~ msgid "" +#~ "\n" +#~ "Cannot execute shell sh\n" +#~ msgstr "" +#~ "\n" +#~ "无法执行 shell sh\n" #~ msgid "" #~ "\n" -#~ "MS-Windows 16/32-bit GUI version" +#~ "Cannot fork\n" #~ msgstr "" #~ "\n" -#~ "MS-Windows 16/32 位图形界面版本" +#~ "无法 fork\n" #~ msgid "" #~ "\n" -#~ "MS-Windows 32-bit GUI version" +#~ "Command terminated\n" #~ msgstr "" #~ "\n" -#~ "MS-Windows 32 位图形界面版本" +#~ "命令已结束\n" -#~ msgid " in Win32s mode" -#~ msgstr " Win32s 模式" +#, fuzzy +#~ msgid "" +#~ "\n" +#~ "Extra patches: " +#~ msgstr "外部符合:\n" -#~ msgid " with OLE support" -#~ msgstr " 带 OLE 支持" +#~ msgid "" +#~ "\n" +#~ "Huge version " +#~ msgstr "" +#~ "\n" +#~ "巨型版本 " #~ msgid "" #~ "\n" -#~ "MS-Windows 32-bit console version" +#~ "Included patches: " #~ msgstr "" #~ "\n" -#~ "MS-Windows 32 位控制台版本" +#~ "包含补丁: " #~ msgid "" #~ "\n" @@ -7634,17 +9395,24 @@ msgstr "E446: 光标处没有文件名" #~ msgid "" #~ "\n" -#~ "32-bit MS-DOS version" +#~ "MS-Windows 16/32-bit GUI version" #~ msgstr "" #~ "\n" -#~ "32 位 MS-DOS 版本" +#~ "MS-Windows 16/32 位图形界面版本" #~ msgid "" #~ "\n" -#~ "16-bit MS-DOS version" +#~ "MS-Windows 32-bit GUI version" #~ msgstr "" #~ "\n" -#~ "16 位 MS-DOS 版本" +#~ "MS-Windows 32 位图形界面版本" + +#~ msgid "" +#~ "\n" +#~ "MS-Windows 32-bit console version" +#~ msgstr "" +#~ "\n" +#~ "MS-Windows 32 位控制台版本" #~ msgid "" #~ "\n" @@ -7669,6 +9437,13 @@ msgstr "E446: 光标处没有文件名" #~ msgid "" #~ "\n" +#~ "Normal version " +#~ msgstr "" +#~ "\n" +#~ "正常版本 " + +#~ msgid "" +#~ "\n" #~ "RISC OS version" #~ msgstr "" #~ "\n" @@ -7676,73 +9451,463 @@ msgstr "E446: 光标处没有文件名" #~ msgid "" #~ "\n" -#~ "Big version " +#~ "Small version " #~ msgstr "" #~ "\n" -#~ "大型版本 " +#~ "小型版本 " #~ msgid "" #~ "\n" -#~ "Normal version " +#~ "Tiny version " #~ msgstr "" #~ "\n" -#~ "正常版本 " +#~ "微型版本 " #~ msgid "" #~ "\n" -#~ "Small version " +#~ "Vim: Got X error\n" #~ msgstr "" #~ "\n" -#~ "小型版本 " +#~ "Vim: X 错误\n" #~ msgid "" #~ "\n" -#~ "Tiny version " +#~ "[bytes] total alloc-freed %<PRIu64>-%<PRIu64>, in use %<PRIu64>, peak use " +#~ "%<PRIu64>\n" #~ msgstr "" #~ "\n" -#~ "微型版本 " +#~ "[字节] 总共 alloc-free %<PRIu64>-%<PRIu64>,使用中 %<PRIu64>,高峰使用 " +#~ "%<PRIu64>\n" -#~ msgid "with GTK2-GNOME GUI." -#~ msgstr "带 GTK2-GNOME 图形界面。" +#, fuzzy +#~ msgid " for Vim defaults " +#~ msgstr " # pid 数据库名称 prepend path\n" -#~ msgid "with GTK-GNOME GUI." -#~ msgstr "带 GTK-GNOME 图形界面。" +#~ msgid " user exrc file: \"" +#~ msgstr " 用户 exrc 文件: \"" -#~ msgid "with GTK2 GUI." -#~ msgstr "带 GTK2 图形界面。" +#~ msgid " user vimrc file: \"" +#~ msgstr " 用户 vimrc 文件: \"" -#~ msgid "with GTK GUI." -#~ msgstr "带 GTK 图形界面。" +#~ msgid " system menu file: \"" +#~ msgstr " 系统菜单文件: \"" -#~ msgid "with X11-Motif GUI." -#~ msgstr "带 X11-Motif 图形界面。" +#~ msgid " user gvimrc file: \"" +#~ msgstr " 用户 gvimrc 文件: \"" -#~ msgid "with X11-neXtaw GUI." -#~ msgstr "带 X11-neXtaw 图形界面。" +#~ msgid " 2nd user exrc file: \"" +#~ msgstr " 第二用户 exrc 文件: \"" -#~ msgid "with X11-Athena GUI." -#~ msgstr "带 X11-Athena 图形界面。" +#~ msgid " DEBUG BUILD" +#~ msgstr " 调试版本" -#~ msgid "with Photon GUI." -#~ msgstr "带 Photon 图形界面。" +#~ msgid " Features included (+) or not (-):\n" +#~ msgstr " 可使用(+)与不可使用(-)的功能:\n" -#~ msgid "with GUI." -#~ msgstr "带图形界面。" +#, fuzzy +#~ msgid " Quit, or continue with caution.\n" +#~ msgstr " 退出,或小心地继续。\n" -#~ msgid "with Carbon GUI." -#~ msgstr "带 Carbon 图形界面。" +#~ msgid " system gvimrc file: \"" +#~ msgstr " 系统 gvimrc 文件: \"" -#~ msgid "with Cocoa GUI." -#~ msgstr "带 Cocoa 图形界面。" +#~ msgid " # pid database name prepend path\n" +#~ msgstr " # pid 数据库名 prepend path\n" -#~ msgid "with (classic) GUI." -#~ msgstr "带(传统)图形界面。" +#~ msgid " (NOT FOUND)" +#~ msgstr " (找不到)" -#~ msgid " system gvimrc file: \"" -#~ msgstr " 系统 gvimrc 文件: \"" +#~ msgid " (RET/BS: line, SPACE/b: page, d/u: half page, q: quit)" +#~ msgstr " (RET/BS: 向下/向上一行, 空格/b: 一页, d/u: 半页, q: 退出)" -#~ msgid " user gvimrc file: \"" -#~ msgstr " 用户 gvimrc 文件: \"" +#~ msgid " (RET: line, SPACE: page, d: half page, q: quit)" +#~ msgstr " (RET: 向下一行, 空白键: 一页, d: 半页, q: 退出)" + +#~ msgid " (lang)" +#~ msgstr " (语言)" + +#~ msgid " 2nd user vimrc file: \"" +#~ msgstr " 第二用户 vimrc 文件: \"" + +#~ msgid " 3rd user vimrc file: \"" +#~ msgstr " 第三用户 vimrc 文件: \"" + +#~ msgid " BLOCK" +#~ msgstr " 块" + +#~ msgid " LINE" +#~ msgstr " 行" + +#~ msgid " in Win32s mode" +#~ msgstr " Win32s 模式" + +#~ msgid " on 1 line" +#~ msgstr "共 1 行" + +#~ msgid " returned\n" +#~ msgstr " 已返回\n" + +#~ msgid " vim [arguments] " +#~ msgstr " vim [参数] " + +#~ msgid " with OLE support" +#~ msgstr " 带 OLE 支持" + +#~ msgid "\"\n" +#~ msgstr "\"\n" + +# do not translate to avoid writing Chinese in files +#, fuzzy, c-format +#~ msgid "# This viminfo file was generated by Vim %s.\n" +#~ msgstr "# 这个 viminfo 文件是由 Vim %s 生成的。\n" + +# do not translate to avoid writing Chinese in files +#, fuzzy +#~ msgid "# Value of 'encoding' when this file was written\n" +#~ msgstr "# 'encoding' 在此文件建立时的值\n" + +# do not translate to avoid writing Chinese in files +#, fuzzy +#~ msgid "" +#~ "# You may edit it if you're careful!\n" +#~ "\n" +#~ msgstr "" +#~ "# 如果要自行修改请特别小心!\n" +#~ "\n" + +#, fuzzy, c-format +#~ msgid "%-5s: %s%*s (Usage: %s)" +#~ msgstr "%-5s: %-30s (用法: %s)" + +#~ msgid "%2d %-5ld %-34s <none>\n" +#~ msgstr "%2d %-5ld %-34s <无>\n" + +#~ msgid "%<%f%h%m%=Page %N" +#~ msgstr "%<%f%h%m%=页 %N" + +#, c-format +#~ msgid "%<PRId64> characters" +#~ msgstr "%<PRId64> 个字符" + +#, c-format +#~ msgid "%<PRId64> fewer lines" +#~ msgstr "少了 %<PRId64> 行" + +#, c-format +#~ msgid "%<PRId64> lines %sed 1 time" +#~ msgstr "%<PRId64> 行 %s 了 1 次" + +#, c-format +#~ msgid "%<PRId64> lines moved" +#~ msgstr "移动了 %<PRId64> 行" + +#, c-format +#~ msgid "%<PRId64> more lines" +#~ msgstr "多了 %<PRId64> 行" + +#, c-format +#~ msgid "%d files to edit\n" +#~ msgstr "还有 %d 个文件等待编辑\n" + +#~ msgid "%d of %d edited" +#~ msgstr "%d 中 %d 已编辑" + +# bad to translate +#, c-format +#~ msgid "%sviminfo: %s in line: " +#~ msgstr "%sviminfo: %s 位于行: " + +#~ msgid "&Cancel" +#~ msgstr "取消(&C)" + +#~ msgid "&Dismiss" +#~ msgstr "取消(&D)" + +#~ msgid "&Filter" +#~ msgstr "过滤(&F)" + +#~ msgid "&Help" +#~ msgstr "帮助(&H)" + +#~ msgid "&OK" +#~ msgstr "确定(&O)" + +#~ msgid "" +#~ "&OK\n" +#~ "&Cancel" +#~ msgstr "" +#~ "确定(&O)\n" +#~ "取消(&C)" + +#~ msgid "" +#~ "&OK\n" +#~ "&Load File" +#~ msgstr "" +#~ "确定(&O)\n" +#~ "加载文件(&L)" + +#~ msgid "&Replace" +#~ msgstr "替换(&R)" + +#~ msgid "&Undo" +#~ msgstr "撤销(&U)" + +#~ msgid "' not known. Available builtin terminals are:" +#~ msgstr "' 未知。可用的内建终端有:" + +#, c-format +#~ msgid "(NFA) COULD NOT OPEN %s !" +#~ msgstr "(NFA) 不能打开 %s !" + +#~ msgid "+\t\t\tStart at end of file" +#~ msgstr "+\t\t\t启动后跳到文件末尾" + +#~ msgid "+<lnum>\t\tStart at line <lnum>" +#~ msgstr "+<lnum>\t\t启动后跳到第 <lnum> 行" + +#~ msgid "+reverse\t\tDon't use reverse video (also: +rv)" +#~ msgstr "+reverse\t\t不使用反显 (也可用 +rv)" + +#~ msgid "--columns <number>\tInitial width of window in columns" +#~ msgstr "--columns <number>\t窗口初始宽度" + +#~ msgid "--help\t\tShow Gnome arguments" +#~ msgstr "--help\t\t显示 Gnome 相关参数" + +#~ msgid "--literal\t\tDon't expand wildcards" +#~ msgstr "--literal\t\t不扩展通配符" + +#~ msgid "--remote <files>\tEdit <files> in a Vim server if possible" +#~ msgstr "--remote <files>\t如有可能,在 Vim 服务器上编辑文件 <files>" + +#~ msgid "" +#~ "--remote-expr <expr>\tEvaluate <expr> in a Vim server and print result" +#~ msgstr "--remote-expr <expr>\t在 Vim 服务器上求 <expr> 的值并打印结果" + +#~ msgid "--remote-send <keys>\tSend <keys> to a Vim server and exit" +#~ msgstr "--remote-send <keys>\t送出 <keys> 到 Vim 服务器并退出" + +#~ msgid "--remote-silent <files> Same, don't complain if there is no server" +#~ msgstr "--remote-silent <files> 同上,找不到服务器时不抱怨" + +#~ msgid "--remote-tab <files> As --remote but open tab page for each file" +#~ msgstr "--remote-tab <files> 同 --remote 但对每个文件打开一个标签页" + +#~ msgid "" +#~ "--remote-wait <files> As --remote but wait for files to have been edited" +#~ msgstr "--remote-wait <files> 同 --remote 但会等待文件完成编辑" + +#~ msgid "" +#~ "--remote-wait-silent <files> Same, don't complain if there is no server" +#~ msgstr "--remote-wait-silent <files> 同上,找不到服务器时不抱怨" + +#~ msgid "--role <role>\tSet a unique role to identify the main window" +#~ msgstr "--role <role>\t设置用于区分主窗口的窗口角色名" + +#~ msgid "--rows <number>\tInitial height of window in rows" +#~ msgstr "--rows <number>\t窗口初始高度" + +#~ msgid "--serverlist\t\tList available Vim server names and exit" +#~ msgstr "--serverlist\t\t列出可用的 Vim 服务器名称并退出" + +#~ msgid "--servername <name>\tSend to/become the Vim server <name>" +#~ msgstr "--servername <name>\t发送到或成为 Vim 服务器 <name>" + +#~ msgid "--socketid <xid>\tOpen Vim inside another GTK widget" +#~ msgstr "--socketid <xid>\t在另一个 GTK 部件中打开 Vim" + +#~ msgid "-A\t\t\tstart in Arabic mode" +#~ msgstr "-A\t\t\t以 Arabic 模式启动" + +#~ msgid "-C\t\t\tCompatible with Vi: 'compatible'" +#~ msgstr "-C\t\t\t兼容传统的 Vi: 'compatible'" + +#~ msgid "-D\t\t\tDebugging mode" +#~ msgstr "-D\t\t\t调试模式" + +#~ msgid "-F\t\t\tStart in Farsi mode" +#~ msgstr "-F\t\t\t以 Farsi 模式启动" + +#~ msgid "-H\t\t\tStart in Hebrew mode" +#~ msgstr "-H\t\t\t以 Hebrew 模式启动" + +#~ msgid "-L\t\t\tSame as -r" +#~ msgstr "-L\t\t\t同 -r" + +#~ msgid "-N\t\t\tNot fully Vi compatible: 'nocompatible'" +#~ msgstr "-N\t\t\t不完全兼容传统的 Vi: 'nocompatible'" + +#~ msgid "-O[N]\t\tLike -o but split vertically" +#~ msgstr "-O[N]\t\t同 -o 但垂直分割" + +#~ msgid "-P <parent title>\tOpen Vim inside parent application" +#~ msgstr "-P <parent title>\t在父应用程序中打开 Vim" + +#~ msgid "-R\t\t\tReadonly mode (like \"view\")" +#~ msgstr "-R\t\t\t只读模式 (同 \"view\")" + +#~ msgid "-T <terminal>\tSet terminal type to <terminal>" +#~ msgstr "-T <terminal>\t设定终端类型为 <terminal>" + +#~ msgid "-U <gvimrc>\t\tUse <gvimrc> instead of any .gvimrc" +#~ msgstr "-U <gvimrc>\t\t使用 <gvimrc> 替代任何 .gvimrc" + +#~ msgid "-V[N]\t\tVerbose level" +#~ msgstr "-V[N]\t\tVerbose 等级" + +#~ msgid "-V[N][fname]\t\tBe verbose [level N] [log messages to fname]" +#~ msgstr "-V[N][fname]\t\t详细 [level N] [log messages to fname]" + +#~ msgid "-W <scriptout>\tWrite all typed commands to file <scriptout>" +#~ msgstr "-W <scriptout>\t将所有输入的命令写入到文件 <scriptout>" + +#~ msgid "-X\t\t\tDo not connect to X server" +#~ msgstr "-X\t\t\t不连接到 X Server" + +#~ msgid "-b\t\t\tBinary mode" +#~ msgstr "-b\t\t\t二进制模式" + +#~ msgid "-background <color>\tUse <color> for the background (also: -bg)" +#~ msgstr "-background <color>\t使用 <color> 作为背景色 (也可用 -bg)" + +#~ msgid "-boldfont <font>\tUse <font> for bold text" +#~ msgstr "-boldfont <font>\t使用 <font> 作为粗体字体" + +#~ msgid "-borderwidth <width>\tUse a border width of <width> (also: -bw)" +#~ msgstr "-borderwidth <width>\t设定边框宽度为 <width> (也可用 -bw)" + +#~ msgid "-d\t\t\tDiff mode (like \"vimdiff\")" +#~ msgstr "-d\t\t\tDiff 模式 (同 \"vimdiff\")" + +#~ msgid "-dev <device>\t\tUse <device> for I/O" +#~ msgstr "-dev <device>\t\t使用 <device> 进行输入输出" + +#~ msgid "-display <display>\tConnect vim to this particular X-server" +#~ msgstr "-display <display>\t将 vim 与指定的 X-server 连接" + +#~ msgid "-display <display>\tRun vim on <display>" +#~ msgstr "-display <display>\t在 <display> 上运行 vim" + +#~ msgid "-display <display>\tRun vim on <display> (also: --display)" +#~ msgstr "-display <display>\t在 <display> 上运行 vim (也可用 --display)" + +#~ msgid "-e\t\t\tEx mode (like \"ex\")" +#~ msgstr "-e\t\t\tEx 模式 (同 \"ex\")" + +#~ msgid "-f\t\t\tDon't use newcli to open window" +#~ msgstr "-f\t\t\t不使用 newcli 来打开窗口" + +#~ msgid "-f or --nofork\tForeground: Don't fork when starting GUI" +#~ msgstr "-f 或 --nofork\t前台: 启动图形界面时不 fork" + +#~ msgid "-font <font>\t\tUse <font> for normal text (also: -fn)" +#~ msgstr "-font <font>\t使用 <font> 作为一般字体 (也可用 -fn)" + +#~ msgid "-foreground <color>\tUse <color> for normal text (also: -fg)" +#~ msgstr "-foreground <color>\t使用 <color> 作为一般文字颜色 (也可用 -fg)" + +#~ msgid "-g\t\t\tRun using GUI (like \"gvim\")" +#~ msgstr "-g\t\t\t使用图形界面 (同 \"gvim\")" + +#~ msgid "-geometry <geom>\tUse <geom> for initial geometry (also: -geom)" +#~ msgstr "-geometry <geom>\t使用 <geom> 作为初始位置 (也可用 -geom)" + +#~ msgid "-h or --help\tPrint Help (this message) and exit" +#~ msgstr "-h 或 --help\t打印帮助(本信息)并退出" + +#~ msgid "-i <viminfo>\t\tUse <viminfo> instead of .viminfo" +#~ msgstr "-i <viminfo>\t\t使用 <viminfo> 取代 .viminfo" + +#~ msgid "-iconic\t\tStart vim iconified" +#~ msgstr "-iconic\t\t启动后最小化" + +#~ msgid "-italicfont <font>\tUse <font> for italic text" +#~ msgstr "-italicfont <font>\t使用 <font> 作为斜体字体" + +#~ msgid "-menuheight <height>\tUse a menu bar height of <height> (also: -mh)" +#~ msgstr "-menuheight <height>\t设定菜单栏高度为 <height> (也可用 -mh)" + +#~ msgid "-name <name>\t\tUse resource as if vim was <name>" +#~ msgstr "-name <name>\t\t读取 Resource 时把 vim 视为 <name>" + +#~ msgid "-r\t\t\tList swap files and exit" +#~ msgstr "-r\t\t\t列出交换文件并退出" + +#~ msgid "-r (with file name)\tRecover crashed session" +#~ msgstr "-r (跟文件名)\t\t恢复崩溃的会话" + +#~ msgid "-register\t\tRegister this gvim for OLE" +#~ msgstr "-register\t\t注册此 gvim 到 OLE" + +#~ msgid "-reverse\t\tUse reverse video (also: -rv)" +#~ msgstr "-reverse\t\t使用反显 (也可用 -rv)" + +#~ msgid "-s\t\t\tSilent (batch) mode (only for \"ex\")" +#~ msgstr "-s\t\t\t安静(批处理)模式 (只能与 \"ex\" 一起使用)" + +#~ msgid "" +#~ "-scrollbarwidth <width> Use a scrollbar width of <width> (also: -sw)" +#~ msgstr "-scrollbarwidth <width> 设定滚动条宽度为 <width> (也可用 -sw)" + +#~ msgid "-u <vimrc>\t\tUse <vimrc> instead of any .vimrc" +#~ msgstr "-u <vimrc>\t\t使用 <vimrc> 替代任何 .vimrc" + +#~ msgid "-unregister\t\tUnregister gvim for OLE" +#~ msgstr "-unregister\t\t取消 OLE 中的 gvim 注册" + +#~ msgid "-v\t\t\tVi mode (like \"vi\")" +#~ msgstr "-v\t\t\tVi 模式 (同 \"vi\")" + +#~ msgid "-w <scriptout>\tAppend all typed commands to file <scriptout>" +#~ msgstr "-w <scriptout>\t将所有输入的命令追加到文件 <scriptout>" + +#~ msgid "-x\t\t\tEdit encrypted files" +#~ msgstr "-x\t\t\t编辑加密的文件" + +#~ msgid "-xrm <resource>\tSet the specified resource" +#~ msgstr "-xrm <resource>\t设定指定的资源" + +#~ msgid "-y\t\t\tEasy mode (like \"evim\", modeless)" +#~ msgstr "-y\t\t\t容易模式 (同 \"evim\",无模式)" + +#~ msgid "1 buffer deleted" +#~ msgstr "删除了 1 个缓冲区" + +#~ msgid "1 buffer unloaded" +#~ msgstr "释放了 1 个缓冲区" + +#~ msgid "1 buffer wiped out" +#~ msgstr "清除了 1 个缓冲区" + +#, c-format +#~ msgid "1 line %sed %d times" +#~ msgstr "1 行 %s 了 %d 次" + +#, c-format +#~ msgid "1 line %sed 1 time" +#~ msgstr "1 行 %s 了 1 次" + +#, c-format +#~ msgid "1 line --%d%%--" +#~ msgstr "1 行 --%d%%--" + +#~ msgid "1 line changed" +#~ msgstr "改变了 1 行" + +#~ msgid "1 line indented " +#~ msgstr "缩进了 1 行 " + +#~ msgid "1 line yanked" +#~ msgstr "复制了 1 行" + +#~ msgid "1 match" +#~ msgstr "1 个匹配," + +#~ msgid "1 more file to edit. Quit anyway?" +#~ msgstr "还有 1 个文件未编辑。确实要退出吗?" + +#~ msgid "1 substitution" +#~ msgstr "1 次替换," #~ msgid "2nd user gvimrc file: \"" #~ msgstr "第二用户 gvimrc 文件: \"" @@ -7750,182 +9915,1439 @@ msgstr "E446: 光标处没有文件名" #~ msgid "3rd user gvimrc file: \"" #~ msgstr "第三用户 gvimrc 文件: \"" -#~ msgid " system menu file: \"" -#~ msgstr " 系统菜单文件: \"" +#~ msgid ": Send expression failed.\n" +#~ msgstr ": 发送表达式失败。\n" + +#~ msgid ": Send failed.\n" +#~ msgstr ": 发送失败。\n" + +#~ msgid ": Send failed. Trying to execute locally\n" +#~ msgstr ": 发送失败。尝试本地执行\n" + +#~ msgid "<buffer object (deleted) at %8lX>" +#~ msgstr "<缓冲区对象(已删除): %8lX>" + +#~ msgid "<cannot open> " +#~ msgstr "<无法打开>" + +#~ msgid "<window %d>" +#~ msgstr "<窗口 %d>" + +#~ msgid "<window object (deleted) at %.8lX>" +#~ msgstr "<窗口对象(已删除): %.8lX>" + +#~ msgid "<window object (unknown) at %.8lX>" +#~ msgstr "<窗口对象(未知): %.8lX>" + +#~ msgid "" +#~ "???: Sorry, this command is disabled, the MzScheme library could not be " +#~ "loaded." +#~ msgstr "???: 抱歉,此命令不可用,无法加载 MzScheme 库" + +#~ msgid "ANCHOR_BUF_SIZE too small." +#~ msgstr "ANCHOR_BUF_SIZE 太小" + +#~ msgid "Add a new database" +#~ msgstr "添加一个新的数据库" + +#, c-format +#~ msgid "Added cscope database %s" +#~ msgstr "添加了 cscope 数据库 %s" + +#~ msgid "Affix flags ignored when PFXPOSTPONE used in %s line %d: %s" +#~ msgstr "%s 第 %d 行,使用 PFXPOSTPONE 时附加标志被忽略: %s" + +#~ msgid "All cscope databases reset" +#~ msgstr "所有 cscope 数据库已被重置" + +#~ msgid "Append File" +#~ msgstr "追加文件" + +#~ msgid "At line" +#~ msgstr "在行号 " + +#~ msgid "Binary tag search" +#~ msgstr "二进制查找(Binary search) 标签(Tags)" + +#~ msgid "Browse class" +#~ msgstr "浏览 class" + +#, c-format +#~ msgid "Calling shell to execute: \"%s\"" +#~ msgstr "调用 shell 执行: \"%s\"" + +#~ msgid "Cancel" +#~ msgstr "取消" + +#~ msgid "Cannot connect to Netbeans" +#~ msgstr "无法连接到 Netbeans" + +#~ msgid "Cannot connect to Netbeans #2" +#~ msgstr "无法连接到 Netbeans #2" + +#~ msgid "" +#~ "Cannot connect to SNiFF+. Check environment (sniffemacs must be found in " +#~ "$PATH).\n" +#~ msgstr "" +#~ "不能连接到 SNiFF+。请检查环境变量 ($PATH 里必需可以找到 sniffemacs)\n" + +#~ msgid "Cannot create " +#~ msgstr "不能创建 " + +#~ msgid "Cannot execute " +#~ msgstr "不能执行 " + +#~ msgid "Cannot open NIL:\n" +#~ msgstr "不能打开 NIL:\n" + +#~ msgid "Close tab" +#~ msgstr "关闭标签" + +#~ msgid "Compilation: " +#~ msgstr "编译方式: " #~ msgid "Compiler: " #~ msgstr "编译器: " -#~ msgid "menu Help->Orphans for information " -#~ msgstr "菜单 帮助->孤儿 查看说明 " +#~ msgid "Conversion in %s not supported" +#~ msgstr "不支持 %s 中的转换" -#~ msgid "Running modeless, typed text is inserted" -#~ msgstr "无模式运行,输入文字即插入" +#~ msgid "Could not fix up function pointers to the DLL!" +#~ msgstr "无法修正到 DLL 的函数指针!" -#~ msgid "menu Edit->Global Settings->Toggle Insert Mode " -#~ msgstr "菜单 编辑->全局设定->开/关插入模式 " +#~ msgid "Could not load vim32.dll!" +#~ msgstr "无法加载 vim32.dll!" -#, fuzzy -#~ msgid " for two modes " -#~ msgstr " # pid 数据库名称 prepend path\n" +#~ msgid "" +#~ "Could not open temporary log file for writing, displaying on stderr ... " +#~ msgstr "无法打开临时日志文件进行写入,显示在 stderr 中..." -#, fuzzy -#~ msgid " for Vim defaults " -#~ msgstr " # pid 数据库名称 prepend path\n" +#, c-format +#~ msgid "Cscope tag: %s" +#~ msgstr "Cscope tag: %s" -#~ msgid "WARNING: Windows 95/98/ME detected" -#~ msgstr "警告: 检测到 Windows 95/98/ME" +#~ msgid "Diff with Vim" +#~ msgstr "用 Vim 比较(diff)" -#~ msgid "type :help windows95<Enter> for info on this" -#~ msgstr "输入 :help windows95<Enter> 查看相关说明 " +#~ msgid "Direction" +#~ msgstr "方向" + +#~ msgid "Directories" +#~ msgstr "目录" + +#~ msgid "Directory\t*.nothing\n" +#~ msgstr "目录\t*.nothing\n" + +#~ msgid "Down" +#~ msgstr "向下" + +#~ msgid "E106: Unknown variable: \"%s\"" +#~ msgstr "E106: 未定义的变量: \"%s\"" + +#~ msgid "E130: Undefined function: %s" +#~ msgstr "E130: 函数 %s 尚未定义" + +#~ msgid "E136: viminfo: Too many errors, skipping rest of file" +#~ msgstr "E136: viminfo: 错误过多,忽略文件的剩余部分" + +#, c-format +#~ msgid "E138: Can't write viminfo file %s!" +#~ msgstr "E138: 无法写入 viminfo 文件 %s!" + +#~ msgid "E14: Invalid address" +#~ msgstr "E14: 无效的地址" + +#~ msgid "E172: Only one file name allowed" +#~ msgstr "E172: 只允许一个文件名" + +#~ msgid "E173: 1 more file to edit" +#~ msgstr "E173: 还有 1 个文件未编辑" + +#~ msgid "E188: Obtaining window position not implemented for this platform" +#~ msgstr "E188: 在此平台上不能获得窗口位置" + +#~ msgid "E195: Cannot open viminfo file for reading" +#~ msgstr "E195: 无法打开并读取 viminfo 文件" + +#~ msgid "E196: No digraphs in this version" +#~ msgstr "E196: 此版本无复合字符(digraph)" + +#~ msgid "E198: cmd_pchar beyond the command length" +#~ msgstr "E198: cmd_pchar 超过命令长度" + +#~ msgid "E229: Cannot start the GUI" +#~ msgstr "E229: 无法启动图形界面" + +#~ msgid "E230: Cannot read from \"%s\"" +#~ msgstr "E230: 无法读取文件 \"%s\"" + +#~ msgid "E231: 'guifontwide' invalid" +#~ msgstr "E231: 无效的 'guifontwide'" + +#~ msgid "E232: Cannot create BalloonEval with both message and callback" +#~ msgstr "E232: 不能同时使用消息和回调函数来创建 BalloonEval" + +#~ msgid "E233: cannot open display" +#~ msgstr "E233: 无法打开 display" + +#~ msgid "E234: Unknown fontset: %s" +#~ msgstr "E234: 未知的 Fontset: %s" + +#~ msgid "E237: Printer selection failed" +#~ msgstr "E237: 选择打印机失败" + +#~ msgid "E238: Print error: %s" +#~ msgstr "E238: 打印错误: %s" + +#~ msgid "E240: No connection to Vim server" +#~ msgstr "E240: 没有到 Vim 服务器的连接" + +#~ msgid "E243: Argument not supported: \"-%s\"; Use the OLE version." +#~ msgstr "E243: 不支持的参数: \"-%s\";请使用 OLE 版本。" + +#~ msgid "E244: Illegal charset name \"%s\" in font name \"%s\"" +#~ msgstr "E244: 字符集 \"%s\" 不能对应字体\"%s\"" + +#~ msgid "E245: Illegal char '%c' in font name \"%s\"" +#~ msgstr "E245: 不正确的字符 '%c' 出现在字体名称 \"%s\" 内" + +#~ msgid "E247: no registered server named \"%s\"" +#~ msgstr "E247: 没有名叫 \"%s\" 的已注册的服务器" + +#~ msgid "E248: Failed to send command to the destination program" +#~ msgstr "E248: 无法发送命令到目的程序" + +#~ msgid "E249: couldn't read VIM instance registry property" +#~ msgstr "E249: 不能读取 VIM 的 注册表属性" + +#~ msgid "E250: Fonts for the following charsets are missing in fontset %s:" +#~ msgstr "E250: Fontset %s 缺少下列字符集的字体:" + +#~ msgid "E251: VIM instance registry property is badly formed. Deleted!" +#~ msgstr "E251: VIM 实例注册属性有误。已删除!" + +#~ msgid "E252: Fontset name: %s" +#~ msgstr "E252: Fontset 名称: %s" + +#~ msgid "E253: Fontset name: %s\n" +#~ msgstr "E253: Fontset 名称: %s\n" + +#~ msgid "E256: Hangul automata ERROR" +#~ msgstr "E256: Hangul automata 错误" + +#~ msgid "E257: cstag: tag not found" +#~ msgstr "E257: cstag: 找不到 tag" + +#~ msgid "E258: Unable to send to client" +#~ msgstr "E258: 无法发送到客户端" + +#, c-format +#~ msgid "E259: no matches found for cscope query %s of %s" +#~ msgstr "E259: cscope 查询 %s %s 没有找到匹配的结果" + +#~ msgid "E260: cscope connection not found" +#~ msgstr "E260: 找不到 cscope 连接" + +#, c-format +#~ msgid "E261: cscope connection %s not found" +#~ msgstr "E261: 找不到 cscope 连接 %s" + +#, c-format +#~ msgid "E262: error reading cscope connection %<PRId64>" +#~ msgstr "E262: 读取 cscope 连接 %<PRId64> 出错" + +#~ msgid "" +#~ "E263: Sorry, this command is disabled, the Python library could not be " +#~ "loaded." +#~ msgstr "E263: 抱歉,此命令不可用,无法加载 Python 库。" + +#~ msgid "E264: Python: Error initialising I/O objects" +#~ msgstr "E264: Python: 初始化 I/O 对象出错" + +#~ msgid "" +#~ "E266: Sorry, this command is disabled, the Ruby library could not be " +#~ "loaded." +#~ msgstr "E266: 抱歉,此命令不可用,无法加载 Ruby 库" + +#~ msgid "E26: Hebrew cannot be used: Not enabled at compile time\n" +#~ msgstr "E26: 无法使用 Hebrew: 编译时没有启用\n" + +#~ msgid "E273: unknown longjmp status %d" +#~ msgstr "E273: 未知的 longjmp 状态 %d" + +#~ msgid "E274: Sniff: Error during read. Disconnected" +#~ msgstr "E274: Sniff: 读取错误. 取消连接" + +#~ msgid "E275: Unknown SNiFF+ request: %s" +#~ msgstr "E275: 不正确的 SNiff+ 调用: %s" + +#~ msgid "E276: Error connecting to SNiFF+" +#~ msgstr "E276: 连接到 SNiFF+ 失败" + +#~ msgid "E277: Unable to read a server reply" +#~ msgstr "E277: 无法读取服务器响应" + +#~ msgid "E278: SNiFF+ not connected" +#~ msgstr "E278: 未连接到 SNiFF+" + +#~ msgid "E279: Not a SNiFF+ buffer" +#~ msgstr "E279: 不是 SNiFF+ 的缓冲区" + +#~ msgid "E27: Farsi cannot be used: Not enabled at compile time\n" +#~ msgstr "E27: 无法使用 Farsi: 编译时没有启用\n" + +#~ msgid "" +#~ "E280: TCL FATAL ERROR: reflist corrupt!? Please report this to vim-" +#~ "dev@vim.org" +#~ msgstr "E280: TCL 严重错误: reflist 损坏!?请报告给 vim-dev@vim.org" + +#~ msgid "" +#~ "E281: TCL ERROR: exit code is not int!? Please report this to vim-dev@vim." +#~ "org" +#~ msgstr "E281: TCL 错误: 退出返回值不是整数!?请报告给 vim-dev@vim.org" + +#~ msgid "E285: Failed to create input context" +#~ msgstr "E285: 无法创建输入上下文" + +#~ msgid "E287: Warning: Could not set destroy callback to IM" +#~ msgstr "E287: 警告: 无法设定输入法的释放回调函数" + +#~ msgid "E288: input method doesn't support any style" +#~ msgstr "E288: 输入法不支持任何风格" + +#~ msgid "E289: input method doesn't support my preedit type" +#~ msgstr "E289: 输入法不支持我的预编辑类型" + +#~ msgid "E290: over-the-spot style requires fontset" +#~ msgstr "E290: over-the-spot 风格需要 Fontset" + +#~ msgid "E291: Your GTK+ is older than 1.2.3. Status area disabled" +#~ msgstr "E291: 你的 GTK+ 比 1.2.3 旧。状态区不可用。" + +#~ msgid "E292: Input Method Server is not running" +#~ msgstr "E292: 输入法服务器未运行" + +#~ msgid "E338: Sorry, no file browser in console mode" +#~ msgstr "E338: 抱歉,控制台模式下没有文件浏览器" + +#~ msgid "E339: Pattern too long" +#~ msgstr "E339: 模式太长" + +#~ msgid "E341: Internal error: lalloc(%<PRId64>, )" +#~ msgstr "E341: 内部错误: lalloc(%<PRId64>, )" + +#~ msgid "E360: Cannot execute shell with -f option" +#~ msgstr "E360: 不能用 -f 选项执行 shell" + +#~ msgid "E361: Crash intercepted; regexp too complex?" +#~ msgstr "E361: 不能执行; regular expression 太复杂?" + +#~ msgid "E363: pattern caused out-of-stack error" +#~ msgstr "E363: regular expression 造成堆栈用光的错误" #~ msgid "E370: Could not load library %s" #~ msgstr "E370: 无法加载库 %s" +#~ msgid "E371: Command not found" +#~ msgstr "E371: 找不到命令" + +#~ msgid "E396: containedin argument not accepted here" +#~ msgstr "E396: 使用了不正确的参数" + +#, c-format +#~ msgid "E422: terminal code too long: %s" +#~ msgstr "E422: 终端编码太长: %s" + +#~ msgid "E430: Tag file path truncated for %s\n" +#~ msgstr "E430: Tag 文件路径被截断为 %s\n" + +#, c-format +#~ msgid "E436: No \"%s\" entry in termcap" +#~ msgstr "E436: termcap 中没有 \"%s\" 项" + +#~ msgid "E437: terminal capability \"cm\" required" +#~ msgstr "E437: 终端需要能力 \"cm\"" + +#~ msgid "E448: Could not load library function %s" +#~ msgstr "E448: 无法加载库函数 %s" + +#~ msgid "E449: Invalid expression received" +#~ msgstr "E449: 收到无效的表达式" + +#~ msgid "E460: The resource fork would be lost (add ! to override)" +#~ msgstr "E460: Resource fork 会丢失 (请加 ! 强制执行)" + +#, c-format +#~ msgid "E469: invalid cscopequickfix flag %c for %c" +#~ msgstr "E469: cscopequickfix 标志 %c 对 %c 无效" + +#~ msgid "E505: " +#~ msgstr "E505: " + +#~ msgid "E506: Can't write to backup file (add ! to override)" +#~ msgstr "E506: 无法写入备份文件 (请加 ! 强制执行)" + +#~ msgid "E507: Close error for backup file (add ! to override)" +#~ msgstr "E507: 关闭备份文件出错 (请加 ! 强制执行)" + +#~ msgid "E508: Can't read file for backup (add ! to override)" +#~ msgstr "E508: 无法读取文件以供备份 (请加 ! 强制执行)" + +#~ msgid "E50: Too many \\z(" +#~ msgstr "E50: 太多 \\z(" + +#, fuzzy, c-format #~ msgid "" -#~ "Sorry, this command is disabled: the Perl library could not be loaded." -#~ msgstr "抱歉,此命令不可用: 无法加载 Perl 库。" +#~ "E513: write error, conversion failed in line %<PRId64> (make 'fenc' empty " +#~ "to override)" +#~ msgstr "E513: 写入错误,转换失败 (请将 'fenc' 置空以强制执行)" -#~ msgid "Edit with &multiple Vims" -#~ msgstr "用多个 Vim 编辑(&M)" +#, c-format +#~ msgid "E51: Too many %s(" +#~ msgstr "E51: 太多 %s(" -#~ msgid "Edit with single &Vim" -#~ msgstr "用单个 Vim 编辑(&V)" +#~ msgid "E522: Not found in termcap" +#~ msgstr "E522: Termcap 里面找不到" -#~ msgid "Diff with Vim" -#~ msgstr "用 Vim 比较(diff)" +#~ msgid "E52: Unmatched \\z(" +#~ msgstr "E52: 不匹配的 \\z(" + +#~ msgid "E530: Cannot change term in GUI" +#~ msgstr "E530: 在图形界面中不能改变终端" + +#~ msgid "E531: Use \":gui\" to start the GUI" +#~ msgstr "E531: 请用 \":gui\" 启动图形界面" + +#~ msgid "E533: can't select wide font" +#~ msgstr "E533: 无法选择宽字体" + +#~ msgid "E538: No mouse support" +#~ msgstr "E538: 不支持鼠标" + +#~ msgid "E541: too many items" +#~ msgstr "E541: 项目过多" + +#~ msgid "E543: Not a valid codepage" +#~ msgstr "E543: 无效的代码页" + +#~ msgid "E547: Illegal mouseshape" +#~ msgstr "E547: 无效的鼠标形状" + +#~ msgid "E557: Cannot open termcap file" +#~ msgstr "E557: 无法打开 termcap 文件" + +#~ msgid "E558: Terminal entry not found in terminfo" +#~ msgstr "E558: 在 terminfo 中找不到终端项" + +#~ msgid "E559: Terminal entry not found in termcap" +#~ msgstr "E559: 在 termcap 中找不到终端项" + +#, c-format +#~ msgid "E560: Usage: cs[cope] %s" +#~ msgstr "E560: 用法: cs[cope] %s" + +#~ msgid "E561: unknown cscope search type" +#~ msgstr "E561: 未知的 cscope 查找类型" + +#~ msgid "E562: Usage: cstag <ident>" +#~ msgstr "E562: 用法: cstag <ident>" + +#~ msgid "E563: stat error" +#~ msgstr "E563: stat 错误" + +#, c-format +#~ msgid "E563: stat(%s) error: %d" +#~ msgstr "E563: stat(%s) 错误: %d" + +#, c-format +#~ msgid "E564: %s is not a directory or a valid cscope database" +#~ msgstr "E564: %s 不是目录或有效的 cscope 数据库" + +#~ msgid "E566: Could not create cscope pipes" +#~ msgstr "E566: 无法创建 cscope 管道" + +#~ msgid "E567: no cscope connections" +#~ msgstr "E567: 没有 cscope 连接" + +#~ msgid "E568: duplicate cscope database not added" +#~ msgstr "E568: 重复的 cscope 数据库未被加入" + +#~ msgid "E569: maximum number of cscope connections reached" +#~ msgstr "E569: 已达到 cscope 的最大连接数" + +#~ msgid "E570: fatal error in cs_manage_matches" +#~ msgstr "E570: cs_manage_matches 严重错误" + +#~ msgid "" +#~ "E571: Sorry, this command is disabled: the Tcl library could not be " +#~ "loaded." +#~ msgstr "E571: 抱歉,此命令不可用,无法加载 Tcl 库" + +#~ msgid "E572: exit code %d" +#~ msgstr "E572: 退出返回值 %d" + +#~ msgid "E573: Invalid server id used: %s" +#~ msgstr "E573: 使用了无效的服务器 id: %s" + +#, c-format +#~ msgid "E574: Unknown register type %d" +#~ msgstr "E574: 未知的寄存器类型 %d" + +#~ msgid "E596: Invalid font(s)" +#~ msgstr "E596: 无效的字体" + +#~ msgid "E597: can't select fontset" +#~ msgstr "E597: 无法选择 Fontset" + +#~ msgid "E599: Value of 'imactivatekey' is invalid" +#~ msgstr "E599: 'imactivatekey' 的值无效" + +#, c-format +#~ msgid "E59: invalid character after %s@" +#~ msgstr "E59: %s@ 后面有无效的字符" + +#, c-format +#~ msgid "E609: Cscope error: %s" +#~ msgstr "E609: Cscope 错误: %s" + +#, c-format +#~ msgid "E60: Too many complex %s{...}s" +#~ msgstr "E60: 太多复杂的 %s{...}s" + +#~ msgid "E613: Unknown printer font: %s" +#~ msgstr "E613: 未知的打印机字体: %s" + +#~ msgid "E614: vim_SelFile: can't return to current directory" +#~ msgstr "E614: vim_SelFile: 无法返回当前目录" + +#~ msgid "E615: vim_SelFile: can't get current directory" +#~ msgstr "E615: vim_SelFile: 无法获取当前目录" + +#~ msgid "E616: vim_SelFile: can't get font %s" +#~ msgstr "E616: vim_SelFile: 无法获取字体 %s" + +#~ msgid "E617: Cannot be changed in the GTK+ 2 GUI" +#~ msgstr "E617: 在 GTK+ 2 图形界面中不能更改" + +#, c-format +#~ msgid "E61: Nested %s*" +#~ msgstr "E61: 嵌套的 %s*" + +#~ msgid "E622: Could not fork for cscope" +#~ msgstr "E622: 无法对 cscope 进行 fork" + +#~ msgid "E625: cannot open cscope database: %s" +#~ msgstr "E625: 无法打开 cscope 数据库: %s" + +#~ msgid "E626: cannot get cscope database information" +#~ msgstr "E626: 无法获取 cscope 数据库信息" + +#, 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 "E658: NetBeans connection lost for buffer %<PRId64>" +#~ msgstr "E658: 缓冲区 %<PRId64> 丢失 NetBeans 连接" + +#~ msgid "E65: Illegal back reference" +#~ msgstr "E65: 无效的回引" + +#~ msgid "E665: Cannot start GUI, no valid font found" +#~ msgstr "E665: 无法启动图形界面,找不到有效的字体" + +#~ msgid "E668: Wrong access mode for NetBeans connection info file: \"%s\"" +#~ msgstr "E668: NetBeans 连接信息文件中错误的访问模式: \"%s\"" + +#~ msgid "E672: Unable to open window inside MDI application" +#~ msgstr "E672: 无法在 MDI 应用程序中打开窗口" + +#, c-format +#~ msgid "E678: Invalid character after %s%%[dxouU]" +#~ msgstr "E678: %s%%[dxouU] 后面有无效的字符" + +#~ msgid "E679: recursive loop loading syncolor.vim" +#~ msgstr "E679: 加载 syncolor.vim 时出现嵌套循环" + +#~ msgid "E68: Invalid character after \\z" +#~ msgstr "E68: \\z 后面有无效的字符" + +#~ msgid "E693: Can only compare Funcref with Funcref" +#~ msgstr "E693: 只能比较 Funcref 和 Funcref" + +#, c-format +#~ msgid "E706: Variable type mismatch for: %s" +#~ msgstr "E706: 变量类型不匹配: %s" + +#~ msgid "E724: variable nested too deep for displaying" +#~ msgstr "E724: 变量嵌套过深无法显示" + +#~ msgid "E729: using Funcref as a String" +#~ msgstr "E729: 将函数当做字符串使用" + +#~ msgid "E744: NetBeans does not allow changes in read-only files" +#~ msgstr "E744: NetBeans 不允许改变只读文件" + +#~ msgid "" +#~ "E747: Cannot change directory, buffer is modified (add ! to override)" +#~ msgstr "E747: 不能改变目录,缓冲区已修改 (请加 ! 强制执行)" + +#~ msgid "E761: Format error in affix file FOL, LOW or UPP" +#~ msgstr "E761: 附加文件 FOL、LOW 或 UPP 中格式错误" + +#~ msgid "E762: Character in FOL, LOW or UPP is out of range" +#~ msgstr "E762: FOL、LOW 或 UPP 中字符超出范围" + +#~ msgid "E775: Eval feature not available" +#~ msgstr "E775: 求值功能不可用" + +#~ msgid "E800: Arabic cannot be used: Not enabled at compile time\n" +#~ msgstr "E800: 无法使用 Arabic: 编译时没有启用\n" + +#~ msgid "E839: Completion function changed window" +#~ msgstr "E839: 补全函数更改了窗口" + +#~ msgid "E865: (NFA) Regexp end encountered prematurely" +#~ msgstr "E865: (NFA) 过早地遇到了正则表达式的结尾" + +#, c-format +#~ msgid "E867: (NFA) Unknown operator '\\%%%c'" +#~ msgstr "E867: (NFA) 未知的操作符 '\\%%%c'" + +#, c-format +#~ msgid "E867: (NFA) Unknown operator '\\z%c'" +#~ msgstr "E867: (NFA) 未知的操作符 '\\z%c'" + +#, c-format +#~ msgid "E869: (NFA) Unknown operator '\\@%c'" +#~ msgstr "E869: (NFA) 未知的操作符 '\\@%c'" + +#~ msgid "E870: (NFA regexp) Error reading repetition limits" +#~ msgstr "E870: (NFA regexp) 读取重复限制时出错" + +#~ msgid "E871: (NFA regexp) Can't have a multi follow a multi !" +#~ msgstr "E871: (NFA regexp) 不能多个跟多个!" + +#~ msgid "E872: (NFA regexp) Too many '('" +#~ msgstr "E872: (NFA regexp) 太多 '('" + +#~ msgid "E873: (NFA regexp) proper termination error" +#~ msgstr "E873: (NFA regexp) 未适当终止" + +#~ msgid "E874: (NFA) Could not pop the stack !" +#~ msgstr "E874: (NFA) 无法出栈!" + +#~ msgid "" +#~ "E875: (NFA regexp) (While converting from postfix to NFA), too many " +#~ "states left on stack" +#~ msgstr "E875: (NFA regexp) (从后缀转换到 NFA 时),栈上遗留了太多状态" + +#~ msgid "E876: (NFA regexp) Not enough space to store the whole NFA " +#~ msgstr "E876: (NFA regexp) 没有足够的空间存储整个NFA " + +#, c-format +#~ msgid "E877: (NFA regexp) Invalid character class: %<PRId64>" +#~ msgstr "E877: (NFA regexp) 不可用的字符类: %<PRId64>" + +#, fuzzy +#~ msgid "E879: (NFA regexp) Too many \\z(" +#~ msgstr "E50: 太多 \\z(" + +#~ msgid "ERROR: " +#~ msgstr "错误: " + +#~ msgid "Edit File" +#~ msgstr "编辑文件" + +#~ msgid "Edit File in new window" +#~ msgstr "在新窗口编辑文件" #~ msgid "Edit with &Vim" #~ msgstr "用 Vim 编辑(&V)" +#~ msgid "Edit with &multiple Vims" +#~ msgstr "用多个 Vim 编辑(&M)" + #~ msgid "Edit with existing Vim - " #~ msgstr "用当前的 Vim 编辑 - " +#~ msgid "Edit with single &Vim" +#~ msgstr "用单个 Vim 编辑(&V)" + #~ msgid "Edits the selected file(s) with Vim" #~ msgstr "用 Vim 编辑选中的文件" +#~ msgid "Encoding:" +#~ msgstr "编码:" + +#~ msgid "Enter encryption key: " +#~ msgstr "输入密码: " + +#~ msgid "Enter nr of choice (<CR> to abort): " +#~ msgstr "输入 nr 或选择 (<CR> 退出): " + +#~ msgid "Enter same key again: " +#~ msgstr "请再输入一次: " + #~ msgid "Error creating process: Check if gvim is in your path!" #~ msgstr "创建进程失败: 请检查 gvim 是否在路径中!" -#~ msgid "gvimext.dll error" -#~ msgstr "gvimext.dll 错误" +# do not translate to avoid writing Chinese in files +#, fuzzy +#~ msgid "Expression" +#~ msgstr "表达式" + +#~ msgid "External submatches:\n" +#~ msgstr "外部符合:\n" + +#~ msgid "Files" +#~ msgstr "文件" + +#~ msgid "Filter" +#~ msgstr "过滤器" + +#~ msgid "Find & Replace (use '\\\\' to find a '\\')" +#~ msgstr "查找和替换字符串 (使用 '\\\\' 来查找 '\\')" + +#~ msgid "Find &Next" +#~ msgstr "查找下一个(&N)" + +#~ msgid "Find Next" +#~ msgstr "查找下一个" + +#~ msgid "Find string (use '\\\\' to find a '\\')" +#~ msgstr "查找字符串 (使用 '\\\\' 来查找 '\\')" + +#~ msgid "Find symbol" +#~ msgstr "查找 symbol" + +#~ msgid "Find what:" +#~ msgstr "查找内容:" + +#~ msgid "Font '%s' is not fixed-width" +#~ msgstr "'%s' 不是固定宽度的字体" + +#~ msgid "Font Selection" +#~ msgstr "选择字体" + +#~ msgid "Font%<PRId64> width is not twice that of font0\n" +#~ msgstr "字体%<PRId64>的宽度不是字体0的两倍\n" + +#~ msgid "Font0 width: %<PRId64>\n" +#~ msgstr "字体0的宽度:%<PRId64>\n" + +#~ msgid "Font0: %s\n" +#~ msgstr "字体0: %s\n" + +#~ msgid "" +#~ "Font1 width: %<PRId64>\n" +#~ "\n" +#~ msgstr "" +#~ "字体1的宽度: %<PRId64>\n" +#~ "\n" + +#~ msgid "Font1: %s\n" +#~ msgstr "字体1: %s\n" + +#~ msgid "Font:" +#~ msgstr "字体:" + +#~ msgid "Generate docu for" +#~ msgstr "产生文件: " + +#~ msgid "Hit ENTER to continue" +#~ msgstr "请按 ENTER 继续" + +#~ msgid "I/O ERROR" +#~ msgstr "I/O 错误" + +#~ msgid "Ignoring long line in tags file" +#~ msgstr "忽略较长的行在 tags 文件中" + +#~ msgid "Illegal register name" +#~ msgstr "无效的寄存器名" + +#~ msgid "Illegal starting char" +#~ msgstr "无效的启动字符" + +# do not translate to avoid writing Chinese in files +#, fuzzy +#~ msgid "Input Line" +#~ msgstr "输入行" + +#~ msgid "Input _Methods" +#~ msgstr "输入法(_M)" + +#~ msgid "Invalid argument for" +#~ msgstr "无效的参数" + +#~ msgid "Invalid font specification" +#~ msgstr "指定了无效的字体" + +#~ msgid "Keys don't match!" +#~ msgstr "两次密码不匹配!" + +#~ msgid "Kill a connection" +#~ msgstr "结束一个连接" + +#~ msgid "Linear tag search" +#~ msgstr "线性查找标签 (Tags)" + +#~ msgid "Linking: " +#~ msgstr "链接方式: " + +#~ msgid "Match case" +#~ msgstr "匹配大小写" + +#~ msgid "Match whole word only" +#~ msgstr "匹配完整的词" + +#~ msgid "Message" +#~ msgstr "消息" + +#~ msgid "Missing '>'" +#~ msgstr "缺少 '>'" + +#, c-format +#~ msgid "Missing FOL/LOW/UPP line in %s" +#~ msgstr "%s 中缺少 FOL/LOW/UPP 行" + +#~ msgid "Modified by " +#~ msgstr "修改者 " + +#~ msgid "Name:" +#~ msgstr "名称:" + +#~ msgid "Need %s version %<PRId64>\n" +#~ msgstr "需要 %s 版本 %<PRId64>\n" + +#~ msgid "Need Amigados version 2.04 or later\n" +#~ msgstr "需要 Amigados 版本 2.04 以上\n" + +#~ msgid "NetBeans disallows writes of unmodified buffers" +#~ msgstr "NetBeans 不允许未修改的缓冲区写入" + +#~ msgid "New tab" +#~ msgstr "新建标签" + +#~ msgid "No display: Send expression failed.\n" +#~ msgstr "没有 display: 发送表达式失败。\n" + +#~ msgid "No match at cursor, finding next" +#~ msgstr "在光标处没有匹配,查找下一个" + +#~ msgid "No undo possible; continue anyway" +#~ msgstr "无法撤销;仍然继续" + +#~ msgid "Not Used" +#~ msgstr "未使用" + +#~ msgid "Nvim: Reading from stdin...\n" +#~ msgstr "Vim: 从标准输入读取...\n" + +#~ msgid "OK" +#~ msgstr "确定" + +#~ msgid "Open File dialog" +#~ msgstr "打开文件对话框" + +#~ msgid "Open Tab..." +#~ msgstr "打开标签..." + +#~ msgid "Open tab..." +#~ msgstr "打开标签..." + +#~ msgid "Opening the X display failed" +#~ msgstr "打开 X display 失败" + +#~ msgid "Opening the X display timed out" +#~ msgstr "打开 X display 超时" + +#~ msgid "Opening the X display took %<PRId64> msec" +#~ msgstr "打开 X display 用时 %<PRId64> 秒" + +#~ msgid "Partial writes disallowed for NetBeans buffers" +#~ msgstr "NetBeans 不允许缓冲区部分写入" #~ msgid "Path length too long!" #~ msgstr "路径太长!" -#~ msgid "E234: Unknown fontset: %s" -#~ msgstr "E234: 未知的 Fontset: %s" +#~ msgid "Pathname:" +#~ msgstr "路径:" -#~ msgid "E235: Unknown font: %s" -#~ msgstr "E235: 未知的字体: %s" +#~ msgid "Query for a pattern" +#~ msgstr "查询一个模式" -#~ msgid "E236: Font \"%s\" is not fixed-width" -#~ msgstr "E236: 字体 \"%s\" 不是等宽字体" +#~ msgid "Reading from stdin..." +#~ msgstr "从标准输入读取..." -#~ msgid "E448: Could not load library function %s" -#~ msgstr "E448: 无法加载库函数 %s" +#~ msgid "Reinit all connections" +#~ msgstr "重置所有连接" -#~ msgid "E26: Hebrew cannot be used: Not enabled at compile time\n" -#~ msgstr "E26: 无法使用 Hebrew: 编译时没有启用\n" +#~ msgid "Replace" +#~ msgstr "替换" -#~ msgid "E27: Farsi cannot be used: Not enabled at compile time\n" -#~ msgstr "E27: 无法使用 Farsi: 编译时没有启用\n" +#~ msgid "Replace &All" +#~ msgstr "全部替换(&A)" -#~ msgid "E800: Arabic cannot be used: Not enabled at compile time\n" -#~ msgstr "E800: 无法使用 Arabic: 编译时没有启用\n" +#~ msgid "Replace All" +#~ msgstr "全部替换" -#~ msgid "E247: no registered server named \"%s\"" -#~ msgstr "E247: 没有名叫 \"%s\" 的已注册的服务器" +#~ msgid "Replace with:" +#~ msgstr "替换为:" -#~ msgid "E233: cannot open display" -#~ msgstr "E233: 无法打开 display" +#~ msgid "Retrieve" +#~ msgstr "恢复" -#~ msgid "E449: Invalid expression received" -#~ msgstr "E449: 收到无效的表达式" +#~ msgid "Retrieve from all projects" +#~ msgstr "恢复: 从所有项目" -#~ msgid "E744: NetBeans does not allow changes in read-only files" -#~ msgstr "E744: NetBeans 不允许改变只读文件" +#~ msgid "Retrieve from file" +#~ msgstr "恢复: 从文件" -#~ msgid "Affix flags ignored when PFXPOSTPONE used in %s line %d: %s" -#~ msgstr "%s 第 %d 行,使用 PFXPOSTPONE 时附加标志被忽略: %s" +#~ msgid "Retrieve from project" +#~ msgstr "恢复: 从对象" -#~ msgid "[No file]" -#~ msgstr "[未命名]" +#~ msgid "Run Macro" +#~ msgstr "执行宏" + +#~ msgid "Running in Vi compatible mode" +#~ msgstr "运行于 Vi 兼容模式" + +#~ msgid "Running modeless, typed text is inserted" +#~ msgstr "无模式运行,输入文字即插入" + +#~ msgid "SNiFF+ is currently " +#~ msgstr "SNiFF+ 目前" + +#~ msgid "Save As" +#~ msgstr "另存为" + +#~ msgid "Save File dialog" +#~ msgstr "保存文件对话框" + +#~ msgid "Save Redirection" +#~ msgstr "保存重定向" + +#~ msgid "Save Session" +#~ msgstr "保存会话" + +#~ msgid "Save Setup" +#~ msgstr "保存设定" + +#~ msgid "Save View" +#~ msgstr "保存视图" + +#~ msgid "Scrollbar Widget: Could not get geometry of thumb pixmap." +#~ msgstr "滚动条部件: 无法获取滑块图像的几何大小" + +# do not translate to avoid writing Chinese in files +#, fuzzy +#~ msgid "Search String" +#~ msgstr "查找字符串" + +#~ msgid "Select Directory dialog" +#~ msgstr "选择目录对话框" + +#~ msgid "Show base class of" +#~ msgstr "显示 base class of:" + +#~ msgid "Show class in hierarchy" +#~ msgstr "显示层次关系的类" + +#~ msgid "Show class in restricted hierarchy" +#~ msgstr "显示 restricted 层次关系的 class" + +#~ msgid "Show connections" +#~ msgstr "显示连接" + +#~ msgid "Show docu of" +#~ msgstr "显示文件: " + +#~ msgid "Show overridden member function" +#~ msgstr "显示被覆盖的成员函数" + +#~ msgid "Show source of" +#~ msgstr "显示源代码: " + +#~ msgid "Show this message" +#~ msgstr "显示此信息" + +#~ msgid "Size:" +#~ msgstr "尺寸:" + +#~ msgid "Sniff: Error during write. Disconnected" +#~ msgstr "Sniff: 写入错误。结束连接" + +#~ msgid "" +#~ "Sorry, this command is disabled: the Perl library could not be loaded." +#~ msgstr "抱歉,此命令不可用: 无法加载 Perl 库。" + +#~ msgid "Source Vim script" +#~ msgstr "执行 Vim 脚本" + +#~ msgid "Style:" +#~ msgstr "风格:" + +#, fuzzy +#~ msgid "Substitute " +#~ msgstr "1 次替换," + +#~ msgid "Swap file already exists!" +#~ msgstr "交换文件已存在!" + +#~ msgid "Tear off this menu" +#~ msgstr "撕下此菜单" + +#~ msgid "Testing the X display failed" +#~ msgstr "测试 X display 失败" + +#~ msgid "Thanks for flying Vim" +#~ msgstr "感谢您选择 Vim" + +#~ msgid "This Vim was not compiled with the diff feature." +#~ msgstr "此 Vim 编译时没有加入 diff 功能" + +#~ msgid "This cscope command does not support splitting the window.\n" +#~ msgstr "这个 cscope 命令不支持分割窗口。\n" + +#~ msgid "Toggle implementation/definition" +#~ msgstr "切换实现/定义" + +#, fuzzy +#~ msgid "Unable to get option value" +#~ msgstr "选项参数后的内容无效" + +#~ msgid "Unable to register a command server name" +#~ msgstr "无法注册命令服务器名" + +#~ msgid "Up" +#~ msgstr "向上" + +#~ msgid "Used CUT_BUFFER0 instead of empty selection" +#~ msgstr "使用 CUT_BUFFER0 来取代空选择" + +#~ msgid "VIM - Search and Replace..." +#~ msgstr "VIM - 查找和替换..." + +#~ msgid "VIM - Search..." +#~ msgstr "VIM - 查找..." + +#~ msgid "VIM - Vi IMproved" +#~ msgstr "VIM - Vi IMproved" + +#~ msgid "VIM Error" +#~ msgstr "VIM 错误" + +#~ msgid "VIM: Can't open window!\n" +#~ msgstr "VIM: 不能打开窗口!\n" + +#~ msgid "" +#~ "VIMRUN.EXE not found in your $PATH.\n" +#~ "External commands will not pause after completion.\n" +#~ "See :help win32-vimrun for more information." +#~ msgstr "" +#~ "在你的 $PATH 中找不到 VIMRUN.EXE。\n" +#~ "外部命令执行完毕后将不会暂停。\n" +#~ "进一步说明请见 :help win32-vimrun" + +#~ msgid "Vim - Font Selector" +#~ msgstr "Vim - 字体选择器" + +#~ msgid "" +#~ "Vim E458: Cannot allocate colormap entry, some colors may be incorrect" +#~ msgstr "Vim E458: 无法分配颜色表项,某些颜色可能不正确" + +#~ msgid "Vim Warning" +#~ msgstr "Vim 警告" + +#~ msgid "Vim dialog" +#~ msgstr "Vim 对话框" + +#~ msgid "Vim dialog..." +#~ msgstr "Vim 对话框..." + +#~ msgid "Vim error" +#~ msgstr "Vim 错误" + +#~ msgid "Vim error: ~a" +#~ msgstr "Vim 错误: ~a" + +#~ msgid "Vim exiting with %d\n" +#~ msgstr "Vim 返回值: %d\n" + +#~ msgid "Vim: Caught %s event\n" +#~ msgstr "Vim: 拦截到 %s 事件\n" + +#~ msgid "Vim: Caught deadly signal\n" +#~ msgstr "Vim: 拦截到致命信号(deadly signal)\n" + +#~ msgid "Vim: Caught deadly signal %s\n" +#~ msgstr "Vim: 拦截到致命信号(deadly signal) %s\n" + +#~ msgid "Vim: Double signal, exiting\n" +#~ msgstr "Vim: 双重信号,退出中\n" + +#~ msgid "Vim: Error: Failure to start gvim from NetBeans\n" +#~ msgstr "Vim: 错误: 无法从 NetBeans 中启动 gvim\n" + +#~ msgid "Vim: Finished.\n" +#~ msgstr "Vim: 结束。\n" + +#~ msgid "Vim: Main window unexpectedly destroyed\n" +#~ msgstr "Vim: 主窗口被意外地摧毁\n" + +#~ msgid "Vim: Received \"die\" request from session manager\n" +#~ msgstr "Vim: 从会话管理器收到 \"die\" 请求\n" + +#~ msgid "Vim: Warning: Input is not from a terminal\n" +#~ msgstr "Vim: 警告: 输入不是来自终端(键盘)\n" + +#~ msgid "Vim: Warning: Output is not to a terminal\n" +#~ msgstr "Vim: 警告: 输出不是到终端(屏幕)\n" + +#~ msgid "Vim: preserving files...\n" +#~ msgstr "Vim: 正在保留文件……\n" + +#~ msgid "WARNING: Windows 95/98/ME detected" +#~ msgstr "警告: 检测到 Windows 95/98/ME" + +#~ msgid "Warning: terminal cannot highlight" +#~ msgstr "警告: 你的终端不能显示高亮" + +#~ msgid "Window position: X %d, Y %d" +#~ msgstr "窗口位置: X %d, Y %d" + +#~ msgid "XSMP ICE connection watch failed" +#~ msgstr "XSMP ICE 连接监视失败" + +#~ msgid "XSMP handling save-yourself request" +#~ msgstr "XSMP 处理 save-yourself 请求" + +#~ msgid "XSMP lost ICE connection" +#~ msgstr "XSMP 丢失了到 ICE 的连接" + +#~ msgid "XSMP opening connection" +#~ msgstr "XSMP 打开连接" + +#~ msgid "Xref has a" +#~ msgstr "Xref 有" + +#~ msgid "Xref referred by" +#~ msgstr "Xref 被谁参考:" + +#~ msgid "Xref refers to" +#~ msgstr "Xref 参考到" + +#~ msgid "Xref used by" +#~ msgstr "Xref 被谁使用:" + +#~ msgid "Zero count" +#~ msgstr "计数为零" #~ msgid "[Error List]" #~ msgstr "[错误列表]" -#~ msgid "E106: Unknown variable: \"%s\"" -#~ msgstr "E106: 未定义的变量: \"%s\"" +#~ msgid "[NL found]" +#~ msgstr "[找到 NL]" -#~ msgid "function " -#~ msgstr "函数 " +#~ msgid "[New file]" +#~ msgstr "[新文件]" -#~ msgid "E130: Undefined function: %s" -#~ msgstr "E130: 函数 %s 尚未定义" +#~ msgid "[No file]" +#~ msgstr "[未命名]" -#~ msgid "Run Macro" -#~ msgstr "执行宏" +#~ msgid "" +#~ "[calls] total re/malloc()'s %<PRIu64>, total free()'s %<PRIu64>\n" +#~ "\n" +#~ msgstr "" +#~ "[调用] 总共 re/malloc(): %<PRIu64>,总共 free()': %<PRIu64>\n" +#~ "\n" -#~ msgid "E242: Color name not recognized: %s" -#~ msgstr "E242: %s 为不能识别的颜色名称" +#~ msgid "[crypted]" +#~ msgstr "[已加密]" -#~ msgid "error reading cscope connection %d" -#~ msgstr "读取 cscope 连接 %d 时错误" +#~ msgid "[fifo/socket]" +#~ msgstr "[fifo/socket]" -#~ msgid "E260: cscope connection not found" -#~ msgstr "E260: 找不到 cscope 连接" +#~ msgid "[string too long]" +#~ msgstr "[字符串太长]" -#~ msgid "cscope connection closed" -#~ msgstr "cscope 连接已关闭" +#~ msgid "attempt to refer to deleted buffer" +#~ msgstr "试图引用已被删除的缓冲区" + +#~ msgid "attempt to refer to deleted window" +#~ msgstr "试图引用已被删除的窗口" + +#~ msgid "block of 1 line yanked" +#~ msgstr "复制了 1 行的块" + +#~ msgid "buffer is invalid" +#~ msgstr "缓冲区无效" + +#~ msgid "by Bram Moolenaar et al." +#~ msgstr "维护人 Bram Moolenaar 等" + +#~ msgid "can't delete OutputObject attributes" +#~ msgstr "不能删除 OutputObject 属性" + +#~ msgid "cannot change console mode ?!\n" +#~ msgstr "不能切换主控台(console)模式 !?\n" + +#~ msgid "cannot create buffer/window command: object is being deleted" +#~ msgstr "无法创建缓冲区/窗口命令: 对象将被删除" + +#~ msgid "cannot delete line" +#~ msgstr "无法删除行" + +#~ msgid "cannot insert line" +#~ msgstr "无法插入行" + +#~ msgid "cannot insert/append line" +#~ msgstr "无法插入/追加行" + +#~ msgid "cannot open " +#~ msgstr "不能打开" + +#~ msgid "" +#~ "cannot register callback command: buffer/window is already being deleted" +#~ msgstr "无法注册回调命令: 缓冲区/窗口已被删除" + +#~ msgid "cannot register callback command: buffer/window reference not found" +#~ msgstr "无法注册回调命令: 找不到缓冲区/窗口引用" + +#~ msgid "cannot replace line" +#~ msgstr "无法替换行" + +#~ msgid "cannot set line(s)" +#~ msgstr "无法设定行" + +#~ msgid "cannot yank; delete anyway" +#~ msgstr "无法复制;改为删除" + +#~ msgid "close" +#~ msgstr "关闭" + +#~ msgid "connected" +#~ msgstr "连接中" #~ msgid "couldn't malloc\n" #~ msgstr "不能使用 malloc\n" -#~ msgid "%2d %-5ld %-34s <none>\n" -#~ msgstr "%2d %-5ld %-34s <无>\n" +#~ msgid "couldn't open buffer" +#~ msgstr "无法打开缓冲区" -#~ msgid "E249: couldn't read VIM instance registry property" -#~ msgstr "E249: 不能读取 VIM 的 注册表属性" +#~ msgid "cs_create_connection exec failed" +#~ msgstr "cs_create_connection 执行失败" -#~ msgid "\"\n" -#~ msgstr "\"\n" +#, fuzzy +#~ msgid "cs_create_connection setpgid failed" +#~ msgstr "cs_create_connection 执行失败" -#~ msgid "--help\t\tShow Gnome arguments" -#~ msgstr "--help\t\t显示 Gnome 相关参数" +#~ msgid "cs_create_connection: fdopen for fr_fp failed" +#~ msgstr "cs_create_connection: fdopen fr_fp 失败" -#~ msgid "[string too long]" -#~ msgstr "[字符串太长]" +#~ msgid "cs_create_connection: fdopen for to_fp failed" +#~ msgstr "cs_create_connection: fdopen to_fp 失败" -#~ msgid "Hit ENTER to continue" -#~ msgstr "请按 ENTER 继续" +#~ msgid "cscope commands:\n" +#~ msgstr "cscope 命令:\n" -#~ msgid " (RET/BS: line, SPACE/b: page, d/u: half page, q: quit)" -#~ msgstr " (RET/BS: 向下/向上一行, 空格/b: 一页, d/u: 半页, q: 退出)" +#, c-format +#~ msgid "cscope connection %s closed" +#~ msgstr "cscope 连接 %s 已关闭" -#~ msgid " (RET: line, SPACE: page, d: half page, q: quit)" -#~ msgstr " (RET: 向下一行, 空白键: 一页, d: 半页, q: 退出)" +#~ msgid "cursor position outside buffer" +#~ msgstr "光标位置在缓冲区外" -#~ msgid "E361: Crash intercepted; regexp too complex?" -#~ msgstr "E361: 不能执行; regular expression 太复杂?" +#~ msgid "defaulting to '" +#~ msgstr "默认值为: '" -#~ msgid "E363: pattern caused out-of-stack error" -#~ msgstr "E363: regular expression 造成堆栈用光的错误" +#~ msgid "error reading cscope connection %d" +#~ msgstr "读取 cscope 连接 %d 时错误" -#~ msgid " BLOCK" -#~ msgstr " 块" +#~ msgid "filename / context / line\n" +#~ msgstr "文件名 / 上下文 / 行\n" -#~ msgid " LINE" -#~ msgstr " 行" +#~ msgid "freeing %<PRId64> lines" +#~ msgstr "释放了 %<PRId64> 行" -#~ msgid "Enter nr of choice (<CR> to abort): " -#~ msgstr "输入 nr 或选择 (<CR> 退出): " +#~ msgid "gvimext.dll error" +#~ msgstr "gvimext.dll 错误" -#~ msgid "Linear tag search" -#~ msgstr "线性查找标签 (Tags)" +#~ msgid "hidden option" +#~ msgstr "隐藏的选项" -#~ msgid "Binary tag search" -#~ msgstr "二进制查找(Binary search) 标签(Tags)" +#~ msgid "internal error: unknown option type" +#~ msgstr "内部错误:未知的选项类型" + +#~ msgid "invalid attribute" +#~ msgstr "无效的属性" + +#~ msgid "invalid buffer number" +#~ msgstr "无效的缓冲区号" + +#~ msgid "invalid expression" +#~ msgstr "无效的表达式" + +#~ msgid "invalid mark name" +#~ msgstr "无效的标记名称" + +#~ msgid "keyboard interrupt" +#~ msgstr "键盘中断" + +#~ msgid "linenr out of range" +#~ msgstr "行号超出范围" + +#~ msgid "logoff" +#~ msgstr "注消" + +#~ msgid "mark not set" +#~ msgstr "没有设定标记" + +#~ msgid "mch_get_shellsize: not a console??\n" +#~ msgstr "mch_get_shellsize: 不是主控台(console)??\n" + +#~ msgid "menu Edit->Global Settings->Toggle Insert Mode " +#~ msgstr "菜单 编辑->全局设定->开/关插入模式 " + +#~ msgid "menu Help->Orphans for information " +#~ msgstr "菜单 帮助->孤儿 查看说明 " + +#~ msgid "new shell started\n" +#~ msgstr "启动新 shell\n" + +#~ msgid "no cscope connections\n" +#~ msgstr "没有 cscope 连接\n" + +#~ msgid "no specific match" +#~ msgstr "找不到匹配的项" + +#~ msgid "no such buffer" +#~ msgstr "无此缓冲区" + +#~ msgid "no such window" +#~ msgstr "无此窗口" + +#~ msgid "not " +#~ msgstr "未" + +#~ msgid "not allowed in the Vim sandbox" +#~ msgstr "不允许在 sandbox 中使用" + +#~ msgid "not implemented yet" +#~ msgstr "尚未实现" + +#~ msgid "number changes time" +#~ msgstr " 编号 改变 时间" + +#~ msgid "read from Netbeans socket" +#~ msgstr "从 Netbeans 套接字读取" + +#~ msgid "readonly attribute" +#~ msgstr "只读属性" + +#~ msgid "row %d column %d" +#~ msgstr "第 %d 行 第 %d 列" + +#~ msgid "shell " +#~ msgstr "shell " + +#~ msgid "shell returned %d" +#~ msgstr "Shell 返回 %d" + +#~ msgid "shutdown" +#~ msgstr "关机" + +#~ msgid "softspace must be an integer" +#~ msgstr "softspace 必须是整数" + +#~ msgid "string cannot contain newlines" +#~ msgstr "字符串不能包含换行(NL)" + +#~ msgid "to %s on %s" +#~ msgstr "从 %s 到 %s" + +#~ msgid "type :help cp-default<Enter> for info on this" +#~ msgstr "输入 :help cp-default<Enter> 查看相关说明 " + +#~ msgid "type :help windows95<Enter> for info on this" +#~ msgstr "输入 :help windows95<Enter> 查看相关说明 " + +#~ msgid "type :help<Enter> or <F1> for on-line help" +#~ msgstr "输入 :help<Enter> 或 <F1> 查看在线帮助 " + +#~ msgid "type :set nocp<Enter> for Vim defaults" +#~ msgstr "输入 :set nocp<Enter> 恢复默认的 Vim " + +#~ msgid "unknown flag: " +#~ msgstr "未知的标志: " + +#~ msgid "unknown option" +#~ msgstr "未知的选项" + +#~ msgid "unknown vimOption" +#~ msgstr "未知的 vim 选项" + +#~ msgid "version " +#~ msgstr "版本 " + +#~ msgid "vim error" +#~ msgstr "vim 错误" + +#~ msgid "window index is out of range" +#~ msgstr "窗口索引超出范围" + +#~ msgid "window is invalid" +#~ msgstr "窗口无效" + +#~ msgid "with (classic) GUI." +#~ msgstr "带(传统)图形界面。" #~ msgid "with BeOS GUI." #~ msgstr "使用 BeOS 图形界面。" + +#~ msgid "with Carbon GUI." +#~ msgstr "带 Carbon 图形界面。" + +#~ msgid "with Cocoa GUI." +#~ msgstr "带 Cocoa 图形界面。" + +#~ msgid "with GTK GUI." +#~ msgstr "带 GTK 图形界面。" + +#~ msgid "with GTK-GNOME GUI." +#~ msgstr "带 GTK-GNOME 图形界面。" + +#~ msgid "with GTK2 GUI." +#~ msgstr "带 GTK2 图形界面。" + +#~ msgid "with GTK2-GNOME GUI." +#~ msgstr "带 GTK2-GNOME 图形界面。" + +#~ msgid "with GUI." +#~ msgstr "带图形界面。" + +#~ msgid "with Photon GUI." +#~ msgstr "带 Photon 图形界面。" + +#~ msgid "with X11-Athena GUI." +#~ msgstr "带 X11-Athena 图形界面。" + +#~ msgid "with X11-Motif GUI." +#~ msgstr "带 X11-Motif 图形界面。" + +#~ msgid "with X11-neXtaw GUI." +#~ msgstr "带 X11-neXtaw 图形界面。" + +#~ msgid "without GUI." +#~ msgstr "无图形界面。" + +#~ msgid "writelines() requires list of strings" +#~ msgstr "writelines() 需要字符串列表作参数" diff --git a/src/nvim/po/zh_TW.UTF-8.po b/src/nvim/po/zh_TW.UTF-8.po index e95b1e2cad..cba95e2af2 100644 --- a/src/nvim/po/zh_TW.UTF-8.po +++ b/src/nvim/po/zh_TW.UTF-8.po @@ -3024,127 +3024,6 @@ msgstr "已搜尋到檔案開頭;再從結尾繼續搜尋" msgid "search hit BOTTOM, continuing at TOP" msgstr "已搜尋到檔案結尾;再從開頭繼續搜尋" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: 缺少 colon" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: 不正確的模式" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: 應該要有數字" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "第 %d 頁" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "沒有要列印的文字" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "列印中: 第 %d 頁 (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr "複製 %d / %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "已列印: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "已取消列印" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: 無法寫入 PostScript 輸出檔" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: 無法開啟檔案 \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: 無法讀取 PostScript 資源檔 \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: 檔案 \"%s\" 不是 PostScript 資源檔 " - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: 不支援 PostScript 資源檔 \"%s\"" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" 資源檔版本錯誤" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: 不兼容的多字節編碼和字元集" - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset 在多字節編碼下不能為空" - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: 沒有指定多字節打印的默認字型" - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: 無法開啟 PostScript 輸出檔" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: 無法開啟檔案 \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: 無法讀取 PostScript 資源檔 \"prolog.ps\"" - -#: ../hardcopy.c:2593 -#, fuzzy -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: 無法讀取 PostScript 資源檔 \"%s.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: 無法讀取 PostScript 資源檔 \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, fuzzy, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620:無法轉換至 \"%s\" 字元編碼" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "傳送資料到印表機..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: 無法列印 PostScript 檔案" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "已送出列印工作。" - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "新增資料庫" diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index 74376c8b8a..245ce87865 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -6,27 +6,35 @@ /// Popup menu (PUM) #include <assert.h> -#include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <string.h> +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/drawscreen.h" -#include "nvim/edit.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds.h" +#include "nvim/getchar.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/insexpand.h" +#include "nvim/keycodes.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/menu.h" +#include "nvim/message.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/popupmenu.h" -#include "nvim/search.h" +#include "nvim/pos.h" +#include "nvim/screen.h" #include "nvim/strings.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" @@ -70,19 +78,19 @@ static void pum_compute_size(void) for (int i = 0; i < pum_size; i++) { int w; if (pum_array[i].pum_text != NULL) { - w = vim_strsize((char *)pum_array[i].pum_text); + w = vim_strsize(pum_array[i].pum_text); if (pum_base_width < w) { pum_base_width = w; } } if (pum_array[i].pum_kind != NULL) { - w = vim_strsize((char *)pum_array[i].pum_kind) + 1; + w = vim_strsize(pum_array[i].pum_kind) + 1; if (pum_kind_width < w) { pum_kind_width = w; } } if (pum_array[i].pum_extra != NULL) { - w = vim_strsize((char *)pum_array[i].pum_extra) + 1; + w = vim_strsize(pum_array[i].pum_extra) + 1; if (pum_extra_width < w) { pum_extra_width = w; } @@ -159,10 +167,10 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i Array arr = arena_array(&arena, (size_t)size); for (int i = 0; i < size; i++) { Array item = arena_array(&arena, 4); - ADD_C(item, STRING_OBJ(cstr_as_string((char *)array[i].pum_text))); - ADD_C(item, STRING_OBJ(cstr_as_string((char *)array[i].pum_kind))); - ADD_C(item, STRING_OBJ(cstr_as_string((char *)array[i].pum_extra))); - ADD_C(item, STRING_OBJ(cstr_as_string((char *)array[i].pum_info))); + ADD_C(item, STRING_OBJ(cstr_as_string(array[i].pum_text))); + ADD_C(item, STRING_OBJ(cstr_as_string(array[i].pum_kind))); + ADD_C(item, STRING_OBJ(cstr_as_string(array[i].pum_extra))); + ADD_C(item, STRING_OBJ(cstr_as_string(array[i].pum_info))); ADD_C(arr, ARRAY_OBJ(item)); } ui_call_popupmenu_show(arr, selected, pum_win_row, cursor_col, @@ -210,11 +218,16 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i // pum above "pum_win_row" pum_above = true; - // Leave two lines of context if possible - if (curwin->w_wrow - curwin->w_cline_row >= 2) { - context_lines = 2; + if (State == MODE_CMDLINE) { + // for cmdline pum, no need for context lines + context_lines = 0; } else { - context_lines = curwin->w_wrow - curwin->w_cline_row; + // Leave two lines of context if possible + if (curwin->w_wrow - curwin->w_cline_row >= 2) { + context_lines = 2; + } else { + context_lines = curwin->w_wrow - curwin->w_cline_row; + } } if (pum_win_row >= size + context_lines) { @@ -233,13 +246,17 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i // pum below "pum_win_row" pum_above = false; - // Leave two lines of context if possible - validate_cheight(); - if (curwin->w_cline_row + curwin->w_cline_height - curwin->w_wrow >= 3) { - context_lines = 3; + if (State == MODE_CMDLINE) { + // for cmdline pum, no need for context lines + context_lines = 0; } else { - context_lines = curwin->w_cline_row - + curwin->w_cline_height - curwin->w_wrow; + // Leave two lines of context if possible + validate_cheight(); + if (curwin->w_cline_row + curwin->w_cline_height - curwin->w_wrow >= 3) { + context_lines = 3; + } else { + context_lines = curwin->w_cline_row + curwin->w_cline_height - curwin->w_wrow; + } } pum_row = pum_win_row + context_lines; @@ -264,12 +281,15 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i pum_row = above_row; pum_height = pum_win_row - above_row; } + + pum_array = array; + // Set "pum_size" before returning so that pum_set_event_info() gets the correct size. + pum_size = size; + if (pum_external) { return; } - pum_array = array; - pum_size = size; pum_compute_size(); int max_width = pum_base_width; @@ -397,8 +417,8 @@ void pum_redraw(void) int attr; int i; int idx; - char_u *s; - char_u *p = NULL; + char *s; + char *p = NULL; int totwidth, width, w; int thumb_pos = 0; int thumb_height = 1; @@ -507,24 +527,24 @@ void pum_redraw(void) if (s == NULL) { s = p; } - w = ptr2cells((char *)p); + w = ptr2cells(p); if ((*p == NUL) || (*p == TAB) || (totwidth + w > pum_width)) { // Display the text that fits or comes before a Tab. // First convert it to printable characters. - char_u *st; - char_u saved = *p; + char *st; + char saved = *p; if (saved != NUL) { *p = NUL; } - st = (char_u *)transstr((const char *)s, true); + st = transstr(s, true); if (saved != NUL) { *p = saved; } if (pum_rl) { - char *rt = reverse_text((char *)st); + char *rt = reverse_text(st); char *rt_start = rt; int size = vim_strsize(rt); @@ -548,7 +568,7 @@ void pum_redraw(void) grid_col -= width; } else { // use grid_puts_len() to truncate the text - grid_puts(&pum_grid, (char *)st, row, grid_col, attr); + grid_puts(&pum_grid, st, row, grid_col, attr); xfree(st); grid_col += width; } @@ -751,20 +771,19 @@ static bool pum_set_selected(int n, int repeat) } if (res == OK) { - char_u *p, *e; + char *p, *e; linenr_T lnum = 0; for (p = pum_array[pum_selected].pum_info; *p != NUL;) { - e = (char_u *)vim_strchr((char *)p, '\n'); + e = vim_strchr(p, '\n'); if (e == NULL) { - ml_append(lnum++, (char *)p, 0, false); + ml_append(lnum++, p, 0, false); break; - } else { - *e = NUL; - ml_append(lnum++, (char *)p, (int)(e - p + 1), false); - *e = '\n'; - p = e + 1; } + *e = NUL; + ml_append(lnum++, p, (int)(e - p + 1), false); + *e = '\n'; + p = e + 1; } // Increase the height of the preview window to show the @@ -805,7 +824,7 @@ static bool pum_set_selected(int n, int repeat) // When the preview window was resized we need to // update the view on the buffer. Only go back to // the window when needed, otherwise it will always be - // redraw. + // redrawn. if (resized) { no_u_sync++; win_enter(curwin_save, true); @@ -868,6 +887,7 @@ void pum_check_clear(void) grid_free(&pum_grid); } pum_is_drawn = false; + pum_external = false; } } @@ -901,6 +921,17 @@ void pum_recompose(void) ui_comp_compose_grid(&pum_grid); } +void pum_ext_select_item(int item, bool insert, bool finish) +{ + if (!pum_visible() || item < -1 || item >= pum_size) { + return; + } + pum_want.active = true; + pum_want.item = item; + pum_want.insert = insert; + pum_want.finish = finish; +} + /// Gets the height of the menu. /// /// @return the height of the popup menu, the number of entries visible. @@ -1030,10 +1061,16 @@ void pum_show_popupmenu(vimmenu_T *menu) pumitem_T *array = (pumitem_T *)xcalloc((size_t)pum_size, sizeof(pumitem_T)); for (vimmenu_T *mp = menu->children; mp != NULL; mp = mp->next) { + char *s = NULL; + // Make a copy of the text, the menu may be redefined in a callback. if (menu_is_separator(mp->dname)) { - array[idx++].pum_text = (char_u *)""; + s = ""; } else if (mp->modes & mp->enabled & mode) { - array[idx++].pum_text = (char_u *)mp->dname; + s = mp->dname; + } + if (s != NULL) { + s = xstrdup(s); + array[idx++].pum_text = s; } } @@ -1104,6 +1141,9 @@ void pum_show_popupmenu(vimmenu_T *menu) } } + for (idx = 0; idx < pum_size; idx++) { + xfree(array[idx].pum_text); + } xfree(array); pum_undisplay(true); if (!p_mousemev) { diff --git a/src/nvim/popupmenu.h b/src/nvim/popupmenu.h index 851ad31486..08b791c509 100644 --- a/src/nvim/popupmenu.h +++ b/src/nvim/popupmenu.h @@ -1,6 +1,8 @@ #ifndef NVIM_POPUPMENU_H #define NVIM_POPUPMENU_H +#include <stdbool.h> + #include "nvim/grid_defs.h" #include "nvim/macros.h" #include "nvim/types.h" @@ -8,14 +10,22 @@ /// Used for popup menu items. typedef struct { - char_u *pum_text; // main menu text - char_u *pum_kind; // extra kind text (may be truncated) - char_u *pum_extra; // extra menu text (may be truncated) - char_u *pum_info; // extra info + char *pum_text; // main menu text + char *pum_kind; // extra kind text (may be truncated) + char *pum_extra; // extra menu text (may be truncated) + char *pum_info; // extra info } pumitem_T; EXTERN ScreenGrid pum_grid INIT(= SCREEN_GRID_INIT); +/// state for pum_ext_select_item. +EXTERN struct { + bool active; + int item; + bool insert; + bool finish; +} pum_want; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "popupmenu.h.generated.h" #endif diff --git a/src/nvim/profile.c b/src/nvim/profile.c index 50a8a371f5..fd024f2d38 100644 --- a/src/nvim/profile.c +++ b/src/nvim/profile.c @@ -3,20 +3,34 @@ #include <assert.h> #include <math.h> +#include <stdbool.h> +#include <stdint.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> -#include "nvim/assert.h" +#include "nvim/ascii.h" #include "nvim/charset.h" #include "nvim/debugger.h" #include "nvim/eval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/fileio.h" -#include "nvim/func_attr.h" -#include "nvim/globals.h" // for the global `time_fd` (startuptime) +#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/hashtab.h" +#include "nvim/keycodes.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option_defs.h" #include "nvim/os/os.h" #include "nvim/os/time.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/runtime.h" +#include "nvim/types.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -283,9 +297,9 @@ void ex_profile(exarg_T *eap) len = (int)(e - eap->arg); e = skipwhite(e); - if (len == 5 && STRNCMP(eap->arg, "start", 5) == 0 && *e != NUL) { + if (len == 5 && strncmp(eap->arg, "start", 5) == 0 && *e != NUL) { xfree(profile_fname); - profile_fname = (char *)expand_env_save_opt((char_u *)e, true); + profile_fname = expand_env_save_opt(e, true); do_profiling = PROF_YES; profile_set_wait(profile_zero()); set_vim_var_nr(VV_PROFILING, 1L); @@ -340,7 +354,6 @@ char *get_profile_name(expand_T *xp, int idx) switch (pexpand_what) { case PEXP_SUBCMD: return pexpand_cmds[idx]; - // case PEXP_FUNC: TODO default: return NULL; } @@ -354,18 +367,22 @@ void set_context_in_profile_cmd(expand_T *xp, const char *arg) pexpand_what = PEXP_SUBCMD; xp->xp_pattern = (char *)arg; - char_u *const end_subcmd = (char_u *)skiptowhite(arg); + char *const end_subcmd = skiptowhite(arg); if (*end_subcmd == NUL) { return; } - if ((const char *)end_subcmd - arg == 5 && strncmp(arg, "start", 5) == 0) { + if ((end_subcmd - arg == 5 && strncmp(arg, "start", 5) == 0) + || (end_subcmd - arg == 4 && strncmp(arg, "file", 4) == 0)) { xp->xp_context = EXPAND_FILES; - xp->xp_pattern = skipwhite((char *)end_subcmd); + xp->xp_pattern = skipwhite(end_subcmd); + return; + } else if (end_subcmd - arg == 4 && strncmp(arg, "func", 4) == 0) { + xp->xp_context = EXPAND_USER_FUNC; + xp->xp_pattern = skipwhite(end_subcmd); return; } - // TODO(tarruda): expand function names after "func" xp->xp_context = EXPAND_NOTHING; } @@ -398,7 +415,7 @@ bool prof_def_func(void) /// Print the count and times for one function or function line. /// /// @param prefer_self when equal print only self time -static void prof_func_line(FILE *fd, int count, proftime_T *total, proftime_T *self, +static void prof_func_line(FILE *fd, int count, const proftime_T *total, const proftime_T *self, bool prefer_self) { if (count > 0) { @@ -430,7 +447,7 @@ static void prof_sort_list(FILE *fd, ufunc_T **sorttab, int st_len, char *title, fp = sorttab[i]; prof_func_line(fd, fp->uf_tm_count, &fp->uf_tm_total, &fp->uf_tm_self, prefer_self); - if (fp->uf_name[0] == K_SPECIAL) { + if ((uint8_t)fp->uf_name[0] == K_SPECIAL) { fprintf(fd, " <SNR>%s()\n", fp->uf_name + 3); } else { fprintf(fd, " %s()\n", fp->uf_name); @@ -601,7 +618,7 @@ static void func_dump_profile(FILE *fd) if (fp->uf_prof_initialized) { sorttab[st_len++] = fp; - if (fp->uf_name[0] == K_SPECIAL) { + if ((uint8_t)fp->uf_name[0] == K_SPECIAL) { fprintf(fd, "FUNCTION <SNR>%s()\n", fp->uf_name + 3); } else { fprintf(fd, "FUNCTION %s()\n", fp->uf_name); @@ -684,19 +701,19 @@ void script_prof_save(proftime_T *tm) } /// Count time spent in children after invoking another script or function. -void script_prof_restore(proftime_T *tm) +void script_prof_restore(const proftime_T *tm) { - scriptitem_T *si; + if (!SCRIPT_ID_VALID(current_sctx.sc_sid)) { + return; + } - if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) { - si = &SCRIPT_ITEM(current_sctx.sc_sid); - if (si->sn_prof_on && --si->sn_pr_nest == 0) { - si->sn_pr_child = profile_end(si->sn_pr_child); - // don't count wait time - si->sn_pr_child = profile_sub_wait(*tm, si->sn_pr_child); - si->sn_pr_children = profile_add(si->sn_pr_children, si->sn_pr_child); - si->sn_prl_children = profile_add(si->sn_prl_children, si->sn_pr_child); - } + scriptitem_T *si = &SCRIPT_ITEM(current_sctx.sc_sid); + if (si->sn_prof_on && --si->sn_pr_nest == 0) { + si->sn_pr_child = profile_end(si->sn_pr_child); + // don't count wait time + si->sn_pr_child = profile_sub_wait(*tm, si->sn_pr_child); + si->sn_pr_children = profile_add(si->sn_pr_children, si->sn_pr_child); + si->sn_prl_children = profile_add(si->sn_prl_children, si->sn_pr_child); } } @@ -728,7 +745,7 @@ static void script_dump_profile(FILE *fd) // Keep going till the end of file, so that trailing // continuation lines are listed. for (int i = 0;; i++) { - if (vim_fgets((char_u *)IObuff, IOSIZE, sfd)) { + if (vim_fgets(IObuff, IOSIZE, sfd)) { break; } // When a line has been truncated, append NL, taking care @@ -770,17 +787,17 @@ static void script_dump_profile(FILE *fd) /// Dump the profiling info. void profile_dump(void) { - FILE *fd; + if (profile_fname == NULL) { + return; + } - if (profile_fname != NULL) { - fd = os_fopen(profile_fname, "w"); - if (fd == NULL) { - semsg(_(e_notopen), profile_fname); - } else { - script_dump_profile(fd); - func_dump_profile(fd); - fclose(fd); - } + FILE *fd = os_fopen(profile_fname, "w"); + if (fd == NULL) { + semsg(_(e_notopen), profile_fname); + } else { + script_dump_profile(fd); + func_dump_profile(fd); + fclose(fd); } } diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index f9d139c466..5518fdfa51 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4,30 +4,44 @@ // quickfix.c: functions for quickfix mode, using a file with error messages #include <assert.h> +#include <errno.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> +#include <time.h> -#include "nvim/api/private/helpers.h" #include "nvim/arglist.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/eval/window.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/help.h" +#include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/mbyte.h" +#include "nvim/memfile_defs.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" @@ -35,14 +49,16 @@ #include "nvim/normal.h" #include "nvim/option.h" #include "nvim/optionstr.h" +#include "nvim/os/fs_defs.h" #include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os_unix.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/search.h" #include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -176,6 +192,7 @@ enum { QF_NOMEM = 3, QF_IGNORE_LINE = 4, QF_MULTISCAN = 5, + QF_ABORT = 6, }; /// State information used to parse lines and add entries to a quickfix/location @@ -336,8 +353,7 @@ static const size_t LINE_MAXLEN = 4096; static struct fmtpattern { char convchar; char *pattern; -} fmt_pat[FMT_PATTERNS] = -{ +} fmt_pat[FMT_PATTERNS] = { { 'f', ".\\+" }, // only used when at end { 'n', "\\d\\+" }, // 1 { 'l', "\\d\\+" }, // 2 @@ -368,9 +384,9 @@ static char *efmpat_to_regpat(const char *efmpat, char *regpat, efm_T *efminfo, return NULL; } if ((idx && idx < FMT_PATTERN_R - && vim_strchr("DXOPQ", efminfo->prefix) != NULL) + && vim_strchr("DXOPQ", (uint8_t)efminfo->prefix) != NULL) || (idx == FMT_PATTERN_R - && vim_strchr("OPQ", efminfo->prefix) == NULL)) { + && vim_strchr("OPQ", (uint8_t)efminfo->prefix) == NULL)) { semsg(_("E373: Unexpected %%%c in format string"), *efmpat); return NULL; } @@ -452,10 +468,10 @@ static char *scanf_fmt_to_regpat(const char **pefmp, const char *efm, int len, c static const char *efm_analyze_prefix(const char *efmp, efm_T *efminfo) FUNC_ATTR_NONNULL_ALL { - if (vim_strchr("+-", *efmp) != NULL) { + if (vim_strchr("+-", (uint8_t)(*efmp)) != NULL) { efminfo->flags = *efmp++; } - if (vim_strchr("DXAEWINCZGOPQ", *efmp) != NULL) { + if (vim_strchr("DXAEWINCZGOPQ", (uint8_t)(*efmp)) != NULL) { efminfo->prefix = *efmp; } else { semsg(_("E376: Invalid %%%c in format string prefix"), *efmp); @@ -494,7 +510,7 @@ static int efm_to_regpat(const char *efm, int len, efm_T *fmt_ptr, char *regpat) if (ptr == NULL) { return FAIL; } - } else if (vim_strchr("%\\.^$~[", *efmp) != NULL) { + } else if (vim_strchr("%\\.^$~[", (uint8_t)(*efmp)) != NULL) { *ptr++ = *efmp; // regexp magic characters } else if (*efmp == '#') { *ptr++ = '*'; @@ -514,7 +530,7 @@ static int efm_to_regpat(const char *efm, int len, efm_T *fmt_ptr, char *regpat) } else { // copy normal character if (*efmp == '\\' && efmp + 1 < efm + len) { efmp++; - } else if (vim_strchr(".*^$~[", *efmp) != NULL) { + } else if (vim_strchr(".*^$~[", (uint8_t)(*efmp)) != NULL) { *ptr++ = '\\'; // escape regexp atoms } if (*efmp) { @@ -562,7 +578,7 @@ static size_t efm_regpat_bufsz(char *efm) } /// Return the length of a 'errorformat' option part (separated by ","). -static int efm_option_part_len(char *efm) +static int efm_option_part_len(const char *efm) { int len; @@ -657,7 +673,7 @@ static int qf_get_next_str_line(qfstate_T *state) if (len > IOSIZE - 2) { state->linebuf = qf_grow_linebuf(state, len); } else { - state->linebuf = (char *)IObuff; + state->linebuf = IObuff; state->linelen = len; } memcpy(state->linebuf, p_str, state->linelen); @@ -692,12 +708,12 @@ static int qf_get_next_list_line(qfstate_T *state) if (len > IOSIZE - 2) { state->linebuf = qf_grow_linebuf(state, len); } else { - state->linebuf = (char *)IObuff; + state->linebuf = IObuff; state->linelen = len; } - STRLCPY(state->linebuf, TV_LIST_ITEM_TV(p_li)->vval.v_string, - state->linelen + 1); + xstrlcpy(state->linebuf, TV_LIST_ITEM_TV(p_li)->vval.v_string, + state->linelen + 1); state->p_li = TV_LIST_ITEM_NEXT(state->p_list, p_li); return QF_OK; @@ -717,10 +733,10 @@ static int qf_get_next_buf_line(qfstate_T *state) if (len > IOSIZE - 2) { state->linebuf = qf_grow_linebuf(state, len); } else { - state->linebuf = (char *)IObuff; + state->linebuf = IObuff; state->linelen = len; } - STRLCPY(state->linebuf, p_buf, state->linelen + 1); + xstrlcpy(state->linebuf, p_buf, state->linelen + 1); return QF_OK; } @@ -730,7 +746,7 @@ static int qf_get_next_file_line(qfstate_T *state) { retry: errno = 0; - if (fgets((char *)IObuff, IOSIZE, state->fd) == NULL) { + if (fgets(IObuff, IOSIZE, state->fd) == NULL) { if (errno == EINTR) { goto retry; } @@ -780,7 +796,7 @@ retry: // The current line is longer than LINE_MAXLEN, continue reading but // discard everything until EOL or EOF is reached. errno = 0; - if (fgets((char *)IObuff, IOSIZE, state->fd) == NULL) { + if (fgets(IObuff, IOSIZE, state->fd) == NULL) { if (errno == EINTR) { continue; } @@ -794,15 +810,15 @@ retry: state->linebuf = state->growbuf; state->linelen = growbuflen; } else { - state->linebuf = (char *)IObuff; + state->linebuf = IObuff; } // Convert a line if it contains a non-ASCII character - if (state->vc.vc_type != CONV_NONE && has_non_ascii((char_u *)state->linebuf)) { + if (state->vc.vc_type != CONV_NONE && has_non_ascii(state->linebuf)) { char *line = string_convert(&state->vc, state->linebuf, &state->linelen); if (line != NULL) { if (state->linelen < IOSIZE) { - STRLCPY(state->linebuf, line, state->linelen + 1); + xstrlcpy(state->linebuf, line, state->linelen + 1); xfree(line); } else { xfree(state->growbuf); @@ -852,7 +868,7 @@ static int qf_get_nextline(qfstate_T *state) #endif } - remove_bom((char_u *)state->linebuf); + remove_bom(state->linebuf); return QF_OK; } @@ -909,7 +925,7 @@ restofline: // match or no match. fields->valid = true; for (; fmt_ptr != NULL; fmt_ptr = fmt_ptr->next) { - idx = (char_u)fmt_ptr->prefix; + idx = (uint8_t)fmt_ptr->prefix; status = qf_parse_get_fields(linebuf, linelen, fmt_ptr, fields, qfl->qf_multiline, qfl->qf_multiscan, &tail); @@ -1058,6 +1074,7 @@ static int qf_init_ext(qf_info_T *qi, int qf_idx, const char *restrict efile, bu { qfstate_T state = { 0 }; qffields_T fields = { 0 }; + qfline_T *old_last = NULL; static efm_T *fmt_first = NULL; static char *last_efm = NULL; int retval = -1; // default: return error flag @@ -1071,7 +1088,6 @@ static int qf_init_ext(qf_info_T *qi, int qf_idx, const char *restrict efile, bu } qf_list_T *qfl; - qfline_T *old_last = NULL; bool adding = false; if (newlist || qf_idx == qi->qf_listcount) { // make place for a new list @@ -1174,13 +1190,15 @@ static void qf_store_title(qf_list_T *qfl, const char *title) { XFREE_CLEAR(qfl->qf_title); - if (title != NULL) { - size_t len = strlen(title) + 1; - char *p = xmallocz(len); - - qfl->qf_title = p; - STRLCPY(p, title, len + 1); + if (title == NULL) { + return; } + + size_t len = strlen(title) + 1; + char *p = xmallocz(len); + + qfl->qf_title = p; + xstrlcpy(p, title, len + 1); } /// The title of a quickfix/location list is set, by default, to the command @@ -1323,9 +1341,9 @@ static int qf_parse_fmt_t(regmatch_T *rmp, int midx, qffields_T *fields) return QF_OK; } -/// Parse the match for '%+' format pattern. The whole matching line is included -/// in the error string. Return the matched line in "fields->errmsg". -static void qf_parse_fmt_plus(const char *linebuf, size_t linelen, qffields_T *fields) +/// Copy a non-error line into the error string. Return the matched line in +/// "fields->errmsg". +static int copy_nonerror_line(const char *linebuf, size_t linelen, qffields_T *fields) FUNC_ATTR_NONNULL_ALL { if (linelen >= fields->errmsglen) { @@ -1333,7 +1351,10 @@ static void qf_parse_fmt_plus(const char *linebuf, size_t linelen, qffields_T *f fields->errmsg = xrealloc(fields->errmsg, linelen + 1); fields->errmsglen = linelen + 1; } - STRLCPY(fields->errmsg, linebuf, linelen + 1); + // copy whole line to error message + xstrlcpy(fields->errmsg, linebuf, linelen + 1); + + return QF_OK; } /// Parse the match for error message ('%m') pattern in regmatch. @@ -1349,7 +1370,7 @@ static int qf_parse_fmt_m(regmatch_T *rmp, int midx, qffields_T *fields) fields->errmsg = xrealloc(fields->errmsg, len + 1); fields->errmsglen = len + 1; } - STRLCPY(fields->errmsg, rmp->startp[midx], len + 1); + xstrlcpy(fields->errmsg, rmp->startp[midx], len + 1); return QF_OK; } @@ -1464,7 +1485,7 @@ static int qf_parse_match(char *linebuf, size_t linelen, efm_T *fmt_ptr, regmatc if ((idx == 'C' || idx == 'Z') && !qf_multiline) { return QF_FAIL; } - if (vim_strchr("EWIN", idx) != NULL) { + if (vim_strchr("EWIN", (uint8_t)idx) != NULL) { fields->type = idx; } else { fields->type = 0; @@ -1480,7 +1501,7 @@ static int qf_parse_match(char *linebuf, size_t linelen, efm_T *fmt_ptr, regmatc status = qf_parse_fmt_f(regmatch, midx, fields, idx); } else if (i == FMT_PATTERN_M) { if (fmt_ptr->flags == '+' && !qf_multiscan) { // %+ - qf_parse_fmt_plus(linebuf, linelen, fields); + status = copy_nonerror_line(linebuf, linelen, fields); } else if (midx > 0) { // %m status = qf_parse_fmt_m(regmatch, midx, fields); } @@ -1505,7 +1526,7 @@ static int qf_parse_match(char *linebuf, size_t linelen, efm_T *fmt_ptr, regmatc static int qf_parse_get_fields(char *linebuf, size_t linelen, efm_T *fmt_ptr, qffields_T *fields, int qf_multiline, int qf_multiscan, char **tail) { - if (qf_multiscan && vim_strchr("OPQ", fmt_ptr->prefix) == NULL) { + if (qf_multiscan && vim_strchr("OPQ", (uint8_t)fmt_ptr->prefix) == NULL) { return QF_FAIL; } @@ -1588,15 +1609,8 @@ static int qf_parse_line_nomatch(char *linebuf, size_t linelen, qffields_T *fiel fields->namebuf[0] = NUL; // no match found, remove file name fields->lnum = 0; // don't jump to this line fields->valid = false; - if (linelen >= fields->errmsglen) { - // linelen + null terminator - fields->errmsg = xrealloc(fields->errmsg, linelen + 1); - fields->errmsglen = linelen + 1; - } - // copy whole line to error message - STRLCPY(fields->errmsg, linebuf, linelen + 1); - return QF_OK; + return copy_nonerror_line(linebuf, linelen, fields); } /// Parse multi-line error format prefixes (%C and %Z) @@ -1672,14 +1686,16 @@ int qf_stack_get_bufnr(void) 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; - } + if (qi->qf_bufnr == INVALID_QFBUFNR) { + return; + } + + 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; } } @@ -1790,7 +1806,7 @@ void check_quickfix_busy(void) /// @param type type character /// @param valid valid entry /// -/// @returns QF_OK or QF_FAIL. +/// @return QF_OK on success or QF_FAIL on failure. static int qf_add_entry(qf_list_T *qfl, char *dir, char *fname, char *module, int bufnum, char *mesg, linenr_T lnum, linenr_T end_lnum, int col, int end_col, char vis_col, char *pattern, int nr, char type, char valid) @@ -2063,7 +2079,7 @@ static int qf_get_fnum(qf_list_T *qfl, char *directory, char *fname) } slash_adjust(fname); #endif - if (directory != NULL && !vim_isAbsName((char_u *)fname)) { + if (directory != NULL && !vim_isAbsName(fname)) { ptr = concat_fnames(directory, fname, true); // Here we check if the file really exists. // This should normally be true, but if make works without @@ -2116,7 +2132,7 @@ static char *qf_push_dir(char *dirbuf, struct dir_stack_T **stackptr, bool is_fi *stackptr = ds_new; // store directory on the stack - if (vim_isAbsName((char_u *)dirbuf) + if (vim_isAbsName(dirbuf) || (*stackptr)->next == NULL || is_file_stack) { (*stackptr)->dirname = xstrdup(dirbuf); @@ -2153,12 +2169,11 @@ static char *qf_push_dir(char *dirbuf, struct dir_stack_T **stackptr, bool is_fi if ((*stackptr)->dirname != NULL) { return (*stackptr)->dirname; - } else { - ds_ptr = *stackptr; - *stackptr = (*stackptr)->next; - xfree(ds_ptr); - return NULL; } + ds_ptr = *stackptr; + *stackptr = (*stackptr)->next; + xfree(ds_ptr); + return NULL; } // pop dirbuf from the directory stack and return previous directory or NULL if @@ -2686,6 +2701,10 @@ static int qf_jump_to_usable_window(int qf_fnum, bool newwin, int *opened_window } /// Edit the selected file or help file. +/// @return OK if successfully edited the file. +/// FAIL on failing to open the buffer. +/// QF_ABORT if the quickfix/location list was freed by an autocmd +/// when opening the buffer. static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int prev_winid, int *opened_window) { @@ -2702,11 +2721,10 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int if (!can_abandon(curbuf, forceit)) { no_write_message(); return FAIL; - } else { - retval = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, (linenr_T)1, - ECMD_HIDE + ECMD_SET_HELP, - prev_winid == curwin->handle ? curwin : NULL); } + retval = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, (linenr_T)1, + ECMD_HIDE + ECMD_SET_HELP, + prev_winid == curwin->handle ? curwin : NULL); } else { retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1, GETF_SETMARK | GETF_SWITCH, forceit); @@ -2719,13 +2737,13 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int if (wp == NULL && curwin->w_llist != qi) { emsg(_("E924: Current window was closed")); *opened_window = false; - return NOTDONE; + return QF_ABORT; } } if (qfl_type == QFLT_QUICKFIX && !qflist_valid(NULL, save_qfid)) { emsg(_(e_current_quickfix_list_was_changed)); - return NOTDONE; + return QF_ABORT; } if (old_qf_curlist != qi->qf_curlist // -V560 @@ -2736,7 +2754,7 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int } else { emsg(_(e_current_location_list_was_changed)); } - return NOTDONE; + return QF_ABORT; } return retval; @@ -2771,7 +2789,7 @@ static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char qf_viscol, char // Move the cursor to the first line in the buffer pos_T save_cursor = curwin->w_cursor; curwin->w_cursor.lnum = 0; - if (!do_search(NULL, '/', '/', (char_u *)qf_pattern, (long)1, SEARCH_KEEP, NULL)) { + if (!do_search(NULL, '/', '/', qf_pattern, (long)1, SEARCH_KEEP, NULL)) { curwin->w_cursor = save_cursor; } } @@ -2789,13 +2807,13 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, buf update_screen(); } } - snprintf((char *)IObuff, IOSIZE, _("(%d of %d)%s%s: "), qf_index, + snprintf(IObuff, IOSIZE, _("(%d of %d)%s%s: "), qf_index, qf_get_curlist(qi)->qf_count, qf_ptr->qf_cleared ? _(" (line deleted)") : "", qf_types(qf_ptr->qf_type, qf_ptr->qf_nr)); // Add the message, skipping leading whitespace and newlines. int len = (int)strlen(IObuff); - qf_fmt_text(skipwhite(qf_ptr->qf_text), (char *)IObuff + len, IOSIZE - len); + qf_fmt_text(skipwhite(qf_ptr->qf_text), IObuff + len, IOSIZE - len); // Output the message. Overwrite to avoid scrolling when the 'O' // flag is present in 'shortmess'; But when not jumping, print the @@ -2807,16 +2825,17 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, buf msg_scroll = false; } msg_ext_set_kind("quickfix"); - msg_attr_keep((char *)IObuff, 0, true, false); + msg_attr_keep(IObuff, 0, true, false); msg_scroll = (int)i; } /// Find a usable window for opening a file from the quickfix/location list. If /// a window is not found then open a new window. If 'newwin' is true, then open /// a new window. -/// Returns OK if successfully jumped or opened a window. Returns FAIL if not -/// able to jump/open a window. Returns NOTDONE if a file is not associated -/// with the entry. +/// @return OK if successfully jumped or opened a window. +/// FAIL if not able to jump/open a window. +/// NOTDONE if a file is not associated with the entry. +/// QF_ABORT if the quickfix/location list was modified by an autocmd. static int qf_jump_open_window(qf_info_T *qi, qfline_T *qf_ptr, bool newwin, int *opened_window) { qf_list_T *qfl = qf_get_curlist(qi); @@ -2838,7 +2857,7 @@ static int qf_jump_open_window(qf_info_T *qi, qfline_T *qf_ptr, bool newwin, int } else { emsg(_(e_current_location_list_was_changed)); } - return FAIL; + return QF_ABORT; } // If currently in the quickfix window, find another window to show the @@ -2863,7 +2882,7 @@ static int qf_jump_open_window(qf_info_T *qi, qfline_T *qf_ptr, bool newwin, int } else { emsg(_(e_current_location_list_was_changed)); } - return FAIL; + return QF_ABORT; } return OK; @@ -2872,9 +2891,9 @@ static int qf_jump_open_window(qf_info_T *qi, qfline_T *qf_ptr, bool newwin, int /// Edit a selected file from the quickfix/location list and jump to a /// particular line/column, adjust the folds and display a message about the /// jump. -/// Returns OK on success and FAIL on failing to open the file/buffer. Returns -/// NOTDONE if the quickfix/location list is freed by an autocmd when opening -/// the file. +/// @return OK on success and FAIL on failing to open the file/buffer. +/// QF_ABORT 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, int prev_winid, int *opened_window, int openfold, int print_message) { @@ -2968,14 +2987,19 @@ static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, boo if (retval == FAIL) { goto failed; } + if (retval == QF_ABORT) { + qi = NULL; + qf_ptr = NULL; + goto theend; + } if (retval == NOTDONE) { goto theend; } 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 + if (retval == QF_ABORT) { + // Quickfix/location list was modified by an autocmd qi = NULL; qf_ptr = NULL; } @@ -3019,7 +3043,7 @@ static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel) { char *fname = NULL; if (qfp->qf_module != NULL && *qfp->qf_module != NUL) { - vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", qf_idx, qfp->qf_module); + vim_snprintf(IObuff, IOSIZE, "%2d %s", qf_idx, qfp->qf_module); } else { buf_T *buf; if (qfp->qf_fnum != 0 @@ -3030,9 +3054,9 @@ static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel) } } if (fname == NULL) { - snprintf((char *)IObuff, IOSIZE, "%2d", qf_idx); + snprintf(IObuff, IOSIZE, "%2d", qf_idx); } else { - vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", qf_idx, fname); + vim_snprintf(IObuff, IOSIZE, "%2d %s", qf_idx, fname); } } @@ -3057,7 +3081,7 @@ static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel) } msg_putchar('\n'); - msg_outtrans_attr((char *)IObuff, cursel ? HL_ATTR(HLF_QFL) : qfFileAttr); + msg_outtrans_attr(IObuff, cursel ? HL_ATTR(HLF_QFL) : qfFileAttr); if (qfp->qf_lnum != 0) { msg_puts_attr(":", qfSepAttr); @@ -3065,13 +3089,13 @@ static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel) if (qfp->qf_lnum == 0) { IObuff[0] = NUL; } else { - qf_range_text(qfp, (char *)IObuff, IOSIZE); + qf_range_text(qfp, IObuff, IOSIZE); } - vim_snprintf((char *)IObuff + strlen(IObuff), IOSIZE, "%s", qf_types(qfp->qf_type, qfp->qf_nr)); + vim_snprintf(IObuff + strlen(IObuff), IOSIZE, "%s", qf_types(qfp->qf_type, qfp->qf_nr)); msg_puts_attr((const char *)IObuff, qfLineAttr); msg_puts_attr(":", qfSepAttr); if (qfp->qf_pattern != NULL) { - qf_fmt_text(qfp->qf_pattern, (char *)IObuff, IOSIZE); + qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE); msg_puts((const char *)IObuff); msg_puts_attr(":", qfSepAttr); } @@ -3148,15 +3172,15 @@ void qf_list(exarg_T *eap) // Get the attributes for the different quickfix highlight items. Note // that this depends on syntax items defined in the qf.vim syntax file - qfFileAttr = syn_name2attr((char_u *)"qfFileName"); + qfFileAttr = syn_name2attr("qfFileName"); if (qfFileAttr == 0) { qfFileAttr = HL_ATTR(HLF_D); } - qfSepAttr = syn_name2attr((char_u *)"qfSeparator"); + qfSepAttr = syn_name2attr("qfSeparator"); if (qfSepAttr == 0) { qfSepAttr = HL_ATTR(HLF_D); } - qfLineAttr = syn_name2attr((char_u *)"qfLineNr"); + qfLineAttr = syn_name2attr("qfLineNr"); if (qfLineAttr == 0) { qfLineAttr = HL_ATTR(HLF_N); } @@ -3461,12 +3485,10 @@ void qf_view_result(bool split) { qf_info_T *qi = &ql_info; - if (!bt_quickfix(curbuf)) { - return; - } if (IS_LL_WINDOW(curwin)) { qi = GET_LOC_LIST(curwin); } + if (qf_list_empty(qf_get_curlist(qi))) { emsg(_(e_no_errors)); return; @@ -3832,10 +3854,11 @@ static buf_T *qf_find_buf(qf_info_T *qi) } /// Process the 'quickfixtextfunc' option value. -/// @return OK or FAIL -int qf_process_qftf_option(void) +void qf_process_qftf_option(char **errmsg) { - return option_set_callback_func(p_qftf, &qftf_cb); + if (option_set_callback_func(p_qftf, &qftf_cb) == FAIL) { + *errmsg = e_invarg; + } } /// Update the w:quickfix_title variable in the quickfix/location list window in @@ -3860,48 +3883,61 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last) { // Check if a buffer for the quickfix list exists. Update it. buf_T *buf = qf_find_buf(qi); - if (buf != NULL) { - linenr_T old_line_count = buf->b_ml.ml_line_count; - int qf_winid = 0; - - win_T *win; - if (IS_LL_STACK(qi)) { - if (curwin->w_llist == qi) { - win = curwin; - } else { - win = qf_find_win_with_loclist(qi); - if (win == NULL) { - return; - } + if (buf == NULL) { + return; + } + + linenr_T old_line_count = buf->b_ml.ml_line_count; + int qf_winid = 0; + + win_T *win; + if (IS_LL_STACK(qi)) { + if (curwin->w_llist == qi) { + win = curwin; + } else { + // Find the file window (non-quickfix) with this location list + win = qf_find_win_with_loclist(qi); + if (win == NULL) { + // File window is not found. Find the location list window. + win = qf_find_win(qi); + } + if (win == NULL) { + return; } - qf_winid = (int)win->handle; } + qf_winid = (int)win->handle; + } - aco_save_T aco; + // autocommands may cause trouble + incr_quickfix_busy(); - if (old_last == NULL) { - // set curwin/curbuf to buf and save a few things - aucmd_prepbuf(&aco, buf); - } + aco_save_T aco; - qf_update_win_titlevar(qi); + if (old_last == NULL) { + // set curwin/curbuf to buf and save a few things + aucmd_prepbuf(&aco, buf); + } - qf_fill_buffer(qf_get_curlist(qi), buf, old_last, qf_winid); - buf_inc_changedtick(buf); + qf_update_win_titlevar(qi); - if (old_last == NULL) { - (void)qf_win_pos_update(qi, 0); + qf_fill_buffer(qf_get_curlist(qi), buf, old_last, qf_winid); + buf_inc_changedtick(buf); - // restore curwin/curbuf and a few other things - aucmd_restbuf(&aco); - } + if (old_last == NULL) { + (void)qf_win_pos_update(qi, 0); - // Only redraw when added lines are visible. This avoids flickering when - // the added lines are not visible. - if ((win = qf_find_win(qi)) != NULL && old_line_count < win->w_botline) { - redraw_buf_later(buf, UPD_NOT_VALID); - } + // restore curwin/curbuf and a few other things + aucmd_restbuf(&aco); + } + + // Only redraw when added lines are visible. This avoids flickering when + // the added lines are not visible. + if ((win = qf_find_win(qi)) != NULL && old_line_count < win->w_botline) { + redraw_buf_later(buf, UPD_NOT_VALID); } + + // always called after incr_quickfix_busy() + decr_quickfix_busy(); } // Add an error line to the quickfix buffer. @@ -3912,31 +3948,31 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum, const qfli // 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); + xstrlcpy(IObuff, qftf_str, IOSIZE); } else { buf_T *errbuf; int len; if (qfp->qf_module != NULL) { - STRLCPY(IObuff, qfp->qf_module, IOSIZE); + xstrlcpy(IObuff, qfp->qf_module, IOSIZE); len = (int)strlen(IObuff); } else if (qfp->qf_fnum != 0 && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL && errbuf->b_fname != NULL) { if (qfp->qf_type == 1) { // :helpgrep - STRLCPY(IObuff, path_tail(errbuf->b_fname), IOSIZE); + xstrlcpy(IObuff, path_tail(errbuf->b_fname), IOSIZE); } else { // Shorten the file name if not done already. // For optimization, do this only for the first entry in a // buffer. if (first_bufline && (errbuf->b_sfname == NULL - || path_is_absolute((char_u *)errbuf->b_sfname))) { + || path_is_absolute(errbuf->b_sfname))) { if (*dirname == NUL) { - os_dirname((char_u *)dirname, MAXPATHL); + os_dirname(dirname, MAXPATHL); } - shorten_buf_fname(errbuf, (char_u *)dirname, false); + shorten_buf_fname(errbuf, dirname, false); } - STRLCPY(IObuff, errbuf->b_fname, IOSIZE); + xstrlcpy(IObuff, errbuf->b_fname, IOSIZE); } len = (int)strlen(IObuff); } else { @@ -3946,14 +3982,14 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum, const qfli IObuff[len++] = '|'; } if (qfp->qf_lnum > 0) { - qf_range_text(qfp, (char *)IObuff + len, IOSIZE - len); + qf_range_text(qfp, IObuff + len, IOSIZE - len); len += (int)strlen(IObuff + len); - snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), "%s", qf_types(qfp->qf_type, - qfp->qf_nr)); + snprintf(IObuff + len, (size_t)(IOSIZE - len), "%s", qf_types(qfp->qf_type, + qfp->qf_nr)); len += (int)strlen(IObuff + len); } else if (qfp->qf_pattern != NULL) { - qf_fmt_text(qfp->qf_pattern, (char *)IObuff + len, IOSIZE - len); + qf_fmt_text(qfp->qf_pattern, IObuff + len, IOSIZE - len); len += (int)strlen(IObuff + len); } if (len < IOSIZE - 2) { @@ -3965,11 +4001,11 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum, const qfli // For an unrecognized line keep the indent, the compiler may // mark a word with ^^^^. qf_fmt_text(len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text, - (char *)IObuff + len, IOSIZE - len); + IObuff + len, IOSIZE - len); } - if (ml_append_buf(buf, lnum, (char_u *)IObuff, - (colnr_T)STRLEN(IObuff) + 1, false) == FAIL) { + if (ml_append_buf(buf, lnum, IObuff, + (colnr_T)strlen(IObuff) + 1, false) == FAIL) { return FAIL; } return OK; @@ -3981,6 +4017,12 @@ static list_T *call_qftf_func(qf_list_T *qfl, int qf_winid, long start_idx, long { Callback *cb = &qftf_cb; list_T *qftf_list = NULL; + static bool recursive = false; + + if (recursive) { + return NULL; // this doesn't work properly recursively + } + recursive = true; // If 'quickfixtextfunc' is set, then use the user-supplied function to get // the text to display. Use the local value of 'quickfixtextfunc' if it is @@ -4014,6 +4056,7 @@ static list_T *call_qftf_func(qf_list_T *qfl, int qf_winid, long start_idx, long tv_dict_unref(dict); } + recursive = false; return qftf_list; } @@ -4040,7 +4083,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int q } // Check if there is anything to display - if (qfl != NULL) { + if (qfl != NULL && qfl->qf_start != NULL) { char dirname[MAXPATHL]; *dirname = NUL; @@ -4152,14 +4195,16 @@ static int qf_id2nr(const qf_info_T *const qi, const unsigned qfid) static int qf_restore_list(qf_info_T *qi, unsigned save_qfid) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - if (qf_get_curlist(qi)->qf_id != save_qfid) { - const int curlist = qf_id2nr(qi, save_qfid); - if (curlist < 0) { - // list is not present - return FAIL; - } - qi->qf_curlist = curlist; + if (qf_get_curlist(qi)->qf_id == save_qfid) { + return OK; + } + + const int curlist = qf_id2nr(qi, save_qfid); + if (curlist < 0) { + // list is not present + return FAIL; } + qi->qf_curlist = curlist; return OK; } @@ -4213,12 +4258,12 @@ static char *make_get_auname(cmdidx_T cmdidx) static char *make_get_fullcmd(const char *makecmd, const char *fname) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { - size_t len = STRLEN(p_shq) * 2 + strlen(makecmd) + 1; + size_t len = strlen(p_shq) * 2 + strlen(makecmd) + 1; if (*p_sp != NUL) { len += strlen(p_sp) + strlen(fname) + 3; } char *const cmd = xmalloc(len); - snprintf(cmd, len, "%s%s%s", (char *)p_shq, (char *)makecmd, (char *)p_shq); + snprintf(cmd, len, "%s%s%s", p_shq, (char *)makecmd, p_shq); // If 'shellpipe' empty: don't redirect to 'errorfile'. if (*p_sp != NUL) { @@ -4275,10 +4320,17 @@ void ex_make(exarg_T *eap) incr_quickfix_busy(); - int res = qf_init(wp, fname, (eap->cmdidx != CMD_make - && eap->cmdidx != CMD_lmake) ? p_gefm : p_efm, - (eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd), - qf_cmdtitle(*eap->cmdlinep), enc); + char *errorformat = p_efm; + bool newlist = true; + + if (eap->cmdidx != CMD_make && eap->cmdidx != CMD_lmake) { + errorformat = p_gefm; + } + if (eap->cmdidx == CMD_grepadd || eap->cmdidx == CMD_lgrepadd) { + newlist = false; + } + + int res = qf_init(wp, fname, errorformat, newlist, qf_cmdtitle(*eap->cmdlinep), enc); qf_info_T *qi = &ql_info; if (wp != NULL) { @@ -4997,8 +5049,8 @@ void ex_cfile(exarg_T *eap) // first error. // :caddfile adds to an existing quickfix list. If there is no // quickfix list then a new list is created. - int res = qf_init(wp, (char *)p_ef, p_efm, (eap->cmdidx != CMD_caddfile - && eap->cmdidx != CMD_laddfile), + int res = qf_init(wp, p_ef, p_efm, (eap->cmdidx != CMD_caddfile + && eap->cmdidx != CMD_laddfile), qf_cmdtitle(*eap->cmdlinep), enc); if (wp != NULL) { qi = GET_LOC_LIST(wp); @@ -5062,7 +5114,7 @@ static void vgr_init_regmatch(regmmatch_T *regmatch, char *s) emsg(_(e_noprevre)); return; } - regmatch->regprog = vim_regcomp((char *)last_search_pat(), RE_MAGIC); + regmatch->regprog = vim_regcomp(last_search_pat(), RE_MAGIC); } else { regmatch->regprog = vim_regcomp(s, RE_MAGIC); } @@ -5120,11 +5172,10 @@ static bool vgr_qflist_valid(win_T *wp, qf_info_T *qi, unsigned qfid, char *titl // An autocmd has freed the location list emsg(_(e_current_location_list_was_changed)); return false; - } else { - // Quickfix list is not found, create a new one. - qf_new_list(qi, title); - return true; } + // Quickfix list is not found, create a new one. + qf_new_list(qi, title); + return true; } if (qf_restore_list(qi, qfid) == FAIL) { return false; @@ -5188,8 +5239,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp const size_t sz = sizeof(matches) / sizeof(matches[0]); // Fuzzy string match - while (fuzzy_match((char_u *)str + col, (char_u *)spat, false, &score, matches, - (int)sz) > 0) { + 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. @@ -5236,7 +5286,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp /// Jump to the first match and update the directory. static void vgr_jump_to_match(qf_info_T *qi, int forceit, bool *redraw_for_dummy, - buf_T *first_match_buf, char *target_dir) + buf_T *first_match_buf, char *target_dir) // NOLINT(readability-non-const-parameter) { buf_T *buf = curbuf; qf_jump(qi, 0, 0, forceit); @@ -5263,7 +5313,7 @@ static bool existing_swapfile(const buf_T *buf) { if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) { const char *const fname = buf->b_ml.ml_mfp->mf_fname; - const size_t len = STRLEN(fname); + const size_t len = strlen(fname); return fname[len - 1] != 'p' || fname[len - 2] != 'w'; } @@ -5305,10 +5355,8 @@ static int vgr_process_args(exarg_T *eap, vgr_args_T *args) } // Parse the list of arguments, wildcards have already been expanded. - if (get_arglist_exp(p, &args->fcount, &args->fnames, true) == FAIL) { - return FAIL; - } - if (args->fcount == 0) { + if (get_arglist_exp(p, &args->fcount, &args->fnames, true) == FAIL + || args->fcount == 0) { emsg(_(e_nomatch)); return FAIL; } @@ -5330,11 +5378,11 @@ static int vgr_process_files(win_T *wp, qf_info_T *qi, vgr_args_T *cmd_args, boo // Remember the current directory, because a BufRead autocommand that does // ":lcd %:p:h" changes the meaning of short path names. - os_dirname((char_u *)dirname_start, MAXPATHL); + os_dirname(dirname_start, MAXPATHL); time_t seconds = (time_t)0; for (int fi = 0; fi < cmd_args->fcount && !got_int && cmd_args->tomatch > 0; fi++) { - char *fname = (char *)path_try_shorten_fname((char_u *)cmd_args->fnames[fi]); + char *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. @@ -5541,7 +5589,7 @@ static void restore_start_dir(char *dirname_start) { char *dirname_now = xmalloc(MAXPATHL); - os_dirname((char_u *)dirname_now, MAXPATHL); + os_dirname(dirname_now, MAXPATHL); if (strcmp(dirname_start, dirname_now) != 0) { // If the directory has changed, change it back by building up an // appropriate ex command and executing it. @@ -5622,6 +5670,7 @@ static buf_T *load_dummy_buffer(char *fname, char *dirname_start, char *resultin // Restore curwin/curbuf and a few other things. aucmd_restbuf(&aco); + if (newbuf_to_wipe.br_buf != NULL && bufref_valid(&newbuf_to_wipe)) { wipe_buffer(newbuf_to_wipe.br_buf, false); } @@ -5634,7 +5683,7 @@ static buf_T *load_dummy_buffer(char *fname, char *dirname_start, char *resultin // When autocommands/'autochdir' option changed directory: go back. // Let the caller know what the resulting dir was first, in case it is // important. - os_dirname((char_u *)resulting_dir, MAXPATHL); + os_dirname(resulting_dir, MAXPATHL); restore_start_dir(dirname_start); if (!bufref_valid(&newbufref)) { @@ -5696,12 +5745,14 @@ static void wipe_dummy_buffer(buf_T *buf, char *dirname_start) // 'autochdir' option have changed it. static void unload_dummy_buffer(buf_T *buf, char *dirname_start) { - if (curbuf != buf) { // safety check - close_buffer(NULL, buf, DOBUF_UNLOAD, false, true); - - // When autocommands/'autochdir' option changed directory: go back. - restore_start_dir(dirname_start); + if (curbuf == buf) { // safety check + return; } + + close_buffer(NULL, buf, DOBUF_UNLOAD, false, true); + + // When autocommands/'autochdir' option changed directory: go back. + restore_start_dir(dirname_start); } /// Copy the specified quickfix entry items into a new dict and append the dict @@ -5826,32 +5877,34 @@ static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict) int status = FAIL; // Only a List value is supported - if (di->di_tv.v_type == VAR_LIST && di->di_tv.vval.v_list != NULL) { - char *errorformat = p_efm; - dictitem_T *efm_di; - // If errorformat is supplied then use it, otherwise use the 'efm' - // option setting - if ((efm_di = tv_dict_find(what, S_LEN("efm"))) != NULL) { - if (efm_di->di_tv.v_type != VAR_STRING - || efm_di->di_tv.vval.v_string == NULL) { - return FAIL; - } - errorformat = efm_di->di_tv.vval.v_string; - } - - list_T *l = tv_list_alloc(kListLenMayKnow); - qf_info_T *const qi = qf_alloc_stack(QFLT_INTERNAL); + if (di->di_tv.v_type != VAR_LIST || di->di_tv.vval.v_list == NULL) { + return FAIL; + } - if (qf_init_ext(qi, 0, NULL, NULL, &di->di_tv, errorformat, - true, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) { - (void)get_errorlist(qi, NULL, 0, 0, l); - qf_free(&qi->qf_lists[0]); + char *errorformat = p_efm; + dictitem_T *efm_di; + // If errorformat is supplied then use it, otherwise use the 'efm' + // option setting + if ((efm_di = tv_dict_find(what, S_LEN("efm"))) != NULL) { + if (efm_di->di_tv.v_type != VAR_STRING + || efm_di->di_tv.vval.v_string == NULL) { + return FAIL; } - xfree(qi); + errorformat = efm_di->di_tv.vval.v_string; + } + + list_T *l = tv_list_alloc(kListLenMayKnow); + qf_info_T *const qi = qf_alloc_stack(QFLT_INTERNAL); - tv_dict_add_list(retdict, S_LEN("items"), l); - status = OK; + if (qf_init_ext(qi, 0, NULL, NULL, &di->di_tv, errorformat, + true, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) { + (void)get_errorlist(qi, NULL, 0, 0, l); + qf_free(&qi->qf_lists[0]); } + xfree(qi); + + tv_dict_add_list(retdict, S_LEN("items"), l); + status = OK; return status; } @@ -6668,7 +6721,8 @@ int set_errorlist(win_T *wp, list_T *list, int action, char *title, dict_T *what return retval; } -/// Mark the context as in use for all the lists in a quickfix stack. +/// Mark the quickfix context and callback function as in use for all the lists +/// in a quickfix stack. static bool mark_quickfix_ctx(qf_info_T *qi, int copyID) { bool abort = false; @@ -6679,6 +6733,9 @@ static bool mark_quickfix_ctx(qf_info_T *qi, int copyID) && ctx->v_type != VAR_STRING && ctx->v_type != VAR_FLOAT) { abort = set_ref_in_item(ctx, copyID, NULL, NULL); } + + Callback *cb = &qi->qf_lists[i].qf_qftf_cb; + abort = abort || set_ref_in_callback(cb, copyID, NULL, NULL); } return abort; @@ -6693,6 +6750,11 @@ bool set_ref_in_quickfix(int copyID) return abort; } + abort = set_ref_in_callback(&qftf_cb, copyID, NULL, NULL); + if (abort) { + return abort; + } + FOR_ALL_TAB_WINDOWS(tp, win) { if (win->w_llist != NULL) { abort = mark_quickfix_ctx(win->w_llist, copyID); @@ -6806,8 +6868,8 @@ void ex_cbuffer(exarg_T *eap) char *qf_title = qf_cmdtitle(*eap->cmdlinep); if (buf->b_sfname) { - vim_snprintf((char *)IObuff, IOSIZE, "%s (%s)", qf_title, buf->b_sfname); - qf_title = (char *)IObuff; + vim_snprintf(IObuff, IOSIZE, "%s (%s)", qf_title, buf->b_sfname); + qf_title = IObuff; } incr_quickfix_busy(); @@ -6885,43 +6947,45 @@ void ex_cexpr(exarg_T *eap) // Evaluate the expression. When the result is a string or a list we can // use it to fill the errorlist. typval_T tv; - if (eval0(eap->arg, &tv, &eap->nextcmd, true) != FAIL) { - if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL) - || tv.v_type == VAR_LIST) { - incr_quickfix_busy(); - int res = qf_init_ext(qi, qi->qf_curlist, NULL, NULL, &tv, p_efm, - (eap->cmdidx != CMD_caddexpr - && eap->cmdidx != CMD_laddexpr), - (linenr_T)0, (linenr_T)0, - qf_cmdtitle(*eap->cmdlinep), NULL); - if (qf_stack_empty(qi)) { - decr_quickfix_busy(); - goto cleanup; - } - if (res >= 0) { - qf_list_changed(qf_get_curlist(qi)); - } - // 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); - } - // Jump to the first error for a new list and if autocmds didn't - // free the list. - if (res > 0 - && (eap->cmdidx == CMD_cexpr || eap->cmdidx == CMD_lexpr) - && qflist_valid(wp, save_qfid)) { - // display the first error - qf_jump_first(qi, save_qfid, eap->forceit); - } + if (eval0(eap->arg, &tv, &eap->nextcmd, true) == FAIL) { + return; + } + + if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL) + || tv.v_type == VAR_LIST) { + incr_quickfix_busy(); + int res = qf_init_ext(qi, qi->qf_curlist, NULL, NULL, &tv, p_efm, + (eap->cmdidx != CMD_caddexpr + && eap->cmdidx != CMD_laddexpr), + (linenr_T)0, (linenr_T)0, + qf_cmdtitle(*eap->cmdlinep), NULL); + if (qf_stack_empty(qi)) { decr_quickfix_busy(); - } else { - emsg(_("E777: String or List expected")); + goto cleanup; } -cleanup: - tv_clear(&tv); + if (res >= 0) { + qf_list_changed(qf_get_curlist(qi)); + } + // 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); + } + // Jump to the first error for a new list and if autocmds didn't + // free the list. + if (res > 0 + && (eap->cmdidx == CMD_cexpr || eap->cmdidx == CMD_lexpr) + && qflist_valid(wp, save_qfid)) { + // display the first error + qf_jump_first(qi, save_qfid, eap->forceit); + } + decr_quickfix_busy(); + } else { + emsg(_("E777: String or List expected")); } +cleanup: + tv_clear(&tv); } // Get the location list for ":lhelpgrep" @@ -6951,8 +7015,8 @@ static void hgr_search_file(qf_list_T *qfl, char *fname, regmatch_T *p_regmatch) } linenr_T lnum = 1; - while (!vim_fgets((char_u *)IObuff, IOSIZE, fd) && !got_int) { - char *line = (char *)IObuff; + while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) { + char *line = IObuff; if (vim_regexec(p_regmatch, line, (colnr_T)0)) { int l = (int)strlen(line); @@ -7035,9 +7099,9 @@ static void hgr_search_in_rtp(qf_list_T *qfl, regmatch_T *p_regmatch, const char // Go through all directories in 'runtimepath' char *p = p_rtp; while (*p != NUL && !got_int) { - copy_option_part(&p, (char *)NameBuff, MAXPATHL, ","); + copy_option_part(&p, NameBuff, MAXPATHL, ","); - hgr_search_files_in_dir(qfl, (char *)NameBuff, p_regmatch, (char *)lang); + hgr_search_files_in_dir(qfl, NameBuff, p_regmatch, (char *)lang); } } @@ -7045,7 +7109,7 @@ static void hgr_search_in_rtp(qf_list_T *qfl, regmatch_T *p_regmatch, const char void ex_helpgrep(exarg_T *eap) { qf_info_T *qi = &ql_info; - char *au_name = NULL; + char *au_name = NULL; switch (eap->cmdidx) { case CMD_helpgrep: @@ -7065,6 +7129,7 @@ void ex_helpgrep(exarg_T *eap) bool updated = false; // Make 'cpoptions' empty, the 'l' flag should not be used here. char *const save_cpo = p_cpo; + const bool save_cpo_allocated = is_option_allocated("cpo"); p_cpo = empty_option; bool new_qi = false; @@ -7104,7 +7169,9 @@ void ex_helpgrep(exarg_T *eap) if (*p_cpo == NUL) { set_option_value_give_err("cpo", 0L, save_cpo, 0); } - free_string_option(save_cpo); + if (save_cpo_allocated) { + free_string_option(save_cpo); + } } if (updated) { @@ -7139,7 +7206,9 @@ void ex_helpgrep(exarg_T *eap) if (new_qi) { ll_free_all(&qi); } - } else if (curwin->w_llist == NULL) { + } else if (curwin->w_llist == NULL && new_qi) { + // current window didn't have a location list associated with it + // before. Associate the new location list now. curwin->w_llist = qi; } } diff --git a/src/nvim/rbuffer.c b/src/nvim/rbuffer.c index 2f718e9c2e..1088dd3778 100644 --- a/src/nvim/rbuffer.c +++ b/src/nvim/rbuffer.c @@ -2,15 +2,16 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> +#include <stdbool.h> #include <stddef.h> #include <string.h> +#include "nvim/macros.h" #include "nvim/memory.h" #include "nvim/rbuffer.h" -#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "rbuffer.c.generated.h" +# include "rbuffer.c.generated.h" // IWYU pragma: export #endif /// Creates a new `RBuffer` instance. @@ -164,7 +165,8 @@ void rbuffer_consumed_compact(RBuffer *buf, size_t count) assert(buf->read_ptr <= buf->write_ptr); rbuffer_consumed(buf, count); if (buf->read_ptr > buf->start_ptr) { - assert((size_t)(buf->read_ptr - buf->write_ptr) == buf->size); + assert((size_t)(buf->write_ptr - buf->read_ptr) == buf->size + || buf->write_ptr == buf->start_ptr); memmove(buf->start_ptr, buf->read_ptr, buf->size); buf->read_ptr = buf->start_ptr; buf->write_ptr = buf->read_ptr + buf->size; diff --git a/src/nvim/rbuffer.h b/src/nvim/rbuffer.h index 3ebbc9d82c..63d5119004 100644 --- a/src/nvim/rbuffer.h +++ b/src/nvim/rbuffer.h @@ -17,6 +17,8 @@ #include <stddef.h> #include <stdint.h> +struct rbuffer; + // Macros that simplify working with the read/write pointers directly by hiding // ring buffer wrap logic. Some examples: // diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index e87382ff7c..122f3e2020 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -1,9 +1,7 @@ // 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 -/* - * Handling of regular expressions: vim_regcomp(), vim_regexec(), vim_regsub() - */ +// Handling of regular expressions: vim_regcomp(), vim_regexec(), vim_regsub() // By default: do not create debugging logs or files related to regular // expressions, even when compiling with -DDEBUG. @@ -15,21 +13,34 @@ #include <inttypes.h> #include <stdbool.h> #include <string.h> +#include <sys/types.h> #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" #include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/keycodes.h" +#include "nvim/macros.h" #include "nvim/mark.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/option_defs.h" #include "nvim/os/input.h" #include "nvim/plines.h" -#include "nvim/profile.h" +#include "nvim/pos.h" #include "nvim/regexp.h" +#include "nvim/regexp_defs.h" #include "nvim/strings.h" +#include "nvim/types.h" +#include "nvim/undo_defs.h" #include "nvim/vim.h" #ifdef REGEXP_DEBUG @@ -41,21 +52,17 @@ # define BT_REGEXP_DEBUG_LOG_NAME "bt_regexp_debug.log" #endif -/* - * 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. - */ +// 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. #define Magic(x) ((int)(x) - 256) #define un_Magic(x) ((x) + 256) #define is_Magic(x) ((x) < 0) -/* - * We should define ftpr as a pointer to a function returning a pointer to - * a function returning a pointer to a function ... - * This is impossible, so we declare a pointer to a function returning a - * pointer to a function returning void. This should work for all compilers. - */ +// We should define ftpr as a pointer to a function returning a pointer to +// a function returning a pointer to a function ... +// This is impossible, so we declare a pointer to a function returning a +// pointer to a function returning void. This should work for all compilers. typedef void (*(*fptr_T)(int *, int))(void); static int no_Magic(int x) @@ -80,7 +87,7 @@ static int toggle_Magic(int x) #define REGMAGIC 0234 // Utility definitions. -#define UCHARAT(p) ((int)(*(char_u *)(p))) +#define UCHARAT(p) ((int)(*(uint8_t *)(p))) // Used for an error (down from) vim_regcomp(): give the error message, set // rc_did_emsg and return NULL @@ -97,20 +104,24 @@ static int toggle_Magic(int x) #define MAX_LIMIT (32767L << 16L) -static char_u e_missingbracket[] = N_("E769: Missing ] after %s["); -static char_u e_reverse_range[] = N_("E944: Reverse range in character class"); -static char_u e_large_class[] = N_("E945: Range too large in character class"); -static char_u e_unmatchedpp[] = N_("E53: Unmatched %s%%("); -static char_u e_unmatchedp[] = N_("E54: Unmatched %s("); -static char_u e_unmatchedpar[] = N_("E55: Unmatched %s)"); -static char_u e_z_not_allowed[] = N_("E66: \\z( not allowed here"); -static char_u e_z1_not_allowed[] = N_("E67: \\z1 - \\z9 not allowed here"); -static char_u e_missing_sb[] = N_("E69: Missing ] after %s%%["); -static char_u e_empty_sb[] = N_("E70: Empty %s%%[]"); -static char_u e_recursive[] = N_("E956: Cannot use pattern recursively"); -static char_u e_regexp_number_after_dot_pos_search[] +static char e_missingbracket[] = N_("E769: Missing ] after %s["); +static char e_reverse_range[] = N_("E944: Reverse range in character class"); +static char e_large_class[] = N_("E945: Range too large in character class"); +static char e_unmatchedpp[] = N_("E53: Unmatched %s%%("); +static char e_unmatchedp[] = N_("E54: Unmatched %s("); +static char e_unmatchedpar[] = N_("E55: Unmatched %s)"); +static char e_z_not_allowed[] = N_("E66: \\z( not allowed here"); +static char e_z1_not_allowed[] = N_("E67: \\z1 - \\z9 not allowed here"); +static char e_missing_sb[] = N_("E69: Missing ] after %s%%["); +static char e_empty_sb[] = N_("E70: Empty %s%%[]"); +static char e_recursive[] = N_("E956: Cannot use pattern recursively"); +static char e_regexp_number_after_dot_pos_search_chr[] = N_("E1204: No Number allowed after .: '\\%%%c'"); -static char_u e_substitute_nesting_too_deep[] = N_("E1290: substitute nesting too deep"); +static char e_nfa_regexp_missing_value_in_chr[] + = N_("E1273: (NFA regexp) missing value in '\\%%%c'"); +static char e_atom_engine_must_be_at_start_of_pattern[] + = N_("E1281: Atom '\\%%#=%c' must be at the start of the pattern"); +static char e_substitute_nesting_too_deep[] = N_("E1290: substitute nesting too deep"); #define NOT_MULTI 0 #define MULTI_ONE 1 @@ -139,28 +150,24 @@ static int re_multi_type(int c) static char *reg_prev_sub = NULL; -/* - * REGEXP_INRANGE contains all characters which are always special in a [] - * range after '\'. - * REGEXP_ABBR contains all characters which act as abbreviations after '\'. - * These are: - * \n - New line (NL). - * \r - Carriage Return (CR). - * \t - Tab (TAB). - * \e - Escape (ESC). - * \b - Backspace (Ctrl_H). - * \d - Character code in decimal, eg \d123 - * \o - Character code in octal, eg \o80 - * \x - Character code in hex, eg \x4a - * \u - Multibyte character code, eg \u20ac - * \U - Long multibyte character code, eg \U12345678 - */ +// REGEXP_INRANGE contains all characters which are always special in a [] +// range after '\'. +// REGEXP_ABBR contains all characters which act as abbreviations after '\'. +// These are: +// \n - New line (NL). +// \r - Carriage Return (CR). +// \t - Tab (TAB). +// \e - Escape (ESC). +// \b - Backspace (Ctrl_H). +// \d - Character code in decimal, eg \d123 +// \o - Character code in octal, eg \o80 +// \x - Character code in hex, eg \x4a +// \u - Multibyte character code, eg \u20ac +// \U - Long multibyte character code, eg \U12345678 static char REGEXP_INRANGE[] = "]^-n\\"; static char REGEXP_ABBR[] = "nrtebdoxuU"; -/* - * Translate '\x' to its control character, except "\n", which is Magic. - */ +// Translate '\x' to its control character, except "\n", which is Magic. static int backslash_trans(int c) { switch (c) { @@ -181,8 +188,7 @@ static int backslash_trans(int c) /// recognized. Otherwise "pp" is advanced to after the item. static int get_char_class(char **pp) { - static const char *(class_names[]) = - { + static const char *(class_names[]) = { "alnum:]", #define CLASS_ALNUM 0 "alpha:]", @@ -227,7 +233,7 @@ static int get_char_class(char **pp) if ((*pp)[1] == ':') { for (i = 0; i < (int)ARRAY_SIZE(class_names); i++) { - if (STRNCMP(*pp + 2, class_names[i], strlen(class_names[i])) == 0) { + if (strncmp(*pp + 2, class_names[i], strlen(class_names[i])) == 0) { *pp += strlen(class_names[i]) + 2; return i; } @@ -236,11 +242,9 @@ static int get_char_class(char **pp) return CLASS_NONE; } -/* - * Specific version of character class functions. - * Using a table to keep this fast. - */ -static short class_tab[256]; +// Specific version of character class functions. +// Using a table to keep this fast. +static int16_t class_tab[256]; #define RI_DIGIT 0x01 #define RI_HEX 0x02 @@ -312,24 +316,18 @@ static int re_has_z; ///< \z item detected 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 magic_T reg_magic; ///< magicness of the pattern static int reg_string; // matching with a string instead of a buffer // line static int reg_strict; // "[abc" is illegal -/* - * META contains all characters that may be magic, except '^' and '$'. - */ +// META contains all characters that may be magic, except '^' and '$'. // uncrustify:off // META[] is used often enough to justify turning it into a table. -static char_u META_flags[] = { +static uint8_t 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, // % & ( ) * + . @@ -363,7 +361,7 @@ static int nextchr; // used for ungetchr() #define REG_NPAREN 3 // \%(\) typedef struct { - char_u *regparse; + char *regparse; int prevchr_len; int curchr; int prevchr; @@ -388,21 +386,19 @@ int re_multiline(const regprog_T *prog) return prog->regflags & RF_HASNL; } -/* - * Check for an equivalence class name "[=a=]". "pp" points to the '['. - * Returns a character representing the class. Zero means that no item was - * recognized. Otherwise "pp" is advanced to after the item. - */ +// Check for an equivalence class name "[=a=]". "pp" points to the '['. +// Returns a character representing the class. Zero means that no item was +// recognized. Otherwise "pp" is advanced to after the item. static int get_equi_class(char **pp) { int c; int l = 1; - char_u *p = (char_u *)(*pp); + char *p = *pp; if (p[1] == '=' && p[2] != NUL) { - l = utfc_ptr2len((char *)p + 2); + l = utfc_ptr2len(p + 2); if (p[l + 2] == '=' && p[l + 3] == ']') { - c = utf_ptr2char((char *)p + 2); + c = utf_ptr2char(p + 2); *pp += l + 4; return c; } @@ -410,22 +406,20 @@ static int get_equi_class(char **pp) return 0; } -/* - * 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. - * Currently only single characters are recognized! - */ +// 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. +// Currently only single characters are recognized! static int get_coll_element(char **pp) { int c; int l = 1; - char_u *p = (char_u *)(*pp); + char *p = *pp; if (p[0] != NUL && p[1] == '.' && p[2] != NUL) { - l = utfc_ptr2len((char *)p + 2); + l = utfc_ptr2len(p + 2); if (p[l + 2] == '.' && p[l + 3] == ']') { - c = utf_ptr2char((char *)p + 2); + c = utf_ptr2char(p + 2); *pp += l + 4; return c; } @@ -443,7 +437,7 @@ static void get_cpo_flags(void) /// Skip over a "[]" range. /// "p" must point to the character after the '['. /// The returned pointer is on the matching ']', or the terminating NUL. -static char_u *skip_anyof(char *p) +static char *skip_anyof(char *p) { int l; @@ -462,9 +456,9 @@ static char_u *skip_anyof(char *p) MB_PTR_ADV(p); } } else if (*p == '\\' - && (vim_strchr(REGEXP_INRANGE, p[1]) != NULL + && (vim_strchr(REGEXP_INRANGE, (uint8_t)p[1]) != NULL || (!reg_cpo_lit - && vim_strchr(REGEXP_ABBR, p[1]) != NULL))) { + && vim_strchr(REGEXP_ABBR, (uint8_t)p[1]) != NULL))) { p += 2; } else if (*p == '[') { if (get_char_class(&p) == CLASS_NONE @@ -478,19 +472,41 @@ static char_u *skip_anyof(char *p) } } - return (char_u *)p; + return p; } /// Skip past regular expression. -/// Stop at end of "startp" or where "dirc" is found ('/', '?', etc). +/// Stop at end of "startp" or where "delim" is found ('/', '?', etc). /// Take care of characters with a backslash in front of it. /// Skip strings inside [ and ]. +char *skip_regexp(char *startp, int delim, int magic) +{ + return skip_regexp_ex(startp, delim, magic, NULL, NULL, NULL); +} + +/// Call skip_regexp() and when the delimiter does not match give an error and +/// return NULL. +char *skip_regexp_err(char *startp, int delim, int magic) +{ + char *p = skip_regexp(startp, delim, magic); + + if (*p != delim) { + semsg(_("E654: missing delimiter after search pattern: %s"), startp); + return NULL; + } + return p; +} + +/// skip_regexp() with extra arguments: /// When "newp" is not NULL and "dirc" is '?', make an allocated copy of the /// expression and change "\?" to "?". If "*newp" is not NULL the expression /// is changed in-place. -char *skip_regexp(char *startp, int dirc, int magic, char **newp) +/// If a "\?" is changed to "?" then "dropped" is incremented, unless NULL. +/// If "magic_val" is not NULL, returns the effective magicness of the pattern +char *skip_regexp_ex(char *startp, int dirc, int magic, char **newp, int *dropped, + magic_T *magic_val) { - int mymagic; + magic_T mymagic; char *p = startp; if (magic) { @@ -506,7 +522,7 @@ char *skip_regexp(char *startp, int dirc, int magic, char **newp) } if ((p[0] == '[' && mymagic >= MAGIC_ON) || (p[0] == '\\' && p[1] == '[' && mymagic <= MAGIC_OFF)) { - p = (char *)skip_anyof(p + 1); + p = skip_anyof(p + 1); if (p[0] == NUL) { break; } @@ -517,6 +533,9 @@ char *skip_regexp(char *startp, int dirc, int magic, char **newp) *newp = xstrdup(startp); p = *newp + (p - startp); } + if (dropped != NULL) { + (*dropped)++; + } STRMOVE(p, p + 1); } else { p++; // skip next character @@ -528,6 +547,9 @@ char *skip_regexp(char *startp, int dirc, int magic, char **newp) } } } + if (magic_val != NULL) { + *magic_val = mymagic; + } return p; } @@ -536,25 +558,21 @@ 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 -/* - * Start parsing at "str". - */ -static void initchr(char_u *str) +// Start parsing at "str". +static void initchr(char *str) { - regparse = (char *)str; + regparse = str; prevchr_len = 0; curchr = prevprevchr = prevchr = nextchr = -1; at_start = true; prev_at_start = false; } -/* - * Save the current parse state, so that it can be restored and parsing - * starts in the same state again. - */ +// Save the current parse state, so that it can be restored and parsing +// starts in the same state again. static void save_parse_state(parse_state_T *ps) { - ps->regparse = (char_u *)regparse; + ps->regparse = regparse; ps->prevchr_len = prevchr_len; ps->curchr = curchr; ps->prevchr = prevchr; @@ -565,12 +583,10 @@ static void save_parse_state(parse_state_T *ps) ps->regnpar = regnpar; } -/* - * Restore a previously saved parse state. - */ +// Restore a previously saved parse state. static void restore_parse_state(parse_state_T *ps) { - regparse = (char *)ps->regparse; + regparse = ps->regparse; prevchr_len = ps->prevchr_len; curchr = ps->curchr; prevchr = ps->prevchr; @@ -581,9 +597,7 @@ static void restore_parse_state(parse_state_T *ps) regnpar = ps->regnpar; } -/* - * Get the next character without advancing. - */ +// Get the next character without advancing. static int peekchr(void) { static int after_slash = false; @@ -663,7 +677,7 @@ static int peekchr(void) // '$' is only magic as the very last char and if it's in front of // either "\|", "\)", "\&", or "\n" if (reg_magic >= MAGIC_OFF) { - char_u *p = (char_u *)regparse + 1; + uint8_t *p = (uint8_t *)regparse + 1; bool is_magic_all = (reg_magic == MAGIC_ALL); // ignore \c \C \m \M \v \V and \Z after '$' @@ -710,9 +724,7 @@ static int peekchr(void) after_slash--; curchr = toggle_Magic(curchr); } else if (vim_strchr(REGEXP_ABBR, c)) { - /* - * Handle abbreviations, like "\t" for TAB -- webb - */ + // Handle abbreviations, like "\t" for TAB -- webb curchr = backslash_trans(c); } else if (reg_magic == MAGIC_NONE && (c == '$' || c == '^')) { curchr = toggle_Magic(c); @@ -731,9 +743,7 @@ static int peekchr(void) return curchr; } -/* - * Eat one lexed character. Do this in a way that we can undo it. - */ +// Eat one lexed character. Do this in a way that we can undo it. static void skipchr(void) { // peekchr() eats a backslash, do the same here @@ -755,10 +765,8 @@ static void skipchr(void) nextchr = -1; } -/* - * Skip a character while keeping the value of prev_at_start for at_start. - * prevchr and prevprevchr are also kept. - */ +// Skip a character while keeping the value of prev_at_start for at_start. +// prevchr and prevprevchr are also kept. static void skipchr_keepstart(void) { int as = prev_at_start; @@ -771,10 +779,8 @@ static void skipchr_keepstart(void) prevprevchr = prpr; } -/* - * Get the next character from the pattern. We know about magic and such, so - * therefore we need a lexical analyzer. - */ +// Get the next character from the pattern. We know about magic and such, so +// therefore we need a lexical analyzer. static int getchr(void) { int chr = peekchr(); @@ -783,9 +789,7 @@ static int getchr(void) return chr; } -/* - * put character back. Works only once! - */ +// put character back. Works only once! static void ungetchr(void) { nextchr = curchr; @@ -799,15 +803,13 @@ static void ungetchr(void) regparse -= prevchr_len; } -/* - * Get and return the value of the hex string at the current position. - * Return -1 if there is no valid hex number. - * The position is updated: - * blahblah\%x20asdf - * before-^ ^-after - * The parameter controls the maximum number of input characters. This will be - * 2 when reading a \%x20 sequence and 4 when reading a \%u20AC sequence. - */ +// Get and return the value of the hex string at the current position. +// Return -1 if there is no valid hex number. +// The position is updated: +// blahblah\%x20asdf +// before-^ ^-after +// The parameter controls the maximum number of input characters. This will be +// 2 when reading a \%x20 sequence and 4 when reading a \%u20AC sequence. static int64_t gethexchrs(int maxinputlen) { int64_t nr = 0; @@ -830,10 +832,8 @@ static int64_t gethexchrs(int maxinputlen) return nr; } -/* - * Get and return the value of the decimal string immediately after the - * current position. Return -1 for invalid. Consumes all digits. - */ +// Get and return the value of the decimal string immediately after the +// current position. Return -1 for invalid. Consumes all digits. static int64_t getdecchrs(void) { int64_t nr = 0; @@ -857,14 +857,12 @@ static int64_t getdecchrs(void) return nr; } -/* - * get and return the value of the octal string immediately after the current - * position. Return -1 for invalid, or 0-255 for valid. Smart enough to handle - * numbers > 377 correctly (for example, 400 is treated as 40) and doesn't - * treat 8 or 9 as recognised characters. Position is updated: - * blahblah\%o210asdf - * before-^ ^-after - */ +// get and return the value of the octal string immediately after the current +// position. Return -1 for invalid, or 0-255 for valid. Smart enough to handle +// numbers > 377 correctly (for example, 400 is treated as 40) and doesn't +// treat 8 or 9 as recognised characters. Position is updated: +// blahblah\%o210asdf +// before-^ ^-after static int64_t getoctchrs(void) { int64_t nr = 0; @@ -887,16 +885,14 @@ static int64_t getoctchrs(void) return nr; } -/* - * read_limits - Read two integers to be taken as a minimum and maximum. - * If the first character is '-', then the range is reversed. - * Should end with 'end'. If minval is missing, zero is default, if maxval is - * missing, a very big number is the default. - */ +// read_limits - Read two integers to be taken as a minimum and maximum. +// If the first character is '-', then the range is reversed. +// Should end with 'end'. If minval is missing, zero is default, if maxval is +// missing, a very big number is the default. static int read_limits(long *minval, long *maxval) { int reverse = false; - char_u *first_char; + char *first_char; long tmp; if (*regparse == '-') { @@ -904,7 +900,7 @@ static int read_limits(long *minval, long *maxval) regparse++; reverse = true; } - first_char = (char_u *)regparse; + first_char = regparse; *minval = getdigits_long(®parse, false, 0); if (*regparse == ',') { // There is a comma. if (ascii_isdigit(*++regparse)) { @@ -924,10 +920,8 @@ static int read_limits(long *minval, long *maxval) EMSG2_RET_FAIL(_("E554: Syntax error in %s{...}"), reg_magic == MAGIC_ALL); } - /* - * Reverse the range if there was a '-', or make sure it is in the right - * order otherwise. - */ + // Reverse the range if there was a '-', or make sure it is in the right + // order otherwise. if ((!reverse && *minval > *maxval) || (reverse && *minval < *maxval)) { tmp = *minval; *minval = *maxval; @@ -937,18 +931,14 @@ static int read_limits(long *minval, long *maxval) return OK; } -/* - * vim_regexec and friends - */ +// vim_regexec and friends -/* - * Global work variables for vim_regexec(). - */ +// Global work variables for vim_regexec(). // 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. -static char_u *reg_tofree = NULL; +static uint8_t *reg_tofree = NULL; static unsigned reg_tofreelen; // Structure used to store the execution state of the regex engine. @@ -969,10 +959,12 @@ static unsigned reg_tofreelen; typedef struct { regmatch_T *reg_match; regmmatch_T *reg_mmatch; - char_u **reg_startp; - char_u **reg_endp; + + uint8_t **reg_startp; + uint8_t **reg_endp; lpos_T *reg_startpos; lpos_T *reg_endpos; + win_T *reg_win; buf_T *reg_buf; linenr_T reg_firstlnum; @@ -981,8 +973,8 @@ 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 "line" + uint8_t *line; ///< start of current line + uint8_t *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 @@ -1026,10 +1018,8 @@ static bool reg_iswordc(int c) return vim_iswordc_buf(c, rex.reg_buf); } -/* - * Get pointer to the line "lnum", which is relative to "reg_firstlnum". - */ -static char_u *reg_getline(linenr_T lnum) +// Get pointer to the line "lnum", which is relative to "reg_firstlnum". +static char *reg_getline(linenr_T lnum) { // when looking behind for a match/no-match lnum is negative. But we // can't go before line 1 @@ -1038,22 +1028,20 @@ static char_u *reg_getline(linenr_T lnum) } if (lnum > rex.reg_maxline) { // Must have matched the "\n" in the last line. - return (char_u *)""; + return ""; } - return (char_u *)ml_get_buf(rex.reg_buf, rex.reg_firstlnum + lnum, false); + return ml_get_buf(rex.reg_buf, rex.reg_firstlnum + lnum, false); } -static char_u *reg_startzp[NSUBEXP]; // Workspace to mark beginning -static char_u *reg_endzp[NSUBEXP]; // and end of \z(...\) matches +static uint8_t *reg_startzp[NSUBEXP]; // Workspace to mark beginning +static uint8_t *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) -/* - * Create a new extmatch and mark it as referenced once. - */ +// Create a new extmatch and mark it as referenced once. static reg_extmatch_T *make_extmatch(void) FUNC_ATTR_NONNULL_RET { @@ -1062,9 +1050,7 @@ static reg_extmatch_T *make_extmatch(void) return em; } -/* - * Add a reference to an extmatch. - */ +// Add a reference to an extmatch. reg_extmatch_T *ref_extmatch(reg_extmatch_T *em) { if (em != NULL) { @@ -1073,10 +1059,8 @@ reg_extmatch_T *ref_extmatch(reg_extmatch_T *em) return em; } -/* - * Remove a reference to an extmatch. If there are no references left, free - * the info. - */ +// Remove a reference to an extmatch. If there are no references left, free +// the info. void unref_extmatch(reg_extmatch_T *em) { int i; @@ -1093,7 +1077,8 @@ void unref_extmatch(reg_extmatch_T *em) static int reg_prev_class(void) { if (rex.input > rex.line) { - return mb_get_class_tab(rex.input - 1 - utf_head_off((char *)rex.line, (char *)rex.input - 1), + return mb_get_class_tab((char *)rex.input - 1 - + utf_head_off((char *)rex.line, (char *)rex.input - 1), rex.reg_buf->b_chartab); } return -1; @@ -1111,8 +1096,8 @@ static bool reg_match_visual(void) colnr_T start2, end2; colnr_T curswant; - // Check if the buffer is the current buffer. - if (rex.reg_buf != curbuf || VIsual.lnum == 0) { + // Check if the buffer is the current buffer and not using a string. + if (rex.reg_buf != curbuf || VIsual.lnum == 0 || !REG_MULTI) { return false; } @@ -1162,10 +1147,10 @@ static bool reg_match_visual(void) } // getvvcol() flushes rex.line, need to get it again - rex.line = reg_getline(rex.lnum); + rex.line = (uint8_t *)reg_getline(rex.lnum); rex.input = rex.line + col; - unsigned int cols_u = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, rex.line, col); + unsigned int cols_u = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, (char *)rex.line, col); assert(cols_u <= MAXCOL); colnr_T cols = (colnr_T)cols_u; if (cols < start || cols > end - (*p_sel == 'e')) { @@ -1175,10 +1160,8 @@ static bool reg_match_visual(void) return true; } -/* - * Check the regexp program for its magic number. - * Return true if it's wrong. - */ +// Check the regexp program for its magic number. +// Return true if it's wrong. static int prog_magic_wrong(void) { regprog_T *prog; @@ -1196,62 +1179,62 @@ static int prog_magic_wrong(void) return false; } -/* - * Cleanup the subexpressions, if this wasn't done yet. - * This construction is used to clear the subexpressions only when they are - * used (to increase speed). - */ +// Cleanup the subexpressions, if this wasn't done yet. +// This construction is used to clear the subexpressions only when they are +// used (to increase speed). static void cleanup_subexpr(void) { - if (rex.need_clear_subexpr) { - if (REG_MULTI) { - // Use 0xff to set lnum to -1 - memset(rex.reg_startpos, 0xff, sizeof(lpos_T) * NSUBEXP); - memset(rex.reg_endpos, 0xff, sizeof(lpos_T) * NSUBEXP); - } else { - memset(rex.reg_startp, 0, sizeof(char_u *) * NSUBEXP); - memset(rex.reg_endp, 0, sizeof(char_u *) * NSUBEXP); - } - rex.need_clear_subexpr = false; + if (!rex.need_clear_subexpr) { + return; + } + + if (REG_MULTI) { + // Use 0xff to set lnum to -1 + memset(rex.reg_startpos, 0xff, sizeof(lpos_T) * NSUBEXP); + memset(rex.reg_endpos, 0xff, sizeof(lpos_T) * NSUBEXP); + } else { + memset(rex.reg_startp, 0, sizeof(char *) * NSUBEXP); + memset(rex.reg_endp, 0, sizeof(char *) * NSUBEXP); } + rex.need_clear_subexpr = false; } static void cleanup_zsubexpr(void) { - if (rex.need_clear_zsubexpr) { - if (REG_MULTI) { - // Use 0xff to set lnum to -1 - memset(reg_startzpos, 0xff, sizeof(lpos_T) * NSUBEXP); - memset(reg_endzpos, 0xff, sizeof(lpos_T) * NSUBEXP); - } else { - memset(reg_startzp, 0, sizeof(char_u *) * NSUBEXP); - memset(reg_endzp, 0, sizeof(char_u *) * NSUBEXP); - } - rex.need_clear_zsubexpr = false; + if (!rex.need_clear_zsubexpr) { + return; + } + + if (REG_MULTI) { + // Use 0xff to set lnum to -1 + memset(reg_startzpos, 0xff, sizeof(lpos_T) * NSUBEXP); + memset(reg_endzpos, 0xff, sizeof(lpos_T) * NSUBEXP); + } else { + memset(reg_startzp, 0, sizeof(char *) * NSUBEXP); + memset(reg_endzp, 0, sizeof(char *) * NSUBEXP); } + rex.need_clear_zsubexpr = false; } // Advance rex.lnum, rex.line and rex.input to the next line. static void reg_nextline(void) { - rex.line = reg_getline(++rex.lnum); + rex.line = (uint8_t *)reg_getline(++rex.lnum); rex.input = rex.line; fast_breakcheck(); } -/* - * Check whether a backreference matches. - * Returns RA_FAIL, RA_NOMATCH or RA_MATCH. - * If "bytelen" is not NULL, it is set to the byte length of the match in the - * last line. - */ +// Check whether a backreference matches. +// Returns RA_FAIL, RA_NOMATCH or RA_MATCH. +// 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) { linenr_T clnum = start_lnum; colnr_T ccol = start_col; int len; - char_u *p; + char *p; if (bytelen != NULL) { *bytelen = 0; @@ -1260,7 +1243,7 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e // Since getting one line may invalidate the other, need to make copy. // Slow! if (rex.line != reg_tofree) { - len = (int)STRLEN(rex.line); + len = (int)strlen((char *)rex.line); if (reg_tofree == NULL || len >= (int)reg_tofreelen) { len += 50; // get some extra xfree(reg_tofree); @@ -1279,10 +1262,10 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e if (clnum == end_lnum) { len = end_col - ccol; } else { - len = (int)STRLEN(p + ccol); + len = (int)strlen(p + ccol); } - if (cstrncmp((char *)p + ccol, (char *)rex.input, &len) != 0) { + if (cstrncmp(p + ccol, (char *)rex.input, &len) != 0) { return RA_NOMATCH; // doesn't match } if (bytelen != NULL) { @@ -1328,8 +1311,7 @@ typedef struct { } decomp_T; // 0xfb20 - 0xfb4f -static decomp_T decomp_table[0xfb4f - 0xfb20 + 1] = -{ +static decomp_T decomp_table[0xfb4f - 0xfb20 + 1] = { { 0x5e2, 0, 0 }, // 0xfb20 alt ayin { 0x5d0, 0, 0 }, // 0xfb21 alt alef { 0x5d3, 0, 0 }, // 0xfb22 alt dalet @@ -1403,7 +1385,7 @@ static int cstrncmp(char *s1, char *s2, int *n) int result; if (!rex.reg_ic) { - result = STRNCMP(s1, s2, *n); + result = strncmp(s1, s2, (size_t)(*n)); } else { assert(*n >= 0); result = mb_strnicmp(s1, s2, (size_t)(*n)); @@ -1421,12 +1403,12 @@ static int cstrncmp(char *s1, char *s2, int *n) str2 = s2; c1 = c2 = 0; while ((int)(str1 - s1) < *n) { - c1 = mb_ptr2char_adv((const char_u **)&str1); - c2 = mb_ptr2char_adv((const char_u **)&str2); + c1 = mb_ptr2char_adv((const char **)&str1); + c2 = mb_ptr2char_adv((const char **)&str2); - /* decompose the character if necessary, into 'base' characters - * because I don't care about Arabic, I will hard-code the Hebrew - * which I *do* care about! So sue me... */ + // decompose the character if necessary, into 'base' characters + // because I don't care about Arabic, I will hard-code the Hebrew + // which I *do* care about! So sue me... if (c1 != c2 && (!rex.reg_ic || utf_fold(c1) != utf_fold(c2))) { // decomposition necessary? mb_decompose(c1, &c11, &junk, &junk); @@ -1456,21 +1438,21 @@ static int cstrncmp(char *s1, char *s2, int *n) /// @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) +static inline char *cstrchr(const char *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 (char_u *)vim_strchr((char *)s, c); + 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((char *)p)) { - if (utf_fold(utf_ptr2char((char *)p)) == folded_c) { - return (char_u *)p; + for (const char *p = s; *p != NUL; p += utfc_ptr2len(p)) { + if (utf_fold(utf_ptr2char(p)) == folded_c) { + return (char *)p; } } return NULL; @@ -1482,11 +1464,11 @@ static inline char_u *cstrchr(const char_u *const s, const int c) } else if (ASCII_ISLOWER(c)) { cc = TOUPPER_ASC(c); } else { - return (char_u *)vim_strchr((char *)s, c); + return vim_strchr(s, c); } char tofind[] = { (char)c, (char)cc, NUL }; - return (char_u *)strpbrk((const char *)s, tofind); + return strpbrk(s, tofind); } //////////////////////////////////////////////////////////////// @@ -1541,7 +1523,7 @@ char *regtilde(char *source, int magic, bool preview) int len; int prevlen; - for (p = newsub; *p; ++p) { + 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 @@ -1658,8 +1640,7 @@ static void clear_submatch_list(staticList10_T *sl) /// references invalid! /// /// Returns the size of the replacement, including terminating NUL. -int vim_regsub(regmatch_T *rmp, char_u *source, typval_T *expr, char_u *dest, int destlen, - int flags) +int vim_regsub(regmatch_T *rmp, char *source, typval_T *expr, char *dest, int destlen, int flags) { regexec_T rex_save; bool rex_in_use_save = rex_in_use; @@ -1685,7 +1666,7 @@ int vim_regsub(regmatch_T *rmp, char_u *source, typval_T *expr, char_u *dest, in return result; } -int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *dest, int destlen, +int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char *source, char *dest, int destlen, int flags) { regexec_T rex_save; @@ -1726,11 +1707,11 @@ void free_resub_eval_result(void) } #endif -static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int destlen, int flags) +static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen, int flags) { - char_u *src; - char_u *dst; - char_u *s; + char *src; + char *dst; + char *s; int c; int cc; int no = -1; @@ -1806,14 +1787,14 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des funcexe.fe_argv_func = fill_submatch_list; funcexe.fe_evaluate = true; if (expr->v_type == VAR_FUNC) { - s = (char_u *)expr->vval.v_string; - call_func((char *)s, -1, &rettv, 1, argv, &funcexe); + s = expr->vval.v_string; + call_func(s, -1, &rettv, 1, argv, &funcexe); } else if (expr->v_type == VAR_PARTIAL) { partial_T *partial = expr->vval.v_partial; - s = (char_u *)partial_name(partial); + s = partial_name(partial); funcexe.fe_partial = partial; - call_func((char *)s, -1, &rettv, 1, argv, &funcexe); + call_func(s, -1, &rettv, 1, argv, &funcexe); } if (tv_list_len(&matchList.sl_list) > 0) { // fill_submatch_list() was called. @@ -1831,14 +1812,14 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des } tv_clear(&rettv); } else { - eval_result[nested] = eval_to_string((char *)source + 2, NULL, true); + eval_result[nested] = eval_to_string(source + 2, NULL, true); } nesting--; if (eval_result[nested] != NULL) { int had_backslash = false; - for (s = (char_u *)eval_result[nested]; *s != NUL; MB_PTR_ADV(s)) { + for (s = eval_result[nested]; *s != NUL; MB_PTR_ADV(s)) { // Change NL to CR, so that it becomes a line break, // unless called from vim_regexec_nl(). // Skip over a backslashed character. @@ -1846,12 +1827,11 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des *s = CAR; } else if (*s == '\\' && s[1] != NUL) { s++; - /* Change NL to CR here too, so that this works: - * :s/abc\\\ndef/\="aaa\\\nbbb"/ on text: - * abc\ - * def - * Not when called from vim_regexec_nl(). - */ + // Change NL to CR here too, so that this works: + // :s/abc\\\ndef/\="aaa\\\nbbb"/ on text: + // abc{backslash} + // def + // Not when called from vim_regexec_nl(). if (*s == NL && !rsm.sm_line_lbr) { *s = CAR; } @@ -1860,9 +1840,9 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des } if (had_backslash && (flags & REGSUB_BACKSLASH)) { // Backslashes will be consumed, need to double them. - s = vim_strsave_escaped((char_u *)eval_result[nested], (char_u *)"\\"); + s = vim_strsave_escaped(eval_result[nested], "\\"); xfree(eval_result[nested]); - eval_result[nested] = (char *)s; + eval_result[nested] = s; } dst += strlen(eval_result[nested]); @@ -1874,7 +1854,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des } } } else { - while ((c = *src++) != NUL) { + while ((c = (uint8_t)(*src++)) != NUL) { if (c == '&' && (flags & REGSUB_MAGIC)) { no = 0; } else if (c == '\\' && *src != NUL) { @@ -1883,7 +1863,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des no = 0; } else if ('0' <= *src && *src <= '9') { no = *src++ - '0'; - } else if (vim_strchr("uUlLeE", *src)) { + } else if (vim_strchr("uUlLeE", (uint8_t)(*src))) { switch (*src++) { case 'u': func_one = (fptr_T)do_upper; @@ -1912,7 +1892,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des iemsg("vim_regsub_both(): not enough space"); return 0; } - *dst++ = (char_u)c; + *dst++ = (char)c; *dst++ = *src++; *dst++ = *src++; } else { @@ -1949,10 +1929,10 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des } dst++; } - c = *src++; + c = (uint8_t)(*src++); } } else { - c = utf_ptr2char((char *)src - 1); + c = utf_ptr2char(src - 1); } // Write to buffer, if copy is set. if (func_one != NULL) { @@ -1964,7 +1944,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des cc = c; } - int totlen = utfc_ptr2len((char *)src - 1); + int totlen = utfc_ptr2len(src - 1); int charlen = utf_char2len(cc); if (copy) { @@ -1972,10 +1952,10 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des iemsg("vim_regsub_both(): not enough space"); return 0; } - utf_char2bytes(cc, (char *)dst); + utf_char2bytes(cc, dst); } dst += charlen - 1; - int clen = utf_ptr2len((char *)src - 1); + int clen = utf_ptr2len(src - 1); // If the character length is shorter than "totlen", there // are composing characters; copy them as-is. @@ -2002,15 +1982,15 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des len = rex.reg_mmatch->endpos[no].col - rex.reg_mmatch->startpos[no].col; } else { - len = (int)STRLEN(s); + len = (int)strlen(s); } } } else { - s = (char_u *)rex.reg_match->startp[no]; + s = rex.reg_match->startp[no]; if (rex.reg_match->endp[no] == NULL) { s = NULL; } else { - len = (int)(rex.reg_match->endp[no] - (char *)s); + len = (int)(rex.reg_match->endp[no] - s); } } if (s != NULL) { @@ -2032,7 +2012,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des if (rex.reg_mmatch->endpos[no].lnum == clnum) { len = rex.reg_mmatch->endpos[no].col; } else { - len = (int)STRLEN(s); + len = (int)strlen(s); } } else { break; @@ -2058,7 +2038,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des } dst += 2; } else { - c = utf_ptr2char((char *)s); + c = utf_ptr2char(s); if (func_one != (fptr_T)NULL) { // Turbo C complains without the typecast @@ -2076,7 +2056,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des // Copy composing characters separately, one // at a time. - l = utf_ptr2len((char *)s) - 1; + l = utf_ptr2len(s) - 1; s += l; len -= l; @@ -2086,7 +2066,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des iemsg("vim_regsub_both(): not enough space"); return 0; } - utf_char2bytes(cc, (char *)dst); + utf_char2bytes(cc, dst); } dst += charlen - 1; } @@ -2122,7 +2102,7 @@ static char *reg_getline_submatch(linenr_T lnum) rex.reg_firstlnum = rsm.sm_firstlnum; rex.reg_maxline = rsm.sm_maxline; - s = (char *)reg_getline(lnum); + s = reg_getline(lnum); rex.reg_firstlnum = save_first; rex.reg_maxline = save_max; @@ -2147,10 +2127,8 @@ char *reg_submatch(int no) if (rsm.sm_match == NULL) { ssize_t len; - /* - * First round: compute the length and allocate memory. - * Second round: copy the text. - */ + // First round: compute the length and allocate memory. + // Second round: copy the text. for (round = 1; round <= 2; round++) { lnum = rsm.sm_mmatch->startpos[no].lnum; if (lnum < 0 || rsm.sm_mmatch->endpos[no].lnum < 0) { @@ -2166,7 +2144,7 @@ char *reg_submatch(int no) // Within one line: take form start to end col. len = rsm.sm_mmatch->endpos[no].col - rsm.sm_mmatch->startpos[no].col; if (round == 2) { - STRLCPY(retval, s, len + 1); + xstrlcpy(retval, s, (size_t)len + 1); } len++; } else { @@ -2191,8 +2169,9 @@ char *reg_submatch(int no) len++; } if (round == 2) { - STRNCPY(retval + len, reg_getline_submatch(lnum), - rsm.sm_mmatch->endpos[no].col); + strncpy(retval + len, // NOLINT(runtime/printf) + reg_getline_submatch(lnum), + (size_t)rsm.sm_mmatch->endpos[no].col); } len += rsm.sm_mmatch->endpos[no].col; if (round == 2) { @@ -2270,22 +2249,39 @@ list_T *reg_submatch_list(int no) return list; } +/// Initialize the values used for matching against multiple lines +/// +/// @param win window in which to search or NULL +/// @param buf buffer in which to search +/// @param lnum nr of line to start looking for match +static void init_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum) +{ + 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; +} + // 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 = -{ +static regengine_T bt_regengine = { bt_regcomp, bt_regfree, bt_regexec_nl, bt_regexec_multi, }; -static regengine_T nfa_regengine = -{ +static regengine_T nfa_regengine = { nfa_regcomp, nfa_regfree, nfa_regexec_nl, @@ -2297,28 +2293,26 @@ static regengine_T nfa_regengine = static int regexp_engine = 0; #ifdef REGEXP_DEBUG -static char_u regname[][30] = { +static uint8_t regname[][30] = { "AUTOMATIC Regexp Engine", "BACKTRACKING Regexp Engine", "NFA Regexp Engine" }; #endif -/* - * Compile a regular expression into internal code. - * Returns the program in allocated memory. - * Use vim_regfree() to free the memory. - * Returns NULL for an error. - */ +// Compile a regular expression into internal code. +// Returns the program in allocated memory. +// Use vim_regfree() to free the memory. +// Returns NULL for an error. regprog_T *vim_regcomp(char *expr_arg, int re_flags) { regprog_T *prog = NULL; - char_u *expr = (char_u *)expr_arg; + char *expr = expr_arg; regexp_engine = (int)p_re; // Check for prefix "\%#=", that sets the regexp engine - if (STRNCMP(expr, "\\%#=", 4) == 0) { + if (strncmp(expr, "\\%#=", 4) == 0) { int newengine = expr[4] - '0'; if (newengine == AUTOMATIC_ENGINE @@ -2348,10 +2342,10 @@ regprog_T *vim_regcomp(char *expr_arg, int re_flags) // const int called_emsg_before = called_emsg; if (regexp_engine != BACKTRACKING_ENGINE) { - prog = nfa_regengine.regcomp(expr, + prog = nfa_regengine.regcomp((uint8_t *)expr, re_flags + (regexp_engine == AUTOMATIC_ENGINE ? RE_AUTO : 0)); } else { - prog = bt_regengine.regcomp(expr, re_flags); + prog = bt_regengine.regcomp((uint8_t *)expr, re_flags); } // Check for error compiling regexp with initial engine. @@ -2376,7 +2370,7 @@ regprog_T *vim_regcomp(char *expr_arg, int re_flags) if (regexp_engine == AUTOMATIC_ENGINE && called_emsg == called_emsg_before) { regexp_engine = BACKTRACKING_ENGINE; report_re_switch(expr); - prog = bt_regengine.regcomp(expr, re_flags); + prog = bt_regengine.regcomp((uint8_t *)expr, re_flags); } } @@ -2390,9 +2384,7 @@ regprog_T *vim_regcomp(char *expr_arg, int re_flags) return prog; } -/* - * Free a compiled regexp program, returned by vim_regcomp(). - */ +// Free a compiled regexp program, returned by vim_regcomp(). void vim_regfree(regprog_T *prog) { if (prog != NULL) { @@ -2411,12 +2403,12 @@ void free_regexp_stuff(void) #endif -static void report_re_switch(char_u *pat) +static void report_re_switch(char *pat) { if (p_verbose > 0) { verbose_enter(); msg_puts(_("Switching to backtracking RE engine for pattern: ")); - msg_puts((char *)pat); + msg_puts(pat); verbose_leave(); } } @@ -2433,7 +2425,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 *line, colnr_T col, bool nl) { regexec_T rex_save; bool rex_in_use_save = rex_in_use; @@ -2456,7 +2448,7 @@ static bool vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col, bool rex.reg_startpos = NULL; rex.reg_endpos = NULL; - int result = rmp->regprog->engine->regexec_nl(rmp, line, col, nl); + int result = rmp->regprog->engine->regexec_nl(rmp, (uint8_t *)line, col, nl); rmp->regprog->re_in_use = false; // NFA engine aborted because it's very slow, use backtracking engine instead. @@ -2468,11 +2460,11 @@ static bool vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col, bool p_re = BACKTRACKING_ENGINE; vim_regfree(rmp->regprog); - report_re_switch((char_u *)pat); + report_re_switch(pat); rmp->regprog = vim_regcomp(pat, re_flags); if (rmp->regprog != NULL) { rmp->regprog->re_in_use = true; - result = rmp->regprog->engine->regexec_nl(rmp, line, col, nl); + result = rmp->regprog->engine->regexec_nl(rmp, (uint8_t *)line, col, nl); rmp->regprog->re_in_use = false; } @@ -2490,7 +2482,7 @@ static bool vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col, bool // 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 *line, colnr_T col) { regmatch_T regmatch = { .regprog = *prog, .rm_ic = ignore_case }; bool r = vim_regexec_string(®match, line, col, false); @@ -2502,13 +2494,13 @@ bool vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line, colnr_T // Return true if there is a match, false if not. bool vim_regexec(regmatch_T *rmp, char *line, colnr_T col) { - return vim_regexec_string(rmp, (char_u *)line, col, false); + return vim_regexec_string(rmp, line, col, false); } // Like vim_regexec(), but consider a "\n" in "line" to be a line break. // Note: "rmp->regprog" may be freed and changed. // Return true if there is a match, false if not. -bool vim_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col) +bool vim_regexec_nl(regmatch_T *rmp, char *line, colnr_T col) { return vim_regexec_string(rmp, line, col, true); } @@ -2560,7 +2552,7 @@ long vim_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum, p_re = BACKTRACKING_ENGINE; regprog_T *prev_prog = rmp->regprog; - report_re_switch((char_u *)pat); + report_re_switch(pat); // checking for \z misuse was already done when compiling for NFA, // allow all here reg_do_extmatch = REX_ALL; diff --git a/src/nvim/regexp.h b/src/nvim/regexp.h index 085f78af54..dcc58fa34c 100644 --- a/src/nvim/regexp.h +++ b/src/nvim/regexp.h @@ -17,10 +17,11 @@ #define REX_ALL (REX_SET | REX_USE) // regexp.c +// uncrustify:off #ifdef INCLUDE_GENERATED_DECLARATIONS # include "regexp.h.generated.h" - # include "regexp_bt.h.generated.h" #endif +// uncrustify:on #endif // NVIM_REGEXP_H diff --git a/src/nvim/regexp_bt.c b/src/nvim/regexp_bt.c index ac33fc0f13..1b32447d77 100644 --- a/src/nvim/regexp_bt.c +++ b/src/nvim/regexp_bt.c @@ -1,137 +1,130 @@ // 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 -/* - * - * 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. - */ +// 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> @@ -139,11 +132,10 @@ #include <string.h> #include "nvim/garray.h" +#include "nvim/profile.h" #include "nvim/regexp.h" -/* - * The opcodes are: - */ +// The opcodes are: // definition number opnd? meaning #define END 0 // End of program or NOMATCH operand. @@ -240,9 +232,7 @@ #define RE_VISUAL 208 // Match Visual area #define RE_COMPOSING 209 // any composing characters -/* - * Flags to be passed up and down. - */ +// 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 +. @@ -252,17 +242,17 @@ 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 uint8_t *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 uint8_t 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 uint8_t *classchars = (uint8_t *)".iIkKfFpPsSdDxXoOwWhHaAlLuU"; static int classcodes[] = { ANY, IDENT, SIDENT, KWORD, SKWORD, FNAME, SFNAME, PRINT, SPRINT, @@ -273,11 +263,9 @@ static int classcodes[] = { 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) +// When regcode is set to this value, code is not emitted and size is computed +// instead. +#define JUST_CALC_SIZE ((uint8_t *)-1) // Values for rs_state in regitem_T. typedef enum regstate_E { @@ -297,14 +285,12 @@ typedef enum regstate_E { 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". - */ +// 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 + uint8_t *ptr; // rex.input pointer, for single-line regexp lpos_T pos; // rex.input pos, for multi-line regexp } rs_u; int rs_len; @@ -313,7 +299,7 @@ typedef struct { // struct to save start/end pointer/position in for \(\) typedef struct { union { - char_u *ptr; + uint8_t *ptr; lpos_T pos; } se_u; } save_se_T; @@ -327,16 +313,14 @@ typedef struct regbehind_S { 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. - */ +// 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 int16_t rs_no; // submatch nr or BEHIND/NOBEHIND - char_u *rs_scan; // current node in program + uint8_t *rs_scan; // current node in program union { save_se_T sesave; regsave_T regsave; @@ -355,73 +339,67 @@ typedef struct regstar_S { // 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 + uint8_t *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 - */ +// "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. - */ +// 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. - */ +// 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) @@ -433,14 +411,14 @@ static regsave_T behind_pos; // 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); +static uint8_t *reg(int paren, int *flagp); #ifdef BT_REGEXP_DUMP -static void regdump(char_u *, bt_regprog_T *); +static void regdump(uint8_t *, bt_regprog_T *); #endif #ifdef REGEXP_DEBUG -static char_u *regprop(char_u *); +static uint8_t *regprop(uint8_t *); static int regnarrate = 0; #endif @@ -449,12 +427,10 @@ static int regnarrate = 0; # 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() +// Setup to parse the regexp. Used once to get the length and once to do it. +static void regcomp_start(uint8_t *expr, int re_flags) // see vim_regcomp() { - initchr(expr); + initchr((char *)expr); if (re_flags & RE_MAGIC) { reg_magic = MAGIC_ON; } else { @@ -484,21 +460,17 @@ static bool use_multibytecode(int c) || utf_iscomposing(c)); } -/* - * Emit (if appropriate) a byte of code - */ +// Emit (if appropriate) a byte of code static void regc(int b) { if (regcode == JUST_CALC_SIZE) { regsize++; } else { - *regcode++ = (char_u)b; + *regcode++ = (uint8_t)b; } } -/* - * Emit (if appropriate) a multi-byte character of code - */ +// Emit (if appropriate) a multi-byte character of code static void regmbc(int c) { if (regcode == JUST_CALC_SIZE) { @@ -508,11 +480,9 @@ static void regmbc(int c) } } -/* - * 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() - */ +// 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) { { @@ -1481,43 +1451,37 @@ static void reg_equi_class(int c) regmbc(c); } -/* - * Emit a node. - * Return pointer to generated code. - */ -static char_u *regnode(int op) +// Emit a node. +// Return pointer to generated code. +static uint8_t *regnode(int op) { - char_u *ret; + uint8_t *ret; ret = regcode; if (ret == JUST_CALC_SIZE) { regsize += 3; } else { - *regcode++ = (char_u)op; + *regcode++ = (uint8_t)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) +// Write a four bytes number at "p" and return pointer to the next char. +static uint8_t *re_put_uint32(uint8_t *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); + *p++ = (uint8_t)((val >> 24) & 0377); + *p++ = (uint8_t)((val >> 16) & 0377); + *p++ = (uint8_t)((val >> 8) & 0377); + *p++ = (uint8_t)(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) +// 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 uint8_t *regnext(uint8_t *p) FUNC_ATTR_NONNULL_ALL { int offset; @@ -1539,7 +1503,7 @@ static char_u *regnext(char_u *p) } // Set the next-pointer at the end of a node chain. -static void regtail(char_u *p, char_u *val) +static void regtail(uint8_t *p, uint8_t *val) { int offset; @@ -1548,9 +1512,9 @@ static void regtail(char_u *p, char_u *val) } // Find last node. - char_u *scan = p; + uint8_t *scan = p; for (;;) { - char_u *temp = regnext(scan); + uint8_t *temp = regnext(scan); if (temp == NULL) { break; } @@ -1568,15 +1532,13 @@ static void regtail(char_u *p, char_u *val) if (offset > 0xffff) { reg_toolong = true; } else { - *(scan + 1) = (char_u)(((unsigned)offset >> 8) & 0377); - *(scan + 2) = (char_u)(offset & 0377); + *(scan + 1) = (uint8_t)(((unsigned)offset >> 8) & 0377); + *(scan + 2) = (uint8_t)(offset & 0377); } } -/* - * Like regtail, on item after a BRANCH; nop if none. - */ -static void regoptail(char_u *p, char_u *val) +// Like regtail, on item after a BRANCH; nop if none. +static void regoptail(uint8_t *p, uint8_t *val) { // When op is neither BRANCH nor BRACE_COMPLEX0-9, it is "operandless" if (p == NULL || p == JUST_CALC_SIZE @@ -1587,16 +1549,14 @@ static void regoptail(char_u *p, char_u *val) 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) +// Insert an operator in front of already-emitted operand +// +// Means relocating the operand. +static void reginsert(int op, uint8_t *opnd) { - char_u *src; - char_u *dst; - char_u *place; + uint8_t *src; + uint8_t *dst; + uint8_t *place; if (regcode == JUST_CALC_SIZE) { regsize += 3; @@ -1610,20 +1570,18 @@ static void reginsert(int op, char_u *opnd) } place = opnd; // Op node, where operand used to be. - *place++ = (char_u)op; + *place++ = (uint8_t)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) +// Insert an operator in front of already-emitted operand. +// Add a number to the operator. +static void reginsert_nr(int op, long val, uint8_t *opnd) { - char_u *src; - char_u *dst; - char_u *place; + uint8_t *src; + uint8_t *dst; + uint8_t *place; if (regcode == JUST_CALC_SIZE) { regsize += 7; @@ -1637,24 +1595,22 @@ static void reginsert_nr(int op, long val, char_u *opnd) } place = opnd; // Op node, where operand used to be. - *place++ = (char_u)op; + *place++ = (uint8_t)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) +// 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, uint8_t *opnd) { - char_u *src; - char_u *dst; - char_u *place; + uint8_t *src; + uint8_t *dst; + uint8_t *place; if (regcode == JUST_CALC_SIZE) { regsize += 11; @@ -1668,7 +1624,7 @@ static void reginsert_limits(int op, long minval, long maxval, char_u *opnd) } place = opnd; // Op node, where operand used to be. - *place++ = (char_u)op; + *place++ = (uint8_t)op; *place++ = NUL; *place++ = NUL; assert(minval >= 0 && (uintmax_t)minval <= UINT32_MAX); @@ -1685,11 +1641,11 @@ static void reginsert_limits(int op, long minval, long maxval, char_u *opnd) static int seen_endbrace(int refnum) { if (!had_endbrace[refnum]) { - char_u *p; + uint8_t *p; // Trick: check if "@<=" or "@<!" follows, in which case // the \1 can appear before the referenced match. - for (p = (char_u *)regparse; *p != NUL; p++) { + for (p = (uint8_t *)regparse; *p != NUL; p++) { if (p[0] == '@' && p[1] == '<' && (p[2] == '!' || p[2] == '=')) { break; } @@ -1704,19 +1660,17 @@ static int seen_endbrace(int refnum) 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) +// 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 uint8_t *regatom(int *flagp) { - char_u *ret; + uint8_t *ret; int flags; int c; - char_u *p; + uint8_t *p; int extra = 0; int save_prev_at_start = prev_at_start; @@ -1792,7 +1746,7 @@ static char_u *regatom(int *flagp) case Magic('L'): case Magic('u'): case Magic('U'): - p = (char_u *)vim_strchr((char *)classchars, no_Magic(c)); + p = (uint8_t *)vim_strchr((char *)classchars, no_Magic(c)); if (p == NULL) { EMSG_RET_NULL(_("E63: invalid use of \\_")); } @@ -1854,17 +1808,17 @@ static char_u *regatom(int *flagp) case Magic('~'): // previous substitute pattern if (reg_prev_sub != NULL) { - char_u *lp; + uint8_t *lp; ret = regnode(EXACTLY); - lp = (char_u *)reg_prev_sub; + lp = (uint8_t *)reg_prev_sub; while (*lp != NUL) { regc(*lp++); } regc(NUL); if (*reg_prev_sub != NUL) { *flagp |= HASWIDTH; - if ((lp - (char_u *)reg_prev_sub) == 1) { + if ((lp - (uint8_t *)reg_prev_sub) == 1) { *flagp |= SIMPLE; } } @@ -1971,6 +1925,11 @@ static char_u *regatom(int *flagp) break; case '#': + if (regparse[0] == '=' && regparse[1] >= 48 && regparse[1] <= 50) { + // misplaced \%#=1 + semsg(_(e_atom_engine_must_be_at_start_of_pattern), regparse[1]); + return FAIL; + } ret = regnode(CURSOR); break; @@ -1989,9 +1948,9 @@ static char_u *regatom(int *flagp) EMSG_ONE_RET_NULL; } { - char_u *lastbranch; - char_u *lastnode = NULL; - char_u *br; + uint8_t *lastbranch; + uint8_t *lastnode = NULL; + uint8_t *br; ret = NULL; while ((c = getchr()) != ']') { @@ -2091,6 +2050,7 @@ static char_u *regatom(int *flagp) uint32_t n = 0; int cmp; bool cur = false; + bool got_digit = false; cmp = c; if (cmp == '<' || cmp == '>') { @@ -2101,6 +2061,7 @@ static char_u *regatom(int *flagp) c = getchr(); } while (ascii_isdigit(c)) { + got_digit = true; n = n * 10 + (uint32_t)(c - '0'); c = getchr(); } @@ -2111,13 +2072,13 @@ static char_u *regatom(int *flagp) if (ret == JUST_CALC_SIZE) { regsize += 2; } else { - *regcode++ = (char_u)c; - *regcode++ = (char_u)cmp; + *regcode++ = (uint8_t)c; + *regcode++ = (uint8_t)cmp; } break; - } else if (c == 'l' || c == 'c' || c == 'v') { + } else if ((c == 'l' || c == 'c' || c == 'v') && (cur || got_digit)) { if (cur && n) { - semsg(_(e_regexp_number_after_dot_pos_search), no_Magic(c)); + semsg(_(e_regexp_number_after_dot_pos_search_chr), no_Magic(c)); rc_did_emsg = true; return NULL; } @@ -2149,7 +2110,7 @@ static char_u *regatom(int *flagp) // put the number and the optional // comparator after the opcode regcode = re_put_uint32(regcode, n); - *regcode++ = (char_u)cmp; + *regcode++ = (uint8_t)cmp; } break; } @@ -2163,11 +2124,11 @@ static char_u *regatom(int *flagp) case Magic('['): collection: { - char_u *lp; + uint8_t *lp; // If there is no matching ']', we assume the '[' is a normal // character. This makes 'incsearch' and ":help [" work. - lp = skip_anyof(regparse); + lp = (uint8_t *)skip_anyof(regparse); if (*lp == ']') { // there is a matching ']' int startc = -1; // > 0 when next '-' is a range int endc; @@ -2204,7 +2165,7 @@ collection: endc = get_coll_element(®parse); } if (endc == 0) { - endc = mb_ptr2char_adv((const char_u **)®parse); + endc = mb_ptr2char_adv((const char **)®parse); } // Handle \o40, \x20 and \u20AC style sequences @@ -2236,10 +2197,10 @@ collection: // 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 + && (vim_strchr(REGEXP_INRANGE, (uint8_t)regparse[1]) != NULL || (!reg_cpo_lit && vim_strchr(REGEXP_ABBR, - regparse[1]) != NULL))) { + (uint8_t)regparse[1]) != NULL))) { regparse++; if (*regparse == 'n') { // '\n' in range: also match NL @@ -2282,8 +2243,7 @@ collection: if (c_class != 0) { // produce equivalence class reg_equi_class(c_class); - } else if ((c_class = - get_coll_element(®parse)) != 0) { + } else if ((c_class = get_coll_element(®parse)) != 0) { // produce a collating element regmbc(c_class); } else { @@ -2459,7 +2419,7 @@ do_multibyte: for (len = 0; c != NUL && (len == 0 || (re_multi_type(peekchr()) == NOT_MULTI && !one_exactly - && !is_Magic(c))); ++len) { + && !is_Magic(c))); len++) { c = no_Magic(c); { regmbc(c); @@ -2493,20 +2453,18 @@ do_multibyte: 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) +// 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 uint8_t *regpiece(int *flagp) { - char_u *ret; + uint8_t *ret; int op; - char_u *next; + uint8_t *next; int flags; long minval; long maxval; @@ -2637,15 +2595,13 @@ static char_u *regpiece(int *flagp) return ret; } -/* - * Parse one alternative of an | or & operator. - * Implements the concatenation operator. - */ -static char_u *regconcat(int *flagp) +// Parse one alternative of an | or & operator. +// Implements the concatenation operator. +static uint8_t *regconcat(int *flagp) { - char_u *first = NULL; - char_u *chain = NULL; - char_u *latest; + uint8_t *first = NULL; + uint8_t *chain = NULL; + uint8_t *latest; int flags; int cont = true; @@ -2715,15 +2671,13 @@ static char_u *regconcat(int *flagp) return first; } -/* - * Parse one alternative of an | operator. - * Implements the & operator. - */ -static char_u *regbranch(int *flagp) +// Parse one alternative of an | operator. +// Implements the & operator. +static uint8_t *regbranch(int *flagp) { - char_u *ret; - char_u *chain = NULL; - char_u *latest; + uint8_t *ret; + uint8_t *chain = NULL; + uint8_t *latest; int flags; *flagp = WORST | HASNL; // Tentatively. @@ -2768,11 +2722,11 @@ static char_u *regbranch(int *flagp) /// follows makes it hard to avoid. /// /// @param paren REG_NOPAREN, REG_PAREN, REG_NPAREN or REG_ZPAREN -static char_u *reg(int paren, int *flagp) +static uint8_t *reg(int paren, int *flagp) { - char_u *ret; - char_u *br; - char_u *ender; + uint8_t *ret; + uint8_t *br; + uint8_t *ender; int parno = 0; int flags; @@ -2867,31 +2821,29 @@ static char_u *reg(int paren, int *flagp) 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) +// 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(uint8_t *expr, int re_flags) { - char_u *scan; - char_u *longest; + uint8_t *scan; + uint8_t *longest; int len; int flags; @@ -2938,7 +2890,7 @@ static regprog_T *bt_regcomp(char_u *expr, int re_flags) r->regflags |= RF_LOOKBH; } // Remember whether this pattern has any \z specials in it. - r->reghasz = (char_u)re_has_z; + r->reghasz = (uint8_t)re_has_z; scan = r->program + 1; // First BRANCH. if (OP(regnext(scan)) == END) { // Only one top-level choice. scan = OPERAND(scan); @@ -2956,7 +2908,7 @@ static regprog_T *bt_regcomp(char_u *expr, int re_flags) || OP(scan) == NOTHING || OP(scan) == MOPEN + 0 || OP(scan) == NOPEN || OP(scan) == MCLOSE + 0 || OP(scan) == NCLOSE) { - char_u *regnext_scan = regnext(scan); + uint8_t *regnext_scan = regnext(scan); if (OP(regnext_scan) == EXACTLY) { r->regstart = utf_ptr2char((char *)OPERAND(regnext_scan)); } @@ -2976,9 +2928,9 @@ static regprog_T *bt_regcomp(char_u *expr, int re_flags) longest = NULL; len = 0; for (; scan != NULL; scan = regnext(scan)) { - if (OP(scan) == EXACTLY && STRLEN(OPERAND(scan)) >= (size_t)len) { + if (OP(scan) == EXACTLY && strlen((char *)OPERAND(scan)) >= (size_t)len) { longest = OPERAND(scan); - len = (int)STRLEN(OPERAND(scan)); + len = (int)strlen((char *)OPERAND(scan)); } } r->regmust = longest; @@ -2992,19 +2944,15 @@ static regprog_T *bt_regcomp(char_u *expr, int re_flags) 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. - */ +// 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. - */ +// 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; @@ -3030,9 +2978,7 @@ static int coll_get_char(void) return (int)nr; } -/* - * Free a compiled regexp program, returned by bt_regcomp(). - */ +// Free a compiled regexp program, returned by bt_regcomp(). static void bt_regfree(regprog_T *prog) { xfree(prog); @@ -3040,11 +2986,9 @@ static void bt_regfree(regprog_T *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). - */ +// 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; @@ -3070,7 +3014,7 @@ static void reg_restore(regsave_T *save, garray_T *gap) // 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.line = (uint8_t *)reg_getline(rex.lnum); } rex.input = rex.line + save->rs_u.pos.col; } else { @@ -3101,13 +3045,11 @@ static bool reg_save_equal(const regsave_T *save) else /* NOLINT */ \ *(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. - */ +// 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; @@ -3115,7 +3057,7 @@ static void save_se_multi(save_se_T *savep, lpos_T *posp) posp->col = (colnr_T)(rex.input - rex.line); } -static void save_se_one(save_se_T *savep, char_u **pp) +static void save_se_one(save_se_T *savep, uint8_t **pp) { savep->se_u.ptr = *pp; *pp = rex.input; @@ -3125,14 +3067,14 @@ static void save_se_one(save_se_T *savep, char_u **pp) /// Advances rex.input (and rex.lnum) to just after the matched chars. /// /// @param maxcount maximum number of matches allowed -static int regrepeat(char_u *p, long maxcount) +static int regrepeat(uint8_t *p, long maxcount) { long count = 0; - char_u *opnd; + uint8_t *opnd; int mask; int testval = 0; - char_u *scan = rex.input; // Make local copy of rex.input for speed. + uint8_t *scan = rex.input; // Make local copy of rex.input for speed. opnd = OPERAND(p); switch (OP(p)) { case ANY: @@ -3443,12 +3385,12 @@ do_class: } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { scan++; } else if ((len = utfc_ptr2len((char *)scan)) > 1) { - if ((cstrchr(opnd, utf_ptr2char((char *)scan)) == NULL) == testval) { + if ((cstrchr((char *)opnd, utf_ptr2char((char *)scan)) == NULL) == testval) { break; } scan += len; } else { - if ((cstrchr(opnd, *scan) == NULL) == testval) { + if ((cstrchr((char *)opnd, *scan) == NULL) == testval) { break; } scan++; @@ -3487,11 +3429,9 @@ do_class: 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) +// 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, uint8_t *scan) { regitem_T *rp; @@ -3509,10 +3449,8 @@ static regitem_T *regstack_push(regstate_T state, char_u *scan) return rp; } -/* - * Pop an item from the regstack. - */ -static void regstack_pop(char_u **scan) +// Pop an item from the regstack. +static void regstack_pop(uint8_t **scan) { regitem_T *rp; @@ -3530,15 +3468,17 @@ static void save_subexpr(regbehind_T *bp) // 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]; - } + if (rex.need_clear_subexpr) { + return; + } + + 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]; } } } @@ -3549,15 +3489,17 @@ static void restore_subexpr(regbehind_T *bp) { // 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; - } + if (rex.need_clear_subexpr) { + return; + } + + 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; } } } @@ -3578,9 +3520,9 @@ static void restore_subexpr(regbehind_T *bp) /// just after the last matched character. /// - false when there is no match. Leaves rex.input and rex.lnum in an /// undefined state! -static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) +static bool regmatch(uint8_t *scan, proftime_T *tm, int *timed_out) { - char_u *next; // Next node. + uint8_t *next; // Next node. int op; int c; regitem_T *rp; @@ -3601,8 +3543,8 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) #ifdef REGEXP_DEBUG if (scan != NULL && regnarrate) { - mch_errmsg((char *)regprop(scan)); - mch_errmsg("(\n"); + os_errmsg((char *)regprop(scan)); + os_errmsg("(\n"); } #endif @@ -3628,18 +3570,18 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) #ifdef REGEXP_DEBUG if (regnarrate) { - mch_errmsg((char *)regprop(scan)); - mch_errmsg("...\n"); + os_errmsg((char *)regprop(scan)); + os_errmsg("...\n"); if (re_extmatch_in != NULL) { int i; - mch_errmsg(_("External submatches:\n")); + os_errmsg(_("External submatches:\n")); for (i = 0; i < NSUBEXP; i++) { - mch_errmsg(" \""); + os_errmsg(" \""); if (re_extmatch_in->matches[i] != NULL) { - mch_errmsg((char *)re_extmatch_in->matches[i]); + os_errmsg((char *)re_extmatch_in->matches[i]); } - mch_errmsg("\"\n"); + os_errmsg("\"\n"); } } } @@ -3709,7 +3651,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) // Line may have been freed, get it again. if (REG_MULTI) { - rex.line = reg_getline(rex.lnum); + rex.line = (uint8_t *)reg_getline(rex.lnum); rex.input = rex.line + col; } @@ -3720,7 +3662,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) pos = &fm->mark; 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)) + ? (colnr_T)strlen((char *)reg_getline(pos->lnum - rex.reg_firstlnum)) : pos->col; if (pos->lnum == rex.lnum + rex.reg_firstlnum @@ -3765,7 +3707,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) if (!re_num_cmp(win_linetabsize(rex.reg_win == NULL ? curwin : rex.reg_win, rex.reg_firstlnum + rex.lnum, - rex.line, + (char *)rex.line, (colnr_T)(rex.input - rex.line)) + 1, scan)) { status = RA_NOMATCH; @@ -3778,7 +3720,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) } 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); + mb_get_class_tab((char *)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) { @@ -3794,7 +3736,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) 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); + this_class = mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab); prev_class = reg_prev_class(); if (this_class == prev_class || prev_class == 0 || prev_class == 1) { @@ -4023,7 +3965,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) case EXACTLY: { int len; - char_u *opnd; + uint8_t *opnd; opnd = OPERAND(scan); // Inline the first byte, for speed. @@ -4038,7 +3980,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) len = 1; // matched a single byte above } else { // Need to match first byte again for multi-byte. - len = (int)STRLEN(opnd); + len = (int)strlen((char *)opnd); if (cstrncmp((char *)opnd, (char *)rex.input, &len) != 0) { status = RA_NOMATCH; } @@ -4065,7 +4007,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) case ANYBUT: if (c == NUL) { status = RA_NOMATCH; - } else if ((cstrchr(OPERAND(scan), c) == NULL) == (op == ANYOF)) { + } else if ((cstrchr((char *)OPERAND(scan), c) == NULL) == (op == ANYOF)) { status = RA_NOMATCH; } else { ADVANCE_REGINPUT(); @@ -4075,7 +4017,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) case MULTIBYTECODE: { int i, len; - const char_u *opnd = OPERAND(scan); + const uint8_t *opnd = OPERAND(scan); // Safety check (just in case 'encoding' was changed since // compiling the program). if ((len = utfc_ptr2len((char *)opnd)) < 2) { @@ -4319,7 +4261,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) no = op - ZREF; if (re_extmatch_in != NULL && re_extmatch_in->matches[no] != NULL) { - int len = (int)STRLEN(re_extmatch_in->matches[no]); + int len = (int)strlen((char *)re_extmatch_in->matches[no]); if (cstrncmp((char *)re_extmatch_in->matches[no], (char *)rex.input, &len) != 0) { status = RA_NOMATCH; } else { @@ -4636,7 +4578,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) // 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 + brace_count[rp->rs_no]--; // decrement match count } regstack_pop(&scan); break; @@ -4646,7 +4588,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) 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]; + brace_count[rp->rs_no]--; // continue with the items after "\{}" status = RA_CONT; } @@ -4745,7 +4687,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) if (limit > 0 && ((rp->rs_un.regsave.rs_u.pos.lnum < behind_pos.rs_u.pos.lnum - ? (colnr_T)STRLEN(rex.line) + ? (colnr_T)strlen((char *)rex.line) : behind_pos.rs_u.pos.col) - rp->rs_un.regsave.rs_u.pos.col >= limit)) { no = FAIL; @@ -4758,11 +4700,11 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) } else { reg_restore(&rp->rs_un.regsave, &backpos); rp->rs_un.regsave.rs_u.pos.col = - (colnr_T)STRLEN(rex.line); + (colnr_T)strlen((char *)rex.line); } } else { - const char_u *const line = - reg_getline(rp->rs_un.regsave.rs_u.pos.lnum); + const uint8_t *const line = + (uint8_t *)reg_getline(rp->rs_un.regsave.rs_u.pos.lnum); rp->rs_un.regsave.rs_u.pos.col -= utf_head_off((char *)line, @@ -4844,12 +4786,12 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) break; } rex.lnum--; - rex.line = reg_getline(rex.lnum); + rex.line = (uint8_t *)reg_getline(rex.lnum); // Just in case regrepeat() didn't count right. if (rex.line == NULL) { break; } - rex.input = rex.line + STRLEN(rex.line); + rex.input = rex.line + strlen((char *)rex.line); fast_breakcheck(); } else { MB_PTR_BACK(rex.line, rex.input); @@ -4975,13 +4917,13 @@ static long regtry(bt_regprog_T *prog, colnr_T col, proftime_T *tm, int *timed_o && reg_endzpos[i].lnum == reg_startzpos[i].lnum && reg_endzpos[i].col >= reg_startzpos[i].col) { re_extmatch_out->matches[i] = - (char_u *)xstrnsave((char *)reg_getline(reg_startzpos[i].lnum) + reg_startzpos[i].col, - (size_t)(reg_endzpos[i].col - reg_startzpos[i].col)); + (uint8_t *)xstrnsave((char *)reg_getline(reg_startzpos[i].lnum) + reg_startzpos[i].col, + (size_t)(reg_endzpos[i].col - reg_startzpos[i].col)); } } else { if (reg_startzp[i] != NULL && reg_endzp[i] != NULL) { re_extmatch_out->matches[i] = - (char_u *)xstrnsave((char *)reg_startzp[i], (size_t)(reg_endzp[i] - reg_startzp[i])); + (uint8_t *)xstrnsave((char *)reg_startzp[i], (size_t)(reg_endzp[i] - reg_startzp[i])); } } } @@ -4992,15 +4934,16 @@ static long regtry(bt_regprog_T *prog, colnr_T col, proftime_T *tm, int *timed_o /// Match a regexp against a string ("line" points to the string) or multiple /// lines (if "line" is NULL, use reg_getline()). /// -/// @param col column to start search +/// @param startcol column to start looking for match /// @param tm timeout limit or NULL /// @param timed_out flag set on timeout or NULL /// /// @return 0 for failure, or number of lines contained in the match. -static long bt_regexec_both(char_u *line, colnr_T col, proftime_T *tm, int *timed_out) +static long bt_regexec_both(uint8_t *line, colnr_T startcol, proftime_T *tm, int *timed_out) { bt_regprog_T *prog; - char_u *s; + uint8_t *s; + colnr_T col = startcol; long retval = 0L; // Create "regstack" and "backpos" if they are not allocated yet. @@ -5023,13 +4966,13 @@ static long bt_regexec_both(char_u *line, colnr_T col, proftime_T *tm, int *time if (REG_MULTI) { prog = (bt_regprog_T *)rex.reg_mmatch->regprog; - line = reg_getline((linenr_T)0); + line = (uint8_t *)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 = (char_u **)rex.reg_match->startp; - rex.reg_endp = (char_u **)rex.reg_match->endp; + rex.reg_startp = (uint8_t **)rex.reg_match->startp; + rex.reg_endp = (uint8_t **)rex.reg_match->endp; } // Be paranoid... @@ -5068,14 +5011,14 @@ static long bt_regexec_both(char_u *line, colnr_T col, proftime_T *tm, int *time // 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 = (char_u *)vim_strchr((char *)s, c)) != NULL) { + while ((s = (uint8_t *)vim_strchr((char *)s, c)) != NULL) { if (cstrncmp((char *)s, (char *)prog->regmust, &prog->regmlen) == 0) { break; // Found it. } MB_PTR_ADV(s); } } else { - while ((s = cstrchr(s, c)) != NULL) { + while ((s = (uint8_t *)cstrchr((char *)s, c)) != NULL) { if (cstrncmp((char *)s, (char *)prog->regmust, &prog->regmlen) == 0) { break; // Found it. } @@ -5110,7 +5053,7 @@ static long bt_regexec_both(char_u *line, colnr_T col, proftime_T *tm, int *time while (!got_int) { if (prog->regstart != NUL) { // Skip until the char we know it must start with. - s = cstrchr(rex.line + col, prog->regstart); + s = (uint8_t *)cstrchr((char *)rex.line + col, prog->regstart); if (s == NULL) { retval = 0; break; @@ -5132,7 +5075,7 @@ static long bt_regexec_both(char_u *line, colnr_T col, proftime_T *tm, int *time // if not currently on the first line, get it again if (rex.lnum != 0) { rex.lnum = 0; - rex.line = reg_getline((linenr_T)0); + rex.line = (uint8_t *)reg_getline((linenr_T)0); } if (rex.line[col] == NUL) { break; @@ -5175,10 +5118,18 @@ theend: || (end->lnum == start->lnum && end->col < start->col)) { rex.reg_mmatch->endpos[0] = rex.reg_mmatch->startpos[0]; } + + // startpos[0] may be set by "\zs", also return the column where + // the whole pattern matched. + rex.reg_mmatch->rmm_matchcol = col; } else { if (rex.reg_match->endp[0] < rex.reg_match->startp[0]) { rex.reg_match->endp[0] = rex.reg_match->startp[0]; } + + // startpos[0] may be set by "\zs", also return the column where + // the whole pattern matched. + rex.reg_match->rm_matchcol = col; } } @@ -5194,7 +5145,7 @@ theend: /// @param col column to start looking for match /// /// @return 0 for failure, number of lines contained in the match otherwise. -static int bt_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col, bool line_lbr) +static int bt_regexec_nl(regmatch_T *rmp, uint8_t *line, colnr_T col, bool line_lbr) { rex.reg_match = rmp; rex.reg_mmatch = NULL; @@ -5226,24 +5177,12 @@ static int bt_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col, bool line_l 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; - + init_regexec_multi(rmp, win, buf, lnum); 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) +// Compare a number with the operand of RE_LNUM, RE_COL or RE_VCOL. +static int re_num_cmp(uint32_t val, uint8_t *scan) { uint32_t n = (uint32_t)OPERAND_MIN(scan); @@ -5258,15 +5197,13 @@ static int re_num_cmp(uint32_t val, char_u *scan) #ifdef BT_REGEXP_DUMP -/* - * regdump - dump a regexp onto stdout in vaguely comprehensible form - */ -static void regdump(char_u *pattern, bt_regprog_T *r) +// regdump - dump a regexp onto stdout in vaguely comprehensible form +static void regdump(uint8_t *pattern, bt_regprog_T *r) { - char_u *s; + uint8_t *s; int op = EXACTLY; // Arbitrary non-END op. - char_u *next; - char_u *end = NULL; + uint8_t *next; + uint8_t *end = NULL; FILE *f; # ifdef BT_REGEXP_LOG @@ -5346,10 +5283,8 @@ static void regdump(char_u *pattern, bt_regprog_T *r) #ifdef REGEXP_DEBUG -/* - * regprop - printable representation of opcode - */ -static char_u *regprop(char_u *op) +// regprop - printable representation of opcode +static uint8_t *regprop(uint8_t *op) { char *p; static char buf[50]; @@ -5721,6 +5656,6 @@ static char_u *regprop(char_u *op) if (p != NULL) { STRCAT(buf, p); } - return (char_u *)buf; + return (uint8_t *)buf; } #endif // REGEXP_DEBUG diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h index b24ed350e8..16bb2db464 100644 --- a/src/nvim/regexp_defs.h +++ b/src/nvim/regexp_defs.h @@ -1,13 +1,11 @@ -/* - * NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE - * - * This is NOT the original regular expression code as written by Henry - * Spencer. This code has been modified specifically for use with Vim, and - * should not be used apart from compiling Vim. If you want a good regular - * expression library, get the original code. - * - * NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE - */ +// NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE +// +// This is NOT the original regular expression code as written by Henry +// Spencer. This code has been modified specifically for use with Vim, and +// should not be used apart from compiling Vim. If you want a good regular +// expression library, get the original code. +// +// NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE #ifndef NVIM_REGEXP_DEFS_H #define NVIM_REGEXP_DEFS_H @@ -17,18 +15,32 @@ #include "nvim/pos.h" #include "nvim/types.h" -/* - * The number of sub-matches is limited to 10. - * The first one (index 0) is the whole match, referenced with "\0". - * The second one (index 1) is the first sub-match, referenced with "\1". - * This goes up to the tenth (index 9), referenced with "\9". - */ +/// Used for "magic_overruled". +typedef enum { + OPTION_MAGIC_NOT_SET, ///< p_magic not overruled + OPTION_MAGIC_ON, ///< magic on inside regexp + OPTION_MAGIC_OFF, ///< magic off inside regexp +} optmagic_T; + +/// Magicness of a pattern, used by regexp code. +/// The order and values matter: +/// magic <= MAGIC_OFF includes MAGIC_NONE +/// magic >= MAGIC_ON includes MAGIC_ALL +typedef enum { + MAGIC_NONE = 1, ///< "\V" very unmagic + MAGIC_OFF = 2, ///< "\M" or 'magic' off + MAGIC_ON = 3, ///< "\m" or 'magic' + MAGIC_ALL = 4, ///< "\v" very magic +} magic_T; + +// The number of sub-matches is limited to 10. +// The first one (index 0) is the whole match, referenced with "\0". +// The second one (index 1) is the first sub-match, referenced with "\1". +// This goes up to the tenth (index 9), referenced with "\9". #define NSUBEXP 10 -/* - * In the NFA engine: how many braces are allowed. - * TODO(RE): Use dynamic memory allocation instead of static, like here - */ +// In the NFA engine: how many braces are allowed. +// TODO(RE): Use dynamic memory allocation instead of static, like here #define NFA_MAX_BRACES 20 // In the NFA engine: how many states are allowed. @@ -55,17 +67,17 @@ typedef struct { regprog_T *regprog; lpos_T startpos[NSUBEXP]; lpos_T endpos[NSUBEXP]; + + colnr_T rmm_matchcol; ///< match start without "\zs" int rmm_ic; colnr_T rmm_maxcol; /// when not zero: maximum column } regmmatch_T; #include "nvim/buffer_defs.h" -/* - * Structure returned by vim_regcomp() to pass on to vim_regexec(). - * This is the general structure. For the actual matcher, two specific - * structures are used. See code below. - */ +// Structure returned by vim_regcomp() to pass on to vim_regexec(). +// This is the general structure. For the actual matcher, two specific +// structures are used. See code below. struct regprog { regengine_T *engine; unsigned regflags; @@ -74,11 +86,9 @@ struct regprog { bool re_in_use; ///< prog is being executed }; -/* - * Structure used by the back track matcher. - * These fields are only to be used in regexp.c! - * See regexp.c for an explanation. - */ +// Structure used by the back track matcher. +// These fields are only to be used in regexp.c! +// See regexp.c for an explanation. typedef struct { // These four members implement regprog_T. regengine_T *engine; @@ -107,9 +117,7 @@ struct nfa_state { int val; }; -/* - * Structure used by the NFA matcher. - */ +// Structure used by the NFA matcher. typedef struct { // These four members implement regprog_T. regengine_T *engine; @@ -133,23 +141,21 @@ typedef struct { nfa_state_T state[1]; // actually longer.. } nfa_regprog_T; -/* - * Structure to be used for single-line matching. - * Sub-match "no" starts at "startp[no]" and ends just before "endp[no]". - * When there is no match, the pointer is NULL. - */ +// Structure to be used for single-line matching. +// Sub-match "no" starts at "startp[no]" and ends just before "endp[no]". +// When there is no match, the pointer is NULL. typedef struct { regprog_T *regprog; char *startp[NSUBEXP]; char *endp[NSUBEXP]; + + colnr_T rm_matchcol; ///< match start without "\zs" bool rm_ic; } regmatch_T; -/* - * Structure used to store external references: "\z\(\)" to "\z\1". - * Use a reference count to avoid the need to copy this around. When it goes - * from 1 to zero the matches need to be freed. - */ +// Structure used to store external references: "\z\(\)" to "\z\1". +// Use a reference count to avoid the need to copy this around. When it goes +// from 1 to zero the matches need to be freed. struct reg_extmatch { int16_t refcnt; char_u *matches[NSUBEXP]; diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index fbd4e26c75..93b03f0632 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -1,11 +1,9 @@ // 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 -/* - * NFA regular expression implementation. - * - * This file is included in "regexp.c". - */ +// NFA regular expression implementation. +// +// This file is included in "regexp.c". #include <assert.h> #include <inttypes.h> @@ -246,10 +244,10 @@ static int nfa_classcodes[] = { NFA_UPPER, NFA_NUPPER }; -static char_u e_nul_found[] = N_("E865: (NFA) Regexp end encountered prematurely"); -static char_u e_misplaced[] = N_("E866: (NFA regexp) Misplaced %c"); -static char_u e_ill_char_class[] = N_("E877: (NFA regexp) Invalid character class: %" PRId64); -static char_u e_value_too_large[] = N_("E951: \\% value too large"); +static char e_nul_found[] = N_("E865: (NFA) Regexp end encountered prematurely"); +static char e_misplaced[] = N_("E866: (NFA regexp) Misplaced %c"); +static char e_ill_char_class[] = N_("E877: (NFA regexp) Invalid character class: %" PRId64); +static char e_value_too_large[] = N_("E951: \\% value too large"); // Since the out pointers in the list are always // uninitialized, we use the pointers themselves @@ -278,10 +276,11 @@ typedef struct { colnr_T end_col; } multi[NSUBEXP]; struct linepos { - char_u *start; - char_u *end; + uint8_t *start; + uint8_t *end; } line[NSUBEXP]; } list; + colnr_T orig_start_col; // list.multi[0].start_col without \zs } regsub_T; typedef struct { @@ -297,7 +296,7 @@ struct nfa_pim_S { regsubs_T subs; // submatch info, only party used union { lpos_T pos; - char_u *ptr; + uint8_t *ptr; } end; // where the match must end }; @@ -355,7 +354,7 @@ static int nfa_ll_index = 0; /// Initialize internal variables before NFA compilation. /// /// @param re_flags @see vim_regcomp() -static void nfa_regcomp_start(char_u *expr, int re_flags) +static void nfa_regcomp_start(uint8_t *expr, int re_flags) { size_t postfix_size; size_t nstate_max; @@ -363,7 +362,7 @@ static void nfa_regcomp_start(char_u *expr, int re_flags) nstate = 0; istate = 0; // A reasonable estimation for maximum size - nstate_max = (STRLEN(expr) + 1) * 25; + nstate_max = (strlen((char *)expr) + 1) * 25; // Some items blow up in size, such as [A-z]. Add more space for that. // When it is still not enough realloc_post_list() will be used. @@ -383,10 +382,8 @@ static void nfa_regcomp_start(char_u *expr, int re_flags) regcomp_start(expr, re_flags); } -/* - * Figure out if the NFA state list starts with an anchor, must match at start - * of the line. - */ +// Figure out if the NFA state list starts with an anchor, must match at start +// of the line. static int nfa_get_reganch(nfa_state_T *start, int depth) { nfa_state_T *p = start; @@ -441,10 +438,8 @@ static int nfa_get_reganch(nfa_state_T *start, int depth) return 0; } -/* - * Figure out if the NFA state list starts with a character which must match - * at start of the match. - */ +// Figure out if the NFA state list starts with a character which must match +// at start of the match. static int nfa_get_regstart(nfa_state_T *start, int depth) { nfa_state_T *p = start; @@ -521,17 +516,15 @@ static int nfa_get_regstart(nfa_state_T *start, int depth) return 0; } -/* - * Figure out if the NFA state list contains just literal text and nothing - * else. If so return a string in allocated memory with what must match after - * regstart. Otherwise return NULL. - */ -static char_u *nfa_get_match_text(nfa_state_T *start) +// Figure out if the NFA state list contains just literal text and nothing +// else. If so return a string in allocated memory with what must match after +// regstart. Otherwise return NULL. +static uint8_t *nfa_get_match_text(nfa_state_T *start) { nfa_state_T *p = start; int len = 0; - char_u *ret; - char_u *s; + uint8_t *ret; + uint8_t *s; if (p->c != NFA_MOPEN) { return NULL; // just in case @@ -557,10 +550,8 @@ static char_u *nfa_get_match_text(nfa_state_T *start) return ret; } -/* - * Allocate more space for post_start. Called when - * running above the estimated number of states. - */ +// Allocate more space for post_start. Called when +// running above the estimated number of states. static void realloc_post_list(void) { // For weird patterns the number of states can be very high. Increasing by @@ -572,17 +563,15 @@ static void realloc_post_list(void) post_start = new_start; } -/* - * Search between "start" and "end" and try to recognize a - * character class in expanded form. For example [0-9]. - * On success, return the id the character class to be emitted. - * On failure, return 0 (=FAIL) - * Start points to the first char of the range, while end should point - * to the closing brace. - * Keep in mind that 'ignorecase' applies at execution time, thus [a-z] may - * need to be interpreted as [a-zA-Z]. - */ -static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) +// Search between "start" and "end" and try to recognize a +// character class in expanded form. For example [0-9]. +// On success, return the id the character class to be emitted. +// On failure, return 0 (=FAIL) +// Start points to the first char of the range, while end should point +// to the closing brace. +// Keep in mind that 'ignorecase' applies at execution time, thus [a-z] may +// need to be interpreted as [a-zA-Z]. +static int nfa_recognize_char_class(uint8_t *start, uint8_t *end, int extra_newl) { #define CLASS_not 0x80 #define CLASS_af 0x40 @@ -593,7 +582,7 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) #define CLASS_o9 0x02 #define CLASS_underscore 0x01 - char_u *p; + uint8_t *p; int config = 0; bool newl = extra_newl == true; @@ -700,14 +689,12 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) return FAIL; } -/* - * Produce the bytes for equivalence class "c". - * Currently only handles latin1, latin9 and utf-8. - * Emits bytes in postfix notation: 'a,b,NFA_OR,c,NFA_OR' is - * equivalent to 'a OR b OR c' - * - * NOTE! When changing this function, also update reg_equi_class() - */ +// Produce the bytes for equivalence class "c". +// Currently only handles latin1, latin9 and utf-8. +// Emits bytes in postfix notation: 'a,b,NFA_OR,c,NFA_OR' is +// equivalent to 'a OR b OR c' +// +// NOTE! When changing this function, also update reg_equi_class() static void nfa_emit_equi_class(int c) { #define EMIT2(c) EMIT(c); EMIT(NFA_CONCAT); @@ -1748,7 +1735,7 @@ static void nfa_emit_equi_class(int c) case 0x1ef5: case 0x1ef7: case 0x1ef9: - EMIT2('y') EMIT2(y_acute) EMIT2(y_diaeresis) + EMIT2('y') EMIT2(y_acute) EMIT2(y_diaeresis) // NOLINT(whitespace/cast) EMIT2(0x177) EMIT2(0x1b4) EMIT2(0x233) EMIT2(0x24f) EMIT2(0x1e8f) EMIT2(0x1e99) EMIT2(0x1ef3) EMIT2(0x1ef5) EMIT2(0x1ef7) EMIT2(0x1ef9) @@ -1778,26 +1765,22 @@ static void nfa_emit_equi_class(int c) #undef EMIT2 } -/* - * Code to parse regular expression. - * - * We try to reuse parsing functions in regexp.c to - * minimize surprise and keep the syntax consistent. - */ - -/* - * Parse the lowest level. - * - * An atom can be one of a long list of items. Many atoms match one character - * in the text. It is often an ordinary character or a character class. - * Braces can be used to make a pattern into an atom. The "\z(\)" construct - * is only for syntax highlighting. - * - * atom ::= ordinary-atom - * or \( pattern \) - * or \%( pattern \) - * or \z( pattern \) - */ +// Code to parse regular expression. +// +// We try to reuse parsing functions in regexp.c to +// minimize surprise and keep the syntax consistent. + +// Parse the lowest level. +// +// An atom can be one of a long list of items. Many atoms match one character +// in the text. It is often an ordinary character or a character class. +// Braces can be used to make a pattern into an atom. The "\z(\)" construct +// is only for syntax highlighting. +// +// atom ::= ordinary-atom +// or \( pattern \) +// or \%( pattern \) +// or \z( pattern \) static int nfa_regatom(void) { int c; @@ -1805,9 +1788,9 @@ static int nfa_regatom(void) int equiclass; int collclass; int got_coll_char; - char_u *p; - char_u *endp; - char_u *old_regparse = (char_u *)regparse; + uint8_t *p; + uint8_t *endp; + uint8_t *old_regparse = (uint8_t *)regparse; int extra = 0; int emit_range; int negated; @@ -1862,9 +1845,7 @@ static int nfa_regatom(void) // "\_x" is character class plus newline FALLTHROUGH; - /* - * Character classes. - */ + // Character classes. case Magic('.'): case Magic('i'): case Magic('I'): @@ -1892,7 +1873,7 @@ static int nfa_regatom(void) case Magic('L'): case Magic('u'): case Magic('U'): - p = (char_u *)vim_strchr((char *)classchars, no_Magic(c)); + p = (uint8_t *)vim_strchr((char *)classchars, no_Magic(c)); if (p == NULL) { if (extra == NFA_ADD_NL) { semsg(_(e_ill_char_class), (int64_t)c); @@ -1905,7 +1886,7 @@ static int nfa_regatom(void) // When '.' is followed by a composing char ignore the dot, so that // the composing char is matched here. if (c == Magic('.') && utf_iscomposing(peekchr())) { - old_regparse = (char_u *)regparse; + old_regparse = (uint8_t *)regparse; c = getchr(); goto nfa_do_multibyte; } @@ -1951,7 +1932,7 @@ static int nfa_regatom(void) return FAIL; case Magic('~'): { - char_u *lp; + uint8_t *lp; // Previous substitute pattern. // Generated as "\%(pattern\)". @@ -1959,9 +1940,9 @@ static int nfa_regatom(void) emsg(_(e_nopresub)); return FAIL; } - for (lp = (char_u *)reg_prev_sub; *lp != NUL; MB_CPTR_ADV(lp)) { + for (lp = (uint8_t *)reg_prev_sub; *lp != NUL; MB_CPTR_ADV(lp)) { EMIT(utf_ptr2char((char *)lp)); - if (lp != (char_u *)reg_prev_sub) { + if (lp != (uint8_t *)reg_prev_sub) { EMIT(NFA_CONCAT); } } @@ -2094,6 +2075,12 @@ static int nfa_regatom(void) break; case '#': + if (regparse[0] == '=' && regparse[1] >= 48 + && regparse[1] <= 50) { + // misplaced \%#=1 + semsg(_(e_atom_engine_must_be_at_start_of_pattern), regparse[1]); + return FAIL; + } EMIT(NFA_CURSOR); break; @@ -2141,6 +2128,7 @@ static int nfa_regatom(void) int64_t n = 0; const int cmp = c; bool cur = false; + bool got_digit = false; if (c == '<' || c == '>') { c = getchr(); @@ -2151,7 +2139,7 @@ static int nfa_regatom(void) } while (ascii_isdigit(c)) { if (cur) { - semsg(_(e_regexp_number_after_dot_pos_search), no_Magic(c)); + semsg(_(e_regexp_number_after_dot_pos_search_chr), no_Magic(c)); return FAIL; } if (n > (INT32_MAX - (c - '0')) / 10) { @@ -2161,10 +2149,15 @@ static int nfa_regatom(void) } n = n * 10 + (c - '0'); c = getchr(); + got_digit = true; } if (c == 'l' || c == 'c' || c == 'v') { int32_t limit = INT32_MAX; + if (!cur && !got_digit) { + semsg(_(e_nfa_regexp_missing_value_in_chr), no_Magic(c)); + return FAIL; + } if (c == 'l') { if (cur) { n = curwin->w_cursor.lnum; @@ -2216,25 +2209,21 @@ static int nfa_regatom(void) case Magic('['): collection: - /* - * [abc] uses NFA_START_COLL - NFA_END_COLL - * [^abc] uses NFA_START_NEG_COLL - NFA_END_NEG_COLL - * Each character is produced as a regular state, using - * NFA_CONCAT to bind them together. - * Besides normal characters there can be: - * - character classes NFA_CLASS_* - * - ranges, two characters followed by NFA_RANGE. - */ - - p = (char_u *)regparse; - endp = skip_anyof((char *)p); + // [abc] uses NFA_START_COLL - NFA_END_COLL + // [^abc] uses NFA_START_NEG_COLL - NFA_END_NEG_COLL + // Each character is produced as a regular state, using + // NFA_CONCAT to bind them together. + // Besides normal characters there can be: + // - character classes NFA_CLASS_* + // - ranges, two characters followed by NFA_RANGE. + + p = (uint8_t *)regparse; + endp = (uint8_t *)skip_anyof((char *)p); if (*endp == ']') { - /* - * Try to reverse engineer character classes. For example, - * recognize that [0-9] stands for \d and [A-Za-z_] for \h, - * and perform the necessary substitutions in the NFA. - */ - int result = nfa_recognize_char_class((char_u *)regparse, endp, extra == NFA_ADD_NL); + // Try to reverse engineer character classes. For example, + // recognize that [0-9] stands for \d and [A-Za-z_] for \h, + // and perform the necessary substitutions in the NFA. + int result = nfa_recognize_char_class((uint8_t *)regparse, endp, extra == NFA_ADD_NL); if (result != FAIL) { if (result >= NFA_FIRST_NL && result <= NFA_LAST_NL) { EMIT(result - NFA_ADD_NL); @@ -2247,10 +2236,8 @@ collection: MB_PTR_ADV(regparse); return OK; } - /* - * Failed to recognize a character class. Use the simple - * version that turns [abc] into 'a' OR 'b' OR 'c' - */ + // Failed to recognize a character class. Use the simple + // version that turns [abc] into 'a' OR 'b' OR 'c' startc = -1; negated = false; if (*regparse == '^') { // negated range @@ -2268,7 +2255,7 @@ collection: } // Emit the OR branches for each character in the [] emit_range = false; - while ((char_u *)regparse < endp) { + while ((uint8_t *)regparse < endp) { int oldstartc = startc; startc = -1; got_coll_char = false; @@ -2375,10 +2362,10 @@ collection: // accepts "\t", "\e", etc., but only when the 'l' flag in // 'cpoptions' is not included. if (*regparse == '\\' - && (char_u *)regparse + 1 <= endp - && (vim_strchr(REGEXP_INRANGE, regparse[1]) != NULL + && (uint8_t *)regparse + 1 <= endp + && (vim_strchr(REGEXP_INRANGE, (uint8_t)regparse[1]) != NULL || (!reg_cpo_lit - && vim_strchr(REGEXP_ABBR, regparse[1]) + && vim_strchr(REGEXP_ABBR, (uint8_t)regparse[1]) != NULL))) { MB_PTR_ADV(regparse); @@ -2542,16 +2529,14 @@ nfa_do_multibyte: return OK; } -/* - * Parse something followed by possible [*+=]. - * - * A piece is an atom, possibly followed by a multi, an indication of how many - * times the atom can be matched. Example: "a*" matches any sequence of "a" - * characters: "", "a", "aa", etc. - * - * piece ::= atom - * or atom multi - */ +// Parse something followed by possible [*+=]. +// +// A piece is an atom, possibly followed by a multi, an indication of how many +// times the atom can be matched. Example: "a*" matches any sequence of "a" +// characters: "", "a", "aa", etc. +// +// piece ::= atom +// or atom multi static int nfa_regpiece(void) { int i; @@ -2589,17 +2574,15 @@ static int nfa_regpiece(void) break; case Magic('+'): - /* - * Trick: Normally, (a*)\+ would match the whole input "aaa". The - * first and only submatch would be "aaa". But the backtracking - * engine interprets the plus as "try matching one more time", and - * a* matches a second time at the end of the input, the empty - * string. - * The submatch will be the empty string. - * - * In order to be consistent with the old engine, we replace - * <atom>+ with <atom><atom>* - */ + // Trick: Normally, (a*)\+ would match the whole input "aaa". The + // first and only submatch would be "aaa". But the backtracking + // engine interprets the plus as "try matching one more time", and + // a* matches a second time at the end of the input, the empty + // string. + // The submatch will be the empty string. + // + // In order to be consistent with the old engine, we replace + // <atom>+ with <atom><atom>* restore_parse_state(&old_state); curchr = -1; if (nfa_regatom() == FAIL) { @@ -2758,16 +2741,14 @@ static int nfa_regpiece(void) return OK; } -/* - * Parse one or more pieces, concatenated. It matches a match for the - * first piece, followed by a match for the second piece, etc. Example: - * "f[0-9]b", first matches "f", then a digit and then "b". - * - * concat ::= piece - * or piece piece - * or piece piece piece - * etc. - */ +// Parse one or more pieces, concatenated. It matches a match for the +// first piece, followed by a match for the second piece, etc. Example: +// "f[0-9]b", first matches "f", then a digit and then "b". +// +// concat ::= piece +// or piece piece +// or piece piece piece +// etc. static int nfa_regconcat(void) { bool cont = true; @@ -2831,18 +2812,16 @@ static int nfa_regconcat(void) return OK; } -/* - * Parse a branch, one or more concats, separated by "\&". It matches the - * last concat, but only if all the preceding concats also match at the same - * position. Examples: - * "foobeep\&..." matches "foo" in "foobeep". - * ".*Peter\&.*Bob" matches in a line containing both "Peter" and "Bob" - * - * branch ::= concat - * or concat \& concat - * or concat \& concat \& concat - * etc. - */ +// Parse a branch, one or more concats, separated by "\&". It matches the +// last concat, but only if all the preceding concats also match at the same +// position. Examples: +// "foobeep\&..." matches "foo" in "foobeep". +// ".*Peter\&.*Bob" matches in a line containing both "Peter" and "Bob" +// +// branch ::= concat +// or concat \& concat +// or concat \& concat \& concat +// etc. static int nfa_regbranch(void) { int old_post_pos; @@ -2948,7 +2927,7 @@ static int nfa_reg(int paren) } #ifdef REGEXP_DEBUG -static char_u code[50]; +static uint8_t code[50]; static void nfa_set_code(int c) { @@ -3296,42 +3275,40 @@ static void nfa_set_code(int c) } static FILE *log_fd; -static char_u e_log_open_failed[] = +static uint8_t e_log_open_failed[] = N_("Could not open temporary log file for writing, displaying on stderr... "); -/* - * Print the postfix notation of the current regexp. - */ -static void nfa_postfix_dump(char_u *expr, int retval) +// Print the postfix notation of the current regexp. +static void nfa_postfix_dump(uint8_t *expr, int retval) { int *p; FILE *f; f = fopen(NFA_REGEXP_DUMP_LOG, "a"); - if (f != NULL) { - fprintf(f, "\n-------------------------\n"); - if (retval == FAIL) { - fprintf(f, ">>> NFA engine failed... \n"); - } else if (retval == OK) { - fprintf(f, ">>> NFA engine succeeded !\n"); - } - fprintf(f, "Regexp: \"%s\"\nPostfix notation (char): \"", expr); - for (p = post_start; *p && p < post_ptr; p++) { - nfa_set_code(*p); - fprintf(f, "%s, ", code); - } - fprintf(f, "\"\nPostfix notation (int): "); - for (p = post_start; *p && p < post_ptr; p++) { - fprintf(f, "%d ", *p); - } - fprintf(f, "\n\n"); - fclose(f); + if (f == NULL) { + return; + } + + fprintf(f, "\n-------------------------\n"); + if (retval == FAIL) { + fprintf(f, ">>> NFA engine failed... \n"); + } else if (retval == OK) { + fprintf(f, ">>> NFA engine succeeded !\n"); + } + fprintf(f, "Regexp: \"%s\"\nPostfix notation (char): \"", expr); + for (p = post_start; *p && p < post_ptr; p++) { + nfa_set_code(*p); + fprintf(f, "%s, ", code); } + fprintf(f, "\"\nPostfix notation (int): "); + for (p = post_start; *p && p < post_ptr; p++) { + fprintf(f, "%d ", *p); + } + fprintf(f, "\n\n"); + fclose(f); } -/* - * Print the NFA starting with a root node "state". - */ +// Print the NFA starting with a root node "state". static void nfa_print_state(FILE *debugf, nfa_state_T *state) { garray_T indent; @@ -3344,7 +3321,7 @@ static void nfa_print_state(FILE *debugf, nfa_state_T *state) static void nfa_print_state2(FILE *debugf, nfa_state_T *state, garray_T *indent) { - char_u *p; + uint8_t *p; if (state == NULL) { return; @@ -3353,15 +3330,15 @@ static void nfa_print_state2(FILE *debugf, nfa_state_T *state, garray_T *indent) fprintf(debugf, "(%2d)", abs(state->id)); // Output indent - p = (char_u *)indent->ga_data; + p = (uint8_t *)indent->ga_data; if (indent->ga_len >= 3) { int last = indent->ga_len - 3; - char_u save[2]; + uint8_t save[2]; - STRNCPY(save, &p[last], 2); - STRNCPY(&p[last], "+-", 2); + strncpy(save, &p[last], 2); // NOLINT(runtime/printf) + memcpy(&p[last], "+-", 2); fprintf(debugf, " %s", p); - STRNCPY(&p[last], save, 2); // NOLINT(runtime/printf) + strncpy(&p[last], save, 2); // NOLINT(runtime/printf) } else { fprintf(debugf, " %s", p); } @@ -3381,9 +3358,9 @@ static void nfa_print_state2(FILE *debugf, nfa_state_T *state, garray_T *indent) // grow indent for state->out indent->ga_len -= 1; if (state->out1) { - ga_concat(indent, (char_u *)"| "); + ga_concat(indent, (uint8_t *)"| "); } else { - ga_concat(indent, (char_u *)" "); + ga_concat(indent, (uint8_t *)" "); } ga_append(indent, NUL); @@ -3391,7 +3368,7 @@ static void nfa_print_state2(FILE *debugf, nfa_state_T *state, garray_T *indent) // replace last part of indent for state->out1 indent->ga_len -= 3; - ga_concat(indent, (char_u *)" "); + ga_concat(indent, (uint8_t *)" "); ga_append(indent, NUL); nfa_print_state2(debugf, state->out1, indent); @@ -3401,36 +3378,34 @@ static void nfa_print_state2(FILE *debugf, nfa_state_T *state, garray_T *indent) ga_append(indent, NUL); } -/* - * Print the NFA state machine. - */ +// Print the NFA state machine. static void nfa_dump(nfa_regprog_T *prog) { FILE *debugf = fopen(NFA_REGEXP_DUMP_LOG, "a"); - if (debugf != NULL) { - nfa_print_state(debugf, prog->start); + if (debugf == NULL) { + return; + } - if (prog->reganch) { - fprintf(debugf, "reganch: %d\n", prog->reganch); - } - if (prog->regstart != NUL) { - fprintf(debugf, "regstart: %c (decimal: %d)\n", - prog->regstart, prog->regstart); - } - if (prog->match_text != NULL) { - fprintf(debugf, "match_text: \"%s\"\n", prog->match_text); - } + nfa_print_state(debugf, prog->start); - fclose(debugf); + if (prog->reganch) { + fprintf(debugf, "reganch: %d\n", prog->reganch); + } + if (prog->regstart != NUL) { + fprintf(debugf, "regstart: %c (decimal: %d)\n", + prog->regstart, prog->regstart); } + if (prog->match_text != NULL) { + fprintf(debugf, "match_text: \"%s\"\n", prog->match_text); + } + + fclose(debugf); } -#endif /* REGEXP_DEBUG */ +#endif // REGEXP_DEBUG -/* - * Parse r.e. @expr and convert it into postfix form. - * Return the postfix string on success, NULL otherwise. - */ +// Parse r.e. @expr and convert it into postfix form. +// Return the postfix string on success, NULL otherwise. static int *re2post(void) { if (nfa_reg(REG_NOPAREN) == FAIL) { @@ -3442,18 +3417,14 @@ static int *re2post(void) // NB. Some of the code below is inspired by Russ's. -/* - * Represents an NFA state plus zero or one or two arrows exiting. - * if c == MATCH, no arrows out; matching state. - * If c == SPLIT, unlabeled arrows to out and out1 (if != NULL). - * If c < 256, labeled arrow with character c to out. - */ +// Represents an NFA state plus zero or one or two arrows exiting. +// if c == MATCH, no arrows out; matching state. +// If c == SPLIT, unlabeled arrows to out and out1 (if != NULL). +// If c < 256, labeled arrow with character c to out. static nfa_state_T *state_ptr; // points to nfa_prog->state -/* - * Allocate and initialize nfa_state_T. - */ +// Allocate and initialize nfa_state_T. static nfa_state_T *alloc_state(int c, nfa_state_T *out, nfa_state_T *out1) { nfa_state_T *s; @@ -3476,16 +3447,12 @@ static nfa_state_T *alloc_state(int c, nfa_state_T *out, nfa_state_T *out1) return s; } -/* - * A partially built NFA without the matching state filled in. - * Frag_T.start points at the start state. - * Frag_T.out is a list of places that need to be set to the - * next state for this fragment. - */ +// A partially built NFA without the matching state filled in. +// Frag_T.start points at the start state. +// Frag_T.out is a list of places that need to be set to the +// next state for this fragment. -/* - * Initialize a Frag_T struct and return it. - */ +// Initialize a Frag_T struct and return it. static Frag_T frag(nfa_state_T *start, Ptrlist *out) { Frag_T n; @@ -3495,9 +3462,7 @@ static Frag_T frag(nfa_state_T *start, Ptrlist *out) return n; } -/* - * Create singleton list containing just outp. - */ +// Create singleton list containing just outp. static Ptrlist *list1(nfa_state_T **outp) { Ptrlist *l; @@ -3507,9 +3472,7 @@ static Ptrlist *list1(nfa_state_T **outp) return l; } -/* - * Patch the list of states at out to point to start. - */ +// Patch the list of states at out to point to start. static void patch(Ptrlist *l, nfa_state_T *s) { Ptrlist *next; @@ -3520,9 +3483,7 @@ static void patch(Ptrlist *l, nfa_state_T *s) } } -/* - * Join the two lists l1 and l2, returning the combination. - */ +// Join the two lists l1 and l2, returning the combination. static Ptrlist *append(Ptrlist *l1, Ptrlist *l2) { Ptrlist *oldl1; @@ -3535,9 +3496,7 @@ static Ptrlist *append(Ptrlist *l1, Ptrlist *l2) return oldl1; } -/* - * Stack used for transforming postfix form into NFA. - */ +// Stack used for transforming postfix form into NFA. static Frag_T empty; static void st_error(int *postfix, int *end, int *p) @@ -3580,9 +3539,7 @@ static void st_error(int *postfix, int *end, int *p) emsg(_("E874: (NFA) Could not pop the stack!")); } -/* - * Push an item onto the stack. - */ +// Push an item onto the stack. static void st_push(Frag_T s, Frag_T **p, Frag_T *stack_end) { Frag_T *stackp = *p; @@ -3594,9 +3551,7 @@ static void st_push(Frag_T s, Frag_T **p, Frag_T *stack_end) *p = *p + 1; } -/* - * Pop an item from the stack. - */ +// Pop an item from the stack. static Frag_T st_pop(Frag_T **p, Frag_T *stack) { Frag_T *stackp; @@ -3609,10 +3564,8 @@ static Frag_T st_pop(Frag_T **p, Frag_T *stack) return **p; } -/* - * Estimate the maximum byte length of anything matching "state". - * When unknown or unlimited return -1. - */ +// Estimate the maximum byte length of anything matching "state". +// When unknown or unlimited return -1. static int nfa_max_width(nfa_state_T *startstate, int depth) { int l, r; @@ -3815,10 +3768,8 @@ static int nfa_max_width(nfa_state_T *startstate, int depth) return -1; } -/* - * Convert a postfix form into its equivalent NFA. - * Return the NFA start state on success, NULL otherwise. - */ +// Convert a postfix form into its equivalent NFA. +// Return the NFA start state on success, NULL otherwise. static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) { int *p; @@ -3854,7 +3805,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) stack_end = stack + (nstate + 1); } - for (p = postfix; p < end; ++p) { + for (p = postfix; p < end; p++) { switch (*p) { case NFA_CONCAT: // Concatenation. @@ -4338,15 +4289,13 @@ theend: #undef PUSH } -/* - * After building the NFA program, inspect it to add optimization hints. - */ +// After building the NFA program, inspect it to add optimization hints. static void nfa_postprocess(nfa_regprog_T *prog) { int i; int c; - for (i = 0; i < prog->nstate; ++i) { + for (i = 0; i < prog->nstate; i++) { c = prog->state[i].c; if (c == NFA_START_INVISIBLE || c == NFA_START_INVISIBLE_NEG @@ -4478,59 +4427,60 @@ static void clear_sub(regsub_T *sub) sub->in_use = 0; } -/* - * Copy the submatches from "from" to "to". - */ +// Copy the submatches from "from" to "to". static void copy_sub(regsub_T *to, regsub_T *from) { to->in_use = from->in_use; - if (from->in_use > 0) { - // Copy the match start and end positions. - if (REG_MULTI) { - memmove(&to->list.multi[0], &from->list.multi[0], - sizeof(struct multipos) * (size_t)from->in_use); - } else { - memmove(&to->list.line[0], &from->list.line[0], - sizeof(struct linepos) * (size_t)from->in_use); - } + if (from->in_use <= 0) { + return; + } + + // Copy the match start and end positions. + if (REG_MULTI) { + memmove(&to->list.multi[0], &from->list.multi[0], + sizeof(struct multipos) * (size_t)from->in_use); + to->orig_start_col = from->orig_start_col; + } else { + memmove(&to->list.line[0], &from->list.line[0], + sizeof(struct linepos) * (size_t)from->in_use); } } -/* - * Like copy_sub() but exclude the main match. - */ +// Like copy_sub() but exclude the main match. static void copy_sub_off(regsub_T *to, regsub_T *from) { if (to->in_use < from->in_use) { to->in_use = from->in_use; } - if (from->in_use > 1) { - // Copy the match start and end positions. - if (REG_MULTI) { - memmove(&to->list.multi[1], &from->list.multi[1], - sizeof(struct multipos) * (size_t)(from->in_use - 1)); - } else { - memmove(&to->list.line[1], &from->list.line[1], - sizeof(struct linepos) * (size_t)(from->in_use - 1)); - } + if (from->in_use <= 1) { + return; + } + + // Copy the match start and end positions. + if (REG_MULTI) { + memmove(&to->list.multi[1], &from->list.multi[1], + sizeof(struct multipos) * (size_t)(from->in_use - 1)); + } else { + memmove(&to->list.line[1], &from->list.line[1], + sizeof(struct linepos) * (size_t)(from->in_use - 1)); } } -/* - * Like copy_sub() but only do the end of the main match if \ze is present. - */ +// Like copy_sub() but only do the end of the main match if \ze is present. static void copy_ze_off(regsub_T *to, regsub_T *from) { - if (rex.nfa_has_zend) { - if (REG_MULTI) { - if (from->list.multi[0].end_lnum >= 0) { - to->list.multi[0].end_lnum = from->list.multi[0].end_lnum; - to->list.multi[0].end_col = from->list.multi[0].end_col; - } - } else { - if (from->list.line[0].end != NULL) { - to->list.line[0].end = from->list.line[0].end; - } + if (!rex.nfa_has_zend) { + return; + } + + if (REG_MULTI) { + if (from->list.multi[0].end_lnum >= 0) { + to->list.multi[0].end_lnum = from->list.multi[0].end_lnum; + to->list.multi[0].end_col = from->list.multi[0].end_col; + } + } else { + if (from->list.line[0].end != NULL) { + to->list.line[0].end = from->list.line[0].end; } } } @@ -4543,8 +4493,8 @@ static bool sub_equal(regsub_T *sub1, regsub_T *sub2) int todo; linenr_T s1; linenr_T s2; - char_u *sp1; - char_u *sp2; + uint8_t *sp1; + uint8_t *sp2; todo = sub1->in_use > sub2->in_use ? sub1->in_use : sub2->in_use; if (REG_MULTI) { @@ -4623,6 +4573,20 @@ static bool sub_equal(regsub_T *sub1, regsub_T *sub2) } #ifdef REGEXP_DEBUG +static void open_debug_log(TriState result) +{ + log_fd = fopen(NFA_REGEXP_RUN_LOG, "a"); + if (log_fd == NULL) { + emsg(_(e_log_open_failed)); + log_fd = stderr; + } + + fprintf(log_fd, "****************************\n"); + fprintf(log_fd, "FINISHED RUNNING nfa_regmatch() recursively\n"); + fprintf(log_fd, "MATCH = %s\n", result == kTrue ? "OK" : result == kNone ? "MAYBE" : "FALSE"); + fprintf(log_fd, "****************************\n"); +} + static void report_state(char *action, regsub_T *sub, nfa_state_T *state, int lid, nfa_pim_T *pim) { int col; @@ -4635,6 +4599,9 @@ static void report_state(char *action, regsub_T *sub, nfa_state_T *state, int li col = (int)(sub->list.line[0].start - rex.line); } nfa_set_code(state->c); + if (log_fd == NULL) { + open_debug_log(kNone); + } fprintf(log_fd, "> %s state %d to list %d. char %d: %s (start col %d)%s\n", action, abs(state->id), lid, state->c, code, col, pim_info(pim)); @@ -4822,7 +4789,7 @@ static regsubs_T *addstate(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs_ar nfa_thread_T *thread; struct multipos save_multipos; int save_in_use; - char_u *save_ptr; + uint8_t *save_ptr; int i; regsub_T *sub; regsubs_T *subs = subs_arg; @@ -4925,7 +4892,7 @@ static regsubs_T *addstate(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs_ar // When called from addstate_here() do insert before // existing states. if (add_here) { - for (k = 0; k < l->n && k < listindex; ++k) { + for (k = 0; k < l->n && k < listindex; k++) { if (l->t[k].state->id == state->id) { found = true; break; @@ -5065,7 +5032,7 @@ skip_add: save_in_use = -1; } else { save_in_use = sub->in_use; - for (i = sub->in_use; i < subidx; ++i) { + for (i = sub->in_use; i < subidx; i++) { sub->list.multi[i].start_lnum = -1; sub->list.multi[i].end_lnum = -1; } @@ -5086,7 +5053,7 @@ skip_add: save_in_use = -1; } else { save_in_use = sub->in_use; - for (i = sub->in_use; i < subidx; ++i) { + for (i = sub->in_use; i < subidx; i++) { sub->list.line[i].start = NULL; sub->list.line[i].end = NULL; } @@ -5279,15 +5246,13 @@ static regsubs_T *addstate_here(nfa_list_T *l, nfa_state_T *state, regsubs_T *su sizeof(nfa_thread_T) * (size_t)count); } } - --l->n; + l->n--; *ip = listidx - 1; return r; } -/* - * Check character class "class" against current character c. - */ +// Check character class "class" against current character c. static int check_char_class(int class, int c) { switch (class) { @@ -5465,7 +5430,7 @@ static int match_zref(int subidx, int *bytelen) return true; } - len = (int)STRLEN(re_extmatch_in->matches[subidx]); + len = (int)strlen((char *)re_extmatch_in->matches[subidx]); if (cstrncmp((char *)re_extmatch_in->matches[subidx], (char *)rex.input, &len) == 0) { *bytelen = len; return true; @@ -5473,11 +5438,9 @@ static int match_zref(int subidx, int *bytelen) return false; } -/* - * Save list IDs for all NFA states of "prog" into "list". - * Also reset the IDs to zero. - * Only used for the recursive value lastlist[1]. - */ +// Save list IDs for all NFA states of "prog" into "list". +// Also reset the IDs to zero. +// Only used for the recursive value lastlist[1]. static void nfa_save_listids(nfa_regprog_T *prog, int *list) { int i; @@ -5492,9 +5455,7 @@ static void nfa_save_listids(nfa_regprog_T *prog, int *list) } } -/* - * Restore list IDs from "list" to all NFA states. - */ +// Restore list IDs from "list" to all NFA states. static void nfa_restore_listids(nfa_regprog_T *prog, int *list) { int i; @@ -5518,11 +5479,9 @@ static bool nfa_re_num_cmp(uintmax_t val, int op, uintmax_t pos) return val == pos; } -/* - * Recursively call nfa_regmatch() - * "pim" is NULL or contains info about a Postponed Invisible Match (start - * position). - */ +// Recursively call nfa_regmatch() +// "pim" is NULL or contains info about a Postponed Invisible Match (start +// position). static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T *prog, regsubs_T *submatch, regsubs_T *m, int **listids, int *listids_len) FUNC_ATTR_NONNULL_ARG(1, 3, 5, 6, 7) @@ -5573,10 +5532,10 @@ static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T // bytes if possible. if (state->val <= 0) { if (REG_MULTI) { - rex.line = reg_getline(--rex.lnum); + rex.line = (uint8_t *)reg_getline(--rex.lnum); if (rex.line == NULL) { // can't go before the first line - rex.line = reg_getline(++rex.lnum); + rex.line = (uint8_t *)reg_getline(++rex.lnum); } } rex.input = rex.line; @@ -5584,13 +5543,13 @@ static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T if (REG_MULTI && (int)(rex.input - rex.line) < state->val) { // Not enough bytes in this line, go to end of // previous line. - rex.line = reg_getline(--rex.lnum); + rex.line = (uint8_t *)reg_getline(--rex.lnum); if (rex.line == NULL) { // can't go before the first line - rex.line = reg_getline(++rex.lnum); + rex.line = (uint8_t *)reg_getline(++rex.lnum); rex.input = rex.line; } else { - rex.input = rex.line + STRLEN(rex.line); + rex.input = rex.line + strlen((char *)rex.line); } } if ((int)(rex.input - rex.line) >= state->val) { @@ -5646,7 +5605,7 @@ static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T // restore position in input text rex.lnum = save_reglnum; if (REG_MULTI) { - rex.line = reg_getline(rex.lnum); + rex.line = (uint8_t *)reg_getline(rex.lnum); } rex.input = rex.line + save_reginput_col; if (result != NFA_TOO_EXPENSIVE) { @@ -5656,27 +5615,16 @@ static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T nfa_endp = save_nfa_endp; #ifdef REGEXP_DEBUG - log_fd = fopen(NFA_REGEXP_RUN_LOG, "a"); - if (log_fd != NULL) { - fprintf(log_fd, "****************************\n"); - fprintf(log_fd, "FINISHED RUNNING nfa_regmatch() recursively\n"); - fprintf(log_fd, "MATCH = %s\n", !result ? "false" : "OK"); - fprintf(log_fd, "****************************\n"); - } else { - emsg(_(e_log_open_failed)); - log_fd = stderr; - } + open_debug_log(result); #endif return result; } -/* - * Estimate the chance of a match with "state" failing. - * empty match: 0 - * NFA_ANY: 1 - * specific character: 99 - */ +// Estimate the chance of a match with "state" failing. +// empty match: 0 +// NFA_ANY: 1 +// specific character: 99 static int failure_chance(nfa_state_T *state, int depth) { int c = state->c; @@ -5831,12 +5779,10 @@ static int failure_chance(nfa_state_T *state, int depth) return 50; } -/* - * Skip until the char "c" we know a match must start with. - */ +// Skip until the char "c" we know a match must start with. static int skip_to_start(int c, colnr_T *colp) { - const char_u *const s = cstrchr(rex.line + *colp, c); + const uint8_t *const s = (uint8_t *)cstrchr((char *)rex.line + *colp, c); if (s == NULL) { return FAIL; } @@ -5844,22 +5790,20 @@ static int skip_to_start(int c, colnr_T *colp) return OK; } -/* - * Check for a match with match_text. - * Called after skip_to_start() has found regstart. - * Returns zero for no match, 1 for a match. - */ -static long find_match_text(colnr_T startcol, int regstart, char_u *match_text) +// Check for a match with match_text. +// Called after skip_to_start() has found regstart. +// Returns zero for no match, 1 for a match. +static long find_match_text(colnr_T *startcol, int regstart, uint8_t *match_text) { #define PTR2LEN(x) utf_ptr2len(x) - colnr_T col = startcol; - int regstart_len = PTR2LEN((char *)rex.line + startcol); + colnr_T col = *startcol; + int regstart_len = PTR2LEN((char *)rex.line + col); for (;;) { bool match = true; - char_u *s1 = match_text; - char_u *s2 = rex.line + col + regstart_len; // skip regstart + uint8_t *s1 = match_text; + uint8_t *s2 = rex.line + col + regstart_len; // skip regstart while (*s1) { int c1_len = PTR2LEN((char *)s1); int c1 = utf_ptr2char((char *)s1); @@ -5887,6 +5831,7 @@ static long find_match_text(colnr_T startcol, int regstart, char_u *match_text) rex.reg_startp[0] = rex.line + col; rex.reg_endp[0] = s2; } + *startcol = col; return 1L; } @@ -5896,6 +5841,8 @@ static long find_match_text(colnr_T startcol, int regstart, char_u *match_text) break; } } + + *startcol = col; return 0L; #undef PTR2LEN @@ -5971,16 +5918,15 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm #ifdef REGEXP_DEBUG log_fd = fopen(NFA_REGEXP_RUN_LOG, "a"); - if (log_fd != NULL) { - fprintf(log_fd, "**********************************\n"); - nfa_set_code(start->c); - fprintf(log_fd, " RUNNING nfa_regmatch() starting with state %d, code %s\n", - abs(start->id), code); - fprintf(log_fd, "**********************************\n"); - } else { + if (log_fd == NULL) { emsg(_(e_log_open_failed)); log_fd = stderr; } + fprintf(log_fd, "**********************************\n"); + nfa_set_code(start->c); + fprintf(log_fd, " RUNNING nfa_regmatch() starting with state %d, code %s\n", + abs(start->id), code); + fprintf(log_fd, "**********************************\n"); #endif thislist = &list[0]; @@ -6000,6 +5946,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm if (REG_MULTI) { m->norm.list.multi[0].start_lnum = rex.lnum; m->norm.list.multi[0].start_col = (colnr_T)(rex.input - rex.line); + m->norm.orig_start_col = m->norm.list.multi[0].start_col; } else { m->norm.list.line[0].start = rex.input; } @@ -6019,9 +5966,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm add_off = clen; \ } - /* - * Run for each character. - */ + // Run for each character. for (;;) { int curc = utf_ptr2char((char *)rex.input); int clen = utfc_ptr2len((char *)rex.input); @@ -6067,9 +6012,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm #ifdef NFA_REGEXP_DEBUG_LOG fprintf(debug, "\n-------------------\n"); #endif - /* - * If the state lists are empty we can stop. - */ + // If the state lists are empty we can stop. if (thislist->n == 0) { break; } @@ -6112,10 +6055,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm } #endif - /* - * Handle the possible codes of the current state. - * The most important is NFA_MATCH. - */ + // Handle the possible codes of the current state. + // The most important is NFA_MATCH. add_state = NULL; add_here = false; add_count = 0; @@ -6410,7 +6351,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm int this_class; // Get class of current and previous char (if it exists). - this_class = mb_get_class_tab(rex.input, rex.reg_buf->b_chartab); + this_class = mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab); if (this_class <= 1) { result = false; } else if (reg_prev_class() == this_class) { @@ -6431,7 +6372,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm 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); + this_class = mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab); prev_class = reg_prev_class(); if (this_class == prev_class || prev_class == 0 || prev_class == 1) { @@ -6910,7 +6851,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm result = col > t->state->val * ts; } if (!result) { - uintmax_t lts = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, rex.line, col); + uintmax_t lts = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, (char *)rex.line, col); assert(t->state->val >= 0); result = nfa_re_num_cmp((uintmax_t)t->state->val, op, lts + 1); } @@ -6929,7 +6870,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm // Line may have been freed, get it again. if (REG_MULTI) { - rex.line = reg_getline(rex.lnum); + rex.line = (uint8_t *)reg_getline(rex.lnum); rex.input = rex.line + col; } @@ -6939,7 +6880,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm pos_T *pos = &fm->mark; 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)) + ? (colnr_T)strlen((char *)reg_getline(pos->lnum - rex.reg_firstlnum)) : pos->col; result = pos->lnum == rex.lnum + rex.reg_firstlnum @@ -7185,6 +7126,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm if (REG_MULTI) { m->norm.list.multi[0].start_col = (colnr_T)(rex.input - rex.line) + clen; + m->norm.orig_start_col = + m->norm.list.multi[0].start_col; } else { m->norm.list.line[0].start = rex.input + clen; } @@ -7318,6 +7261,9 @@ static long nfa_regtry(nfa_regprog_T *prog, colnr_T col, proftime_T *tm, int *ti rex.reg_endpos[i].lnum = subs.norm.list.multi[i].end_lnum; rex.reg_endpos[i].col = subs.norm.list.multi[i].end_col; } + if (rex.reg_mmatch != NULL) { + rex.reg_mmatch->rmm_matchcol = subs.norm.orig_start_col; + } if (rex.reg_startpos[0].lnum < 0) { rex.reg_startpos[0].lnum = 0; @@ -7362,15 +7308,15 @@ static long nfa_regtry(nfa_regprog_T *prog, colnr_T col, proftime_T *tm, int *ti && mpos->start_lnum == mpos->end_lnum && mpos->end_col >= mpos->start_col) { re_extmatch_out->matches[i] = - (char_u *)xstrnsave((char *)reg_getline(mpos->start_lnum) + mpos->start_col, - (size_t)(mpos->end_col - mpos->start_col)); + (uint8_t *)xstrnsave((char *)reg_getline(mpos->start_lnum) + mpos->start_col, + (size_t)(mpos->end_col - mpos->start_col)); } } else { struct linepos *lpos = &subs.synt.list.line[i]; if (lpos->start != NULL && lpos->end != NULL) { re_extmatch_out->matches[i] = - (char_u *)xstrnsave((char *)lpos->start, (size_t)(lpos->end - lpos->start)); + (uint8_t *)xstrnsave((char *)lpos->start, (size_t)(lpos->end - lpos->start)); } } } @@ -7389,7 +7335,7 @@ static long nfa_regtry(nfa_regprog_T *prog, colnr_T col, proftime_T *tm, int *ti /// /// @return <= 0 if there is no match and number of lines contained in the /// match otherwise. -static long nfa_regexec_both(char_u *line, colnr_T startcol, proftime_T *tm, int *timed_out) +static long nfa_regexec_both(uint8_t *line, colnr_T startcol, proftime_T *tm, int *timed_out) { nfa_regprog_T *prog; long retval = 0L; @@ -7397,13 +7343,13 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol, proftime_T *tm, int if (REG_MULTI) { prog = (nfa_regprog_T *)rex.reg_mmatch->regprog; - line = reg_getline((linenr_T)0); // relative to the cursor + line = (uint8_t *)reg_getline((linenr_T)0); // relative to the cursor rex.reg_startpos = rex.reg_mmatch->startpos; rex.reg_endpos = rex.reg_mmatch->endpos; } else { prog = (nfa_regprog_T *)rex.reg_match->regprog; - rex.reg_startp = (char_u **)rex.reg_match->startp; - rex.reg_endp = (char_u **)rex.reg_match->endp; + rex.reg_startp = (uint8_t **)rex.reg_match->startp; + rex.reg_endp = (uint8_t **)rex.reg_match->endp; } // Be paranoid... @@ -7460,7 +7406,13 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol, proftime_T *tm, int // If match_text is set it contains the full text that must match. // Nothing else to try. Doesn't handle combining chars well. if (prog->match_text != NULL && !rex.reg_icombine) { - return find_match_text(col, prog->regstart, prog->match_text); + retval = find_match_text(&col, prog->regstart, prog->match_text); + if (REG_MULTI) { + rex.reg_mmatch->rmm_matchcol = col; + } else { + rex.reg_match->rm_matchcol = col; + } + return retval; } } @@ -7500,17 +7452,19 @@ theend: if (rex.reg_match->endp[0] < rex.reg_match->startp[0]) { rex.reg_match->endp[0] = rex.reg_match->startp[0]; } + + // startpos[0] may be set by "\zs", also return the column where + // the whole pattern matched. + rex.reg_match->rm_matchcol = col; } } return retval; } -/* - * Compile a regular expression into internal code for the NFA matcher. - * Returns the program in allocated space. Returns NULL for an error. - */ -static regprog_T *nfa_regcomp(char_u *expr, int re_flags) +// Compile a regular expression into internal code for the NFA matcher. +// Returns the program in allocated space. Returns NULL for an error. +static regprog_T *nfa_regcomp(uint8_t *expr, int re_flags) { nfa_regprog_T *prog = NULL; int *postfix; @@ -7535,11 +7489,9 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags) goto fail; // Cascaded (syntax?) error } - /* - * In order to build the NFA, we parse the input regexp twice: - * 1. first pass to count size (so we can allocate space) - * 2. second to emit code - */ + // In order to build the NFA, we parse the input regexp twice: + // 1. first pass to count size (so we can allocate space) + // 2. second to emit code #ifdef REGEXP_DEBUG { FILE *f = fopen(NFA_REGEXP_RUN_LOG, "a"); @@ -7554,10 +7506,8 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags) } #endif - /* - * PASS 1 - * Count number of NFA states in "nstate". Do not build the NFA. - */ + // PASS 1 + // Count number of NFA states in "nstate". Do not build the NFA. post2nfa(postfix, post_ptr, true); // allocate the regprog with space for the compiled regexp @@ -7566,10 +7516,8 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags) state_ptr = prog->state; prog->re_in_use = false; - /* - * PASS 2 - * Build the NFA - */ + // PASS 2 + // Build the NFA prog->start = post2nfa(postfix, post_ptr, false); if (prog->start == NULL) { goto fail; @@ -7613,16 +7561,16 @@ fail: goto out; } -/* - * Free a compiled regexp program, returned by nfa_regcomp(). - */ +// Free a compiled regexp program, returned by nfa_regcomp(). static void nfa_regfree(regprog_T *prog) { - if (prog != NULL) { - xfree(((nfa_regprog_T *)prog)->match_text); - xfree(((nfa_regprog_T *)prog)->pattern); - xfree(prog); + if (prog == NULL) { + return; } + + xfree(((nfa_regprog_T *)prog)->match_text); + xfree(((nfa_regprog_T *)prog)->pattern); + xfree(prog); } /// Match a regexp against a string. @@ -7634,7 +7582,7 @@ static void nfa_regfree(regprog_T *prog) /// @param col column to start looking for match /// /// @return <= 0 for failure, number of lines contained in the match otherwise. -static int nfa_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col, bool line_lbr) +static int nfa_regexec_nl(regmatch_T *rmp, uint8_t *line, colnr_T col, bool line_lbr) { rex.reg_match = rmp; rex.reg_mmatch = NULL; @@ -7686,16 +7634,6 @@ static int nfa_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col, bool line_ static long nfa_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; - + init_regexec_multi(rmp, win, buf, lnum); return nfa_regexec_both(NULL, col, tm, timed_out); } diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index 935bf4c507..24500b80b9 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -5,25 +5,46 @@ /// /// Management of runtime files (including packages) +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <stdio.h> +#include <string.h> +#include <uv.h> + +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" #include "nvim/debugger.h" #include "nvim/eval.h" #include "nvim/eval/userfunc.h" -#include "nvim/ex_cmds.h" -#include "nvim/ex_cmds2.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" +#include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" +#include "nvim/map.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/os/os.h" +#include "nvim/os/stdpaths_defs.h" +#include "nvim/path.h" #include "nvim/profile.h" #include "nvim/runtime.h" +#include "nvim/strings.h" +#include "nvim/usercmd.h" #include "nvim/vim.h" /// Structure used to store info for each sourced file. @@ -83,8 +104,7 @@ estack_T *estack_push(etype_T type, char *name, linenr_T lnum) void estack_push_ufunc(ufunc_T *ufunc, linenr_T lnum) { estack_T *entry = estack_push(ETYPE_UFUNC, - (char *)(ufunc->uf_name_exp != NULL - ? ufunc->uf_name_exp : ufunc->uf_name), + ufunc->uf_name_exp != NULL ? ufunc->uf_name_exp : ufunc->uf_name, lnum); if (entry != NULL) { entry->es_info.ufunc = ufunc; @@ -193,19 +213,19 @@ void ex_runtime(exarg_T *eap) { char *arg = eap->arg; char *p = skiptowhite(arg); - ptrdiff_t len = p - arg; + size_t len = (size_t)(p - arg); int flags = eap->forceit ? DIP_ALL : 0; - if (STRNCMP(arg, "START", len) == 0) { + if (strncmp(arg, "START", len) == 0) { flags += DIP_START + DIP_NORTP; arg = skipwhite(arg + len); - } else if (STRNCMP(arg, "OPT", len) == 0) { + } else if (strncmp(arg, "OPT", len) == 0) { flags += DIP_OPT + DIP_NORTP; arg = skipwhite(arg + len); - } else if (STRNCMP(arg, "PACK", len) == 0) { + } else if (strncmp(arg, "PACK", len) == 0) { flags += DIP_START + DIP_OPT + DIP_NORTP; arg = skipwhite(arg + len); - } else if (STRNCMP(arg, "ALL", len) == 0) { + } else if (strncmp(arg, "ALL", len) == 0) { flags += DIP_START + DIP_OPT; arg = skipwhite(arg + len); } @@ -343,7 +363,7 @@ RuntimeSearchPath copy_runtime_search_path(const RuntimeSearchPath src) return dst; } -void runtime_search_path_unref(RuntimeSearchPath path, int *ref) +void runtime_search_path_unref(RuntimeSearchPath path, const int *ref) FUNC_ATTR_NONNULL_ALL { if (*ref) { @@ -633,8 +653,8 @@ static void expand_pack_entry(RuntimeSearchPath *search_path, Map(String, handle if (pack_entry_len + strlen(start_pat[i]) + 1 > sizeof buf) { continue; } - STRLCPY(buf, pack_entry, sizeof buf); - STRLCPY(buf + pack_entry_len, start_pat[i], sizeof buf - pack_entry_len); + xstrlcpy(buf, pack_entry, sizeof buf); + xstrlcpy(buf + pack_entry_len, start_pat[i], sizeof buf - pack_entry_len); expand_rtp_entry(search_path, rtp_used, buf, false); size_t after_size = strlen(buf) + 7; char *after = xmallocz(after_size); @@ -801,12 +821,14 @@ static void source_all_matches(char *pat) int num_files; char **files; - if (gen_expand_wildcards(1, &pat, &num_files, &files, EW_FILE) == OK) { - for (int i = 0; i < num_files; i++) { - (void)do_source(files[i], false, DOSO_NONE); - } - FreeWild(num_files, files); + if (gen_expand_wildcards(1, &pat, &num_files, &files, EW_FILE) != OK) { + return; } + + for (int i = 0; i < num_files; i++) { + (void)do_source(files[i], false, DOSO_NONE); + } + FreeWild(num_files, files); } /// Add the package directory to 'runtimepath' @@ -1064,7 +1086,7 @@ static void add_pack_start_dir(char *fname, void *cookie) if (strlen(fname) + strlen(start_pat[i]) + 1 > MAXPATHL) { continue; } - STRLCPY(buf, fname, MAXPATHL); + xstrlcpy(buf, fname, MAXPATHL); xstrlcat(buf, start_pat[i], sizeof buf); if (pack_has_entries(buf)) { add_pack_dir_to_rtp(buf, true); @@ -1155,12 +1177,11 @@ void ex_packadd(exarg_T *eap) /// Expand color scheme, compiler or filetype names. /// Search from 'runtimepath': -/// 'runtimepath'/{dirnames}/{pat}.vim -/// When "flags" has DIP_START: search also from 'start' of 'packpath': -/// 'packpath'/pack/ * /start/ * /{dirnames}/{pat}.vim -/// When "flags" has DIP_OPT: search also from 'opt' of 'packpath': -/// 'packpath'/pack/ * /opt/ * /{dirnames}/{pat}.vim -/// When "flags" has DIP_LUA: search also performed for .lua files +/// 'runtimepath'/{dirnames}/{pat}.(vim|lua) +/// When "flags" has DIP_START: search also from "start" of 'packpath': +/// 'packpath'/pack/*/start/*/{dirnames}/{pat}.(vim|lua) +/// When "flags" has DIP_OPT: search also from "opt" of 'packpath': +/// 'packpath'/pack/*/opt/*/{dirnames}/{pat}.(vim|lua) /// "dirnames" is an array with one or more directory names. int ExpandRTDir(char *pat, int flags, int *num_file, char ***file, char *dirnames[]) { @@ -1171,67 +1192,47 @@ int ExpandRTDir(char *pat, int flags, int *num_file, char ***file, char *dirname garray_T ga; ga_init(&ga, (int)sizeof(char *), 10); - // TODO(bfredl): this is bullshit, exandpath should not reinvent path logic. + // TODO(bfredl): this is bullshit, expandpath should not reinvent path logic. for (int i = 0; dirnames[i] != NULL; i++) { - size_t size = strlen(dirnames[i]) + pat_len + 7; + size_t size = strlen(dirnames[i]) + pat_len + 16; char *s = xmalloc(size); - snprintf(s, size, "%s/%s*.vim", dirnames[i], pat); + snprintf(s, size, "%s/%s*.\\(vim\\|lua\\)", dirnames[i], pat); globpath(p_rtp, s, &ga, 0); - if (flags & DIP_LUA) { - snprintf(s, size, "%s/%s*.lua", dirnames[i], pat); - globpath(p_rtp, s, &ga, 0); - } xfree(s); } if (flags & DIP_START) { for (int i = 0; dirnames[i] != NULL; i++) { - size_t size = strlen(dirnames[i]) + pat_len + 22; + size_t size = strlen(dirnames[i]) + pat_len + 31; char *s = xmalloc(size); - snprintf(s, size, "pack/*/start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT + snprintf(s, size, "pack/*/start/*/%s/%s*.\\(vim\\|lua\\)", dirnames[i], pat); // NOLINT globpath(p_pp, s, &ga, 0); - if (flags & DIP_LUA) { - snprintf(s, size, "pack/*/start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT - globpath(p_pp, s, &ga, 0); - } xfree(s); } for (int i = 0; dirnames[i] != NULL; i++) { - size_t size = strlen(dirnames[i]) + pat_len + 22; + size_t size = strlen(dirnames[i]) + pat_len + 31; char *s = xmalloc(size); - snprintf(s, size, "start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT + snprintf(s, size, "start/*/%s/%s*.\\(vim\\|lua\\)", dirnames[i], pat); // NOLINT globpath(p_pp, s, &ga, 0); - if (flags & DIP_LUA) { - snprintf(s, size, "start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT - globpath(p_pp, s, &ga, 0); - } xfree(s); } } if (flags & DIP_OPT) { for (int i = 0; dirnames[i] != NULL; i++) { - size_t size = strlen(dirnames[i]) + pat_len + 20; + size_t size = strlen(dirnames[i]) + pat_len + 29; char *s = xmalloc(size); - snprintf(s, size, "pack/*/opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT + snprintf(s, size, "pack/*/opt/*/%s/%s*.\\(vim\\|lua\\)", dirnames[i], pat); // NOLINT globpath(p_pp, s, &ga, 0); - if (flags & DIP_LUA) { - snprintf(s, size, "pack/*/opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT - globpath(p_pp, s, &ga, 0); - } xfree(s); } for (int i = 0; dirnames[i] != NULL; i++) { - size_t size = strlen(dirnames[i]) + pat_len + 20; + size_t size = strlen(dirnames[i]) + pat_len + 29; char *s = xmalloc(size); - snprintf(s, size, "opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT + snprintf(s, size, "opt/*/%s/%s*.\\(vim\\|lua\\)", dirnames[i], pat); // NOLINT globpath(p_pp, s, &ga, 0); - if (flags & DIP_LUA) { - snprintf(s, size, "opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT - globpath(p_pp, s, &ga, 0); - } xfree(s); } } @@ -1241,8 +1242,7 @@ int ExpandRTDir(char *pat, int flags, int *num_file, char ***file, char *dirname char *s = match; char *e = s + strlen(s); if (e - s > 4 && (STRNICMP(e - 4, ".vim", 4) == 0 - || ((flags & DIP_LUA) - && STRNICMP(e - 4, ".lua", 4) == 0))) { + || STRNICMP(e - 4, ".lua", 4) == 0)) { e -= 4; for (s = e; s > match; MB_PTR_BACK(match, s)) { if (vim_ispathsep(*s)) { @@ -1633,7 +1633,7 @@ void ex_options(exarg_T *eap) bool multi_mods = 0; buf[0] = NUL; - (void)add_win_cmd_modifers(buf, &cmdmod, &multi_mods); + (void)add_win_cmd_modifiers(buf, &cmdmod, &multi_mods); os_setenv("OPTWIN_CMD", buf, 1); cmd_source(SYS_OPTWIN_FILE, NULL); @@ -1698,7 +1698,7 @@ static bool concat_continued_line(garray_T *const ga, const int init_growsize, c const char *const line = skipwhite_len((char *)p, len); len -= (size_t)(line - p); // Skip lines starting with '\" ', concat lines starting with '\' - if (len >= 3 && STRNCMP(line, "\"\\ ", 3) == 0) { + if (len >= 3 && strncmp(line, "\"\\ ", 3) == 0) { return true; } else if (len == 0 || line[0] != '\\') { return false; @@ -1908,7 +1908,7 @@ int do_source(char *fname, int check_other, int is_vimrc) cookie.fp = fopen_noinh_readbin(fname_exp); if (cookie.fp == NULL && check_other) { - // Try again, replacing file name ".vimrc" by "_vimrc" or vice versa, + // Try again, replacing file name ".nvimrc" by "_nvimrc" or vice versa, // and ".exrc" by "_exrc" or vice versa. p = path_tail(fname_exp); if ((*p == '.' || *p == '_') @@ -2021,7 +2021,7 @@ int do_source(char *fname, int check_other, int is_vimrc) } else { // Read the first line so we can check for a UTF-8 BOM. firstline = (uint8_t *)getsourceline(0, (void *)&cookie, 0, true); - if (firstline != NULL && STRLEN(firstline) >= 3 && firstline[0] == 0xef + if (firstline != NULL && strlen((char *)firstline) >= 3 && firstline[0] == 0xef && firstline[1] == 0xbb && firstline[2] == 0xbf) { // Found BOM; setup conversion, skip over BOM and recode the line. convert_setup(&cookie.conv, "utf-8", p_enc); @@ -2064,8 +2064,8 @@ int do_source(char *fname, int check_other, int is_vimrc) } if (l_time_fd != NULL) { - vim_snprintf((char *)IObuff, IOSIZE, "sourcing %s", fname); - time_msg((char *)IObuff, &start_time); + vim_snprintf(IObuff, IOSIZE, "sourcing %s", fname); + time_msg(IObuff, &start_time); time_pop(rel_time); } @@ -2140,12 +2140,17 @@ scriptitem_T *get_current_script_id(char **fnamep, sctx_T *ret_sctx) /// ":scriptnames" void ex_scriptnames(exarg_T *eap) { - if (eap->addr_count > 0) { + if (eap->addr_count > 0 || *eap->arg != NUL) { // :script {scriptId}: edit the script - if (eap->line2 < 1 || eap->line2 > script_items.ga_len) { + if (eap->addr_count > 0 && !SCRIPT_ID_VALID(eap->line2)) { emsg(_(e_invarg)); } else { - eap->arg = SCRIPT_ITEM(eap->line2).sn_name; + if (eap->addr_count > 0) { + eap->arg = SCRIPT_ITEM(eap->line2).sn_name; + } else { + expand_env(eap->arg, NameBuff, MAXPATHL); + eap->arg = NameBuff; + } do_exedit(eap, NULL); } return; @@ -2153,11 +2158,11 @@ 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, (char *)NameBuff, MAXPATHL, true); - vim_snprintf((char *)IObuff, IOSIZE, "%3d: %s", i, NameBuff); - if (!message_filtered((char *)IObuff)) { + home_replace(NULL, SCRIPT_ITEM(i).sn_name, NameBuff, MAXPATHL, true); + vim_snprintf(IObuff, IOSIZE, "%3d: %s", i, NameBuff); + if (!message_filtered(IObuff)) { msg_putchar('\n'); - msg_outtrans((char *)IObuff); + msg_outtrans(IObuff); line_breakcheck(); } } @@ -2199,16 +2204,16 @@ char *get_scriptname(LastSet last_set, bool *should_free) case SID_LUA: return _("Lua"); case SID_API_CLIENT: - snprintf((char *)IObuff, IOSIZE, _("API client (channel id %" PRIu64 ")"), last_set.channel_id); - return (char *)IObuff; + snprintf(IObuff, IOSIZE, _("API client (channel id %" PRIu64 ")"), last_set.channel_id); + return IObuff; case SID_STR: return _("anonymous :source"); default: { char *const sname = SCRIPT_ITEM(last_set.script_ctx.sc_sid).sn_name; if (sname == NULL) { - snprintf((char *)IObuff, IOSIZE, _("anonymous :source (script id %d)"), + snprintf(IObuff, IOSIZE, _("anonymous :source (script id %d)"), last_set.script_ctx.sc_sid); - return (char *)IObuff; + return IObuff; } *should_free = true; diff --git a/src/nvim/runtime.h b/src/nvim/runtime.h index 053c71212e..97063b900c 100644 --- a/src/nvim/runtime.h +++ b/src/nvim/runtime.h @@ -3,10 +3,15 @@ #include <stdbool.h> +#include "klib/kvec.h" #include "nvim/autocmd.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/ex_eval_defs.h" +#include "nvim/garray.h" +#include "nvim/pos.h" +#include "nvim/types.h" typedef enum { ETYPE_TOP, ///< toplevel @@ -74,6 +79,7 @@ typedef struct scriptitem_S { /// Growarray to store info about already sourced scripts. extern garray_T script_items; #define SCRIPT_ITEM(id) (((scriptitem_T *)script_items.ga_data)[(id) - 1]) +#define SCRIPT_ID_VALID(id) ((id) > 0 && (id) <= script_items.ga_len) typedef void (*DoInRuntimepathCB)(char *, void *); @@ -99,7 +105,6 @@ typedef kvec_t(char *) CharVec; #define DIP_NORTP 0x20 // do not use 'runtimepath' #define DIP_NOAFTER 0x40 // skip "after" directories #define DIP_AFTER 0x80 // only use "after" directories -#define DIP_LUA 0x100 // also use ".lua" files #define DIP_DIRFILE 0x200 // find both files and directories #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 0c35a08d40..ba65a335e6 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -10,35 +10,44 @@ #include <assert.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> +#include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" -#include "nvim/cmdexpand.h" #include "nvim/cursor.h" -#include "nvim/drawscreen.h" #include "nvim/eval.h" -#include "nvim/extmark.h" -#include "nvim/fileio.h" #include "nvim/fold.h" -#include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/grid.h" +#include "nvim/grid_defs.h" #include "nvim/highlight.h" -#include "nvim/highlight_group.h" -#include "nvim/menu.h" +#include "nvim/mbyte.h" +#include "nvim/memline_defs.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/move.h" +#include "nvim/normal.h" #include "nvim/option.h" -#include "nvim/optionstr.h" +#include "nvim/os/os.h" +#include "nvim/os/time.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/regexp.h" #include "nvim/screen.h" #include "nvim/search.h" #include "nvim/state.h" #include "nvim/statusline.h" -#include "nvim/ui_compositor.h" -#include "nvim/undo.h" +#include "nvim/strings.h" +#include "nvim/types.h" +#include "nvim/ui.h" +#include "nvim/vim.h" #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -180,7 +189,7 @@ int compute_foldcolumn(win_T *wp, int col) /// /// Assume monocell characters /// @return number of chars added to \param p -size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum) +size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum) { int i = 0; int level; @@ -214,7 +223,7 @@ size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum) symbol = '>'; } - len = utf_char2bytes(symbol, (char *)&p[char_counter]); + len = utf_char2bytes(symbol, &p[char_counter]); char_counter += (size_t)len; if (first_level + i >= level) { i++; @@ -228,7 +237,7 @@ size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum) char_counter -= (size_t)len; memset(&p[char_counter], ' ', (size_t)len); } - len = utf_char2bytes(wp->w_p_fcs_chars.foldclosed, (char *)&p[char_counter]); + len = utf_char2bytes(wp->w_p_fcs_chars.foldclosed, &p[char_counter]); char_counter += (size_t)len; } @@ -237,250 +246,18 @@ size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum) /// Mirror text "str" for right-left displaying. /// Only works for single-byte characters (e.g., numbers). -void rl_mirror(char_u *str) +void rl_mirror(char *str) { - char_u *p1, *p2; - char_u t; + char *p1, *p2; + char t; - for (p1 = str, p2 = str + STRLEN(str) - 1; p1 < p2; p1++, p2--) { + for (p1 = str, p2 = str + strlen(str) - 1; p1 < p2; p1++, p2--) { t = *p1; *p1 = *p2; *p2 = t; } } -/// Get the length of an item as it will be shown in the status line. -static int wildmenu_match_len(expand_T *xp, char_u *s) -{ - int len = 0; - - int emenu = (xp->xp_context == EXPAND_MENUS - || xp->xp_context == EXPAND_MENUNAMES); - - // Check for menu separators - replace with '|'. - if (emenu && menu_is_separator((char *)s)) { - return 1; - } - - while (*s != NUL) { - s += skip_wildmenu_char(xp, s); - len += ptr2cells((char *)s); - MB_PTR_ADV(s); - } - - return len; -} - -/// Return the number of characters that should be skipped in the wildmenu -/// These are backslashes used for escaping. Do show backslashes in help tags. -static int skip_wildmenu_char(expand_T *xp, char_u *s) -{ - if ((rem_backslash((char *)s) && xp->xp_context != EXPAND_HELP) - || ((xp->xp_context == EXPAND_MENUS - || xp->xp_context == EXPAND_MENUNAMES) - && (s[0] == '\t' - || (s[0] == '\\' && s[1] != NUL)))) { -#ifndef BACKSLASH_IN_FILENAME - // TODO(bfredl): Why in the actual fuck are we special casing the - // shell variety deep in the redraw logic? Shell special snowflakiness - // should already be eliminated multiple layers before reaching the - // screen infracstructure. - if (xp->xp_shell && csh_like_shell() && s[1] == '\\' && s[2] == '!') { - return 2; - } -#endif - return 1; - } - return 0; -} - -/// Show wildchar matches in the status line. -/// Show at least the "match" item. -/// We start at item 'first_match' in the list and show all matches that fit. -/// -/// If inversion is possible we use it. Else '=' characters are used. -/// -/// @param matches list of matches -void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int match, int showtail) -{ -#define L_MATCH(m) (showtail ? sm_gettail(matches[m], false) : matches[m]) - int row; - char_u *buf; - int len; - int clen; // length in screen cells - int fillchar; - int attr; - int i; - bool highlight = true; - char_u *selstart = NULL; - int selstart_col = 0; - char_u *selend = NULL; - static int first_match = 0; - bool add_left = false; - char_u *s; - int emenu; - int l; - - if (matches == NULL) { // interrupted completion? - return; - } - - buf = xmalloc((size_t)Columns * MB_MAXBYTES + 1); - - if (match == -1) { // don't show match but original text - match = 0; - highlight = false; - } - // count 1 for the ending ">" - clen = wildmenu_match_len(xp, (char_u *)L_MATCH(match)) + 3; - if (match == 0) { - first_match = 0; - } else if (match < first_match) { - // jumping left, as far as we can go - first_match = match; - add_left = true; - } else { - // check if match fits on the screen - for (i = first_match; i < match; i++) { - clen += wildmenu_match_len(xp, (char_u *)L_MATCH(i)) + 2; - } - if (first_match > 0) { - clen += 2; - } - // jumping right, put match at the left - if ((long)clen > Columns) { - first_match = match; - // if showing the last match, we can add some on the left - clen = 2; - for (i = match; i < num_matches; i++) { - clen += wildmenu_match_len(xp, (char_u *)L_MATCH(i)) + 2; - if ((long)clen >= Columns) { - break; - } - } - if (i == num_matches) { - add_left = true; - } - } - } - if (add_left) { - while (first_match > 0) { - clen += wildmenu_match_len(xp, (char_u *)L_MATCH(first_match - 1)) + 2; - if ((long)clen >= Columns) { - break; - } - first_match--; - } - } - - fillchar = fillchar_status(&attr, curwin); - - if (first_match == 0) { - *buf = NUL; - len = 0; - } else { - STRCPY(buf, "< "); - len = 2; - } - clen = len; - - i = first_match; - while (clen + wildmenu_match_len(xp, (char_u *)L_MATCH(i)) + 2 < Columns) { - if (i == match) { - selstart = buf + len; - selstart_col = clen; - } - - s = (char_u *)L_MATCH(i); - // Check for menu separators - replace with '|' - emenu = (xp->xp_context == EXPAND_MENUS - || xp->xp_context == EXPAND_MENUNAMES); - if (emenu && menu_is_separator((char *)s)) { - STRCPY(buf + len, transchar('|')); - l = (int)STRLEN(buf + len); - len += l; - clen += l; - } else { - for (; *s != NUL; s++) { - s += skip_wildmenu_char(xp, s); - clen += ptr2cells((char *)s); - if ((l = utfc_ptr2len((char *)s)) > 1) { - STRNCPY(buf + len, s, l); // NOLINT(runtime/printf) - s += l - 1; - len += l; - } else { - STRCPY(buf + len, transchar_byte(*s)); - len += (int)STRLEN(buf + len); - } - } - } - if (i == match) { - selend = buf + len; - } - - *(buf + len++) = ' '; - *(buf + len++) = ' '; - clen += 2; - if (++i == num_matches) { - break; - } - } - - if (i != num_matches) { - *(buf + len++) = '>'; - clen++; - } - - buf[len] = NUL; - - row = cmdline_row - 1; - if (row >= 0) { - if (wild_menu_showing == 0 || wild_menu_showing == WM_LIST) { - if (msg_scrolled > 0) { - // Put the wildmenu just above the command line. If there is - // no room, scroll the screen one line up. - if (cmdline_row == Rows - 1) { - msg_scroll_up(false, false); - msg_scrolled++; - } else { - cmdline_row++; - row++; - } - wild_menu_showing = WM_SCROLLED; - } else { - // 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 && global_stl_height() == 0) { - save_p_ls = (int)p_ls; - save_p_wmh = (int)p_wmh; - p_ls = 2; - p_wmh = 0; - last_status(false); - } - wild_menu_showing = WM_SHOWN; - } - } - - // Tricky: wildmenu can be drawn either over a status line, or at empty - // scrolled space in the message output - ScreenGrid *grid = (wild_menu_showing == WM_SCROLLED) - ? &msg_grid_adj : &default_grid; - - grid_puts(grid, (char *)buf, row, 0, attr); - if (selstart != NULL && highlight) { - *selend = NUL; - grid_puts(grid, (char *)selstart, row, selstart_col, HL_ATTR(HLF_WM)); - } - - grid_fill(grid, row, row + 1, clen, Columns, - fillchar, fillchar, attr); - } - - win_redraw_last_status(topframe); - xfree(buf); -} - /// Only call if (wp->w_vsep_width != 0). /// /// @return true if the status line of window "wp" is connected to the status @@ -518,52 +295,54 @@ bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len) return false; } - { - buf_T *old_curbuf = curbuf; - win_T *old_curwin = curwin; - char *s; - - curbuf = wp->w_buffer; - curwin = wp; - STRCPY(buf, "b:keymap_name"); // must be writable - emsg_skip++; - s = p = eval_to_string(buf, NULL, false); - emsg_skip--; - curbuf = old_curbuf; - curwin = old_curwin; - if (p == NULL || *p == NUL) { - if (wp->w_buffer->b_kmap_state & KEYMAP_LOADED) { - p = wp->w_buffer->b_p_keymap; - } else { - p = "lang"; - } - } - if (vim_snprintf(buf, (size_t)len, fmt, p) > len - 1) { - buf[0] = NUL; + buf_T *old_curbuf = curbuf; + win_T *old_curwin = curwin; + char *s; + + curbuf = wp->w_buffer; + curwin = wp; + STRCPY(buf, "b:keymap_name"); // must be writable + emsg_skip++; + s = p = eval_to_string(buf, NULL, false); + emsg_skip--; + curbuf = old_curbuf; + curwin = old_curwin; + if (p == NULL || *p == NUL) { + if (wp->w_buffer->b_kmap_state & KEYMAP_LOADED) { + p = wp->w_buffer->b_p_keymap; + } else { + p = "lang"; } - xfree(s); } + if (vim_snprintf(buf, (size_t)len, fmt, p) > len - 1) { + buf[0] = NUL; + } + xfree(s); return buf[0] != NUL; } /// Prepare for 'hlsearch' highlighting. void start_search_hl(void) { - if (p_hls && !no_hlsearch) { - end_search_hl(); // just in case it wasn't called before - last_pat_prog(&screen_search_hl.rm); - // Set the time limit to 'redrawtime'. - screen_search_hl.tm = profile_setlimit(p_rdt); + if (!p_hls || no_hlsearch) { + return; } + + end_search_hl(); // just in case it wasn't called before + last_pat_prog(&screen_search_hl.rm); + // Set the time limit to 'redrawtime'. + screen_search_hl.tm = profile_setlimit(p_rdt); } /// Clean up for 'hlsearch' highlighting. void end_search_hl(void) { - if (screen_search_hl.rm.regprog != NULL) { - vim_regfree(screen_search_hl.rm.regprog); - screen_search_hl.rm.regprog = NULL; + if (screen_search_hl.rm.regprog == NULL) { + return; } + + vim_regfree(screen_search_hl.rm.regprog); + screen_search_hl.rm.regprog = NULL; } /// Check if there should be a delay. Used before clearing or redrawing the @@ -572,7 +351,8 @@ void check_for_delay(bool check_msg_scroll) { if ((emsg_on_display || (check_msg_scroll && msg_scroll)) && !did_wait_return - && emsg_silent == 0) { + && emsg_silent == 0 + && !in_assert_fails) { ui_flush(); os_delay(1006L, true); emsg_on_display = false; @@ -582,22 +362,6 @@ void check_for_delay(bool check_msg_scroll) } } -/// Clear status line, window bar or tab page line click definition table -/// -/// @param[out] tpcd Table to clear. -/// @param[in] tpcd_size Size of the table. -void stl_clear_click_defs(StlClickDefinition *const click_defs, const long click_defs_size) -{ - if (click_defs != NULL) { - for (long i = 0; i < click_defs_size; i++) { - if (i == 0 || click_defs[i].func != click_defs[i - 1].func) { - xfree(click_defs[i].func); - } - } - memset(click_defs, 0, (size_t)click_defs_size * sizeof(click_defs[0])); - } -} - /// Set cursor to its position in the current window. void setcursor(void) { @@ -785,8 +549,8 @@ int showmode(void) if (curwin->w_p_arab) { msg_puts_attr(_(" Arabic"), attr); } else if (get_keymap_str(curwin, " (%s)", - (char *)NameBuff, MAXPATHL)) { - msg_puts_attr((char *)NameBuff, attr); + NameBuff, MAXPATHL)) { + msg_puts_attr(NameBuff, attr); } } if ((State & MODE_INSERT) && p_paste) { @@ -860,6 +624,7 @@ int showmode(void) if (redrawing() && last->w_status_height == 0 && global_stl_height() == 0) { win_redr_ruler(last, true); } + redraw_cmdline = false; redraw_mode = false; clear_cmdline = false; @@ -908,241 +673,23 @@ void clearmode(void) static void recording_mode(int attr) { msg_puts_attr(_("recording"), attr); - if (!shortmess(SHM_RECORDING)) { - char s[4]; - snprintf(s, ARRAY_SIZE(s), " @%c", reg_recording); - msg_puts_attr(s, attr); - } -} - -/// Draw the tab pages line at the top of the Vim window. -void draw_tabline(void) -{ - int tabcount = 0; - int tabwidth = 0; - int col = 0; - int scol = 0; - int attr; - win_T *wp; - win_T *cwp; - int wincount; - int modified; - int c; - int len; - int attr_nosel = HL_ATTR(HLF_TP); - int attr_fill = HL_ATTR(HLF_TPF); - char_u *p; - int room; - int use_sep_chars = (t_colors < 8); - - if (default_grid.chars == NULL) { + if (shortmess(SHM_RECORDING)) { return; } - redraw_tabline = false; - - if (ui_has(kUITabline)) { - ui_ext_tabline_update(); - return; - } - - if (tabline_height() < 1) { - return; - } - - // Init TabPageIdxs[] to zero: Clicking outside of tabs has no effect. - assert(Columns == tab_page_click_defs_size); - stl_clear_click_defs(tab_page_click_defs, tab_page_click_defs_size); - - // Use the 'tabline' option if it's set. - if (*p_tal != NUL) { - int saved_did_emsg = did_emsg; - - // Check for an error. If there is one we would loop in redrawing the - // screen. Avoid that by making 'tabline' empty. - did_emsg = false; - win_redr_custom(NULL, false, false); - if (did_emsg) { - set_string_option_direct("tabline", -1, "", OPT_FREE, SID_ERROR); - } - did_emsg |= saved_did_emsg; - } else { - FOR_ALL_TABS(tp) { - tabcount++; - } - - if (tabcount > 0) { - tabwidth = (Columns - 1 + tabcount / 2) / tabcount; - } - - if (tabwidth < 6) { - tabwidth = 6; - } - - attr = attr_nosel; - tabcount = 0; - FOR_ALL_TABS(tp) { - if (col >= Columns - 4) { - break; - } - - scol = col; - - if (tp == curtab) { - cwp = curwin; - wp = firstwin; - } else { - cwp = tp->tp_curwin; - wp = tp->tp_firstwin; - } - - if (tp->tp_topframe == topframe) { - attr = win_hl_attr(cwp, HLF_TPS); - } - if (use_sep_chars && col > 0) { - grid_putchar(&default_grid, '|', 0, col++, attr); - } - - if (tp->tp_topframe != topframe) { - attr = win_hl_attr(cwp, HLF_TP); - } - - grid_putchar(&default_grid, ' ', 0, col++, attr); - - modified = false; - - for (wincount = 0; wp != NULL; wp = wp->w_next, wincount++) { - if (bufIsChanged(wp->w_buffer)) { - modified = true; - } - } - - if (modified || wincount > 1) { - if (wincount > 1) { - vim_snprintf((char *)NameBuff, MAXPATHL, "%d", wincount); - len = (int)strlen(NameBuff); - if (col + len >= Columns - 3) { - break; - } - grid_puts_len(&default_grid, NameBuff, len, 0, col, - hl_combine_attr(attr, win_hl_attr(cwp, HLF_T))); - col += len; - } - if (modified) { - grid_puts_len(&default_grid, "+", 1, 0, col++, attr); - } - grid_putchar(&default_grid, ' ', 0, col++, attr); - } - - room = scol - col + tabwidth - 1; - if (room > 0) { - // Get buffer name in NameBuff[] - get_trans_bufname(cwp->w_buffer); - shorten_dir(NameBuff); - len = vim_strsize((char *)NameBuff); - p = (char_u *)NameBuff; - while (len > room) { - len -= ptr2cells((char *)p); - MB_PTR_ADV(p); - } - if (len > Columns - col - 1) { - len = Columns - col - 1; - } - - grid_puts_len(&default_grid, (char *)p, (int)STRLEN(p), 0, col, attr); - col += len; - } - grid_putchar(&default_grid, ' ', 0, col++, attr); - - // Store the tab page number in tab_page_click_defs[], so that - // jump_to_mouse() knows where each one is. - tabcount++; - while (scol < col) { - tab_page_click_defs[scol++] = (StlClickDefinition) { - .type = kStlClickTabSwitch, - .tabnr = tabcount, - .func = NULL, - }; - } - } - - if (use_sep_chars) { - c = '_'; - } else { - c = ' '; - } - grid_fill(&default_grid, 0, 1, col, Columns, c, c, attr_fill); - - // Put an "X" for closing the current tab if there are several. - if (first_tabpage->tp_next != NULL) { - grid_putchar(&default_grid, 'X', 0, Columns - 1, attr_nosel); - tab_page_click_defs[Columns - 1] = (StlClickDefinition) { - .type = kStlClickTabClose, - .tabnr = 999, - .func = NULL, - }; - } - } - - // Reset the flag here again, in case evaluating 'tabline' causes it to be - // set. - redraw_tabline = false; -} - -static void ui_ext_tabline_update(void) -{ - Arena arena = ARENA_EMPTY; - - size_t n_tabs = 0; - FOR_ALL_TABS(tp) { - n_tabs++; - } - - Array tabs = arena_array(&arena, n_tabs); - FOR_ALL_TABS(tp) { - Dictionary tab_info = arena_dict(&arena, 2); - PUT_C(tab_info, "tab", TABPAGE_OBJ(tp->handle)); - - win_T *cwp = (tp == curtab) ? curwin : tp->tp_curwin; - get_trans_bufname(cwp->w_buffer); - PUT_C(tab_info, "name", STRING_OBJ(arena_string(&arena, cstr_as_string((char *)NameBuff)))); - - ADD_C(tabs, DICTIONARY_OBJ(tab_info)); - } - - size_t n_buffers = 0; - FOR_ALL_BUFFERS(buf) { - n_buffers += buf->b_p_bl ? 1 : 0; - } - - Array buffers = arena_array(&arena, n_buffers); - FOR_ALL_BUFFERS(buf) { - // Do not include unlisted buffers - if (!buf->b_p_bl) { - continue; - } - - Dictionary buffer_info = arena_dict(&arena, 2); - PUT_C(buffer_info, "buffer", BUFFER_OBJ(buf->handle)); - - get_trans_bufname(buf); - PUT_C(buffer_info, "name", STRING_OBJ(arena_string(&arena, cstr_as_string((char *)NameBuff)))); - - ADD_C(buffers, DICTIONARY_OBJ(buffer_info)); - } - - ui_call_tabline_update(curtab->handle, tabs, curbuf->handle, buffers); - arena_mem_free(arena_finish(&arena)); + char s[4]; + snprintf(s, ARRAY_SIZE(s), " @%c", reg_recording); + msg_puts_attr(s, attr); } void get_trans_bufname(buf_T *buf) { if (buf_spname(buf) != NULL) { - STRLCPY(NameBuff, buf_spname(buf), MAXPATHL); + xstrlcpy(NameBuff, buf_spname(buf), MAXPATHL); } else { - home_replace(buf, buf->b_fname, (char *)NameBuff, MAXPATHL, true); + home_replace(buf, buf->b_fname, NameBuff, MAXPATHL, true); } - trans_characters((char *)NameBuff, MAXPATHL); + trans_characters(NameBuff, MAXPATHL); } /// Get the character to use in a separator between vertically split windows. @@ -1237,6 +784,16 @@ int number_width(win_T *wp) } wp->w_nrwidth_line_count = lnum; + // make best estimate for 'statuscolumn' + if (*wp->w_p_stc != NUL) { + char buf[MAXPATHL]; + wp->w_nrwidth_width = 0; + n = build_statuscol_str(wp, lnum, 0, 0, NUL, buf, NULL, NULL); + n = MAX(n, (wp->w_p_nu || wp->w_p_rnu) * (int)wp->w_p_nuw); + wp->w_nrwidth_width = MIN(n, MAX_NUMBERWIDTH); + return wp->w_nrwidth_width; + } + n = 0; do { lnum /= 10; @@ -1262,15 +819,15 @@ int number_width(win_T *wp) /// Calls mb_cptr2char_adv(p) and returns the character. /// If "p" starts with "\x", "\u" or "\U" the hex or unicode value is used. /// Returns 0 for invalid hex or invalid UTF-8 byte. -static int get_encoded_char_adv(const char_u **p) +static int get_encoded_char_adv(const char **p) { - const char_u *s = *p; + const char *s = *p; if (s[0] == '\\' && (s[1] == 'x' || s[1] == 'u' || s[1] == 'U')) { int64_t num = 0; for (int bytes = s[1] == 'x' ? 1 : s[1] == 'u' ? 2 : 4; bytes > 0; bytes--) { *p += 2; - int n = hexhex2nr((char *)(*p)); + int n = hexhex2nr(*p); if (n < 0) { return 0; } @@ -1281,7 +838,7 @@ static int get_encoded_char_adv(const char_u **p) } // TODO(bfredl): use schar_T representation and utfc_ptr2len - int clen = utf_ptr2len((const char *)s); + int clen = utf_ptr2len(s); int c = mb_cptr2char_adv(p); if (clen == 1 && c > 127) { // Invalid UTF-8 byte return 0; @@ -1297,8 +854,8 @@ static int get_encoded_char_adv(const char_u **p) /// @return error message, NULL if it's OK. char *set_chars_option(win_T *wp, char **varp, bool apply) { - const char_u *last_multispace = NULL; // Last occurrence of "multispace:" - const char_u *last_lmultispace = NULL; // Last occurrence of "leadmultispace:" + const char *last_multispace = NULL; // Last occurrence of "multispace:" + const char *last_lmultispace = NULL; // Last occurrence of "leadmultispace:" int multispace_len = 0; // Length of lcs-multispace string int lead_multispace_len = 0; // Length of lcs-leadmultispace string const bool is_listchars = (varp == &p_lcs || varp == &wp->w_p_lcs); @@ -1346,18 +903,18 @@ char *set_chars_option(win_T *wp, char **varp, bool apply) struct chars_tab *tab; int entries; - const char_u *value = (char_u *)(*varp); + const char *value = *varp; if (is_listchars) { tab = lcs_tab; entries = ARRAY_SIZE(lcs_tab); if (varp == &wp->w_p_lcs && wp->w_p_lcs[0] == NUL) { - value = (char_u *)p_lcs; // local value is empty, use the global value + value = p_lcs; // local value is empty, use the global value } } else { tab = fcs_tab; entries = ARRAY_SIZE(fcs_tab); if (varp == &wp->w_p_fcs && wp->w_p_fcs[0] == NUL) { - value = (char_u *)p_fcs; // local value is empty, use the global value + value = p_fcs; // local value is empty, use the global value } } @@ -1392,15 +949,15 @@ char *set_chars_option(win_T *wp, char **varp, bool apply) } } } - const char_u *p = value; + const char *p = value; while (*p) { int i; for (i = 0; i < entries; i++) { const size_t len = strlen(tab[i].name); - if (STRNCMP(p, tab[i].name, len) == 0 + if (strncmp(p, tab[i].name, len) == 0 && p[len] == ':' && p[len + 1] != NUL) { - const char_u *s = p + len + 1; + const char *s = p + len + 1; int c1 = get_encoded_char_adv(&s); if (c1 == 0 || char2cells(c1) > 1) { return e_invarg; @@ -1441,10 +998,10 @@ char *set_chars_option(win_T *wp, char **varp, bool apply) const size_t len = strlen("multispace"); const size_t len2 = strlen("leadmultispace"); if (is_listchars - && STRNCMP(p, "multispace", len) == 0 + && strncmp(p, "multispace", len) == 0 && p[len] == ':' && p[len + 1] != NUL) { - const char_u *s = p + len + 1; + const char *s = p + len + 1; if (round == 0) { // Get length of lcs-multispace string in the first round last_multispace = p; @@ -1472,10 +1029,10 @@ char *set_chars_option(win_T *wp, char **varp, bool apply) p = s; } } else if (is_listchars - && STRNCMP(p, "leadmultispace", len2) == 0 + && strncmp(p, "leadmultispace", len2) == 0 && p[len2] == ':' && p[len2 + 1] != NUL) { - const char_u *s = p + len2 + 1; + const char *s = p + len2 + 1; if (round == 0) { // get length of lcs-leadmultispace string in first round last_lmultispace = p; diff --git a/src/nvim/screen.h b/src/nvim/screen.h index ea1c58cd80..1d8de8ca21 100644 --- a/src/nvim/screen.h +++ b/src/nvim/screen.h @@ -6,15 +6,10 @@ #include "nvim/buffer_defs.h" #include "nvim/fold.h" #include "nvim/grid_defs.h" +#include "nvim/macros.h" EXTERN match_T screen_search_hl; // used for 'hlsearch' highlight matching -/// Array defining what should be done when tabline is clicked -EXTERN StlClickDefinition *tab_page_click_defs INIT(= NULL); - -/// Size of the tab_page_click_defs array -EXTERN long tab_page_click_defs_size INIT(= 0); - #define W_ENDCOL(wp) ((wp)->w_wincol + (wp)->w_width) #define W_ENDROW(wp) ((wp)->w_winrow + (wp)->w_height) diff --git a/src/nvim/search.c b/src/nvim/search.c index ed0f25cba0..b24b6ad27c 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -5,31 +5,35 @@ #include <assert.h> #include <inttypes.h> -#include <limits.h> // for INT_MAX on MSVC +#include <limits.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cmdhist.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" -#include "nvim/edit.h" #include "nvim/eval.h" -#include "nvim/eval/funcs.h" +#include "nvim/eval/typval.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/getchar.h" -#include "nvim/indent.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" #include "nvim/indent_c.h" #include "nvim/insexpand.h" -#include "nvim/main.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" @@ -39,11 +43,13 @@ #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" +#include "nvim/os/fs.h" #include "nvim/os/input.h" #include "nvim/os/time.h" #include "nvim/path.h" #include "nvim/profile.h" #include "nvim/regexp.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/ui.h" @@ -80,8 +86,7 @@ // one for other searches. last_idx points to the one that was used the last // time. -static struct spat spats[2] = -{ +static struct spat spats[2] = { // Last used search pattern [0] = { NULL, true, false, 0, { '/', false, false, 0L }, NULL }, // Last used substitute pattern @@ -93,22 +98,23 @@ static int last_idx = 0; // index in spats[] for RE_LAST static char_u lastc[2] = { NUL, NUL }; // last character searched for static Direction lastcdir = FORWARD; // last direction of character search static int last_t_cmd = true; // last search t_cmd -static char_u lastc_bytes[MB_MAXBYTES + 1]; +static char lastc_bytes[MB_MAXBYTES + 1]; static int lastc_bytelen = 1; // >1 for multi-byte char // copy of spats[], for keeping the search patterns while executing autocmds static struct spat saved_spats[2]; +static char *saved_mr_pattern = NULL; static int saved_spats_last_idx = 0; static bool saved_spats_no_hlsearch = false; -static char_u *mr_pattern = NULL; // pattern used by search_regcomp() -static bool mr_pattern_alloced = false; // mr_pattern was allocated +// allocated copy of pattern used by search_regcomp() +static char *mr_pattern = NULL; // Type used by find_pattern_in_path() to remember which included files have // been searched already. typedef struct SearchedFile { FILE *fp; // File pointer - char_u *name; // Full name of file + char *name; // Full name of file linenr_T lnum; // Line we were up to in file int matched; // Found a match in this file } SearchedFile; @@ -127,13 +133,14 @@ typedef struct SearchedFile { /// @param regmatch return: pattern and ignore-case flag /// /// @return FAIL if failed, OK otherwise. -int search_regcomp(char_u *pat, int pat_save, int pat_use, int options, regmmatch_T *regmatch) +int search_regcomp(char *pat, char **used_pat, int pat_save, int pat_use, int options, + regmmatch_T *regmatch) { int magic; int i; rc_did_emsg = false; - magic = p_magic; + magic = magic_isset(); // If no pattern given, use a previously defined pattern. if (pat == NULL || *pat == NUL) { @@ -155,19 +162,18 @@ int search_regcomp(char_u *pat, int pat_save, int pat_use, int options, regmmatc magic = spats[i].magic; no_smartcase = spats[i].no_scs; } else if (options & SEARCH_HIS) { // put new pattern in history - add_to_history(HIST_SEARCH, (char *)pat, true, NUL); + add_to_history(HIST_SEARCH, pat, true, NUL); } - if (mr_pattern_alloced) { - xfree(mr_pattern); - mr_pattern_alloced = false; + if (used_pat) { + *used_pat = pat; } + xfree(mr_pattern); if (curwin->w_p_rl && *curwin->w_p_rlc == 's') { - mr_pattern = (char_u *)reverse_text((char *)pat); - mr_pattern_alloced = true; + mr_pattern = reverse_text(pat); } else { - mr_pattern = pat; + mr_pattern = xstrdup(pat); } // Save the currently used pattern in the appropriate place, @@ -175,45 +181,47 @@ int search_regcomp(char_u *pat, int pat_save, int pat_use, int options, regmmatc if (!(options & SEARCH_KEEP) && (cmdmod.cmod_flags & CMOD_KEEPPATTERNS) == 0) { // search or global command if (pat_save == RE_SEARCH || pat_save == RE_BOTH) { - save_re_pat(RE_SEARCH, (char *)pat, magic); + save_re_pat(RE_SEARCH, pat, magic); } // substitute or global command if (pat_save == RE_SUBST || pat_save == RE_BOTH) { - save_re_pat(RE_SUBST, (char *)pat, magic); + save_re_pat(RE_SUBST, pat, magic); } } regmatch->rmm_ic = ignorecase(pat); regmatch->rmm_maxcol = 0; - regmatch->regprog = vim_regcomp((char *)pat, magic ? RE_MAGIC : 0); + regmatch->regprog = vim_regcomp(pat, magic ? RE_MAGIC : 0); if (regmatch->regprog == NULL) { return FAIL; } return OK; } -// Get search pattern used by search_regcomp(). -char_u *get_search_pat(void) +/// Get search pattern used by search_regcomp(). +char *get_search_pat(void) { return mr_pattern; } void save_re_pat(int idx, char *pat, int magic) { - if (spats[idx].pat != (char_u *)pat) { - free_spat(&spats[idx]); - spats[idx].pat = (char_u *)xstrdup(pat); - spats[idx].magic = magic; - spats[idx].no_scs = no_smartcase; - spats[idx].timestamp = os_time(); - spats[idx].additional_data = NULL; - last_idx = idx; - // If 'hlsearch' set and search pat changed: need redraw. - if (p_hls) { - redraw_all_later(UPD_SOME_VALID); - } - set_no_hlsearch(false); + if (spats[idx].pat == pat) { + return; } + + free_spat(&spats[idx]); + spats[idx].pat = xstrdup(pat); + spats[idx].magic = magic; + spats[idx].no_scs = no_smartcase; + spats[idx].timestamp = os_time(); + spats[idx].additional_data = NULL; + last_idx = idx; + // If 'hlsearch' set and search pat changed: need redraw. + if (p_hls) { + redraw_all_later(UPD_SOME_VALID); + } + set_no_hlsearch(false); } // Save the search patterns, so they can be restored later. @@ -222,31 +230,42 @@ static int save_level = 0; void save_search_patterns(void) { - if (save_level++ == 0) { - saved_spats[0] = spats[0]; - if (spats[0].pat != NULL) { - saved_spats[0].pat = (char_u *)xstrdup((char *)spats[0].pat); - } - saved_spats[1] = spats[1]; - if (spats[1].pat != NULL) { - saved_spats[1].pat = (char_u *)xstrdup((char *)spats[1].pat); - } - saved_spats_last_idx = last_idx; - saved_spats_no_hlsearch = no_hlsearch; + if (save_level++ != 0) { + return; } + + saved_spats[0] = spats[0]; + if (spats[0].pat != NULL) { + saved_spats[0].pat = xstrdup(spats[0].pat); + } + saved_spats[1] = spats[1]; + if (spats[1].pat != NULL) { + saved_spats[1].pat = xstrdup(spats[1].pat); + } + if (mr_pattern == NULL) { + saved_mr_pattern = NULL; + } else { + saved_mr_pattern = xstrdup(mr_pattern); + } + saved_spats_last_idx = last_idx; + saved_spats_no_hlsearch = no_hlsearch; } void restore_search_patterns(void) { - if (--save_level == 0) { - free_spat(&spats[0]); - spats[0] = saved_spats[0]; - set_vv_searchforward(); - free_spat(&spats[1]); - spats[1] = saved_spats[1]; - last_idx = saved_spats_last_idx; - set_no_hlsearch(saved_spats_no_hlsearch); + if (--save_level != 0) { + return; } + + free_spat(&spats[0]); + spats[0] = saved_spats[0]; + set_vv_searchforward(); + free_spat(&spats[1]); + spats[1] = saved_spats[1]; + xfree(mr_pattern); + mr_pattern = saved_mr_pattern; + last_idx = saved_spats_last_idx; + set_no_hlsearch(saved_spats_no_hlsearch); } static inline void free_spat(struct spat *const spat) @@ -263,11 +282,7 @@ void free_search_patterns(void) CLEAR_FIELD(spats); - if (mr_pattern_alloced) { - xfree(mr_pattern); - mr_pattern_alloced = false; - mr_pattern = NULL; - } + XFREE_CLEAR(mr_pattern); } #endif @@ -296,7 +311,7 @@ void save_last_search_pattern(void) saved_last_search_spat = spats[RE_SEARCH]; if (spats[RE_SEARCH].pat != NULL) { - saved_last_search_spat.pat = (char_u *)xstrdup((char *)spats[RE_SEARCH].pat); + saved_last_search_spat.pat = xstrdup(spats[RE_SEARCH].pat); } saved_last_idx = last_idx; saved_no_hlsearch = no_hlsearch; @@ -337,20 +352,20 @@ static void restore_incsearch_state(void) search_match_lines = saved_search_match_lines; } -char_u *last_search_pattern(void) +char *last_search_pattern(void) { return spats[RE_SEARCH].pat; } /// Return true when case should be ignored for search pattern "pat". /// Uses the 'ignorecase' and 'smartcase' options. -int ignorecase(char_u *pat) +int ignorecase(char *pat) { return ignorecase_opt(pat, p_ic, p_scs); } /// As ignorecase() put pass the "ic" and "scs" flags. -int ignorecase_opt(char_u *pat, int ic_in, int scs) +int ignorecase_opt(char *pat, int ic_in, int scs) { int ic = ic_in; if (ic && !no_smartcase && scs @@ -364,20 +379,24 @@ int ignorecase_opt(char_u *pat, int ic_in, int scs) } /// Returns true if pattern `pat` has an uppercase character. -bool pat_has_uppercase(char_u *pat) +bool pat_has_uppercase(char *pat) FUNC_ATTR_NONNULL_ALL { - char_u *p = pat; + char *p = pat; + magic_T magic_val = MAGIC_ON; + + // get the magicness of the pattern + (void)skip_regexp_ex(pat, NUL, magic_isset(), NULL, NULL, &magic_val); while (*p != NUL) { - const int l = utfc_ptr2len((char *)p); + const int l = utfc_ptr2len(p); if (l > 1) { - if (mb_isupper(utf_ptr2char((char *)p))) { + if (mb_isupper(utf_ptr2char(p))) { return true; } p += l; - } else if (*p == '\\') { + } else if (*p == '\\' && magic_val <= MAGIC_ON) { if (p[1] == '_' && p[2] != NUL) { // skip "\_X" p += 3; } else if (p[1] == '%' && p[2] != NUL) { // skip "\%X" @@ -387,7 +406,13 @@ bool pat_has_uppercase(char_u *pat) } else { p += 1; } - } else if (mb_isupper(*p)) { + } else if ((*p == '%' || *p == '_') && magic_val == MAGIC_ALL) { + if (p[1] != NUL) { // skip "_X" and %X + p += 2; + } else { + p++; + } + } else if (mb_isupper((uint8_t)(*p))) { return true; } else { p++; @@ -399,7 +424,7 @@ bool pat_has_uppercase(char_u *pat) const char *last_csearch(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - return (const char *)lastc_bytes; + return lastc_bytes; } int last_csearch_forward(void) @@ -412,7 +437,7 @@ int last_csearch_until(void) return last_t_cmd == true; } -void set_last_csearch(int c, char_u *s, int len) +void set_last_csearch(int c, char *s, int len) { *lastc = (char_u)c; lastc_bytelen = len; @@ -433,7 +458,7 @@ void set_csearch_until(int t_cmd) last_t_cmd = t_cmd; } -char_u *last_search_pat(void) +char *last_search_pat(void) { return spats[last_idx].pat; } @@ -447,14 +472,14 @@ void reset_search_dir(void) // Set the last search pattern. For ":let @/ =" and ShaDa file. // Also set the saved search pattern, so that this works in an autocommand. -void set_last_search_pat(const char_u *s, int idx, int magic, int setlast) +void set_last_search_pat(const char *s, int idx, int magic, int setlast) { free_spat(&spats[idx]); // An empty string means that nothing should be matched. if (*s == NUL) { spats[idx].pat = NULL; } else { - spats[idx].pat = (char_u *)xstrdup((char *)s); + spats[idx].pat = xstrdup(s); } spats[idx].timestamp = os_time(); spats[idx].additional_data = NULL; @@ -474,7 +499,7 @@ void set_last_search_pat(const char_u *s, int idx, int magic, int setlast) if (spats[idx].pat == NULL) { saved_spats[idx].pat = NULL; } else { - saved_spats[idx].pat = (char_u *)xstrdup((char *)spats[idx].pat); + saved_spats[idx].pat = xstrdup(spats[idx].pat); } saved_spats_last_idx = last_idx; } @@ -494,7 +519,7 @@ void last_pat_prog(regmmatch_T *regmatch) return; } emsg_off++; // So it doesn't beep if bad expr - (void)search_regcomp((char_u *)"", 0, last_idx, SEARCH_KEEP, regmatch); + (void)search_regcomp("", NULL, 0, last_idx, SEARCH_KEEP, regmatch); emsg_off--; } @@ -521,13 +546,13 @@ void last_pat_prog(regmmatch_T *regmatch) /// @returns FAIL (zero) for failure, non-zero for success. /// the index of the first matching /// subpattern plus one; one if there was none. -int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, char_u *pat, +int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, char *pat, long count, int options, int pat_use, searchit_arg_T *extra_arg) { int found; linenr_T lnum; // no init to shut up Apollo cc regmmatch_T regmatch; - char_u *ptr; + char *ptr; colnr_T matchcol; lpos_T endpos; lpos_T matchpos; @@ -552,7 +577,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, timed_out = &extra_arg->sa_timed_out; } - if (search_regcomp(pat, RE_SEARCH, pat_use, + if (search_regcomp(pat, NULL, RE_SEARCH, pat_use, (options & (SEARCH_HIS + SEARCH_KEEP)), ®match) == FAIL) { if ((options & SEARCH_MSG) && !rc_did_emsg) { semsg(_("E383: Invalid search string: %s"), mr_pattern); @@ -571,11 +596,11 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, && pos->lnum <= buf->b_ml.ml_line_count && pos->col < MAXCOL - 2) { // Watch out for the "col" being MAXCOL - 2, used in a closed fold. - ptr = (char_u *)ml_get_buf(buf, pos->lnum, false); - if ((int)STRLEN(ptr) <= pos->col) { + ptr = ml_get_buf(buf, pos->lnum, false); + if ((int)strlen(ptr) <= pos->col) { start_char_len = 1; } else { - start_char_len = utfc_ptr2len((char *)ptr + pos->col); + start_char_len = utfc_ptr2len(ptr + pos->col); } } else { start_char_len = 1; @@ -640,9 +665,9 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, submatch = first_submatch(®match); // "lnum" may be past end of buffer for "\n\zs". if (lnum + matchpos.lnum > buf->b_ml.ml_line_count) { - ptr = (char_u *)""; + ptr = ""; } else { - ptr = (char_u *)ml_get_buf(buf, lnum + matchpos.lnum, false); + ptr = ml_get_buf(buf, lnum + matchpos.lnum, false); } // Forward search in the first line: match should be after @@ -650,6 +675,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, // match (this is vi compatible) or on the next char. if (dir == FORWARD && at_first_line) { match_ok = true; + // When the match starts in a next line it's certainly // past the start position. // When match lands on a NUL the cursor will be put @@ -674,20 +700,22 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, break; } matchcol = endpos.col; - // for empty match (matchcol == matchpos.col): advance one char + // for empty match: advance one char + if (matchcol == matchpos.col && ptr[matchcol] != NUL) { + matchcol += utfc_ptr2len(ptr + matchcol); + } } else { - // Prepare to start after first matched character. - matchcol = matchpos.col; - } - - if (matchcol == matchpos.col && ptr[matchcol] != NUL) { - matchcol += utfc_ptr2len((char *)ptr + matchcol); + // Advance "matchcol" to the next character. + // This uses rmm_matchcol, the actual start of + // the match, ignoring "\zs". + matchcol = regmatch.rmm_matchcol; + if (ptr[matchcol] != NUL) { + matchcol += utfc_ptr2len(ptr + matchcol); + } } - if (matchcol == 0 && (options & SEARCH_START)) { break; } - if (ptr[matchcol] == NUL || (nmatched = vim_regexec_multi(®match, win, buf, lnum, matchcol, tm, @@ -711,7 +739,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, } // Need to get the line pointer again, a multi-line search may // have made it invalid. - ptr = (char_u *)ml_get_buf(buf, lnum, false); + ptr = ml_get_buf(buf, lnum, false); } if (!match_ok) { continue; @@ -764,7 +792,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, // for empty match: advance one char if (matchcol == matchpos.col && ptr[matchcol] != NUL) { - matchcol += utfc_ptr2len((char *)ptr + matchcol); + matchcol += utfc_ptr2len(ptr + matchcol); } } else { // Stop when the match is in a next line. @@ -773,13 +801,12 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, } matchcol = matchpos.col; if (ptr[matchcol] != NUL) { - matchcol += utfc_ptr2len((char *)ptr + matchcol); + matchcol += utfc_ptr2len(ptr + matchcol); } } if (ptr[matchcol] == NUL - || (nmatched = - vim_regexec_multi(®match, win, buf, lnum + matchpos.lnum, matchcol, - tm, timed_out)) == 0) { + || (nmatched = vim_regexec_multi(®match, win, buf, lnum + matchpos.lnum, + matchcol, tm, timed_out)) == 0) { // If the search timed out, we did find a match // but it might be the wrong one, so that's not // OK. @@ -794,7 +821,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, } // Need to get the line pointer again, a // multi-line search may have made it invalid. - ptr = (char_u *)ml_get_buf(buf, lnum + matchpos.lnum, false); + ptr = ml_get_buf(buf, lnum + matchpos.lnum, false); } // If there is only a match after the cursor, skip @@ -822,8 +849,8 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, } else { pos->col--; if (pos->lnum <= buf->b_ml.ml_line_count) { - ptr = (char_u *)ml_get_buf(buf, pos->lnum, false); - pos->col -= utf_head_off((char *)ptr, (char *)ptr + pos->col); + ptr = ml_get_buf(buf, pos->lnum, false); + pos->col -= utf_head_off(ptr, ptr + pos->col); } } if (end_pos != NULL) { @@ -996,19 +1023,19 @@ static int first_submatch(regmmatch_T *rp) /// @param sia optional arguments or NULL /// /// @return 0 for failure, 1 for found, 2 for found and line offset added. -int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, int options, +int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, long count, int options, searchit_arg_T *sia) { pos_T pos; // position of the last match - char_u *searchstr; + char *searchstr; struct soffset old_off; int retval; // Return value - char_u *p; + char *p; long c; - char_u *dircp; + char *dircp; char *strcopy = NULL; - char_u *ps; - char_u *msgbuf = NULL; + char *ps; + char *msgbuf = NULL; size_t len; bool has_offset = false; @@ -1074,20 +1101,20 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, } } else { // make search_regcomp() use spats[RE_SEARCH].pat - searchstr = (char_u *)""; + searchstr = ""; } } if (pat != NULL && *pat != NUL) { // look for (new) offset // Find end of regular expression. // If there is a matching '/' or '?', toss it. - ps = (char_u *)strcopy; - p = (char_u *)skip_regexp((char *)pat, search_delim, p_magic, &strcopy); - if (strcopy != (char *)ps) { + ps = strcopy; + p = skip_regexp_ex(pat, search_delim, magic_isset(), &strcopy, NULL, NULL); + if (strcopy != ps) { // made a copy of "pat" to change "\?" to "?" - searchcmdlen += (int)(STRLEN(pat) - strlen(strcopy)); - pat = (char_u *)strcopy; - searchstr = (char_u *)strcopy; + searchcmdlen += (int)(strlen(pat) - strlen(strcopy)); + pat = strcopy; + searchstr = strcopy; } if (*p == search_delim) { dircp = p; // remember where we put the NUL @@ -1112,7 +1139,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, if (ascii_isdigit(*p) || *p == '+' || *p == '-') { // got an offset // 'nr' or '+nr' or '-nr' if (ascii_isdigit(*p) || ascii_isdigit(*(p + 1))) { - spats[0].off.off = atol((char *)p); + spats[0].off.off = atol(p); } else if (*p == '-') { // single '-' spats[0].off.off = -1; } else { // single '+' @@ -1132,8 +1159,8 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, if ((options & SEARCH_ECHO) && messaging() && !msg_silent && (!cmd_silent || !shortmess(SHM_SEARCHCOUNT))) { - char_u *trunc; - char_u off_buf[40]; + char *trunc; + char off_buf[40]; size_t off_len = 0; // Compute msg_row early. @@ -1143,7 +1170,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, if (!cmd_silent && (spats[0].off.line || spats[0].off.end || spats[0].off.off)) { p = off_buf; // -V507 - *p++ = (char_u)dirc; + *p++ = (char)dirc; if (spats[0].off.end) { *p++ = 'e'; } else if (!spats[0].off.line) { @@ -1154,10 +1181,10 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, } *p = NUL; if (spats[0].off.off != 0 || spats[0].off.line) { - snprintf((char *)p, sizeof(off_buf) - 1 - (size_t)(p - off_buf), + snprintf(p, sizeof(off_buf) - 1 - (size_t)(p - off_buf), "%" PRId64, spats[0].off.off); } - off_len = STRLEN(off_buf); + off_len = strlen(off_buf); } if (*searchstr == NUL) { @@ -1180,12 +1207,12 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, // Use up to 'showcmd' column. len = (size_t)((Rows - msg_row - 1) * Columns + sc_col - 1); } - if (len < STRLEN(p) + off_len + SEARCH_STAT_BUF_LEN + 3) { - len = STRLEN(p) + off_len + SEARCH_STAT_BUF_LEN + 3; + if (len < strlen(p) + off_len + SEARCH_STAT_BUF_LEN + 3) { + len = strlen(p) + off_len + SEARCH_STAT_BUF_LEN + 3; } } else { // Reserve enough space for the search pattern + offset. - len = STRLEN(p) + off_len + 3; + len = strlen(p) + off_len + 3; } xfree(msgbuf); @@ -1196,19 +1223,19 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, // do not fill the msgbuf buffer, if cmd_silent is set, leave it // empty for the search_stat feature. if (!cmd_silent) { - msgbuf[0] = (char_u)dirc; - if (utf_iscomposing(utf_ptr2char((char *)p))) { + msgbuf[0] = (char)dirc; + if (utf_iscomposing(utf_ptr2char(p))) { // Use a space to draw the composing char on. msgbuf[1] = ' '; - memmove(msgbuf + 2, p, STRLEN(p)); + memmove(msgbuf + 2, p, strlen(p)); } else { - memmove(msgbuf + 1, p, STRLEN(p)); + memmove(msgbuf + 1, p, strlen(p)); } if (off_len > 0) { - memmove(msgbuf + STRLEN(p) + 1, off_buf, off_len); + memmove(msgbuf + strlen(p) + 1, off_buf, off_len); } - trunc = (char_u *)msg_strtrunc((char *)msgbuf, true); + trunc = msg_strtrunc(msgbuf, true); if (trunc != NULL) { xfree(msgbuf); msgbuf = trunc; @@ -1219,14 +1246,14 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, // it would be blanked out again very soon. Show it on the // left, but do reverse the text. if (curwin->w_p_rl && *curwin->w_p_rlc == 's') { - char_u *r = (char_u *)reverse_text(trunc != NULL ? (char *)trunc : (char *)msgbuf); + char *r = reverse_text(trunc != NULL ? trunc : msgbuf); xfree(msgbuf); msgbuf = r; // move reversed text to beginning of buffer while (*r == ' ') { r++; } - size_t pat_len = (size_t)(msgbuf + STRLEN(msgbuf) - r); + size_t pat_len = (size_t)(msgbuf + strlen(msgbuf) - r); memmove(msgbuf, r, pat_len); // overwrite old text if ((size_t)(r - msgbuf) >= pat_len) { @@ -1235,7 +1262,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, memset(msgbuf + pat_len, ' ', (size_t)(r - msgbuf)); } } - msg_outtrans((char *)msgbuf); + msg_outtrans(msgbuf); msg_clr_eos(); msg_check(); @@ -1284,7 +1311,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, RE_LAST, sia); if (dircp != NULL) { - *dircp = (char_u)search_delim; // restore second '/' or '?' for normal_cmd() + *dircp = (char)search_delim; // restore second '/' or '?' for normal_cmd() } if (!shortmess(SHM_SEARCH) @@ -1331,9 +1358,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, break; } } - } - // to the left, check for start of file - else { + } else { // to the left, check for start of file while (c++ < 0) { if (decl(&pos) == -1) { break; @@ -1373,7 +1398,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, break; } - dirc = *++pat; + dirc = (uint8_t)(*++pat); search_delim = dirc; if (dirc != '?' && dirc != '/') { retval = 0; @@ -1393,6 +1418,7 @@ end_do_search: if ((options & SEARCH_KEEP) || (cmdmod.cmod_flags & CMOD_KEEPPATTERNS)) { spats[0].off = old_off; } + xfree(strcopy); xfree(msgbuf); return retval; @@ -1405,11 +1431,11 @@ end_do_search: // contain the position of the match found. Blank lines match only if // ADDING is set. If p_ic is set then the pattern must be in lowercase. // Return OK for success, or FAIL if no line found. -int search_for_exact_line(buf_T *buf, pos_T *pos, Direction dir, char_u *pat) +int search_for_exact_line(buf_T *buf, pos_T *pos, Direction dir, char *pat) { linenr_T start = 0; - char_u *ptr; - char_u *p; + char *ptr; + char *p; if (buf->b_ml.ml_line_count == 0) { return FAIL; @@ -1443,8 +1469,8 @@ int search_for_exact_line(buf_T *buf, pos_T *pos, Direction dir, char_u *pat) if (start == 0) { start = pos->lnum; } - ptr = (char_u *)ml_get_buf(buf, pos->lnum, false); - p = (char_u *)skipwhite((char *)ptr); + ptr = ml_get_buf(buf, pos->lnum, false); + p = skipwhite(ptr); pos->col = (colnr_T)(p - ptr); // when adding lines the matching line may be empty but it is not @@ -1456,8 +1482,8 @@ int search_for_exact_line(buf_T *buf, pos_T *pos, Direction dir, char_u *pat) } else if (*p != NUL) { // Ignore empty lines. // Expanding lines or words. assert(ins_compl_len() >= 0); - if ((p_ic ? mb_strnicmp((char *)p, (char *)pat, (size_t)ins_compl_len()) - : STRNCMP(p, pat, ins_compl_len())) == 0) { + if ((p_ic ? mb_strnicmp(p, pat, (size_t)ins_compl_len()) + : strncmp(p, pat, (size_t)ins_compl_len())) == 0) { return OK; } } @@ -1478,7 +1504,7 @@ int searchc(cmdarg_T *cap, int t_cmd) int dir = cap->arg; // true for searching forward long count = cap->count1; // repeat count int col; - char_u *p; + char *p; int len; bool stop = true; @@ -1487,13 +1513,13 @@ int searchc(cmdarg_T *cap, int t_cmd) *lastc = (char_u)c; set_csearch_direction(dir); set_csearch_until(t_cmd); - lastc_bytelen = utf_char2bytes(c, (char *)lastc_bytes); + lastc_bytelen = utf_char2bytes(c, lastc_bytes); if (cap->ncharC1 != 0) { lastc_bytelen += utf_char2bytes(cap->ncharC1, - (char *)lastc_bytes + lastc_bytelen); + lastc_bytes + lastc_bytelen); if (cap->ncharC2 != 0) { lastc_bytelen += utf_char2bytes(cap->ncharC2, - (char *)lastc_bytes + lastc_bytelen); + lastc_bytes + lastc_bytelen); } } } @@ -1524,14 +1550,14 @@ int searchc(cmdarg_T *cap, int t_cmd) cap->oap->inclusive = true; } - p = (char_u *)get_cursor_line_ptr(); + p = get_cursor_line_ptr(); col = curwin->w_cursor.col; - len = (int)STRLEN(p); + len = (int)strlen(p); while (count--) { for (;;) { if (dir > 0) { - col += utfc_ptr2len((char *)p + col); + col += utfc_ptr2len(p + col); if (col >= len) { return FAIL; } @@ -1539,13 +1565,13 @@ int searchc(cmdarg_T *cap, int t_cmd) if (col == 0) { return FAIL; } - col -= utf_head_off((char *)p, (char *)p + col - 1) + 1; + col -= utf_head_off(p, p + col - 1) + 1; } if (lastc_bytelen == 1) { if (p[col] == c && stop) { break; } - } else if (STRNCMP(p + col, lastc_bytes, lastc_bytelen) == 0 && stop) { + } else if (strncmp(p + col, lastc_bytes, (size_t)lastc_bytelen) == 0 && stop) { break; } stop = true; @@ -1560,7 +1586,7 @@ int searchc(cmdarg_T *cap, int t_cmd) col += lastc_bytelen - 1; } else { // To previous char, which may be multi-byte. - col -= utf_head_off((char *)p, (char *)p + col); + col -= utf_head_off(p, p + col); } } curwin->w_cursor.col = col; @@ -1583,16 +1609,16 @@ pos_T *findmatch(oparg_T *oap, int initc) // Update "*prevcol" to the column of the previous character, unless "prevcol" // is NULL. // Handles multibyte string correctly. -static bool check_prevcol(char_u *linep, int col, int ch, int *prevcol) +static bool check_prevcol(char *linep, int col, int ch, int *prevcol) { col--; if (col > 0) { - col -= utf_head_off((char *)linep, (char *)linep + col); + col -= utf_head_off(linep, linep + col); } if (prevcol) { *prevcol = col; } - return col >= 0 && linep[col] == ch; + return col >= 0 && (uint8_t)linep[col] == ch; } /// Raw string start is found at linep[startpos.col - 1]. @@ -1616,7 +1642,7 @@ static bool find_rawstring_end(char *linep, pos_T *startpos, pos_T *endpos) break; } if (*p == ')' - && STRNCMP(delim_copy, p + 1, delim_len) == 0 + && strncmp(delim_copy, p + 1, delim_len) == 0 && p[delim_len + 1] == '"') { found = true; break; @@ -1697,7 +1723,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) bool backwards = false; // init for gcc bool raw_string = false; // search for raw string bool inquote = false; // true when inside quotes - char_u *ptr; + char *ptr; int hash_dir = 0; // Direction searched for # things int comment_dir = 0; // Direction searched for comments int traveled = 0; // how far we've searched so far @@ -1710,7 +1736,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) pos = curwin->w_cursor; pos.coladd = 0; - char_u *linep = (char_u *)ml_get(pos.lnum); // pointer to current line + char *linep = ml_get(pos.lnum); // pointer to current line // vi compatible matching bool cpo_match = (vim_strchr(p_cpo, CPO_MATCH) != NULL); @@ -1757,17 +1783,15 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) // Only check for special things when 'cpo' doesn't have '%'. if (!cpo_match) { // Are we before or at #if, #else etc.? - ptr = (char_u *)skipwhite((char *)linep); + ptr = skipwhite(linep); if (*ptr == '#' && pos.col <= (colnr_T)(ptr - linep)) { - ptr = (char_u *)skipwhite((char *)ptr + 1); - if (STRNCMP(ptr, "if", 2) == 0 - || STRNCMP(ptr, "endif", 5) == 0 - || STRNCMP(ptr, "el", 2) == 0) { + ptr = skipwhite(ptr + 1); + if (strncmp(ptr, "if", 2) == 0 + || strncmp(ptr, "endif", 5) == 0 + || strncmp(ptr, "el", 2) == 0) { hash_dir = 1; } - } - // Are we on a comment? - else if (linep[pos.col] == '/') { + } else if (linep[pos.col] == '/') { // Are we on a comment? if (linep[pos.col + 1] == '*') { comment_dir = FORWARD; backwards = false; @@ -1798,7 +1822,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) pos.col--; } for (;;) { - initc = utf_ptr2char((char *)linep + pos.col); + initc = utf_ptr2char(linep + pos.col); if (initc == NUL) { break; } @@ -1807,11 +1831,11 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) if (findc) { break; } - pos.col += utfc_ptr2len((char *)linep + pos.col); + pos.col += utfc_ptr2len(linep + pos.col); } if (!findc) { // no brace in the line, maybe use " #if" then - if (!cpo_match && *skipwhite((char *)linep) == '#') { + if (!cpo_match && *skipwhite(linep) == '#') { hash_dir = 1; } else { return NULL; @@ -1834,10 +1858,10 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) oap->motion_type = kMTLineWise; // Linewise for this case only } if (initc != '#') { - ptr = (char_u *)skipwhite(skipwhite((char *)linep) + 1); - if (STRNCMP(ptr, "if", 2) == 0 || STRNCMP(ptr, "el", 2) == 0) { + ptr = skipwhite(skipwhite(linep) + 1); + if (strncmp(ptr, "if", 2) == 0 || strncmp(ptr, "el", 2) == 0) { hash_dir = 1; - } else if (STRNCMP(ptr, "endif", 5) == 0) { + } else if (strncmp(ptr, "endif", 5) == 0) { hash_dir = -1; } else { return NULL; @@ -1853,38 +1877,38 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) break; } pos.lnum += hash_dir; - linep = (char_u *)ml_get(pos.lnum); + linep = ml_get(pos.lnum); line_breakcheck(); // check for CTRL-C typed - ptr = (char_u *)skipwhite((char *)linep); + ptr = skipwhite(linep); if (*ptr != '#') { continue; } pos.col = (colnr_T)(ptr - linep); - ptr = (char_u *)skipwhite((char *)ptr + 1); + ptr = skipwhite(ptr + 1); if (hash_dir > 0) { - if (STRNCMP(ptr, "if", 2) == 0) { + if (strncmp(ptr, "if", 2) == 0) { count++; - } else if (STRNCMP(ptr, "el", 2) == 0) { + } else if (strncmp(ptr, "el", 2) == 0) { if (count == 0) { return &pos; } - } else if (STRNCMP(ptr, "endif", 5) == 0) { + } else if (strncmp(ptr, "endif", 5) == 0) { if (count == 0) { return &pos; } count--; } } else { - if (STRNCMP(ptr, "if", 2) == 0) { + if (strncmp(ptr, "if", 2) == 0) { if (count == 0) { return &pos; } count--; - } else if (initc == '#' && STRNCMP(ptr, "el", 2) == 0) { + } else if (initc == '#' && strncmp(ptr, "el", 2) == 0) { if (count == 0) { return &pos; } - } else if (STRNCMP(ptr, "endif", 5) == 0) { + } else if (strncmp(ptr, "endif", 5) == 0) { count++; } } @@ -1907,7 +1931,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) // backward search: Check if this line contains a single-line comment if ((backwards && comment_dir) || lisp) { - comment_col = check_linecomment((char *)linep); + comment_col = check_linecomment(linep); } if (lisp && comment_col != MAXCOL && pos.col > (colnr_T)comment_col) { lispcomm = true; // find match inside this comment @@ -1931,14 +1955,14 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) break; } - linep = (char_u *)ml_get(pos.lnum); - pos.col = (colnr_T)STRLEN(linep); // pos.col on trailing NUL + linep = ml_get(pos.lnum); + pos.col = (colnr_T)strlen(linep); // pos.col on trailing NUL do_quotes = -1; line_breakcheck(); // Check if this line contains a single-line comment if (comment_dir || lisp) { - comment_col = check_linecomment((char *)linep); + comment_col = check_linecomment(linep); } // skip comment if (lisp && comment_col != MAXCOL) { @@ -1946,7 +1970,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) } } else { pos.col--; - pos.col -= utf_head_off((char *)linep, (char *)linep + pos.col); + pos.col -= utf_head_off(linep, linep + pos.col); } } else { // forward search if (linep[pos.col] == NUL @@ -1966,15 +1990,15 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) break; } - linep = (char_u *)ml_get(pos.lnum); + linep = ml_get(pos.lnum); pos.col = 0; do_quotes = -1; line_breakcheck(); if (lisp) { // find comment pos in new line - comment_col = check_linecomment((char *)linep); + comment_col = check_linecomment(linep); } } else { - pos.col += utfc_ptr2len((char *)linep + pos.col); + pos.col += utfc_ptr2len(linep + pos.col); } } @@ -1989,7 +2013,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) if (comment_dir) { // Note: comments do not nest, and we ignore quotes in them - // TODO: ignore comment brackets inside strings + // TODO(vim): ignore comment brackets inside strings if (comment_dir == FORWARD) { if (linep[pos.col] == '*' && linep[pos.col + 1] == '/') { pos.col++; @@ -2003,18 +2027,18 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) } else if (raw_string) { if (linep[pos.col - 1] == 'R' && linep[pos.col] == '"' - && vim_strchr((char *)linep + pos.col + 1, '(') != NULL) { + && vim_strchr(linep + pos.col + 1, '(') != NULL) { // Possible start of raw string. Now that we have the // delimiter we can check if it ends before where we // started searching, or before the previously found // raw string start. - if (!find_rawstring_end((char *)linep, &pos, + if (!find_rawstring_end(linep, &pos, count > 0 ? &match_pos : &curwin->w_cursor)) { count++; match_pos = pos; match_pos.col--; } - linep = (char_u *)ml_get(pos.lnum); // may have been released + linep = ml_get(pos.lnum); // may have been released } } else if (linep[pos.col - 1] == '/' && linep[pos.col] == '*' @@ -2078,8 +2102,8 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) } } if (pos.lnum > 1) { - ptr = (char_u *)ml_get(pos.lnum - 1); - if (*ptr && *(ptr + STRLEN(ptr) - 1) == '\\') { + ptr = ml_get(pos.lnum - 1); + if (*ptr && *(ptr + strlen(ptr) - 1) == '\\') { do_quotes = 1; if (start_in_quotes == kNone) { inquote = at_start; @@ -2092,7 +2116,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) } // ml_get() only keeps one line, need to get linep again - linep = (char_u *)ml_get(pos.lnum); + linep = ml_get(pos.lnum); } } } @@ -2109,7 +2133,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) // we do not know which part to ignore. Therefore we only set // inquote if the number of quotes in a line is even, unless this // line or the previous one ends in a '\'. Complicated, isn't it? - const int c = utf_ptr2char((char *)linep + pos.col); + const int c = utf_ptr2char(linep + pos.col); switch (c) { case NUL: // at end of line without trailing backslash, reset inquote @@ -2236,7 +2260,7 @@ int check_linecomment(const char *line) } } else if (!in_str && ((p - line) < 2 || (*(p - 1) != '\\' && *(p - 2) != '#')) - && !is_pos_in_string((char_u *)line, (colnr_T)(p - line))) { + && !is_pos_in_string(line, (colnr_T)(p - line))) { break; // found! } p++; @@ -2250,7 +2274,7 @@ int check_linecomment(const char *line) // 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((char_u *)line, (colnr_T)(p - line))) { + && !is_pos_in_string(line, (colnr_T)(p - line))) { break; } p++; @@ -2280,19 +2304,19 @@ void showmatch(int c) long save_siso; int save_state; colnr_T save_dollar_vcol; - char_u *p; + char *p; // Only show match for chars in the 'matchpairs' option. // 'matchpairs' is "x:y,x:y" - for (p = (char_u *)curbuf->b_p_mps; *p != NUL; p++) { - if (utf_ptr2char((char *)p) == c && (curwin->w_p_rl ^ p_ri)) { + for (p = curbuf->b_p_mps; *p != NUL; p++) { + if (utf_ptr2char(p) == c && (curwin->w_p_rl ^ p_ri)) { break; } - p += utfc_ptr2len((char *)p) + 1; - if (utf_ptr2char((char *)p) == c && !(curwin->w_p_rl ^ p_ri)) { + p += utfc_ptr2len(p) + 1; + if (utf_ptr2char(p) == c && !(curwin->w_p_rl ^ p_ri)) { break; } - p += utfc_ptr2len((char *)p); + p += utfc_ptr2len(p); if (*p == NUL) { return; } @@ -2303,55 +2327,63 @@ void showmatch(int c) if ((lpos = findmatch(NULL, NUL)) == NULL) { // no match, so beep vim_beep(BO_MATCH); - } else if (lpos->lnum >= curwin->w_topline - && lpos->lnum < curwin->w_botline) { - if (!curwin->w_p_wrap) { - getvcol(curwin, lpos, NULL, &vcol, NULL); - } - if (curwin->w_p_wrap - || (vcol >= curwin->w_leftcol - && vcol < curwin->w_leftcol + curwin->w_width_inner)) { - mpos = *lpos; // save the pos, update_screen() may change it - save_cursor = curwin->w_cursor; - save_so = *so; - save_siso = *siso; - // Handle "$" in 'cpo': If the ')' is typed on top of the "$", - // stop displaying the "$". - if (dollar_vcol >= 0 && dollar_vcol == curwin->w_virtcol) { - dollar_vcol = -1; - } - curwin->w_virtcol++; // do display ')' just before "$" - update_screen(); // show the new char first - - save_dollar_vcol = dollar_vcol; - save_state = State; - State = MODE_SHOWMATCH; - ui_cursor_shape(); // may show different cursor shape - curwin->w_cursor = mpos; // move to matching char - *so = 0; // don't use 'scrolloff' here - *siso = 0; // don't use 'sidescrolloff' here - show_cursor_info(false); - setcursor(); - ui_flush(); - // Restore dollar_vcol(), because setcursor() may call curs_rows() - // which resets it if the matching position is in a previous line - // and has a higher column number. - dollar_vcol = save_dollar_vcol; - - // brief pause, unless 'm' is present in 'cpo' and a character is - // available. - if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL) { - os_delay((uint64_t)p_mat * 100L + 8, true); - } else if (!char_avail()) { - os_delay((uint64_t)p_mat * 100L + 9, false); - } - curwin->w_cursor = save_cursor; // restore cursor position - *so = save_so; - *siso = save_siso; - State = save_state; - ui_cursor_shape(); // may show different cursor shape - } + return; + } + + if (lpos->lnum < curwin->w_topline || lpos->lnum >= curwin->w_botline) { + return; + } + + if (!curwin->w_p_wrap) { + getvcol(curwin, lpos, NULL, &vcol, NULL); + } + + bool col_visible = curwin->w_p_wrap + || (vcol >= curwin->w_leftcol + && vcol < curwin->w_leftcol + curwin->w_width_inner); + if (!col_visible) { + return; + } + + mpos = *lpos; // save the pos, update_screen() may change it + save_cursor = curwin->w_cursor; + save_so = *so; + save_siso = *siso; + // Handle "$" in 'cpo': If the ')' is typed on top of the "$", + // stop displaying the "$". + if (dollar_vcol >= 0 && dollar_vcol == curwin->w_virtcol) { + dollar_vcol = -1; + } + curwin->w_virtcol++; // do display ')' just before "$" + update_screen(); // show the new char first + + save_dollar_vcol = dollar_vcol; + save_state = State; + State = MODE_SHOWMATCH; + ui_cursor_shape(); // may show different cursor shape + curwin->w_cursor = mpos; // move to matching char + *so = 0; // don't use 'scrolloff' here + *siso = 0; // don't use 'sidescrolloff' here + show_cursor_info(false); + setcursor(); + ui_flush(); + // Restore dollar_vcol(), because setcursor() may call curs_rows() + // which resets it if the matching position is in a previous line + // and has a higher column number. + dollar_vcol = save_dollar_vcol; + + // brief pause, unless 'm' is present in 'cpo' and a character is + // available. + if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL) { + os_delay((uint64_t)p_mat * 100L + 8, true); + } else if (!char_avail()) { + os_delay((uint64_t)p_mat * 100L + 9, false); } + curwin->w_cursor = save_cursor; // restore cursor position + *so = save_so; + *siso = save_siso; + State = save_state; + ui_cursor_shape(); // may show different cursor shape } /// Find next search match under cursor, cursor at end. @@ -2389,8 +2421,7 @@ int current_search(long count, bool forward) } // Is the pattern is zero-width?, this time, don't care about the direction - int zero_width = is_zero_width(spats[last_idx].pat, true, &curwin->w_cursor, - FORWARD); + int zero_width = is_zero_width(spats[last_idx].pat, true, &curwin->w_cursor, FORWARD); if (zero_width == -1) { return FAIL; // pattern not found } @@ -2497,7 +2528,7 @@ int current_search(long count, bool forward) /// else from position "cur". /// "direction" is FORWARD or BACKWARD. /// Returns true, false or -1 for failure. -static int is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direction) +static int is_zero_width(char *pattern, int move, pos_T *cur, Direction direction) { regmmatch_T regmatch; int nmatched = 0; @@ -2510,7 +2541,7 @@ static int is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direct pattern = spats[last_idx].pat; } - if (search_regcomp(pattern, RE_SEARCH, RE_SEARCH, + if (search_regcomp(pattern, NULL, RE_SEARCH, RE_SEARCH, SEARCH_KEEP, ®match) == FAIL) { return -1; } @@ -2556,71 +2587,73 @@ static int is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direct /// return true if line 'lnum' is empty or has white chars only. int linewhite(linenr_T lnum) { - char_u *p; + char *p; - p = (char_u *)skipwhite(ml_get(lnum)); + p = skipwhite(ml_get(lnum)); return *p == NUL; } -// Add the search count "[3/19]" to "msgbuf". -// See update_search_stat() for other arguments. +/// Add the search count "[3/19]" to "msgbuf". +/// See update_search_stat() for other arguments. static void cmdline_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, bool show_top_bot_msg, - char_u *msgbuf, bool recompute, int maxcount, long timeout) + char *msgbuf, bool recompute, int maxcount, long timeout) { searchstat_T stat; update_search_stat(dirc, pos, cursor_pos, &stat, recompute, maxcount, timeout); - if (stat.cur > 0) { - char t[SEARCH_STAT_BUF_LEN]; - - if (curwin->w_p_rl && *curwin->w_p_rlc == 's') { - if (stat.incomplete == 1) { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]"); - } else if (stat.cnt > maxcount && stat.cur > maxcount) { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]", - maxcount, maxcount); - } else if (stat.cnt > maxcount) { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/%d]", - maxcount, stat.cur); - } else { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", - stat.cnt, stat.cur); - } + if (stat.cur <= 0) { + return; + } + + char t[SEARCH_STAT_BUF_LEN]; + + if (curwin->w_p_rl && *curwin->w_p_rlc == 's') { + if (stat.incomplete == 1) { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]"); + } else if (stat.cnt > maxcount && stat.cur > maxcount) { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]", + maxcount, maxcount); + } else if (stat.cnt > maxcount) { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/%d]", + maxcount, stat.cur); } else { - if (stat.incomplete == 1) { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]"); - } else if (stat.cnt > maxcount && stat.cur > maxcount) { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]", - maxcount, maxcount); - } else if (stat.cnt > maxcount) { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>%d]", - stat.cur, maxcount); - } else { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", - stat.cur, stat.cnt); - } + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", + stat.cnt, stat.cur); } - - size_t len = strlen(t); - if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN) { - memmove(t + 2, t, len); - t[0] = 'W'; - t[1] = ' '; - len += 2; + } else { + if (stat.incomplete == 1) { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]"); + } else if (stat.cnt > maxcount && stat.cur > maxcount) { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]", + maxcount, maxcount); + } else if (stat.cnt > maxcount) { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>%d]", + stat.cur, maxcount); + } else { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", + stat.cur, stat.cnt); } + } - memmove(msgbuf + STRLEN(msgbuf) - len, t, len); - if (dirc == '?' && stat.cur == maxcount + 1) { - stat.cur = -1; - } + size_t len = strlen(t); + if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN) { + memmove(t + 2, t, len); + t[0] = 'W'; + t[1] = ' '; + len += 2; + } - // keep the message even after redraw, but don't put in history - msg_hist_off = true; - msg_ext_set_kind("search_count"); - give_warning((char *)msgbuf, false); - msg_hist_off = false; + memmove(msgbuf + strlen(msgbuf) - len, t, len); + if (dirc == '?' && stat.cur == maxcount + 1) { + stat.cur = -1; } + + // keep the message even after redraw, but don't put in history + msg_hist_off = true; + msg_ext_set_kind("search_count"); + give_warning(msgbuf, false); + msg_hist_off = false; } // Add the search count information to "stat". @@ -2642,7 +2675,7 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst static int incomplete = 0; static int last_maxcount = SEARCH_STAT_DEF_MAX_COUNT; static int chgtick = 0; - static char_u *lastpat = NULL; + static char *lastpat = NULL; static buf_T *lbuf = NULL; proftime_T start; @@ -2666,8 +2699,8 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst // XXX: above comment should be "no MB_STRCMP function" ? if (!(chgtick == buf_get_changedtick(curbuf) && lastpat != NULL // suppress clang/NULL passed as nonnull parameter - && STRNICMP(lastpat, spats[last_idx].pat, STRLEN(lastpat)) == 0 - && STRLEN(lastpat) == STRLEN(spats[last_idx].pat) + && STRNICMP(lastpat, spats[last_idx].pat, strlen(lastpat)) == 0 + && strlen(lastpat) == strlen(spats[last_idx].pat) && equalpos(lastpos, *cursor_pos) && lbuf == curbuf) || wraparound || cur < 0 || (maxcount > 0 && cur > maxcount) @@ -2717,7 +2750,7 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst } if (done_search) { xfree(lastpat); - lastpat = (char_u *)xstrdup((char *)spats[last_idx].pat); + lastpat = xstrdup(spats[last_idx].pat); chgtick = (int)buf_get_changedtick(curbuf); lbuf = curbuf; lastpos = p; @@ -2735,7 +2768,7 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst void f_searchcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { pos_T pos = curwin->w_cursor; - char_u *pattern = NULL; + char *pattern = NULL; int maxcount = SEARCH_STAT_DEF_MAX_COUNT; long timeout = SEARCH_STAT_DEF_TIMEOUT; bool recompute = true; @@ -2779,9 +2812,9 @@ void f_searchcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } } - di = tv_dict_find(dict, (const char *)"pattern", -1); + di = tv_dict_find(dict, "pattern", -1); if (di != NULL) { - pattern = (char_u *)tv_get_string_chk(&di->di_tv); + pattern = (char *)tv_get_string_chk(&di->di_tv); if (pattern == NULL) { return; } @@ -2827,7 +2860,7 @@ void f_searchcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) goto the_end; } xfree(spats[last_idx].pat); - spats[last_idx].pat = (char_u *)xstrdup((char *)pattern); + spats[last_idx].pat = xstrdup(pattern); } if (spats[last_idx].pat == NULL || *spats[last_idx].pat == NUL) { goto the_end; // the previous pattern was never defined @@ -2925,8 +2958,8 @@ typedef struct { #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, +/// are in "matches". +static int fuzzy_match_compute_score(const char *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 { @@ -2963,14 +2996,14 @@ static int fuzzy_match_compute_score(const char_u *const str, const int strSz, // Check for bonuses based on neighbor character value if (currIdx > 0) { // Camel case - const char_u *p = str; + const char *p = str; int neighbor = ' '; for (uint32_t sidx = 0; sidx < currIdx; sidx++) { - neighbor = utf_ptr2char((char *)p); + neighbor = utf_ptr2char(p); MB_PTR_ADV(p); } - const int curr = utf_ptr2char((char *)p); + const int curr = utf_ptr2char(p); if (mb_islower(neighbor) && mb_isupper(curr)) { score += CAMEL_BONUS; @@ -2990,13 +3023,12 @@ static int fuzzy_match_compute_score(const char_u *const str, const int strSz, return score; } -/// Perform a recursive search for fuzzy matching 'fuzpat' in 'str'. +/// 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) +static int fuzzy_match_recursive(const char *fuzpat, const char *str, uint32_t strIdx, + int *const outScore, const char *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 @@ -3037,7 +3069,7 @@ static int fuzzy_match_recursive(const char_u *fuzpat, const char_u *str, uint32 // Recursive call that "skips" this match uint32_t recursiveMatches[MAX_FUZZY_MATCHES]; int recursiveScore = 0; - const char_u *const next_char = str + utfc_ptr2len((char *)str); + const char *const next_char = (char *)str + utfc_ptr2len((char *)str); if (fuzzy_match_recursive(fuzpat, next_char, strIdx + 1, &recursiveScore, strBegin, strLen, matches, recursiveMatches, sizeof(recursiveMatches) / sizeof(recursiveMatches[0]), nextMatch, @@ -3091,11 +3123,11 @@ static int fuzzy_match_recursive(const char_u *fuzpat, const char_u *str, uint32 /// 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, +/// @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 *const str, const char *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 + FUNC_ATTR_NONNULL_ALL { const int len = mb_charlen(str); bool complete = false; @@ -3103,22 +3135,22 @@ bool fuzzy_match(char_u *const str, const char_u *const pat_arg, const bool matc *outScore = 0; - char_u *const save_pat = (char_u *)xstrdup((char *)pat_arg); - char_u *pat = save_pat; - char_u *p = pat; + char *const save_pat = xstrdup(pat_arg); + char *pat = save_pat; + char *p = pat; - // Try matching each word in 'pat_arg' in 'str' + // 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 = (char_u *)skipwhite((char *)p); + p = skipwhite(p); if (*p == NUL) { break; } pat = p; - while (*p != NUL && !ascii_iswhite(utf_ptr2char((char *)p))) { + while (*p != NUL && !ascii_iswhite(utf_ptr2char(p))) { MB_PTR_ADV(p); } if (*p == NUL) { // processed all the words @@ -3130,7 +3162,8 @@ bool fuzzy_match(char_u *const str, const char_u *const pat_arg, const bool matc int score = 0; int recursionCount = 0; const int matchCount - = fuzzy_match_recursive(pat, str, 0, &score, str, len, NULL, matches + numMatches, + = fuzzy_match_recursive(pat, str, 0, &score, str, len, NULL, + matches + numMatches, maxMatches - numMatches, 0, &recursionCount); if (matchCount == 0) { numMatches = 0; @@ -3166,17 +3199,17 @@ static int fuzzy_match_item_compare(const void *const s1, const void *const s2) 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 +/// 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' +/// 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 l, char_u *const str, const bool matchseq, - const char_u *const key, Callback *const item_cb, +static void fuzzy_match_in_list(list_T *const l, char *const str, const bool matchseq, + const char *const key, Callback *const item_cb, const bool retmatchpos, list_T *const fmatchlist, const long max_matches) FUNC_ATTR_NONNULL_ARG(2, 5, 7) @@ -3199,17 +3232,17 @@ static void fuzzy_match_in_list(list_T *const l, char_u *const str, const bool m break; } - char_u *itemstr = NULL; + char *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 = (char_u *)tv->vval.v_string; + 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); + itemstr = tv_dict_get_string(tv->vval.v_dict, (const char *)key, false); } else { typval_T argv[2]; @@ -3220,7 +3253,7 @@ static void fuzzy_match_in_list(list_T *const l, char_u *const str, const bool m argv[1].v_type = VAR_UNKNOWN; if (callback_call(item_cb, 1, argv, &rettv)) { if (rettv.v_type == VAR_STRING) { - itemstr = (char_u *)rettv.vval.v_string; + itemstr = rettv.vval.v_string; } } tv_dict_unref(tv->vval.v_dict); @@ -3235,13 +3268,13 @@ static void fuzzy_match_in_list(list_T *const l, char_u *const str, const bool m items[match_count].score = score; // Copy the list of matching positions in itemstr to a list, if - // 'retmatchpos' is set. + // "retmatchpos" is set. if (retmatchpos) { items[match_count].lmatchpos = tv_list_alloc(kListLenMayKnow); int j = 0; - const char_u *p = str; + const char *p = (char *)str; while (*p != NUL) { - if (!ascii_iswhite(utf_ptr2char((char *)p)) || matchseq) { + if (!ascii_iswhite(utf_ptr2char(p)) || matchseq) { tv_list_append_number(items[match_count].lmatchpos, matches[j]); j++; } @@ -3309,8 +3342,8 @@ static void fuzzy_match_in_list(list_T *const l, char_u *const str, const bool m xfree(items); } -/// Do fuzzy matching. Returns the list of matched strings in 'rettv'. -/// If 'retmatchpos' is true, also returns the matching character positions. +/// 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 @@ -3326,7 +3359,7 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv, } Callback cb = CALLBACK_NONE; - const char_u *key = NULL; + const char *key = NULL; bool matchseq = false; long max_matches = 0; if (argvars[2].v_type != VAR_UNKNOWN) { @@ -3345,7 +3378,7 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv, semsg(_(e_invarg2), tv_get_string(&di->di_tv)); return; } - key = (const char_u *)tv_get_string(&di->di_tv); + key = tv_get_string(&di->di_tv); } else if (!tv_dict_get_callback(d, "text_cb", -1, &cb)) { semsg(_(e_invargval), "text_cb"); return; @@ -3376,7 +3409,8 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv, 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, + fuzzy_match_in_list(argvars[0].vval.v_list, + (char *)tv_get_string(&argvars[1]), matchseq, key, &cb, retmatchpos, rettv->vval.v_list, max_matches); callback_free(&cb); } @@ -3393,13 +3427,116 @@ void f_matchfuzzypos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) do_fuzzymatch(argvars, rettv, true); } +/// Same as fuzzy_match_item_compare() except for use with a string match +static int fuzzy_match_str_compare(const void *const s1, const void *const s2) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE +{ + const int v1 = ((fuzmatch_str_T *)s1)->score; + const int v2 = ((fuzmatch_str_T *)s2)->score; + const int idx1 = ((fuzmatch_str_T *)s1)->idx; + const int idx2 = ((fuzmatch_str_T *)s2)->idx; + + return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1; +} + +/// Sort fuzzy matches by score +static void fuzzy_match_str_sort(fuzmatch_str_T *const fm, const int sz) + FUNC_ATTR_NONNULL_ALL +{ + // Sort the list by the descending order of the match score + qsort(fm, (size_t)sz, sizeof(fuzmatch_str_T), fuzzy_match_str_compare); +} + +/// Same as fuzzy_match_item_compare() except for use with a function name +/// string match. <SNR> functions should be sorted to the end. +static int fuzzy_match_func_compare(const void *const s1, const void *const s2) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE +{ + const int v1 = ((fuzmatch_str_T *)s1)->score; + const int v2 = ((fuzmatch_str_T *)s2)->score; + const int idx1 = ((fuzmatch_str_T *)s1)->idx; + const int idx2 = ((fuzmatch_str_T *)s2)->idx; + const char *const str1 = ((fuzmatch_str_T *)s1)->str; + const char *const str2 = ((fuzmatch_str_T *)s2)->str; + + if (*str1 != '<' && *str2 == '<') { + return -1; + } + if (*str1 == '<' && *str2 != '<') { + return 1; + } + return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1; +} + +/// Sort fuzzy matches of function names by score. +/// <SNR> functions should be sorted to the end. +static void fuzzy_match_func_sort(fuzmatch_str_T *const fm, const int sz) + FUNC_ATTR_NONNULL_ALL +{ + // Sort the list by the descending order of the match score + qsort(fm, (size_t)sz, sizeof(fuzmatch_str_T), fuzzy_match_func_compare); +} + +/// Fuzzy match "pat" in "str". +/// @returns 0 if there is no match. Otherwise, returns the match score. +int fuzzy_match_str(char *const str, const char *const pat) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (str == NULL || pat == NULL) { + return 0; + } + + int score = 0; + uint32_t matchpos[MAX_FUZZY_MATCHES]; + fuzzy_match(str, pat, true, &score, matchpos, sizeof(matchpos) / sizeof(matchpos[0])); + + return score; +} + +/// Copy a list of fuzzy matches into a string list after sorting the matches by +/// the fuzzy score. Frees the memory allocated for "fuzmatch". +void fuzzymatches_to_strmatches(fuzmatch_str_T *const fuzmatch, char ***const matches, + const int count, const bool funcsort) + FUNC_ATTR_NONNULL_ARG(2) +{ + if (count <= 0) { + return; + } + + *matches = xmalloc((size_t)count * sizeof(char *)); + + // Sort the list by the descending order of the match score + if (funcsort) { + fuzzy_match_func_sort(fuzmatch, count); + } else { + fuzzy_match_str_sort(fuzmatch, count); + } + + for (int i = 0; i < count; i++) { + (*matches)[i] = fuzmatch[i].str; + } + xfree(fuzmatch); +} + +/// Free a list of fuzzy string matches. +void fuzmatch_str_free(fuzmatch_str_T *const fuzmatch, int count) +{ + if (count <= 0 || fuzmatch == NULL) { + return; + } + while (count--) { + xfree(fuzmatch[count].str); + } + xfree(fuzmatch); +} + /// Get line "lnum" and copy it into "buf[LSIZE]". /// The copy is made because the regexp may make the line invalid when using a /// mark. -static char_u *get_line_and_copy(linenr_T lnum, char_u *buf) +static char *get_line_and_copy(linenr_T lnum, char *buf) { - char_u *line = (char_u *)ml_get(lnum); - STRLCPY(buf, line, LSIZE); + char *line = ml_get(lnum); + xstrlcpy(buf, line, LSIZE); return buf; } @@ -3415,7 +3552,7 @@ static char_u *get_line_and_copy(linenr_T lnum, char_u *buf) /// @param action What to do when we find it /// @param start_lnum first line to start searching /// @param end_lnum last line for searching -void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bool skip_comments, +void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool skip_comments, int type, long count, int action, linenr_T start_lnum, linenr_T end_lnum) { SearchedFile *files; // Stack of included files @@ -3423,19 +3560,19 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo int max_path_depth = 50; long match_count = 1; - char_u *pat; - char_u *new_fname; - char_u *curr_fname = (char_u *)curbuf->b_fname; - char_u *prev_fname = NULL; + char *pat; + char *new_fname; + char *curr_fname = curbuf->b_fname; + char *prev_fname = NULL; linenr_T lnum; int depth; int depth_displayed; // For type==CHECK_PATH int old_files; int already_searched; - char_u *file_line; - char_u *line; - char_u *p; - char_u save_char; + char *file_line; + char *line; + char *p; + char save_char; bool define_matched; regmatch_T regmatch; regmatch_T incl_regmatch; @@ -3444,8 +3581,8 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo bool did_show = false; bool found = false; int i; - char_u *already = NULL; - char_u *startp = NULL; + char *already = NULL; + char *startp = NULL; win_T *curwin_save = NULL; const int l_g_do_tagpreview = g_do_tagpreview; @@ -3459,12 +3596,13 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo // when CONT_SOL is set compare "ptr" with the beginning of the // line is faster than quote_meta/regcomp/regexec "ptr" -- Acevedo && !compl_status_sol()) { - pat = xmalloc(len + 5); + size_t patlen = len + 5; + pat = xmalloc(patlen); assert(len <= INT_MAX); - sprintf((char *)pat, whole ? "\\<%.*s\\>" : "%.*s", (int)len, ptr); + snprintf(pat, patlen, whole ? "\\<%.*s\\>" : "%.*s", (int)len, ptr); // ignore case according to p_ic, p_scs and pat regmatch.rm_ic = ignorecase(pat); - regmatch.regprog = vim_regcomp((char *)pat, p_magic ? RE_MAGIC : 0); + regmatch.regprog = vim_regcomp(pat, magic_isset() ? RE_MAGIC : 0); xfree(pat); if (regmatch.regprog == NULL) { goto fpip_end; @@ -3472,7 +3610,7 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo } char *inc_opt = (*curbuf->b_p_inc == NUL) ? p_inc : curbuf->b_p_inc; if (*inc_opt != NUL) { - incl_regmatch.regprog = vim_regcomp(inc_opt, p_magic ? RE_MAGIC : 0); + incl_regmatch.regprog = vim_regcomp(inc_opt, magic_isset() ? RE_MAGIC : 0); if (incl_regmatch.regprog == NULL) { goto fpip_end; } @@ -3481,7 +3619,7 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo if (type == FIND_DEFINE && (*curbuf->b_p_def != NUL || *p_def != NUL)) { def_regmatch.regprog = vim_regcomp(*curbuf->b_p_def == NUL ? p_def : curbuf->b_p_def, - p_magic ? RE_MAGIC : 0); + magic_isset() ? RE_MAGIC : 0); if (def_regmatch.regprog == NULL) { goto fpip_end; } @@ -3502,21 +3640,22 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo for (;;) { if (incl_regmatch.regprog != NULL - && vim_regexec(&incl_regmatch, (char *)line, (colnr_T)0)) { - char_u *p_fname = (curr_fname == (char_u *)curbuf->b_fname) - ? (char_u *)curbuf->b_ffname : curr_fname; + && vim_regexec(&incl_regmatch, line, (colnr_T)0)) { + char *p_fname = (curr_fname == curbuf->b_fname) + ? curbuf->b_ffname : curr_fname; if (inc_opt != NULL && strstr(inc_opt, "\\zs") != NULL) { // Use text from '\zs' to '\ze' (or end) of 'include'. - new_fname = (char_u *)find_file_name_in_path(incl_regmatch.startp[0], - (size_t)(incl_regmatch.endp[0] - - incl_regmatch.startp[0]), - FNAME_EXP|FNAME_INCL|FNAME_REL, - 1L, (char *)p_fname); + new_fname = find_file_name_in_path(incl_regmatch.startp[0], + (size_t)(incl_regmatch.endp[0] + - incl_regmatch.startp[0]), + FNAME_EXP|FNAME_INCL|FNAME_REL, + 1L, p_fname); } else { // Use text after match with 'include'. - new_fname = file_name_in_line((char_u *)incl_regmatch.endp[0], 0, - FNAME_EXP|FNAME_INCL|FNAME_REL, 1L, p_fname, NULL); + new_fname = file_name_in_line(incl_regmatch.endp[0], 0, + FNAME_EXP|FNAME_INCL|FNAME_REL, 1L, p_fname, + NULL); } already_searched = false; if (new_fname != NULL) { @@ -3528,14 +3667,14 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo if (i == max_path_depth) { break; } - if (path_full_compare((char *)new_fname, (char *)files[i].name, true, + if (path_full_compare(new_fname, files[i].name, true, true) & kEqualFiles) { if (type != CHECK_PATH && action == ACTION_SHOW_ALL && files[i].matched) { msg_putchar('\n'); // cursor below last one */ if (!got_int) { // don't display if 'q' typed at "--more--" // message - msg_home_replace_hl((char *)new_fname); + msg_home_replace_hl(new_fname); msg_puts(_(" (includes previously listed match)")); prev_fname = NULL; } @@ -3565,7 +3704,7 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo for (i = 0; i < depth_displayed; i++) { msg_puts(" "); } - msg_home_replace((char *)files[depth_displayed].name); + msg_home_replace(files[depth_displayed].name); msg_puts(" -->\n"); } if (!got_int) { // don't display if 'q' typed @@ -3576,27 +3715,27 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo if (new_fname != NULL) { // using "new_fname" is more reliable, e.g., when // 'includeexpr' is set. - msg_outtrans_attr((char *)new_fname, HL_ATTR(HLF_D)); + msg_outtrans_attr(new_fname, HL_ATTR(HLF_D)); } else { // Isolate the file name. // Include the surrounding "" or <> if present. if (inc_opt != NULL && strstr(inc_opt, "\\zs") != NULL) { // pattern contains \zs, use the match - p = (char_u *)incl_regmatch.startp[0]; + p = incl_regmatch.startp[0]; i = (int)(incl_regmatch.endp[0] - incl_regmatch.startp[0]); } else { // find the file name after the end of the match - for (p = (char_u *)incl_regmatch.endp[0]; - *p && !vim_isfilec(*p); p++) {} - for (i = 0; vim_isfilec(p[i]); i++) {} + for (p = incl_regmatch.endp[0]; + *p && !vim_isfilec((uint8_t)(*p)); p++) {} + for (i = 0; vim_isfilec((uint8_t)p[i]); i++) {} } if (i == 0) { // Nothing found, use the rest of the line. - p = (char_u *)incl_regmatch.endp[0]; - i = (int)STRLEN(p); + p = incl_regmatch.endp[0]; + i = (int)strlen(p); } else if (p > line) { // Avoid checking before the start of the line, can // happen if \zs appears in the regexp. @@ -3610,7 +3749,7 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo } save_char = p[i]; p[i] = NUL; - msg_outtrans_attr((char *)p, HL_ATTR(HLF_D)); + msg_outtrans_attr(p, HL_ATTR(HLF_D)); p[i] = save_char; } @@ -3645,7 +3784,7 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo xfree(files); files = bigger; } - if ((files[depth + 1].fp = os_fopen((char *)new_fname, "r")) == NULL) { + if ((files[depth + 1].fp = os_fopen(new_fname, "r")) == NULL) { xfree(new_fname); } else { if (++depth == old_files) { @@ -3659,14 +3798,13 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo files[depth].matched = false; if (action == ACTION_EXPAND) { msg_hist_off = true; // reset in msg_trunc_attr() - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Scanning included file: %s"), - (char *)new_fname); - msg_trunc_attr((char *)IObuff, true, HL_ATTR(HLF_R)); + new_fname); + msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R)); } else if (p_verbose >= 5) { verbose_enter(); - smsg(_("Searching included file %s"), - (char *)new_fname); + smsg(_("Searching included file %s"), new_fname); verbose_leave(); } } @@ -3677,12 +3815,12 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo search_line: define_matched = false; if (def_regmatch.regprog != NULL - && vim_regexec(&def_regmatch, (char *)line, (colnr_T)0)) { + && vim_regexec(&def_regmatch, line, (colnr_T)0)) { // Pattern must be first identifier after 'define', so skip // to that position before checking for match of pattern. Also // don't let it match beyond the end of this identifier. - p = (char_u *)def_regmatch.endp[0]; - while (*p && !vim_iswordc(*p)) { + p = def_regmatch.endp[0]; + while (*p && !vim_iswordc((uint8_t)(*p))) { p++; } define_matched = true; @@ -3693,27 +3831,27 @@ search_line: if (def_regmatch.regprog == NULL || define_matched) { if (define_matched || compl_status_sol()) { // compare the first "len" chars from "ptr" - startp = (char_u *)skipwhite((char *)p); + startp = skipwhite(p); if (p_ic) { - matched = !mb_strnicmp((char *)startp, (char *)ptr, len); + matched = !mb_strnicmp(startp, ptr, len); } else { - matched = !STRNCMP(startp, ptr, len); + matched = !strncmp(startp, ptr, len); } if (matched && define_matched && whole - && vim_iswordc(startp[len])) { + && vim_iswordc((uint8_t)startp[len])) { matched = false; } } else if (regmatch.regprog != NULL - && vim_regexec(®match, (char *)line, (colnr_T)(p - line))) { + && vim_regexec(®match, line, (colnr_T)(p - line))) { matched = true; - startp = (char_u *)regmatch.startp[0]; + startp = regmatch.startp[0]; // Check if the line is not a comment line (unless we are // looking for a define). A line starting with "# define" // is not considered to be a comment line. if (skip_comments) { if ((*line != '#' - || STRNCMP(skipwhite((char *)line + 1), "define", 6) != 0) - && get_leader_len((char *)line, NULL, false, true)) { + || strncmp(skipwhite(line + 1), "define", 6) != 0) + && get_leader_len(line, NULL, false, true)) { matched = false; } @@ -3721,7 +3859,7 @@ search_line: // Skips lines like "int backwards; / * normal index // * /" when looking for "normal". // Note: Doesn't skip "/ *" in comments. - p = (char_u *)skipwhite((char *)line); + p = skipwhite(line); if (matched || (p[0] == '/' && p[1] == '*') || p[0] == '*') { for (p = line; *p && p < startp; p++) { @@ -3748,13 +3886,12 @@ search_line: if (matched) { if (action == ACTION_EXPAND) { bool cont_s_ipos = false; - char_u *aux; if (depth == -1 && lnum == curwin->w_cursor.lnum) { break; } found = true; - aux = p = startp; + char *aux = p = startp; if (compl_status_adding()) { p += ins_compl_len(); if (vim_iswordp(p)) { @@ -3766,8 +3903,8 @@ search_line: i = (int)(p - aux); if (compl_status_adding() && i == ins_compl_len()) { - // IOSIZE > compl_length, so the STRNCPY works - STRNCPY(IObuff, aux, i); + // IOSIZE > compl_length, so the strncpy works + strncpy(IObuff, aux, (size_t)i); // NOLINT(runtime/printf) // Get the next line: when "depth" < 0 from the current // buffer, otherwise from the included file. Jump to @@ -3785,7 +3922,7 @@ search_line: // we read a line, set "already" to check this "line" later // if depth >= 0 we'll increase files[depth].lnum far // below -- Acevedo - already = aux = p = (char_u *)skipwhite((char *)line); + already = aux = p = skipwhite(line); p = find_word_start(p); p = find_word_end(p); if (p > aux) { @@ -3805,12 +3942,12 @@ search_line: if (p - aux >= IOSIZE - i) { p = aux + IOSIZE - i - 1; } - STRNCPY(IObuff + i, aux, p - aux); + strncpy(IObuff + i, aux, (size_t)(p - aux)); // NOLINT(runtime/printf) i += (int)(p - aux); cont_s_ipos = true; } IObuff[i] = NUL; - aux = (char_u *)IObuff; + aux = IObuff; if (i == ins_compl_len()) { goto exit_matched; @@ -3818,7 +3955,7 @@ search_line: } const int add_r = ins_compl_add_infercase(aux, i, p_ic, - curr_fname == (char_u *)curbuf->b_fname + curr_fname == curbuf->b_fname ? NULL : curr_fname, dir, cont_s_ipos); if (add_r == OK) { @@ -3838,7 +3975,7 @@ search_line: } if (!got_int) { // don't display if 'q' typed // at "--more--" message - msg_home_replace_hl((char *)curr_fname); + msg_home_replace_hl(curr_fname); } prev_fname = curr_fname; } @@ -3924,7 +4061,7 @@ exit_matched: && action == ACTION_EXPAND && !compl_status_sol() && *startp != NUL - && *(p = startp + utfc_ptr2len((char *)startp)) != NUL) { + && *(p = startp + utfc_ptr2len(startp)) != NUL) { goto search_line; } } @@ -3946,7 +4083,7 @@ exit_matched: files[old_files].name = files[depth].name; files[old_files].matched = files[depth].matched; depth--; - curr_fname = (depth == -1) ? (char_u *)curbuf->b_fname + curr_fname = (depth == -1) ? curbuf->b_fname : files[depth].name; if (depth < depth_displayed) { depth_displayed = depth; @@ -3955,7 +4092,7 @@ exit_matched: if (depth >= 0) { // we could read the line files[depth].lnum++; // Remove any CR and LF from the line. - i = (int)STRLEN(line); + i = (int)strlen(line); if (i > 0 && line[i - 1] == '\n') { line[--i] = NUL; } @@ -4011,11 +4148,11 @@ fpip_end: vim_regfree(def_regmatch.regprog); } -static void show_pat_in_path(char_u *line, int type, bool did_show, int action, FILE *fp, +static void show_pat_in_path(char *line, int type, bool did_show, int action, FILE *fp, linenr_T *lnum, long count) FUNC_ATTR_NONNULL_ARG(1, 6) { - char_u *p; + char *p; if (did_show) { msg_putchar('\n'); // cursor below last one @@ -4026,7 +4163,7 @@ static void show_pat_in_path(char_u *line, int type, bool did_show, int action, return; } for (;;) { - p = line + STRLEN(line) - 1; + p = line + strlen(line) - 1; if (fp != NULL) { // We used fgets(), so get rid of newline at end if (p >= line && *p == '\n') { @@ -4038,14 +4175,14 @@ static void show_pat_in_path(char_u *line, int type, bool did_show, int action, *(p + 1) = NUL; } if (action == ACTION_SHOW_ALL) { - snprintf((char *)IObuff, IOSIZE, "%3ld: ", count); // Show match nr. + snprintf(IObuff, IOSIZE, "%3ld: ", count); // Show match nr. msg_puts((const char *)IObuff); - snprintf((char *)IObuff, IOSIZE, "%4" PRIdLINENR, *lnum); // Show line nr. + snprintf(IObuff, IOSIZE, "%4" PRIdLINENR, *lnum); // Show line nr. // Highlight line numbers. msg_puts_attr((const char *)IObuff, HL_ATTR(HLF_N)); msg_puts(" "); } - msg_prt_line((char *)line, false); + msg_prt_line(line, false); // Definition continues until line that doesn't end with '\' if (got_int || type != FIND_DEFINE || p < line || *p != '\\') { @@ -4056,12 +4193,12 @@ static void show_pat_in_path(char_u *line, int type, bool did_show, int action, if (vim_fgets(line, LSIZE, fp)) { // end of file break; } - ++*lnum; + (*lnum)++; } else { if (++*lnum > curbuf->b_ml.ml_line_count) { break; } - line = (char_u *)ml_get(*lnum); + line = ml_get(*lnum); } msg_putchar('\n'); } diff --git a/src/nvim/search.h b/src/nvim/search.h index ff843bb59e..2f140ba840 100644 --- a/src/nvim/search.h +++ b/src/nvim/search.h @@ -6,8 +6,11 @@ #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/normal.h" #include "nvim/os/time.h" +#include "nvim/pos.h" +#include "nvim/types.h" #include "nvim/vim.h" // Values for the find_pattern_in_path() function args 'type' and 'action': @@ -70,7 +73,7 @@ typedef struct soffset { /// Structure containing last search pattern and its attributes. typedef struct spat { - char_u *pat; ///< The pattern (in allocated memory) or NULL. + char *pat; ///< The pattern (in allocated memory) or NULL. bool magic; ///< Magicness of the pattern. bool no_scs; ///< No smartcase for this pattern. Timestamp timestamp; ///< Time of the last change. @@ -96,6 +99,14 @@ typedef struct searchstat { int last_maxcount; // the max count of the last search } searchstat_T; +/// Fuzzy matched string list item. Used for fuzzy match completion. Items are +/// usually sorted by "score". The "idx" member is used for stable-sort. +typedef struct { + int idx; + char *str; + int score; +} fuzmatch_str_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "search.h.generated.h" #endif diff --git a/src/nvim/sha256.c b/src/nvim/sha256.c index 012f145875..db647f3ecb 100644 --- a/src/nvim/sha256.c +++ b/src/nvim/sha256.c @@ -13,11 +13,13 @@ /// Vim specific notes: /// sha256_self_test() is implicitly called once. -#include <stddef.h> // for size_t -#include <stdio.h> // for snprintf(). +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> -#include "nvim/sha256.h" // for context_sha256_T -#include "nvim/vim.h" // for STRCPY()/strlen(). +#include "nvim/sha256.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "sha256.c.generated.h" @@ -30,10 +32,10 @@ } #define PUT_UINT32(n, b, i) { \ - (b)[(i)] = (char_u)((n) >> 24); \ - (b)[(i) + 1] = (char_u)((n) >> 16); \ - (b)[(i) + 2] = (char_u)((n) >> 8); \ - (b)[(i) + 3] = (char_u)((n)); \ + (b)[(i)] = (uint8_t)((n) >> 24); \ + (b)[(i) + 1] = (uint8_t)((n) >> 16); \ + (b)[(i) + 2] = (uint8_t)((n) >> 8); \ + (b)[(i) + 3] = (uint8_t)((n)); \ } void sha256_start(context_sha256_T *ctx) @@ -51,7 +53,7 @@ void sha256_start(context_sha256_T *ctx) ctx->state[7] = 0x5BE0CD19; } -static void sha256_process(context_sha256_T *ctx, const char_u data[SHA256_BUFFER_SIZE]) +static void sha256_process(context_sha256_T *ctx, const uint8_t data[SHA256_BUFFER_SIZE]) { uint32_t temp1, temp2, W[SHA256_BUFFER_SIZE]; uint32_t A, B, C, D, E, F, G, H; @@ -178,7 +180,7 @@ static void sha256_process(context_sha256_T *ctx, const char_u data[SHA256_BUFFE ctx->state[7] += H; } -void sha256_update(context_sha256_T *ctx, const char_u *input, size_t length) +void sha256_update(context_sha256_T *ctx, const uint8_t *input, size_t length) { if (length == 0) { return; @@ -196,7 +198,7 @@ void sha256_update(context_sha256_T *ctx, const char_u *input, size_t length) size_t fill = SHA256_BUFFER_SIZE - left; if (left && (length >= fill)) { - memcpy((void *)(ctx->buffer + left), (void *)input, fill); + memcpy(ctx->buffer + left, input, fill); sha256_process(ctx, ctx->buffer); length -= fill; input += fill; @@ -210,22 +212,22 @@ void sha256_update(context_sha256_T *ctx, const char_u *input, size_t length) } if (length) { - memcpy((void *)(ctx->buffer + left), (void *)input, length); + memcpy(ctx->buffer + left, input, length); } } -static char_u sha256_padding[SHA256_BUFFER_SIZE] = { +static uint8_t sha256_padding[SHA256_BUFFER_SIZE] = { 0x80, 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, 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 }; -void sha256_finish(context_sha256_T *ctx, char_u digest[SHA256_SUM_SIZE]) +void sha256_finish(context_sha256_T *ctx, uint8_t digest[SHA256_SUM_SIZE]) { uint32_t last, padn; uint32_t high, low; - char_u msglen[8]; + uint8_t msglen[8]; high = (ctx->total[0] >> 29) | (ctx->total[1] << 3); low = (ctx->total[0] << 3); @@ -263,7 +265,7 @@ void sha256_finish(context_sha256_T *ctx, char_u digest[SHA256_SUM_SIZE]) const char *sha256_bytes(const uint8_t *restrict buf, size_t buf_len, const uint8_t *restrict salt, size_t salt_len) { - char_u sha256sum[SHA256_SUM_SIZE]; + uint8_t sha256sum[SHA256_SUM_SIZE]; static char hexit[SHA256_BUFFER_SIZE + 1]; // buf size + NULL context_sha256_T ctx; @@ -307,8 +309,8 @@ bool sha256_self_test(void) { char output[SHA256_BUFFER_SIZE + 1]; // buf size + NULL context_sha256_T ctx; - char_u buf[1000]; - char_u sha256sum[SHA256_SUM_SIZE]; + uint8_t buf[1000]; + uint8_t sha256sum[SHA256_SUM_SIZE]; const char *hexit; static bool sha256_self_tested = false; @@ -339,7 +341,7 @@ bool sha256_self_test(void) } } - if (memcmp(output, sha_self_test_vector[i], SHA256_BUFFER_SIZE)) { + if (memcmp(output, sha_self_test_vector[i], SHA256_BUFFER_SIZE) != 0) { failures = true; output[sizeof(output) - 1] = '\0'; diff --git a/src/nvim/sha256.h b/src/nvim/sha256.h index b52d300de6..eeb4031509 100644 --- a/src/nvim/sha256.h +++ b/src/nvim/sha256.h @@ -2,9 +2,9 @@ #define NVIM_SHA256_H #include <stddef.h> -#include <stdint.h> // for uint32_t +#include <stdint.h> -#include "nvim/types.h" // for char_u +#include "nvim/types.h" #define SHA256_BUFFER_SIZE 64 #define SHA256_SUM_SIZE 32 @@ -12,7 +12,7 @@ typedef struct { uint32_t total[2]; uint32_t state[8]; - char_u buffer[SHA256_BUFFER_SIZE]; + uint8_t buffer[SHA256_BUFFER_SIZE]; } context_sha256_T; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 7580cc8897..90a01aaf97 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -2,44 +2,53 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> -#include <errno.h> #include <inttypes.h> -#include <msgpack.h> +#include <msgpack/object.h> +#include <msgpack/pack.h> +#include <msgpack/sbuffer.h> +#include <msgpack/unpack.h> #include <stdbool.h> #include <stddef.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/stat.h> #include <uv.h> +#include "auto/config.h" #include "klib/khash.h" -#include "klib/kvec.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/buffer.h" -#include "nvim/buffer_defs.h" #include "nvim/cmdhist.h" +#include "nvim/eval.h" #include "nvim/eval/decode.h" #include "nvim/eval/encode.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/fileio.h" #include "nvim/garray.h" +#include "nvim/gettext.h" #include "nvim/globals.h" +#include "nvim/hashtab.h" #include "nvim/macros.h" #include "nvim/mark.h" +#include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/msgpack_rpc/helpers.h" +#include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/os/fileio.h" +#include "nvim/os/fs_defs.h" #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/path.h" #include "nvim/pos.h" -#include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/search.h" #include "nvim/shada.h" @@ -166,7 +175,7 @@ typedef enum { /// Possible results of shada_write function. typedef enum { - kSDWriteSuccessfull, ///< Writing was successful. + kSDWriteSuccessful, ///< Writing was successful. kSDWriteReadNotShada, ///< Writing was successful, but when reading it ///< attempted to read file that did not look like ///< a ShaDa file. @@ -288,8 +297,6 @@ typedef struct { } data; } ShadaEntry; -struct hm_llist_entry; - /// One entry in sized linked list typedef struct hm_llist_entry { ShadaEntry data; ///< Entry data. @@ -454,10 +461,7 @@ static const ShadaEntry sd_default_values[] = { .additional_data = NULL), DEF_SDE(Variable, global_var, .name = NULL, - .value = { - .v_type = VAR_UNKNOWN, - .vval = { .v_string = NULL } - }, + .value = { .v_type = VAR_UNKNOWN, .vval = { .v_string = NULL } }, .additional_elements = NULL), DEF_SDE(GlobalMark, filemark, .name = '"', @@ -1127,34 +1131,35 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) const bool get_old_files = (flags & (kShaDaGetOldfiles | kShaDaForceit) && (force || tv_list_len(oldfiles_list) == 0)); const bool want_marks = flags & kShaDaWantMarks; - const unsigned srni_flags = (unsigned)( - (flags & kShaDaWantInfo - ? (kSDReadUndisableableData - | kSDReadRegisters - | kSDReadGlobalMarks - | (p_hi ? kSDReadHistory : 0) - | (find_shada_parameter('!') != NULL - ? kSDReadVariables - : 0) - | (find_shada_parameter('%') != NULL - && ARGCOUNT == 0 - ? kSDReadBufferList - : 0)) - : 0) - | (want_marks && get_shada_parameter('\'') > 0 - ? kSDReadLocalMarks | kSDReadChanges - : 0) - | (get_old_files - ? kSDReadLocalMarks - : 0)); + const unsigned srni_flags = + (unsigned)( + (flags & kShaDaWantInfo + ? (kSDReadUndisableableData + | kSDReadRegisters + | kSDReadGlobalMarks + | (p_hi ? kSDReadHistory : 0) + | (find_shada_parameter('!') != NULL + ? kSDReadVariables + : 0) + | (find_shada_parameter('%') != NULL + && ARGCOUNT == 0 + ? kSDReadBufferList + : 0)) + : 0) + | (want_marks && get_shada_parameter('\'') > 0 + ? kSDReadLocalMarks | kSDReadChanges + : 0) + | (get_old_files + ? kSDReadLocalMarks + : 0)); if (srni_flags == 0) { // Nothing to do. return; } HistoryMergerState hms[HIST_COUNT]; if (srni_flags & kSDReadHistory) { - for (uint8_t i = 0; i < HIST_COUNT; i++) { - hms_init(&hms[i], i, (size_t)p_hi, true, true); + for (HistoryType i = 0; i < HIST_COUNT; i++) { + hms_init(&hms[i], (uint8_t)i, (size_t)p_hi, true, true); } } ShadaEntry cur_entry; @@ -1191,17 +1196,18 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) case kSDItemSearchPattern: if (!force) { SearchPattern pat; - (cur_entry.data.search_pattern.is_substitute_pattern - ? &get_substitute_pattern - : &get_search_pattern)(&pat); + if (cur_entry.data.search_pattern.is_substitute_pattern) { + get_substitute_pattern(&pat); + } else { + get_search_pattern(&pat); + } if (pat.pat != NULL && pat.timestamp >= cur_entry.timestamp) { shada_free_shada_entry(&cur_entry); break; } } - (cur_entry.data.search_pattern.is_substitute_pattern - ? &set_substitute_pattern - : &set_search_pattern)((SearchPattern) { + + SearchPattern spat = (SearchPattern) { .magic = cur_entry.data.search_pattern.magic, .no_scs = !cur_entry.data.search_pattern.smartcase, .off = { @@ -1210,10 +1216,17 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) .end = cur_entry.data.search_pattern.place_cursor_at_end, .off = cur_entry.data.search_pattern.offset, }, - .pat = (char_u *)cur_entry.data.search_pattern.pat, + .pat = cur_entry.data.search_pattern.pat, .additional_data = cur_entry.data.search_pattern.additional_data, .timestamp = cur_entry.timestamp, - }); + }; + + if (cur_entry.data.search_pattern.is_substitute_pattern) { + set_substitute_pattern(spat); + } else { + set_search_pattern(spat); + } + if (cur_entry.data.search_pattern.is_last_used) { set_last_used_pattern(cur_entry.data.search_pattern.is_substitute_pattern); set_no_hlsearch(!cur_entry.data.search_pattern.highlighted); @@ -1238,7 +1251,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)regtilde(cur_entry.data.sub_string.sub, p_magic, false); + (void)regtilde(cur_entry.data.sub_string.sub, magic_isset(), false); // Do not free shada entry: its allocated memory was saved above. break; case kSDItemHistoryEntry: @@ -1327,7 +1340,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) case kSDItemBufferList: for (size_t i = 0; i < cur_entry.data.buffer_list.size; i++) { char *const sfname = - (char *)path_try_shorten_fname((char_u *)cur_entry.data.buffer_list.buffers[i].fname); + path_try_shorten_fname(cur_entry.data.buffer_list.buffers[i].fname); buf_T *const buf = buflist_new(cur_entry.data.buffer_list.buffers[i].fname, sfname, 0, BLN_LISTED); if (buf != NULL) { @@ -1410,12 +1423,12 @@ shada_read_main_cycle_end: // memory for the history string itself and separator character which // may be assigned right away. if (srni_flags & kSDReadHistory) { - for (uint8_t i = 0; i < HIST_COUNT; i++) { + for (HistoryType i = 0; i < HIST_COUNT; i++) { hms_insert_whole_neovim_history(&hms[i]); clr_history(i); int *new_hisidx; int *new_hisnum; - histentry_T *hist = hist_get_array(i, &new_hisidx, &new_hisnum); + histentry_T *hist = hist_get_array((uint8_t)i, &new_hisidx, &new_hisnum); if (hist != NULL) { hms_to_he_array(&hms[i], hist, new_hisidx, new_hisnum); } @@ -1469,8 +1482,8 @@ static char *shada_filename(const char *file) if (p_shadafile != NULL && *p_shadafile != NUL) { file = p_shadafile; } else { - if ((file = (char *)find_shada_parameter('n')) == NULL || *file == NUL) { - file = shada_get_default_file(); + if ((file = find_shada_parameter('n')) == NULL || *file == NUL) { + file = shada_get_default_file(); } // XXX It used to be one level lower, so that whatever is in // `p_shadafile` was expanded. I intentionally moved it here @@ -1505,7 +1518,7 @@ static char *shada_filename(const char *file) /// @param[in] max_kbyte Maximum size of an item in KiB. Zero means no /// restrictions. /// -/// @return kSDWriteSuccessfull, kSDWriteFailed or kSDWriteIgnError. +/// @return kSDWriteSuccessful, kSDWriteFailed or kSDWriteIgnError. static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntry entry, const size_t max_kbyte) FUNC_ATTR_NONNULL_ALL @@ -1551,6 +1564,18 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr (sd_default_values[(entry).type].data.attr == (entry).data.attr) #define ONE_IF_NOT_DEFAULT(entry, attr) \ ((size_t)(!CHECK_DEFAULT(entry, attr))) + +#define PACK_BOOL(entry, name, attr) \ + do { \ + if (!CHECK_DEFAULT(entry, search_pattern.attr)) { \ + PACK_STATIC_STR(name); \ + if (sd_default_values[(entry).type].data.search_pattern.attr) { \ + msgpack_pack_false(spacker); \ + } else { \ + msgpack_pack_true(spacker); \ + } \ + } \ + } while (0) switch (entry.type) { case kSDItemMissing: abort(); @@ -1634,17 +1659,6 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr msgpack_pack_map(spacker, map_size); PACK_STATIC_STR(SEARCH_KEY_PAT); PACK_BIN(cstr_as_string(entry.data.search_pattern.pat)); -#define PACK_BOOL(entry, name, attr) \ - do { \ - if (!CHECK_DEFAULT(entry, search_pattern.attr)) { \ - PACK_STATIC_STR(name); \ - if (sd_default_values[(entry).type].data.search_pattern.attr) { \ - msgpack_pack_false(spacker); \ - } else { \ - msgpack_pack_true(spacker); \ - } \ - } \ - } while (0) PACK_BOOL(entry, SEARCH_KEY_MAGIC, magic); PACK_BOOL(entry, SEARCH_KEY_IS_LAST_USED, is_last_used); PACK_BOOL(entry, SEARCH_KEY_SMARTCASE, smartcase); @@ -1688,8 +1702,8 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr msgpack_pack_long(spacker, entry.data.filemark.mark.col); } assert(entry.type == kSDItemJump || entry.type == kSDItemChange - ? CHECK_DEFAULT(entry, filemark.name) - : true); + ? CHECK_DEFAULT(entry, filemark.name) + : true); if (!CHECK_DEFAULT(entry, filemark.name)) { PACK_STATIC_STR(KEY_NAME_CHAR); msgpack_pack_uint8(spacker, (uint8_t)entry.data.filemark.name); @@ -1699,15 +1713,14 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr break; } case kSDItemRegister: { - const size_t map_size = (size_t)( - 2 // Register contents and name + const size_t map_size = (size_t)(2 // Register contents and name + ONE_IF_NOT_DEFAULT(entry, reg.type) + ONE_IF_NOT_DEFAULT(entry, reg.width) + ONE_IF_NOT_DEFAULT(entry, reg.is_unnamed) // Additional entries, if any: + (size_t)(entry.data.reg.additional_data == NULL - ? 0 - : entry.data.reg.additional_data->dv_hashtab.ht_used)); + ? 0 + : entry.data.reg.additional_data->dv_hashtab.ht_used)); msgpack_pack_map(spacker, map_size); PACK_STATIC_STR(REG_KEY_CONTENTS); msgpack_pack_array(spacker, entry.data.reg.contents_size); @@ -1813,7 +1826,7 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr } msgpack_packer_free(spacker); msgpack_sbuffer_destroy(&sbuf); - return kSDWriteSuccessfull; + return kSDWriteSuccessful; shada_pack_entry_error: msgpack_packer_free(spacker); msgpack_sbuffer_destroy(&sbuf); @@ -1833,7 +1846,7 @@ static inline ShaDaWriteResult shada_pack_pfreed_entry(msgpack_packer *const pac const size_t max_kbyte) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE { - ShaDaWriteResult ret = kSDWriteSuccessfull; + ShaDaWriteResult ret = kSDWriteSuccessful; ret = shada_pack_entry(packer, entry.data, max_kbyte); if (entry.can_free_entry) { shada_free_shada_entry(&entry.data); @@ -1952,6 +1965,28 @@ static const char *shada_format_entry(const ShadaEntry entry) ret[0] = 0; vim_snprintf(S_LEN(ret), "%s", "[ ] ts=%" PRIu64 " "); // ^ Space for `can_free_entry` +#define FORMAT_MARK_ENTRY(entry_name, name_fmt, name_fmt_arg) \ + do { \ + typval_T ad_tv = { \ + .v_type = VAR_DICT, \ + .vval.v_dict = entry.data.filemark.additional_data \ + }; \ + size_t ad_len; \ + char *const ad = encode_tv2string(&ad_tv, &ad_len); \ + vim_snprintf_add(S_LEN(ret), \ + entry_name " {" name_fmt " file=[%zu]\"%.512s\", " \ + "pos={l=%" PRIdLINENR ",c=%" PRIdCOLNR ",a=%" PRIdCOLNR "}, " \ + "ad={%p:[%zu]%.64s} }", \ + name_fmt_arg, \ + strlen(entry.data.filemark.fname), \ + entry.data.filemark.fname, \ + entry.data.filemark.mark.lnum, \ + entry.data.filemark.mark.col, \ + entry.data.filemark.mark.coladd, \ + (void *)entry.data.filemark.additional_data, \ + ad_len, \ + ad); \ + } while (0) switch (entry.type) { case kSDItemMissing: vim_snprintf_add(S_LEN(ret), "Missing"); @@ -1980,28 +2015,6 @@ static const char *shada_format_entry(const ShadaEntry entry) case kSDItemVariable: vim_snprintf_add(S_LEN(ret), "Variable { TODO }"); break; -#define FORMAT_MARK_ENTRY(entry_name, name_fmt, name_fmt_arg) \ - do { \ - typval_T ad_tv = { \ - .v_type = VAR_DICT, \ - .vval.v_dict = entry.data.filemark.additional_data \ - }; \ - size_t ad_len; \ - char *const ad = encode_tv2string(&ad_tv, &ad_len); \ - vim_snprintf_add(S_LEN(ret), \ - entry_name " {" name_fmt " file=[%zu]\"%.512s\", " \ - "pos={l=%" PRIdLINENR ",c=%" PRIdCOLNR ",a=%" PRIdCOLNR "}, " \ - "ad={%p:[%zu]%.64s} }", \ - name_fmt_arg, \ - strlen(entry.data.filemark.fname), \ - entry.data.filemark.fname, \ - entry.data.filemark.mark.lnum, \ - entry.data.filemark.mark.col, \ - entry.data.filemark.mark.coladd, \ - (void *)entry.data.filemark.additional_data, \ - ad_len, \ - ad); \ - } while (0) case kSDItemGlobalMark: FORMAT_MARK_ENTRY("GlobalMark", " name='%c',", entry.data.filemark.name); break; @@ -2047,9 +2060,35 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re msgpack_packer *const packer) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - ShaDaWriteResult ret = kSDWriteSuccessfull; + ShaDaWriteResult ret = kSDWriteSuccessful; ShadaEntry entry; ShaDaReadResult srni_ret; + +#define COMPARE_WITH_ENTRY(wms_entry_, entry) \ + do { \ + PossiblyFreedShadaEntry *const wms_entry = (wms_entry_); \ + if (wms_entry->data.type != kSDItemMissing) { \ + if (wms_entry->data.timestamp >= (entry).timestamp) { \ + shada_free_shada_entry(&(entry)); \ + break; \ + } \ + if (wms_entry->can_free_entry) { \ + shada_free_shada_entry(&wms_entry->data); \ + } \ + } \ + *wms_entry = pfs_entry; \ + } while (0) + +#define FREE_POSSIBLY_FREED_SHADA_ENTRY(entry) \ + do { \ + if ((entry).can_free_entry) { \ + shada_free_shada_entry(&(entry).data); \ + } \ + } while (0) + +#define SDE_TO_PFSDE(entry) \ + ((PossiblyFreedShadaEntry) { .can_free_entry = true, .data = (entry) }) + while ((srni_ret = shada_read_next_item(sd_reader, &entry, srni_flags, max_kbyte)) != kSDReadStatusFinished) { @@ -2067,20 +2106,6 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re case kSDReadStatusMalformed: continue; } -#define COMPARE_WITH_ENTRY(wms_entry_, entry) \ - do { \ - PossiblyFreedShadaEntry *const wms_entry = (wms_entry_); \ - if (wms_entry->data.type != kSDItemMissing) { \ - if (wms_entry->data.timestamp >= (entry).timestamp) { \ - shada_free_shada_entry(&(entry)); \ - break; \ - } \ - if (wms_entry->can_free_entry) { \ - shada_free_shada_entry(&wms_entry->data); \ - } \ - } \ - *wms_entry = pfs_entry; \ - } while (0) const PossiblyFreedShadaEntry pfs_entry = { .can_free_entry = true, .data = entry, @@ -2097,8 +2122,8 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re break; case kSDItemSearchPattern: COMPARE_WITH_ENTRY((entry.data.search_pattern.is_substitute_pattern - ? &wms->sub_search_pattern - : &wms->search_pattern), entry); + ? &wms->sub_search_pattern + : &wms->search_pattern), entry); break; case kSDItemSubString: COMPARE_WITH_ENTRY(&wms->replacement, entry); @@ -2220,14 +2245,6 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re *wms_entry = pfs_entry; } } else { -#define FREE_POSSIBLY_FREED_SHADA_ENTRY(entry) \ - do { \ - if ((entry).can_free_entry) { \ - shada_free_shada_entry(&(entry).data); \ - } \ - } while (0) -#define SDE_TO_PFSDE(entry) \ - ((PossiblyFreedShadaEntry) { .can_free_entry = true, .data = (entry) }) #define AFTERFREE_DUMMY(entry) #define DUMMY_IDX_ADJ(i) MERGE_JUMPS(filemarks->changes_size, filemarks->changes, @@ -2361,7 +2378,7 @@ static inline void add_search_pattern(PossiblyFreedShadaEntry *const ret_pse, .is_substitute_pattern = is_substitute_pattern, .highlighted = ((is_substitute_pattern ^ search_last_used) && search_highlighted), - .pat = (char *)pat.pat, + .pat = pat.pat, .additional_data = pat.additional_data, .search_backward = (!is_substitute_pattern && pat.off.dir == '?'), } @@ -2482,7 +2499,7 @@ static int hist_type2char(const int type) static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef *const sd_reader) FUNC_ATTR_NONNULL_ARG(1) { - ShaDaWriteResult ret = kSDWriteSuccessfull; + ShaDaWriteResult ret = kSDWriteSuccessful; int max_kbyte_i = get_shada_parameter('s'); if (max_kbyte_i < 0) { max_kbyte_i = 10; @@ -2506,7 +2523,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef bool dump_history = false; // Initialize history merger - for (uint8_t i = 0; i < HIST_COUNT; i++) { + for (HistoryType i = 0; i < HIST_COUNT; i++) { long num_saved = get_shada_parameter(hist_type2char(i)); if (num_saved == -1) { num_saved = p_hi; @@ -2514,7 +2531,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef if (num_saved > 0) { dump_history = true; dump_one_history[i] = true; - hms_init(&wms->hms[i], i, (size_t)num_saved, sd_reader != NULL, false); + hms_init(&wms->hms[i], (uint8_t)i, (size_t)num_saved, sd_reader != NULL, false); } else { dump_one_history[i] = false; } @@ -2642,7 +2659,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef } tv_clear(&vartv); tv_clear(&tgttv); - if (spe_ret == kSDWriteSuccessfull) { + if (spe_ret == kSDWriteSuccessful) { int kh_ret; (void)kh_put(strset, &wms->dumped_variables, name, &kh_ret); } @@ -2650,8 +2667,6 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef } // Initialize jump list - setpcmark(); - cleanup_jumplist(curwin, false); wms->jumps_size = shada_init_jumps(wms->jumps, &removable_bufs); const bool search_highlighted = !(no_hlsearch @@ -2807,7 +2822,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef if (sd_reader != NULL) { const ShaDaWriteResult srww_ret = shada_read_when_writing(sd_reader, srni_flags, max_kbyte, wms, packer); - if (srww_ret != kSDWriteSuccessfull) { + if (srww_ret != kSDWriteSuccessful) { ret = srww_ret; } } @@ -3007,10 +3022,9 @@ shada_write_file_open: {} assert(sd_reader.close != NULL); sd_reader.close(&sd_reader); return FAIL; - } else { - (*wp)++; - goto shada_write_file_open; } + (*wp)++; + goto shada_write_file_open; } else { semsg(_(SERR "System error while opening temporary ShaDa file %s " "for writing: %s"), tempname, os_strerror(error)); @@ -3068,33 +3082,38 @@ shada_write_file_nomerge: {} if (!nomerge) { sd_reader.close(&sd_reader); bool did_remove = false; - if (sw_ret == kSDWriteSuccessfull) { -#ifdef UNIX - // For Unix we check the owner of the file. It's not very nice to - // overwrite a user’s viminfo file after a "su root", with a - // viminfo file that the user can't read. + if (sw_ret == kSDWriteSuccessful) { FileInfo old_info; - if (os_fileinfo(fname, &old_info)) { - if (getuid() == ROOT_UID) { - if (old_info.stat.st_uid != ROOT_UID - || old_info.stat.st_gid != getgid()) { - const uv_uid_t old_uid = (uv_uid_t)old_info.stat.st_uid; - const uv_gid_t old_gid = (uv_gid_t)old_info.stat.st_gid; - const int fchown_ret = os_fchown(file_fd(sd_writer.cookie), - old_uid, old_gid); - if (fchown_ret != 0) { - semsg(_(RNERR "Failed setting uid and gid for file %s: %s"), - tempname, os_strerror(fchown_ret)); - goto shada_write_file_did_not_remove; - } + if (!os_fileinfo(fname, &old_info) + || S_ISDIR(old_info.stat.st_mode) +#ifdef UNIX + // For Unix we check the owner of the file. It's not very nice + // to overwrite a user's viminfo file after a "su root", with a + // viminfo file that the user can't read. + || (getuid() != ROOT_UID + && !(old_info.stat.st_uid == getuid() + ? (old_info.stat.st_mode & 0200) + : (old_info.stat.st_gid == getgid() + ? (old_info.stat.st_mode & 0020) + : (old_info.stat.st_mode & 0002)))) +#endif + ) { + semsg(_("E137: ShaDa file is not writable: %s"), fname); + goto shada_write_file_did_not_remove; + } +#ifdef UNIX + if (getuid() == ROOT_UID) { + if (old_info.stat.st_uid != ROOT_UID + || old_info.stat.st_gid != getgid()) { + const uv_uid_t old_uid = (uv_uid_t)old_info.stat.st_uid; + const uv_gid_t old_gid = (uv_gid_t)old_info.stat.st_gid; + const int fchown_ret = os_fchown(file_fd(sd_writer.cookie), + old_uid, old_gid); + if (fchown_ret != 0) { + semsg(_(RNERR "Failed setting uid and gid for file %s: %s"), + tempname, os_strerror(fchown_ret)); + goto shada_write_file_did_not_remove; } - } else if (!(old_info.stat.st_uid == getuid() - ? (old_info.stat.st_mode & 0200) - : (old_info.stat.st_gid == getgid() - ? (old_info.stat.st_mode & 0020) - : (old_info.stat.st_mode & 0002)))) { - semsg(_("E137: ShaDa file is not writable: %s"), fname); - goto shada_write_file_did_not_remove; } } #endif @@ -3115,9 +3134,7 @@ shada_write_file_nomerge: {} } } if (!did_remove) { -#ifdef UNIX shada_write_file_did_not_remove: -#endif semsg(_(RNERR "Do not forget to remove %s or rename it manually to %s."), tempname, fname); } @@ -3247,13 +3264,12 @@ static ShaDaReadResult fread_len(ShaDaReadDef *const sd_reader, char *const buff semsg(_(SERR "System error while reading ShaDa file: %s"), sd_reader->error); return kSDReadStatusReadError; - } else { - semsg(_(RCERR "Error while reading ShaDa file: " - "last entry specified that it occupies %" PRIu64 " bytes, " - "but file ended earlier"), - (uint64_t)length); - return kSDReadStatusNotShaDa; } + semsg(_(RCERR "Error while reading ShaDa file: " + "last entry specified that it occupies %" PRIu64 " bytes, " + "but file ended earlier"), + (uint64_t)length); + return kSDReadStatusNotShaDa; } return kSDReadStatusSuccess; } @@ -3336,7 +3352,7 @@ static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader, const error_desc #define CHECK_KEY(key, \ expected) ((key).via.str.size == (sizeof(expected) - 1) \ - && STRNCMP((key).via.str.ptr, expected, (sizeof(expected) - 1)) == 0) + && strncmp((key).via.str.ptr, expected, (sizeof(expected) - 1)) == 0) #define CLEAR_GA_AND_ERROR_OUT(ga) \ do { \ ga_clear(&(ga)); \ @@ -3566,16 +3582,15 @@ shada_read_next_item_start: entry->type = kSDItemMissing; } return spm_ret; - } else { - entry->data.unknown_item.contents = xmalloc(length); - const ShaDaReadResult fl_ret = - fread_len(sd_reader, entry->data.unknown_item.contents, length); - if (fl_ret != kSDReadStatusSuccess) { - shada_free_shada_entry(entry); - entry->type = kSDItemMissing; - } - return fl_ret; } + entry->data.unknown_item.contents = xmalloc(length); + const ShaDaReadResult fl_ret = + fread_len(sd_reader, entry->data.unknown_item.contents, length); + if (fl_ret != kSDReadStatusSuccess) { + shada_free_shada_entry(entry); + entry->type = kSDItemMissing; + } + return fl_ret; } msgpack_unpacked unpacked; @@ -3999,7 +4014,7 @@ static bool shada_removable(const char *name) for (p = p_shada; *p;) { (void)copy_option_part(&p, part, ARRAY_SIZE(part), ", "); if (part[0] == 'r') { - home_replace(NULL, part + 1, (char *)NameBuff, MAXPATHL, true); + home_replace(NULL, part + 1, NameBuff, MAXPATHL, true); size_t n = strlen(NameBuff); if (mb_strnicmp(NameBuff, new_name, n) == 0) { retval = true; @@ -4021,13 +4036,11 @@ static bool shada_removable(const char *name) static inline size_t shada_init_jumps(PossiblyFreedShadaEntry *jumps, khash_t(bufset) *const removable_bufs) { - if (!curwin->w_jumplistlen) { - return 0; - } - + // Initialize jump list size_t jumps_size = 0; const void *jump_iter = NULL; - + setpcmark(); + cleanup_jumplist(curwin, false); do { xfmark_T fm; jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm); @@ -4100,7 +4113,6 @@ void shada_encode_jumps(msgpack_sbuffer *const sbuf) khash_t(bufset) removable_bufs = KHASH_EMPTY_TABLE(bufset); find_removable_bufs(&removable_bufs); PossiblyFreedShadaEntry jumps[JUMPLISTSIZE]; - cleanup_jumplist(curwin, true); size_t jumps_size = shada_init_jumps(jumps, &removable_bufs); msgpack_packer packer; msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write); diff --git a/src/nvim/sign.c b/src/nvim/sign.c index 9c517098b9..d0c093d93a 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -5,20 +5,42 @@ // sign.c: functions for managing with signs // +#include <inttypes.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + #include "nvim/ascii.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval/funcs.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/fold.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/hashtab.h" +#include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/memline_defs.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/move.h" #include "nvim/option.h" +#include "nvim/pos.h" #include "nvim/sign.h" -#include "nvim/syntax.h" +#include "nvim/sign_defs.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -77,7 +99,7 @@ static signgroup_T *sign_group_ref(const char *groupname) hashitem_T *hi; signgroup_T *group; - hash = hash_hash((char_u *)groupname); + hash = hash_hash(groupname); hi = hash_lookup(&sg_table, (char *)groupname, strlen(groupname), hash); if (HASHITEM_EMPTY(hi)) { // new group @@ -86,7 +108,7 @@ static signgroup_T *sign_group_ref(const char *groupname) STRCPY(group->sg_name, groupname); group->sg_refcount = 1; group->sg_next_sign_id = 1; - hash_add_item(&sg_table, hi, (char_u *)group->sg_name, hash); + hash_add_item(&sg_table, hi, group->sg_name, hash); } else { // existing group group = HI2SG(hi); @@ -100,17 +122,17 @@ static signgroup_T *sign_group_ref(const char *groupname) /// removed, then remove the group. static void sign_group_unref(char *groupname) { - signgroup_T *group; - hashitem_T *hi = hash_find(&sg_table, groupname); - if (!HASHITEM_EMPTY(hi)) { - group = HI2SG(hi); - group->sg_refcount--; - if (group->sg_refcount == 0) { - // All the signs in this group are removed - hash_remove(&sg_table, hi); - xfree(group); - } + if (HASHITEM_EMPTY(hi)) { + return; + } + + signgroup_T *group = HI2SG(hi); + group->sg_refcount--; + if (group->sg_refcount == 0) { + // All the signs in this group are removed + hash_remove(&sg_table, hi); + xfree(group); } } @@ -1193,27 +1215,27 @@ static void sign_define_cmd(char *sign_name, char *cmdline) break; } p = skiptowhite_esc(arg); - if (STRNCMP(arg, "icon=", 5) == 0) { + if (strncmp(arg, "icon=", 5) == 0) { arg += 5; XFREE_CLEAR(icon); icon = xstrnsave(arg, (size_t)(p - arg)); - } else if (STRNCMP(arg, "text=", 5) == 0) { + } else if (strncmp(arg, "text=", 5) == 0) { arg += 5; XFREE_CLEAR(text); text = xstrnsave(arg, (size_t)(p - arg)); - } else if (STRNCMP(arg, "linehl=", 7) == 0) { + } else if (strncmp(arg, "linehl=", 7) == 0) { arg += 7; XFREE_CLEAR(linehl); linehl = xstrnsave(arg, (size_t)(p - arg)); - } else if (STRNCMP(arg, "texthl=", 7) == 0) { + } else if (strncmp(arg, "texthl=", 7) == 0) { arg += 7; XFREE_CLEAR(texthl); texthl = xstrnsave(arg, (size_t)(p - arg)); - } else if (STRNCMP(arg, "culhl=", 6) == 0) { + } else if (strncmp(arg, "culhl=", 6) == 0) { arg += 6; XFREE_CLEAR(culhl); culhl = xstrnsave(arg, (size_t)(p - arg)); - } else if (STRNCMP(arg, "numhl=", 6) == 0) { + } else if (strncmp(arg, "numhl=", 6) == 0) { arg += 6; XFREE_CLEAR(numhl); numhl = xstrnsave(arg, (size_t)(p - arg)); @@ -1271,7 +1293,7 @@ static void sign_place_cmd(buf_T *buf, linenr_T lnum, char *sign_name, int id, c } /// ":sign unplace" command -static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, char *sign_name, int id, char *group) +static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, const char *sign_name, int id, char *group) { if (lnum >= 0 || sign_name != NULL || (group != NULL && *group == '\0')) { emsg(_(e_invarg)); @@ -1328,7 +1350,7 @@ static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, char *sign_name, int id, /// :sign jump {id} buffer={nr} /// :sign jump {id} group={group} file={fname} /// :sign jump {id} group={group} buffer={nr} -static void sign_jump_cmd(buf_T *buf, linenr_T lnum, char *sign_name, int id, char *group) +static void sign_jump_cmd(buf_T *buf, linenr_T lnum, const char *sign_name, int id, char *group) { if (sign_name == NULL && group == NULL && id == -1) { emsg(_(e_argreq)); @@ -1371,19 +1393,19 @@ static int parse_sign_cmd_args(int cmd, char *arg, char **sign_name, int *signid } while (*arg != NUL) { - if (STRNCMP(arg, "line=", 5) == 0) { + if (strncmp(arg, "line=", 5) == 0) { arg += 5; *lnum = atoi(arg); arg = skiptowhite(arg); lnum_arg = true; - } else if (STRNCMP(arg, "*", 1) == 0 && cmd == SIGNCMD_UNPLACE) { + } else if (strncmp(arg, "*", 1) == 0 && cmd == SIGNCMD_UNPLACE) { if (*signid != -1) { emsg(_(e_invarg)); return FAIL; } *signid = -2; arg = skiptowhite(arg + 1); - } else if (STRNCMP(arg, "name=", 5) == 0) { + } else if (strncmp(arg, "name=", 5) == 0) { arg += 5; name = arg; arg = skiptowhite(arg); @@ -1394,23 +1416,23 @@ static int parse_sign_cmd_args(int cmd, char *arg, char **sign_name, int *signid name++; } *sign_name = name; - } else if (STRNCMP(arg, "group=", 6) == 0) { + } else if (strncmp(arg, "group=", 6) == 0) { arg += 6; *group = arg; arg = skiptowhite(arg); if (*arg != NUL) { *arg++ = NUL; } - } else if (STRNCMP(arg, "priority=", 9) == 0) { + } else if (strncmp(arg, "priority=", 9) == 0) { arg += 9; *prio = atoi(arg); arg = skiptowhite(arg); - } else if (STRNCMP(arg, "file=", 5) == 0) { + } else if (strncmp(arg, "file=", 5) == 0) { arg += 5; filename = arg; *buf = buflist_findname_exp(arg); break; - } else if (STRNCMP(arg, "buffer=", 7) == 0) { + } else if (strncmp(arg, "buffer=", 7) == 0) { arg += 7; filename = arg; *buf = buflist_findnr(getdigits_int(&arg, true, 0)); @@ -1878,23 +1900,23 @@ void set_context_in_sign_cmd(expand_T *xp, char *arg) xp->xp_pattern = p + 1; switch (cmd_idx) { case SIGNCMD_DEFINE: - if (STRNCMP(last, "texthl", 6) == 0 - || STRNCMP(last, "linehl", 6) == 0 - || STRNCMP(last, "culhl", 5) == 0 - || STRNCMP(last, "numhl", 5) == 0) { + 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) { + } else if (strncmp(last, "icon", 4) == 0) { xp->xp_context = EXPAND_FILES; } else { xp->xp_context = EXPAND_NOTHING; } break; case SIGNCMD_PLACE: - if (STRNCMP(last, "name", 4) == 0) { + if (strncmp(last, "name", 4) == 0) { expand_what = EXP_SIGN_NAMES; - } else if (STRNCMP(last, "group", 5) == 0) { + } else if (strncmp(last, "group", 5) == 0) { expand_what = EXP_SIGN_GROUPS; - } else if (STRNCMP(last, "file", 4) == 0) { + } else if (strncmp(last, "file", 4) == 0) { xp->xp_context = EXPAND_BUFFERS; } else { xp->xp_context = EXPAND_NOTHING; @@ -1902,9 +1924,9 @@ void set_context_in_sign_cmd(expand_T *xp, char *arg) break; case SIGNCMD_UNPLACE: case SIGNCMD_JUMP: - if (STRNCMP(last, "group", 5) == 0) { + if (strncmp(last, "group", 5) == 0) { expand_what = EXP_SIGN_GROUPS; - } else if (STRNCMP(last, "file", 4) == 0) { + } else if (strncmp(last, "file", 4) == 0) { xp->xp_context = EXPAND_BUFFERS; } else { xp->xp_context = EXPAND_NOTHING; diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 7d2b58ff46..8e18be5bd1 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -50,94 +50,94 @@ // Use SPELL_PRINTTREE for debugging: dump the word tree after adding a word. // Only use it for small word lists! -// Use SPELL_COMPRESS_ALLWAYS for debugging: compress the word tree after +// Use SPELL_COMPRESS_ALWAYS for debugging: compress the word tree after // adding a word. Only use it for small word lists! // Use DEBUG_TRIEWALK to print the changes made in suggest_trie_walk() for a // specific word. -#include <assert.h> // for assert -#include <inttypes.h> // for uint32_t, uint16_t, uint8_t -#include <limits.h> // for INT_MAX -#include <stdbool.h> // for false, true, bool -#include <stddef.h> // for NULL, size_t, ptrdiff_t -#include <stdio.h> // for snprintf -#include <string.h> // for memmove, strstr, memcpy, memset - -#include "nvim/ascii.h" // for NUL, ascii_isdigit, ascii_iswhite -#include "nvim/autocmd.h" // for apply_autocmds -#include "nvim/buffer.h" // for bufref_valid, set_bufref, buf_is_empty -#include "nvim/buffer_defs.h" // for win_T, synblock_T, buf_T, w_p_... -#include "nvim/change.h" // for changed_bytes -#include "nvim/charset.h" // for skipwhite, getwhitecols, skipbin -#include "nvim/cursor.h" // for get_cursor_line_ptr +#include <assert.h> +#include <inttypes.h> +#include <limits.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> + +#include "nvim/ascii.h" +#include "nvim/autocmd.h" +#include "nvim/buffer.h" +#include "nvim/change.h" +#include "nvim/charset.h" +#include "nvim/cursor.h" #include "nvim/decoration.h" -#include "nvim/drawscreen.h" // for NOT_VALID, redraw_later -#include "nvim/eval/typval.h" // for semsg -#include "nvim/ex_cmds.h" // for do_sub_msg -#include "nvim/ex_cmds_defs.h" // for exarg_T -#include "nvim/ex_docmd.h" // for do_cmdline_cmd -#include "nvim/garray.h" // for garray_T, GA_EMPTY, GA_APPEND_... -#include "nvim/gettext.h" // for _, N_ -#include "nvim/hashtab.h" // for hash_clear_all, hash_init, has... -#include "nvim/highlight_defs.h" // for HLF_COUNT, hlf_T, HLF_SPB, HLF... -#include "nvim/insexpand.h" // for ins_compl_add_infercase, ins_c... -#include "nvim/log.h" // for ELOG -#include "nvim/macros.h" // for MB_PTR_ADV, MB_PTR_BACK, ASCII... -#include "nvim/mark.h" // for clearpos -#include "nvim/mbyte.h" // for utf_ptr2char, utf_char2bytes -#include "nvim/memline.h" // for ml_append, ml_get_buf, ml_close -#include "nvim/memline_defs.h" // for memline_T -#include "nvim/memory.h" // for xfree, xmalloc, xcalloc, xstrdup -#include "nvim/message.h" // for emsg, msg_puts, give_warning -#include "nvim/option.h" // for copy_option_part, set_option_v... -#include "nvim/option_defs.h" // for p_ws, OPT_LOCAL, p_enc, SHM_SE... -#include "nvim/os/fs.h" // for os_remove -#include "nvim/os/input.h" // for line_breakcheck -#include "nvim/os/os_defs.h" // for MAXPATHL -#include "nvim/path.h" // for path_full_compare, path_tail... -#include "nvim/pos.h" // for pos_T, colnr_T, linenr_T -#include "nvim/regexp.h" // for vim_regfree, vim_regexec, vim_... -#include "nvim/regexp_defs.h" // for regmatch_T, regprog_T -#include "nvim/runtime.h" // for DIP_ALL, do_in_runtimepath -#include "nvim/search.h" // for SEARCH_KEEP, for do_search -#include "nvim/spell.h" // for FUNC_ATTR_NONNULL_ALL, FUNC_AT... -#include "nvim/spell_defs.h" // for slang_T, langp_T, MAXWLEN, sal... -#include "nvim/spellfile.h" // for spell_load_file -#include "nvim/spellsuggest.h" // for spell_suggest_list -#include "nvim/strings.h" // for vim_strchr, vim_snprintf, conc... -#include "nvim/syntax.h" // for syn_get_id, syntax_present -#include "nvim/types.h" // for char_u -#include "nvim/undo.h" // for u_save_cursor -#include "nvim/vim.h" // for curwin, strlen, STRLCPY, STRNCMP +#include "nvim/decoration_provider.h" +#include "nvim/drawscreen.h" +#include "nvim/ex_cmds.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/ex_docmd.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/hashtab.h" +#include "nvim/highlight_defs.h" +#include "nvim/insexpand.h" +#include "nvim/log.h" +#include "nvim/macros.h" +#include "nvim/mark.h" +#include "nvim/mbyte.h" +#include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option.h" +#include "nvim/os/fs.h" +#include "nvim/os/input.h" +#include "nvim/os/os_defs.h" +#include "nvim/path.h" +#include "nvim/pos.h" +#include "nvim/regexp.h" +#include "nvim/runtime.h" +#include "nvim/search.h" +#include "nvim/spell.h" +#include "nvim/spell_defs.h" +#include "nvim/spellfile.h" +#include "nvim/spellsuggest.h" +#include "nvim/strings.h" +#include "nvim/syntax.h" +#include "nvim/types.h" +#include "nvim/undo.h" +#include "nvim/vim.h" +#include "nvim/window.h" // Result values. Lower number is accepted over higher one. -#define SP_BANNED (-1) -#define SP_RARE 0 -#define SP_OK 1 -#define SP_LOCAL 2 -#define SP_BAD 3 +enum { + SP_BANNED = -1, + SP_RARE = 0, + SP_OK = 1, + SP_LOCAL = 2, + SP_BAD = 3, +}; // First language that is loaded, start of the linked list of loaded // languages. slang_T *first_lang = NULL; // file used for "zG" and "zW" -char_u *int_wordlist = NULL; +char *int_wordlist = NULL; // Structure to store info for word matching. typedef struct matchinf_S { langp_T *mi_lp; // info for language and region // pointers to original text to be checked - char_u *mi_word; // start of word being checked - char_u *mi_end; // end of matching word so far - char_u *mi_fend; // next char to be added to mi_fword - char_u *mi_cend; // char after what was used for + char *mi_word; // start of word being checked + char *mi_end; // end of matching word so far + char *mi_fend; // next char to be added to mi_fword + char *mi_cend; // char after what was used for // mi_capflags // case-folded text - char_u mi_fword[MAXWLEN + 1]; // mi_word case-folded + char mi_fword[MAXWLEN + 1]; // mi_word case-folded int mi_fwordlen; // nr of valid bytes in mi_fword // for when checking word after a prefix @@ -160,20 +160,20 @@ typedef struct matchinf_S { win_T *mi_win; // buffer being checked // for NOBREAK - int mi_result2; // "mi_resul" without following word - char_u *mi_end2; // "mi_end" without following word + int mi_result2; // "mi_result" without following word + char *mi_end2; // "mi_end" without following word } matchinf_T; // Structure used for the cookie argument of do_in_runtimepath(). typedef struct spelload_S { - char_u sl_lang[MAXWLEN + 1]; // language name + char sl_lang[MAXWLEN + 1]; // language name slang_T *sl_slang; // resulting slang_T struct int sl_nobreak; // NOBREAK language found } spelload_T; #define SY_MAXLEN 30 typedef struct syl_item_S { - char_u sy_chars[SY_MAXLEN]; // the sequence of chars + char sy_chars[SY_MAXLEN]; // the sequence of chars int sy_len; } syl_item_T; @@ -214,7 +214,7 @@ char *repl_to = NULL; /// /// @return the length of the word in bytes, also when it's OK, so that the /// caller can skip over the word. -size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docount) +size_t spell_check(win_T *wp, char *ptr, hlf_T *attrp, int *capcol, bool docount) { matchinf_T mi; // Most things are put in "mi" so that it can // be passed to functions quickly. @@ -226,7 +226,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou // A word never starts at a space or a control character. Return quickly // then, skipping over the character. - if (*ptr <= ' ') { + if ((uint8_t)(*ptr) <= ' ') { return 1; } @@ -242,11 +242,11 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou // julifeest". if (*ptr >= '0' && *ptr <= '9') { if (*ptr == '0' && (ptr[1] == 'b' || ptr[1] == 'B')) { - mi.mi_end = (char_u *)skipbin((char *)ptr + 2); + mi.mi_end = (char *)skipbin(ptr + 2); } else if (*ptr == '0' && (ptr[1] == 'x' || ptr[1] == 'X')) { - mi.mi_end = (char_u *)skiphex((char *)ptr + 2); + mi.mi_end = skiphex(ptr + 2); } else { - mi.mi_end = (char_u *)skipdigits((char *)ptr); + mi.mi_end = skipdigits(ptr); } nrlen = (size_t)(mi.mi_end - ptr); } @@ -258,7 +258,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou bool this_upper = false; // init for gcc if (use_camel_case) { - int c = utf_ptr2char((char *)mi.mi_fend); + int c = utf_ptr2char(mi.mi_fend); this_upper = SPELL_ISUPPER(c); } @@ -266,7 +266,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou MB_PTR_ADV(mi.mi_fend); if (use_camel_case) { const bool prev_upper = this_upper; - int c = utf_ptr2char((char *)mi.mi_fend); + int c = utf_ptr2char(mi.mi_fend); this_upper = SPELL_ISUPPER(c); camel_case = !prev_upper && this_upper; } @@ -275,7 +275,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou if (capcol != NULL && *capcol == 0 && wp->w_s->b_cap_prog != NULL) { // Check word starting with capital letter. - int c = utf_ptr2char((char *)ptr); + int c = utf_ptr2char(ptr); if (!SPELL_ISUPPER(c)) { wrongcaplen = (size_t)(mi.mi_fend - ptr); } @@ -302,7 +302,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou (void)spell_casefold(wp, ptr, (int)(mi.mi_fend - ptr), mi.mi_fword, MAXWLEN + 1); - mi.mi_fwordlen = (int)STRLEN(mi.mi_fword); + mi.mi_fwordlen = (int)strlen(mi.mi_fword); if (camel_case && mi.mi_fwordlen > 0) { // introduce a fake word end space into the folded word. @@ -366,21 +366,21 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou // Check for end of sentence. regmatch.regprog = wp->w_s->b_cap_prog; regmatch.rm_ic = false; - int r = vim_regexec(®match, (char *)ptr, 0); + int r = vim_regexec(®match, ptr, 0); wp->w_s->b_cap_prog = regmatch.regprog; if (r) { - *capcol = (int)(regmatch.endp[0] - (char *)ptr); + *capcol = (int)(regmatch.endp[0] - ptr); } } - return (size_t)(utfc_ptr2len((char *)ptr)); + return (size_t)(utfc_ptr2len(ptr)); } else if (mi.mi_end == ptr) { // Always include at least one character. Required for when there // is a mixup in "midword". MB_PTR_ADV(mi.mi_end); } else if (mi.mi_result == SP_BAD && LANGP_ENTRY(wp->w_s->b_langp, 0)->lp_slang->sl_nobreak) { - char_u *p, *fp; + char *p, *fp; int save_result = mi.mi_result; // First language in 'spelllang' is NOBREAK. Find first position @@ -435,7 +435,7 @@ static void find_word(matchinf_T *mip, int mode) { int wlen = 0; int flen; - char_u *ptr; + char *ptr; slang_T *slang = mip->mi_lp->lp_slang; char_u *byts; idx_T *idxs; @@ -444,7 +444,7 @@ static void find_word(matchinf_T *mip, int mode) // Check for word with matching case in keep-case tree. ptr = mip->mi_word; flen = 9999; // no case folding, always enough bytes - byts = slang->sl_kbyts; + byts = (char_u *)slang->sl_kbyts; idxs = slang->sl_kidxs; if (mode == FIND_KEEPCOMPOUND) { @@ -455,7 +455,7 @@ static void find_word(matchinf_T *mip, int mode) // Check for case-folded in case-folded tree. ptr = mip->mi_fword; flen = mip->mi_fwordlen; // available case-folded bytes - byts = slang->sl_fbyts; + byts = (char_u *)slang->sl_fbyts; idxs = slang->sl_fidxs; if (mode == FIND_PREFIX) { @@ -518,7 +518,7 @@ static void find_word(matchinf_T *mip, int mode) } // Perform a binary search in the list of accepted bytes. - c = ptr[wlen]; + c = (uint8_t)ptr[wlen]; if (c == TAB) { // <Tab> is handled like <Space> c = ' '; } @@ -562,7 +562,7 @@ static void find_word(matchinf_T *mip, int mode) } } - char_u *p; + char *p; bool word_ends; // Verify that one of the possible endings is valid. Try the longest @@ -572,7 +572,7 @@ static void find_word(matchinf_T *mip, int mode) arridx = endidx[endidxcnt]; wlen = endlen[endidxcnt]; - if (utf_head_off((char *)ptr, (char *)ptr + wlen) > 0) { + if (utf_head_off(ptr, ptr + wlen) > 0) { continue; // not at first byte of character } if (spell_iswordp(ptr + wlen, mip->mi_win)) { @@ -592,8 +592,8 @@ static void find_word(matchinf_T *mip, int mode) // when folding case. This can be slow, take a shortcut when the // case-folded word is equal to the keep-case word. p = mip->mi_word; - if (STRNCMP(ptr, p, wlen) != 0) { - for (char_u *s = ptr; s < ptr + wlen; MB_PTR_ADV(s)) { + if (strncmp(ptr, p, (size_t)wlen) != 0) { + for (char *s = ptr; s < ptr + wlen; MB_PTR_ADV(s)) { MB_PTR_ADV(p); } wlen = (int)(p - mip->mi_word); @@ -687,7 +687,8 @@ static void find_word(matchinf_T *mip, int mode) } // Quickly check if compounding is possible with this flag. - if (!byte_in_str(mip->mi_complen == 0 ? slang->sl_compstartflags : slang->sl_compallflags, + if (!byte_in_str(mip->mi_complen == + 0 ? slang->sl_compstartflags : slang->sl_compallflags, (int)((unsigned)flags >> 24))) { continue; } @@ -703,10 +704,10 @@ static void find_word(matchinf_T *mip, int mode) // Need to check the caps type of the appended compound // word. - if (STRNCMP(ptr, mip->mi_word, mip->mi_compoff) != 0) { + if (strncmp(ptr, mip->mi_word, (size_t)mip->mi_compoff) != 0) { // case folding may have changed the length p = mip->mi_word; - for (char_u *s = ptr; s < ptr + mip->mi_compoff; MB_PTR_ADV(s)) { + for (char *s = ptr; s < ptr + mip->mi_compoff; MB_PTR_ADV(s)) { MB_PTR_ADV(p); } } else { @@ -739,14 +740,14 @@ static void find_word(matchinf_T *mip, int mode) mip->mi_compflags[mip->mi_complen] = (char_u)((unsigned)flags >> 24); mip->mi_compflags[mip->mi_complen + 1] = NUL; if (word_ends) { - char_u fword[MAXWLEN] = { 0 }; + char fword[MAXWLEN] = { 0 }; if (slang->sl_compsylmax < MAXWLEN) { // "fword" is only needed for checking syllables. if (ptr == mip->mi_word) { (void)spell_casefold(mip->mi_win, ptr, wlen, fword, MAXWLEN); } else { - STRLCPY(fword, ptr, endlen[endidxcnt] + 1); + xstrlcpy(fword, ptr, (size_t)endlen[endidxcnt] + 1); } } if (!can_compound(slang, fword, mip->mi_compflags)) { @@ -767,7 +768,7 @@ static void find_word(matchinf_T *mip, int mode) if (!word_ends) { int save_result = mip->mi_result; - char_u *save_end = mip->mi_end; + char *save_end = mip->mi_end; langp_T *save_lp = mip->mi_lp; // Check that a valid word follows. If there is one and we @@ -787,8 +788,8 @@ static void find_word(matchinf_T *mip, int mode) // folding case. This can be slow, take a shortcut when // the case-folded word is equal to the keep-case word. p = mip->mi_fword; - if (STRNCMP(ptr, p, wlen) != 0) { - for (char_u *s = ptr; s < ptr + wlen; MB_PTR_ADV(s)) { + if (strncmp(ptr, p, (size_t)wlen) != 0) { + for (char *s = ptr; s < ptr + wlen; MB_PTR_ADV(s)) { MB_PTR_ADV(p); } mip->mi_compoff = (int)(p - mip->mi_fword); @@ -908,16 +909,16 @@ static void find_word(matchinf_T *mip, int mode) /// end of ptr[wlen] and the second part matches after it. /// /// @param gap &sl_comppat -bool match_checkcompoundpattern(char_u *ptr, int wlen, garray_T *gap) +bool match_checkcompoundpattern(char *ptr, int wlen, garray_T *gap) { for (int i = 0; i + 1 < gap->ga_len; i += 2) { - char_u *p = ((char_u **)gap->ga_data)[i + 1]; - if (STRNCMP(ptr + wlen, p, STRLEN(p)) == 0) { + char *p = ((char **)gap->ga_data)[i + 1]; + if (strncmp(ptr + wlen, p, strlen(p)) == 0) { // Second part matches at start of following compound word, now // check if first part matches at end of previous word. - p = ((char_u **)gap->ga_data)[i]; - int len = (int)STRLEN(p); - if (len <= wlen && STRNCMP(ptr + wlen - len, p, len) == 0) { + p = ((char **)gap->ga_data)[i]; + int len = (int)strlen(p); + if (len <= wlen && strncmp(ptr + wlen - len, p, (size_t)len) == 0) { return true; } } @@ -925,20 +926,20 @@ bool match_checkcompoundpattern(char_u *ptr, int wlen, garray_T *gap) return false; } -// Returns true if "flags" is a valid sequence of compound flags and "word" -// does not have too many syllables. -bool can_compound(slang_T *slang, const char_u *word, const char_u *flags) +/// @return true if "flags" is a valid sequence of compound flags and "word" +/// does not have too many syllables. +bool can_compound(slang_T *slang, const char *word, const uint8_t *flags) FUNC_ATTR_NONNULL_ALL { - char_u uflags[MAXWLEN * 2] = { 0 }; + char uflags[MAXWLEN * 2] = { 0 }; if (slang->sl_compprog == NULL) { return false; } // Need to convert the single byte flags to utf8 characters. - char_u *p = uflags; + char *p = uflags; for (int i = 0; flags[i] != NUL; i++) { - p += utf_char2bytes(flags[i], (char *)p); + p += utf_char2bytes(flags[i], p); } *p = NUL; p = uflags; @@ -951,7 +952,7 @@ bool can_compound(slang_T *slang, const char_u *word, const char_u *flags) // COMPOUNDWORDMAX then compounding is not allowed. if (slang->sl_compsylmax < MAXWLEN && count_syllables(slang, word) > slang->sl_compsylmax) { - return (int)STRLEN(flags) < slang->sl_compmax; + return (int)strlen((char *)flags) < slang->sl_compmax; } return true; } @@ -960,10 +961,10 @@ bool can_compound(slang_T *slang, const char_u *word, const char_u *flags) // compound rule. This is used to stop trying a compound if the flags // collected so far can't possibly match any compound rule. // Caller must check that slang->sl_comprules is not NULL. -bool match_compoundrule(slang_T *slang, char_u *compflags) +bool match_compoundrule(slang_T *slang, const char_u *compflags) { // loop over all the COMPOUNDRULE entries - for (char_u *p = slang->sl_comprules; *p != NUL; p++) { + for (char_u *p = (char_u *)slang->sl_comprules; *p != NUL; p++) { // loop over the flags in the compound word we have made, match // them against the current rule entry for (int i = 0;; i++) { @@ -1013,7 +1014,7 @@ bool match_compoundrule(slang_T *slang, char_u *compflags) /// @param totprefcnt nr of prefix IDs /// @param arridx idx in sl_pidxs[] /// @param cond_req only use prefixes with a condition -int valid_word_prefix(int totprefcnt, int arridx, int flags, char_u *word, slang_T *slang, +int valid_word_prefix(int totprefcnt, int arridx, int flags, char *word, slang_T *slang, bool cond_req) { int prefid = (int)((unsigned)flags >> 24); @@ -1061,13 +1062,13 @@ static void find_prefix(matchinf_T *mip, int mode) int wlen = 0; slang_T *slang = mip->mi_lp->lp_slang; - char_u *byts = slang->sl_pbyts; + char_u *byts = (char_u *)slang->sl_pbyts; if (byts == NULL) { return; // array is empty } // We use the case-folded word here, since prefixes are always // case-folded. - char_u *ptr = mip->mi_fword; + char_u *ptr = (char_u *)mip->mi_fword; int flen = mip->mi_fwordlen; // available case-folded bytes if (mode == FIND_COMPOUND) { // Skip over the previously found word(s). @@ -1157,7 +1158,7 @@ static void find_prefix(matchinf_T *mip, int mode) // Return the length of the folded chars in bytes. static int fold_more(matchinf_T *mip) { - char_u *p = mip->mi_fend; + char *p = mip->mi_fend; do { MB_PTR_ADV(mip->mi_fend); } while (*mip->mi_fend != NUL && spell_iswordp(mip->mi_fend, mip->mi_win)); @@ -1170,7 +1171,7 @@ static int fold_more(matchinf_T *mip) (void)spell_casefold(mip->mi_win, p, (int)(mip->mi_fend - p), mip->mi_fword + mip->mi_fwordlen, MAXWLEN - mip->mi_fwordlen); - int flen = (int)STRLEN(mip->mi_fword + mip->mi_fwordlen); + int flen = (int)strlen(mip->mi_fword + mip->mi_fwordlen); mip->mi_fwordlen += flen; return flen; } @@ -1214,7 +1215,14 @@ static bool decor_spell_nav_col(win_T *wp, linenr_T lnum, linenr_T *decor_lnum, *decor_lnum = lnum; } decor_redraw_col(wp->w_buffer, col, col, false, &decor_state); - return decor_state.spell; + return decor_state.spell == kTrue; +} + +static inline bool can_syn_spell(win_T *wp, linenr_T lnum, int col) +{ + bool can_spell; + (void)syn_get_id(wp, lnum, col, false, &can_spell, false); + return can_spell; } /// Moves to the next spell error. @@ -1236,7 +1244,7 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att size_t len; int has_syntax = syntax_present(wp); colnr_T col; - char_u *buf = NULL; + char *buf = NULL; size_t buflen = 0; int skip = 0; colnr_T capcol = -1; @@ -1275,9 +1283,9 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att decor_spell_nav_start(wp); while (!got_int) { - char_u *line = (char_u *)ml_get_buf(wp->w_buffer, lnum, false); + char *line = ml_get_buf(wp->w_buffer, lnum, false); - len = STRLEN(line); + len = strlen(line); if (buflen < len + MAXWLEN + 2) { xfree(buf); buflen = len + MAXWLEN + 2; @@ -1292,17 +1300,17 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att // For checking first word with a capital skip white space. if (capcol == 0) { - capcol = (colnr_T)getwhitecols((char *)line); + capcol = (colnr_T)getwhitecols(line); } else if (curline && wp == curwin) { // For spellbadword(): check if first word needs a capital. - col = (colnr_T)getwhitecols((char *)line); + col = (colnr_T)getwhitecols(line); if (check_need_cap(lnum, col)) { capcol = col; } // Need to get the line again, may have looked at the previous // one. - line = (char_u *)ml_get_buf(wp->w_buffer, lnum, false); + line = ml_get_buf(wp->w_buffer, lnum, false); } // Copy the line into "buf" and append the start of the next line if @@ -1311,12 +1319,12 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att bool empty_line = *skipwhite((const char *)line) == NUL; STRCPY(buf, line); if (lnum < wp->w_buffer->b_ml.ml_line_count) { - spell_cat_line(buf + STRLEN(buf), - (char_u *)ml_get_buf(wp->w_buffer, lnum + 1, false), + spell_cat_line(buf + strlen(buf), + ml_get_buf(wp->w_buffer, lnum + 1, false), MAXWLEN); } - char_u *p = buf + skip; - char_u *endp = buf + len; + char *p = buf + skip; + char *endp = buf + len; while (p < endp) { // When searching backward don't search after the cursor. Unless // we wrapped around the end of the buffer. @@ -1344,15 +1352,9 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att : p - buf) > wp->w_cursor.col)) { col = (colnr_T)(p - buf); - bool can_spell = decor_spell_nav_col(wp, lnum, &decor_lnum, col, &decor_error); - - if (!can_spell) { - if (has_syntax) { - (void)syn_get_id(wp, lnum, col, false, &can_spell, false); - } else { - can_spell = (wp->w_s->b_p_spo_flags & SPO_NPBUFFER) == 0; - } - } + bool can_spell = (!has_syntax && (wp->w_s->b_p_spo_flags & SPO_NPBUFFER) == 0) + || decor_spell_nav_col(wp, lnum, &decor_lnum, col, &decor_error) + || (has_syntax && can_syn_spell(wp, lnum, col)); if (!can_spell) { attr = HLF_COUNT; @@ -1479,27 +1481,29 @@ theend: // "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) +void spell_cat_line(char *buf, char *line, int maxlen) { - char_u *p = (char_u *)skipwhite((char *)line); - while (vim_strchr("*#/\"\t", *p) != NULL) { + char_u *p = (char_u *)skipwhite(line); + while (vim_strchr("*#/\"\t", (uint8_t)(*p)) != NULL) { p = (char_u *)skipwhite((char *)p + 1); } - if (*p != NUL) { - // Only worth concatenating if there is something else than spaces to - // concatenate. - int n = (int)(p - line) + 1; - if (n < maxlen - 1) { - memset(buf, ' ', (size_t)n); - STRLCPY(buf + n, p, maxlen - n); - } + if (*p == NUL) { + return; + } + + // Only worth concatenating if there is something else than spaces to + // concatenate. + int n = (int)(p - (char_u *)line) + 1; + if (n < maxlen - 1) { + memset(buf, ' ', (size_t)n); + xstrlcpy(buf + n, (char *)p, (size_t)(maxlen - n)); } } // Load word list(s) for "lang" from Vim spell file(s). // "lang" must be the language without the region: e.g., "en". -static void spell_load_lang(char_u *lang) +static void spell_load_lang(char *lang) { char fname_enc[85]; int r; @@ -1511,22 +1515,26 @@ static void spell_load_lang(char_u *lang) sl.sl_slang = NULL; sl.sl_nobreak = false; + // Disallow deleting the current buffer. Autocommands can do weird things + // and cause "lang" to be freed. + curbuf->b_locked++; + // We may retry when no spell file is found for the language, an // autocommand may load it then. for (int round = 1; round <= 2; round++) { // Find the first spell file for "lang" in 'runtimepath' and load it. - vim_snprintf((char *)fname_enc, sizeof(fname_enc) - 5, + vim_snprintf(fname_enc, sizeof(fname_enc) - 5, "spell/%s.%s.spl", lang, spell_enc()); - r = do_in_runtimepath((char *)fname_enc, 0, spell_load_cb, &sl); + r = do_in_runtimepath(fname_enc, 0, spell_load_cb, &sl); if (r == FAIL && *sl.sl_lang != NUL) { // Try loading the ASCII version. - vim_snprintf((char *)fname_enc, sizeof(fname_enc) - 5, + vim_snprintf(fname_enc, sizeof(fname_enc) - 5, "spell/%s.ascii.spl", lang); - r = do_in_runtimepath((char *)fname_enc, 0, spell_load_cb, &sl); + r = do_in_runtimepath(fname_enc, 0, spell_load_cb, &sl); if (r == FAIL && *sl.sl_lang != NUL && round == 1 - && apply_autocmds(EVENT_SPELLFILEMISSING, (char *)lang, + && apply_autocmds(EVENT_SPELLFILEMISSING, lang, curbuf->b_fname, false, curbuf)) { continue; } @@ -1551,25 +1559,27 @@ static void spell_load_lang(char_u *lang) } else if (sl.sl_slang != NULL) { // At least one file was loaded, now load ALL the additions. STRCPY(fname_enc + strlen(fname_enc) - 3, "add.spl"); - do_in_runtimepath((char *)fname_enc, DIP_ALL, spell_load_cb, &sl); + do_in_runtimepath(fname_enc, DIP_ALL, spell_load_cb, &sl); } + + curbuf->b_locked--; } // Return the encoding used for spell checking: Use 'encoding', except that we // use "latin1" for "latin9". And limit to 60 characters (just in case). -char_u *spell_enc(void) +char *spell_enc(void) { - if (STRLEN(p_enc) < 60 && strcmp(p_enc, "iso-8859-15") != 0) { - return (char_u *)p_enc; + if (strlen(p_enc) < 60 && strcmp(p_enc, "iso-8859-15") != 0) { + return p_enc; } - return (char_u *)"latin1"; + return "latin1"; } // Get the name of the .spl file for the internal wordlist into // "fname[MAXPATHL]". -static void int_wordlist_spl(char_u *fname) +static void int_wordlist_spl(char *fname) { - vim_snprintf((char *)fname, MAXPATHL, SPL_FNAME_TMPL, + vim_snprintf(fname, MAXPATHL, SPL_FNAME_TMPL, int_wordlist, spell_enc()); } @@ -1693,18 +1703,20 @@ void slang_clear_sug(slang_T *lp) static void spell_load_cb(char *fname, void *cookie) { spelload_T *slp = (spelload_T *)cookie; - slang_T *slang = spell_load_file(fname, (char *)slp->sl_lang, NULL, false); - if (slang != NULL) { - // When a previously loaded file has NOBREAK also use it for the - // ".add" files. - if (slp->sl_nobreak && slang->sl_add) { - slang->sl_nobreak = true; - } else if (slang->sl_nobreak) { - slp->sl_nobreak = true; - } + slang_T *slang = spell_load_file(fname, slp->sl_lang, NULL, false); + if (slang == NULL) { + return; + } - slp->sl_slang = slang; + // When a previously loaded file has NOBREAK also use it for the + // ".add" files. + if (slp->sl_nobreak && slang->sl_add) { + slang->sl_nobreak = true; + } else if (slang->sl_nobreak) { + slp->sl_nobreak = true; } + + slp->sl_slang = slang; } /// Add a word to the hashtable of common words. @@ -1714,29 +1726,29 @@ static void spell_load_cb(char *fname, void *cookie) /// @param[in] word added to common words hashtable /// @param[in] len length of word or -1 for NUL terminated /// @param[in] count 1 to count once, 10 to init -void count_common_word(slang_T *lp, char_u *word, int len, uint8_t count) +void count_common_word(slang_T *lp, char *word, int len, uint8_t count) { - char_u buf[MAXWLEN]; - char_u *p; + char buf[MAXWLEN]; + char *p; if (len == -1) { p = word; } else if (len >= MAXWLEN) { return; } else { - STRLCPY(buf, word, len + 1); + xstrlcpy(buf, word, (size_t)len + 1); p = buf; } wordcount_T *wc; hash_T hash = hash_hash(p); - const size_t p_len = STRLEN(p); + const size_t p_len = strlen(p); hashitem_T *hi = hash_lookup(&lp->sl_wordcount, (const char *)p, p_len, hash); if (HASHITEM_EMPTY(hi)) { wc = xmalloc(sizeof(wordcount_T) + p_len); memcpy(wc->wc_word, p, p_len + 1); wc->wc_count = count; - hash_add_item(&lp->sl_wordcount, hi, wc->wc_word, hash); + hash_add_item(&lp->sl_wordcount, hi, (char *)wc->wc_word, hash); } else { wc = HI2WC(hi); wc->wc_count = (uint16_t)(wc->wc_count + count); @@ -1748,9 +1760,9 @@ void count_common_word(slang_T *lp, char_u *word, int len, uint8_t count) // Returns true if byte "n" appears in "str". // Like strchr() but independent of locale. -bool byte_in_str(char_u *str, int n) +bool byte_in_str(uint8_t *str, int n) { - for (char_u *p = str; *p != NUL; p++) { + for (uint8_t *p = str; *p != NUL; p++) { if (*p == n) { return true; } @@ -1763,17 +1775,17 @@ bool byte_in_str(char_u *str, int n) int init_syl_tab(slang_T *slang) { ga_init(&slang->sl_syl_items, sizeof(syl_item_T), 4); - char_u *p = (char_u *)vim_strchr((char *)slang->sl_syllable, '/'); + char *p = vim_strchr((char *)slang->sl_syllable, '/'); while (p != NULL) { *p++ = NUL; if (*p == NUL) { // trailing slash break; } - char_u *s = p; - p = (char_u *)vim_strchr((char *)p, '/'); + char *s = p; + p = vim_strchr(p, '/'); int l; if (p == NULL) { - l = (int)STRLEN(s); + l = (int)strlen(s); } else { l = (int)(p - s); } @@ -1782,7 +1794,7 @@ int init_syl_tab(slang_T *slang) } syl_item_T *syl = GA_APPEND_VIA_PTR(syl_item_T, &slang->sl_syl_items); - STRLCPY(syl->sy_chars, s, l + 1); + xstrlcpy(syl->sy_chars, s, (size_t)l + 1); syl->sy_len = l; } return OK; @@ -1791,7 +1803,7 @@ int init_syl_tab(slang_T *slang) // Count the number of syllables in "word". // When "word" contains spaces the syllables after the last space are counted. // Returns zero if syllables are not defines. -static int count_syllables(slang_T *slang, const char_u *word) +static int count_syllables(slang_T *slang, const char *word) FUNC_ATTR_NONNULL_ALL { int cnt = 0; @@ -1802,7 +1814,7 @@ static int count_syllables(slang_T *slang, const char_u *word) return 0; } - for (const char_u *p = word; *p != NUL; p += len) { + for (const char *p = word; *p != NUL; p += len) { // When running into a space reset counter. if (*p == ' ') { len = 1; @@ -1815,7 +1827,7 @@ static int count_syllables(slang_T *slang, const char_u *word) for (int i = 0; i < slang->sl_syl_items.ga_len; i++) { syl_item_T *syl = ((syl_item_T *)slang->sl_syl_items.ga_data) + i; if (syl->sy_len > len - && STRNCMP(p, syl->sy_chars, syl->sy_len) == 0) { + && strncmp(p, syl->sy_chars, (size_t)syl->sy_len) == 0) { len = syl->sy_len; } } @@ -1824,8 +1836,8 @@ static int count_syllables(slang_T *slang, const char_u *word) skip = false; } else { // No recognized syllable item, at least a syllable char then? - int c = utf_ptr2char((char *)p); - len = utfc_ptr2len((char *)p); + int c = utf_ptr2char(p); + len = utfc_ptr2len(p); if (vim_strchr((char *)slang->sl_syllable, c) == NULL) { skip = false; // No, search for next syllable } else if (!skip) { @@ -1886,11 +1898,11 @@ char *did_set_spelllang(win_T *wp) // Loop over comma separated language names. for (splp = spl_copy; *splp != NUL;) { // Get one language name. - copy_option_part(&splp, (char *)lang, MAXWLEN, ","); + copy_option_part(&splp, lang, MAXWLEN, ","); region = NULL; len = (int)strlen(lang); - if (!valid_spelllang((char *)lang)) { + if (!valid_spelllang(lang)) { continue; } @@ -1906,10 +1918,10 @@ char *did_set_spelllang(win_T *wp) filename = true; // Locate a region and remove it from the file name. - p = vim_strchr(path_tail((char *)lang), '_'); + p = vim_strchr(path_tail(lang), '_'); if (p != NULL && ASCII_ISALPHA(p[1]) && ASCII_ISALPHA(p[2]) && !ASCII_ISALPHA(p[3])) { - STRLCPY(region_cp, p + 1, 3); + xstrlcpy(region_cp, p + 1, 3); memmove(p, p + 3, (size_t)(len - (p - lang) - 2)); region = region_cp; } else { @@ -1918,7 +1930,7 @@ char *did_set_spelllang(win_T *wp) // Check if we loaded this language before. for (slang = first_lang; slang != NULL; slang = slang->sl_next) { - if (path_full_compare((char *)lang, slang->sl_fname, false, true) + if (path_full_compare(lang, slang->sl_fname, false, true) == kEqualFiles) { break; } @@ -1952,12 +1964,12 @@ char *did_set_spelllang(win_T *wp) // If not found try loading the language now. if (slang == NULL) { if (filename) { - (void)spell_load_file((char *)lang, (char *)lang, NULL, false); + (void)spell_load_file(lang, lang, NULL, false); } else { - spell_load_lang((char_u *)lang); + spell_load_lang(lang); // SpellFileMissing autocommands may do anything, including - // destroying the buffer we are using... - if (!bufref_valid(&bufref)) { + // destroying the buffer we are using or closing the window. + if (!bufref_valid(&bufref) || !win_valid_any_tab(wp)) { ret_msg = N_("E797: SpellFileMissing autocommand deleted buffer"); goto theend; } @@ -1967,12 +1979,12 @@ char *did_set_spelllang(win_T *wp) // Loop over the languages, there can be several files for "lang". for (slang = first_lang; slang != NULL; slang = slang->sl_next) { if (filename - ? path_full_compare((char *)lang, slang->sl_fname, false, true) == kEqualFiles + ? path_full_compare(lang, slang->sl_fname, false, true) == kEqualFiles : STRICMP(lang, slang->sl_name) == 0) { region_mask = REGION_ALL; if (!filename && region != NULL) { // find region in sl_regions - c = find_region(slang->sl_regions, (char_u *)region); + c = find_region(slang->sl_regions, region); if (c == REGION_ALL) { if (slang->sl_add) { if (*slang->sl_regions != NUL) { @@ -2015,17 +2027,17 @@ char *did_set_spelllang(win_T *wp) if (int_wordlist == NULL) { continue; } - int_wordlist_spl((char_u *)spf_name); + int_wordlist_spl(spf_name); } else { // One entry in 'spellfile'. - copy_option_part(&spf, (char *)spf_name, MAXPATHL - 5, ","); + copy_option_part(&spf, spf_name, MAXPATHL - 5, ","); STRCAT(spf_name, ".spl"); // If it was already found above then skip it. for (c = 0; c < ga.ga_len; c++) { p = LANGP_ENTRY(ga, c)->lp_slang->sl_fname; if (p != NULL - && path_full_compare((char *)spf_name, p, false, true) == kEqualFiles) { + && path_full_compare(spf_name, p, false, true) == kEqualFiles) { break; } } @@ -2036,7 +2048,7 @@ char *did_set_spelllang(win_T *wp) // Check if it was loaded already. for (slang = first_lang; slang != NULL; slang = slang->sl_next) { - if (path_full_compare((char *)spf_name, slang->sl_fname, false, true) + if (path_full_compare(spf_name, slang->sl_fname, false, true) == kEqualFiles) { break; } @@ -2048,13 +2060,13 @@ char *did_set_spelllang(win_T *wp) if (round == 0) { STRCPY(lang, "internal wordlist"); } else { - STRLCPY(lang, path_tail((char *)spf_name), MAXWLEN + 1); - p = vim_strchr((char *)lang, '.'); + xstrlcpy(lang, path_tail(spf_name), MAXWLEN + 1); + p = vim_strchr(lang, '.'); if (p != NULL) { *p = NUL; // truncate at ".encoding.add" } } - slang = spell_load_file((char *)spf_name, (char *)lang, NULL, true); + slang = spell_load_file(spf_name, lang, NULL, true); // If one of the languages has NOBREAK we assume the addition // files also have this. @@ -2066,7 +2078,7 @@ char *did_set_spelllang(win_T *wp) region_mask = REGION_ALL; if (use_region != NULL && !dont_use_region) { // find region in sl_regions - c = find_region(slang->sl_regions, (char_u *)use_region); + c = find_region(slang->sl_regions, use_region); if (c != REGION_ALL) { region_mask = 1 << c; } else if (*slang->sl_regions != NUL) { @@ -2106,7 +2118,7 @@ char *did_set_spelllang(win_T *wp) for (int j = 0; j < ga.ga_len; j++) { lp2 = LANGP_ENTRY(ga, j); if (!GA_EMPTY(&lp2->lp_slang->sl_sal) - && STRNCMP(lp->lp_slang->sl_name, + && strncmp(lp->lp_slang->sl_name, lp2->lp_slang->sl_name, 2) == 0) { lp->lp_sallang = lp2->lp_slang; break; @@ -2123,7 +2135,7 @@ char *did_set_spelllang(win_T *wp) for (int j = 0; j < ga.ga_len; j++) { lp2 = LANGP_ENTRY(ga, j); if (!GA_EMPTY(&lp2->lp_slang->sl_rep) - && STRNCMP(lp->lp_slang->sl_name, + && strncmp(lp->lp_slang->sl_name, lp2->lp_slang->sl_name, 2) == 0) { lp->lp_replang = lp2->lp_slang; break; @@ -2169,7 +2181,7 @@ static void use_midword(slang_T *lp, win_T *wp) char *bp = xstrnsave(wp->w_s->b_spell_ismw_mb, (size_t)n + (size_t)l); xfree(wp->w_s->b_spell_ismw_mb); wp->w_s->b_spell_ismw_mb = bp; - STRLCPY(bp + n, p, l + 1); + xstrlcpy(bp + n, p, (size_t)l + 1); } p += l; } @@ -2178,7 +2190,7 @@ static void use_midword(slang_T *lp, win_T *wp) // Find the region "region[2]" in "rp" (points to "sl_regions"). // Each region is simply stored as the two characters of its name. // Returns the index if found (first is 0), REGION_ALL if not found. -static int find_region(char_u *rp, char_u *region) +static int find_region(const char *rp, const char *region) { int i; @@ -2203,10 +2215,10 @@ static int find_region(char_u *rp, char_u *region) /// @param[in] end End of word or NULL for NUL delimited string /// /// @returns Case type of word -int captype(char_u *word, char_u *end) +int captype(char *word, const char *end) FUNC_ATTR_NONNULL_ARG(1) { - char_u *p; + char *p; // find first letter for (p = word; !spell_iswordp_nmw(p, curwin); MB_PTR_ADV(p)) { @@ -2214,7 +2226,7 @@ int captype(char_u *word, char_u *end) return 0; // only non-word characters, illegal word } } - int c = mb_ptr2char_adv((const char_u **)&p); + int c = mb_ptr2char_adv((const char **)&p); bool allcap; bool firstcap = allcap = SPELL_ISUPPER(c); bool past_second = false; // past second word char @@ -2223,7 +2235,7 @@ int captype(char_u *word, char_u *end) // But a word with an upper char only at start is a ONECAP. for (; end == NULL ? *p != NUL : p < end; MB_PTR_ADV(p)) { if (spell_iswordp_nmw(p, curwin)) { - c = utf_ptr2char((char *)p); + c = utf_ptr2char(p); if (!SPELL_ISUPPER(c)) { // UUl -> KEEPCAP if (past_second && allcap) { @@ -2250,13 +2262,15 @@ int captype(char_u *word, char_u *end) // Delete the internal wordlist and its .spl file. void spell_delete_wordlist(void) { - if (int_wordlist != NULL) { - char_u fname[MAXPATHL] = { 0 }; - os_remove((char *)int_wordlist); - int_wordlist_spl(fname); - os_remove((char *)fname); - XFREE_CLEAR(int_wordlist); + if (int_wordlist == NULL) { + return; } + + char fname[MAXPATHL] = { 0 }; + os_remove(int_wordlist); + int_wordlist_spl(fname); + os_remove(fname); + XFREE_CLEAR(int_wordlist); } // Free all languages. @@ -2324,10 +2338,12 @@ buf_T *open_spellbuf(void) // Close the buffer used for spell info. void close_spellbuf(buf_T *buf) { - if (buf != NULL) { - ml_close(buf, true); - xfree(buf); + if (buf == NULL) { + return; } + + ml_close(buf, true); + xfree(buf); } // Init the chartab used for spelling for ASCII. @@ -2386,18 +2402,18 @@ void init_spell_chartab(void) /// Thus this only works properly when past the first character of the word. /// /// @param wp Buffer used. -bool spell_iswordp(const char_u *p, const win_T *wp) +bool spell_iswordp(const char *p, const win_T *wp) FUNC_ATTR_NONNULL_ALL { - const int l = utfc_ptr2len((char *)p); - const char_u *s = p; + const int l = utfc_ptr2len(p); + const char *s = p; if (l == 1) { // be quick for ASCII - if (wp->w_s->b_spell_ismw[*p]) { + if (wp->w_s->b_spell_ismw[(uint8_t)(*p)]) { s = p + 1; // skip a mid-word character } } else { - int c = utf_ptr2char((char *)p); + int c = utf_ptr2char(p); if (c < 256 ? wp->w_s->b_spell_ismw[c] : (wp->w_s->b_spell_ismw_mb != NULL @@ -2406,7 +2422,7 @@ bool spell_iswordp(const char_u *p, const win_T *wp) } } - int c = utf_ptr2char((char *)s); + int c = utf_ptr2char(s); if (c > 255) { return spell_mb_isword_class(mb_get_class(s), wp); } @@ -2415,9 +2431,9 @@ bool spell_iswordp(const char_u *p, const win_T *wp) // Returns true if "p" points to a word character. // Unlike spell_iswordp() this doesn't check for "midword" characters. -bool spell_iswordp_nmw(const char_u *p, win_T *wp) +bool spell_iswordp_nmw(const char *p, win_T *wp) { - int c = utf_ptr2char((char *)p); + int c = utf_ptr2char(p); if (c > 255) { return spell_mb_isword_class(mb_get_class(p), wp); } @@ -2464,7 +2480,7 @@ static bool spell_iswordp_w(const int *p, const win_T *wp) // Uses the character definitions from the .spl file. // When using a multi-byte 'encoding' the length may change! // Returns FAIL when something wrong. -int spell_casefold(const win_T *wp, char_u *str, int len, char_u *buf, int buflen) +int spell_casefold(const win_T *wp, char *str, int len, char *buf, int buflen) FUNC_ATTR_NONNULL_ALL { if (len >= buflen) { @@ -2475,12 +2491,12 @@ int spell_casefold(const win_T *wp, char_u *str, int len, char_u *buf, int bufle int outi = 0; // Fold one character at a time. - for (char_u *p = str; p < str + len;) { + for (char *p = str; p < str + len;) { if (outi + MB_MAXBYTES > buflen) { buf[outi] = NUL; return FAIL; } - int c = mb_cptr2char_adv((const char_u **)&p); + int c = mb_cptr2char_adv((const char **)&p); // Exception: greek capital sigma 0x03A3 folds to 0x03C3, except // when it is the last character in a word, then it folds to @@ -2495,7 +2511,7 @@ int spell_casefold(const win_T *wp, char_u *str, int len, char_u *buf, int bufle c = SPELL_TOFOLD(c); } - outi += utf_char2bytes(c, (char *)buf + outi); + outi += utf_char2bytes(c, buf + outi); } buf[outi] = NUL; @@ -2512,23 +2528,23 @@ bool check_need_cap(linenr_T lnum, colnr_T col) return false; } - char_u *line = (char_u *)get_cursor_line_ptr(); - char_u *line_copy = NULL; + char *line = get_cursor_line_ptr(); + char *line_copy = NULL; colnr_T endcol = 0; - if (getwhitecols((char *)line) >= (int)col) { + if (getwhitecols(line) >= (int)col) { // At start of line, check if previous line is empty or sentence // ends there. if (lnum == 1) { need_cap = true; } else { - line = (char_u *)ml_get(lnum - 1); - if (*skipwhite((char *)line) == NUL) { + line = ml_get(lnum - 1); + if (*skipwhite(line) == NUL) { need_cap = true; } else { // Append a space in place of the line break. - line_copy = (char_u *)concat_str((char *)line, " "); + line_copy = concat_str(line, " "); line = line_copy; - endcol = (colnr_T)STRLEN(line); + endcol = (colnr_T)strlen(line); } } } else { @@ -2541,14 +2557,14 @@ bool check_need_cap(linenr_T lnum, colnr_T col) .regprog = curwin->w_s->b_cap_prog, .rm_ic = false }; - char_u *p = line + endcol; + char *p = line + endcol; for (;;) { MB_PTR_BACK(line, p); if (p == line || spell_iswordp_nmw(p, curwin)) { break; } - if (vim_regexec(®match, (char *)p, 0) - && (char_u *)regmatch.endp[0] == line + endcol) { + if (vim_regexec(®match, p, 0) + && regmatch.endp[0] == line + endcol) { need_cap = true; break; } @@ -2575,8 +2591,8 @@ void ex_spellrepall(exarg_T *eap) int addlen = (int)(strlen(repl_to) - strlen(repl_from)); size_t frompatlen = strlen(repl_from) + 7; - char_u *frompat = xmalloc(frompatlen); - snprintf((char *)frompat, frompatlen, "\\V\\<%s\\>", repl_from); + char *frompat = xmalloc(frompatlen); + snprintf(frompat, frompatlen, "\\V\\<%s\\>", repl_from); p_ws = false; sub_nsubs = 0; @@ -2590,14 +2606,14 @@ void ex_spellrepall(exarg_T *eap) // Only replace when the right word isn't there yet. This happens // when changing "etc" to "etc.". - char_u *line = (char_u *)get_cursor_line_ptr(); - if (addlen <= 0 || STRNCMP(line + curwin->w_cursor.col, + char *line = get_cursor_line_ptr(); + if (addlen <= 0 || strncmp(line + curwin->w_cursor.col, repl_to, strlen(repl_to)) != 0) { - char_u *p = xmalloc(STRLEN(line) + (size_t)addlen + 1); + char *p = xmalloc(strlen(line) + (size_t)addlen + 1); memmove(p, line, (size_t)curwin->w_cursor.col); STRCPY(p + curwin->w_cursor.col, repl_to); STRCAT(p, line + curwin->w_cursor.col + strlen(repl_from)); - ml_replace(curwin->w_cursor.lnum, (char *)p, false); + ml_replace(curwin->w_cursor.lnum, p, false); changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col); if (curwin->w_cursor.lnum != prev_lnum) { @@ -2627,30 +2643,30 @@ void ex_spellrepall(exarg_T *eap) /// @param[in] word source string to copy /// @param[in,out] wcopy copied string, with case of first letter changed /// @param[in] upper True to upper case, otherwise lower case -void onecap_copy(char_u *word, char_u *wcopy, bool upper) +void onecap_copy(char *word, char *wcopy, bool upper) { - char_u *p = word; - int c = mb_cptr2char_adv((const char_u **)&p); + char *p = word; + int c = mb_cptr2char_adv((const char **)&p); if (upper) { c = SPELL_TOUPPER(c); } else { c = SPELL_TOFOLD(c); } - int l = utf_char2bytes(c, (char *)wcopy); - STRLCPY(wcopy + l, p, MAXWLEN - l); + int l = utf_char2bytes(c, wcopy); + xstrlcpy(wcopy + l, p, (size_t)(MAXWLEN - l)); } // Make a copy of "word" with all the letters upper cased into // "wcopy[MAXWLEN]". The result is NUL terminated. -void allcap_copy(char_u *word, char_u *wcopy) +void allcap_copy(char *word, char *wcopy) { - char_u *d = wcopy; - for (char_u *s = word; *s != NUL;) { - int c = mb_cptr2char_adv((const char_u **)&s); + char_u *d = (char_u *)wcopy; + for (char *s = word; *s != NUL;) { + int c = mb_cptr2char_adv((const char **)&s); if (c == 0xdf) { c = 'S'; - if (d - wcopy >= MAXWLEN - 1) { + if (d - (char_u *)wcopy >= MAXWLEN - 1) { break; } *d++ = (char_u)c; @@ -2658,7 +2674,7 @@ void allcap_copy(char_u *word, char_u *wcopy) c = SPELL_TOUPPER(c); } - if (d - wcopy >= MAXWLEN - MB_MAXBYTES) { + if (d - (char_u *)wcopy >= MAXWLEN - MB_MAXBYTES) { break; } d += utf_char2bytes(c, (char *)d); @@ -2668,9 +2684,9 @@ void allcap_copy(char_u *word, char_u *wcopy) // Case-folding may change the number of bytes: Count nr of chars in // fword[flen] and return the byte length of that many chars in "word". -int nofold_len(char_u *fword, int flen, char_u *word) +int nofold_len(char *fword, int flen, char *word) { - char_u *p; + char *p; int i = 0; for (p = fword; p < fword + flen; MB_PTR_ADV(p)) { @@ -2683,7 +2699,7 @@ int nofold_len(char_u *fword, int flen, char_u *word) } // Copy "fword" to "cword", fixing case according to "flags". -void make_case_word(char_u *fword, char_u *cword, int flags) +void make_case_word(char *fword, char *cword, int flags) { if (flags & WF_ALLCAP) { // Make it all upper-case @@ -2713,9 +2729,9 @@ char *eval_soundfold(const char *const word) langp_T *const lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); if (!GA_EMPTY(&lp->lp_slang->sl_sal)) { // soundfold the word - char_u sound[MAXWLEN]; - spell_soundfold(lp->lp_slang, (char_u *)word, false, sound); - return xstrdup((const char *)sound); + char sound[MAXWLEN]; + spell_soundfold(lp->lp_slang, (char *)word, false, sound); + return xstrdup(sound); } } } @@ -2739,19 +2755,19 @@ char *eval_soundfold(const char *const word) /// @param[in] inword word to soundfold /// @param[in] folded whether inword is already case-folded /// @param[in,out] res destination for soundfolded word -void spell_soundfold(slang_T *slang, char_u *inword, bool folded, char_u *res) +void spell_soundfold(slang_T *slang, char *inword, bool folded, char *res) { if (slang->sl_sofo) { // SOFOFROM and SOFOTO used spell_soundfold_sofo(slang, inword, res); } else { - char_u fword[MAXWLEN]; - char_u *word; + char fword[MAXWLEN]; + char *word; // SAL items used. Requires the word to be case-folded. if (folded) { word = inword; } else { - (void)spell_casefold(curwin, inword, (int)STRLEN(inword), fword, MAXWLEN); + (void)spell_casefold(curwin, inword, (int)strlen(inword), fword, MAXWLEN); word = fword; } @@ -2761,7 +2777,7 @@ void spell_soundfold(slang_T *slang, char_u *inword, bool folded, char_u *res) // Perform sound folding of "inword" into "res" according to SOFOFROM and // SOFOTO lines. -static void spell_soundfold_sofo(slang_T *slang, char_u *inword, char_u *res) +static void spell_soundfold_sofo(slang_T *slang, char *inword, char *res) { int ri = 0; @@ -2769,8 +2785,8 @@ static void spell_soundfold_sofo(slang_T *slang, char_u *inword, char_u *res) // The sl_sal_first[] table contains the translation for chars up to // 255, sl_sal the rest. - for (char_u *s = inword; *s != NUL;) { - int c = mb_cptr2char_adv((const char_u **)&s); + for (char *s = inword; *s != NUL;) { + int c = mb_cptr2char_adv((const char **)&s); if (utf_class(c) == 0) { c = ' '; } else if (c < 256) { @@ -2795,7 +2811,7 @@ static void spell_soundfold_sofo(slang_T *slang, char_u *inword, char_u *res) } if (c != NUL && c != prevc) { - ri += utf_char2bytes(c, (char *)res + ri); + ri += utf_char2bytes(c, res + ri); if (ri + MB_MAXBYTES > MAXWLEN) { break; } @@ -2808,7 +2824,7 @@ static void spell_soundfold_sofo(slang_T *slang, char_u *inword, char_u *res) // Turn "inword" into its sound-a-like equivalent in "res[MAXWLEN]". // Multi-byte version of spell_soundfold(). -static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) +static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res) { salitem_T *smp = (salitem_T *)slang->sl_sal.ga_data; int word[MAXWLEN] = { 0 }; @@ -2830,8 +2846,8 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) // Remove accents, if wanted. We actually remove all non-word characters. // But keep white space. int wordlen = 0; - for (const char_u *s = inword; *s != NUL;) { - const char_u *t = s; + for (const char *s = (char *)inword; *s != NUL;) { + const char_u *t = (char_u *)s; int c = mb_cptr2char_adv(&s); if (slang->sl_rem_accents) { if (utf_class(c) == 0) { @@ -2842,7 +2858,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) did_white = true; } else { did_white = false; - if (!spell_iswordp_nmw(t, curwin)) { + if (!spell_iswordp_nmw((char *)t, curwin)) { continue; } } @@ -2899,7 +2915,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) } k++; } - char_u *s = smp[n].sm_rules; + char_u *s = (char_u *)smp[n].sm_rules; pri = 5; // default priority p0 = *s; @@ -2976,7 +2992,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) } p0 = 5; - s = smp[n0].sm_rules; + s = (char_u *)smp[n0].sm_rules; while (*s == '-') { // "k0" gets NOT reduced because // "if (k0 == k)" @@ -3017,7 +3033,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) // replace string ws = smp[n].sm_to_w; - s = smp[n].sm_rules; + s = (char_u *)smp[n].sm_rules; p0 = (vim_strchr((char *)s, '<') != NULL) ? 1 : 0; if (p0 == 1 && z == 0) { // rule with '<' is used @@ -3095,7 +3111,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) // Convert wide characters in "wres" to a multi-byte string in "res". int l = 0; for (int n = 0; n < reslen; n++) { - l += utf_char2bytes(wres[n], (char *)res + l); + l += utf_char2bytes(wres[n], res + l); if (l + MB_MAXBYTES > MAXWLEN) { break; } @@ -3139,7 +3155,7 @@ void ex_spelldump(exarg_T *eap) } char *spl; long dummy; - (void)get_option_value("spl", &dummy, &spl, OPT_LOCAL); + (void)get_option_value("spl", &dummy, &spl, NULL, OPT_LOCAL); // Create a new empty buffer in a new window. do_cmdline_cmd("new"); @@ -3197,11 +3213,11 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg) if (ic) { dumpflags |= DUMPFLAG_ICASE; } else { - n = captype((char_u *)pat, NULL); + n = captype(pat, NULL); if (n == WF_ONECAP) { dumpflags |= DUMPFLAG_ONECAP; } else if (n == WF_ALLCAP - && (int)STRLEN(pat) > utfc_ptr2len(pat)) { + && (int)strlen(pat) > utfc_ptr2len(pat)) { dumpflags |= DUMPFLAG_ALLCAP; } } @@ -3211,7 +3227,7 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg) // regions or none at all. for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) { lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); - p = (char *)lp->lp_slang->sl_regions; + p = lp->lp_slang->sl_regions; if (p[0] != 0) { if (region_names == NULL) { // first language with regions region_names = p; @@ -3224,8 +3240,8 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg) if (do_region && region_names != NULL) { if (pat == NULL) { - vim_snprintf((char *)IObuff, IOSIZE, "/regions=%s", region_names); - ml_append(lnum++, (char *)IObuff, (colnr_T)0, false); + vim_snprintf(IObuff, IOSIZE, "/regions=%s", region_names); + ml_append(lnum++, IObuff, (colnr_T)0, false); } } else { do_region = false; @@ -3240,8 +3256,8 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg) } if (pat == NULL) { - vim_snprintf((char *)IObuff, IOSIZE, "# file: %s", slang->sl_fname); - ml_append(lnum++, (char *)IObuff, (colnr_T)0, false); + vim_snprintf(IObuff, IOSIZE, "# file: %s", slang->sl_fname); + ml_append(lnum++, IObuff, (colnr_T)0, false); } // When matching with a pattern and there are no prefixes only use @@ -3257,11 +3273,11 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg) for (int round = 1; round <= 2; round++) { if (round == 1) { dumpflags &= ~DUMPFLAG_KEEPCASE; - byts = (char *)slang->sl_fbyts; + byts = slang->sl_fbyts; idxs = slang->sl_fidxs; } else { dumpflags |= DUMPFLAG_KEEPCASE; - byts = (char *)slang->sl_kbyts; + byts = slang->sl_kbyts; idxs = slang->sl_kidxs; } if (byts == NULL) { @@ -3304,8 +3320,7 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg) // when it's the first one. c = (int)((unsigned)flags >> 24); if (c == 0 || curi[depth] == 2) { - dump_word(slang, (char_u *)word, (char_u *)pat, dir, - dumpflags, flags, lnum); + dump_word(slang, word, pat, dir, dumpflags, flags, lnum); if (pat == NULL) { lnum++; } @@ -3313,7 +3328,7 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg) // Apply the prefix, if there is one. if (c != 0) { - lnum = dump_prefixes(slang, (char_u *)word, (char_u *)pat, dir, + lnum = dump_prefixes(slang, word, pat, dir, dumpflags, flags, lnum); } } @@ -3331,7 +3346,7 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg) // ignore case... assert(depth >= 0); if (depth <= patlen - && mb_strnicmp((char *)word, pat, (size_t)depth) != 0) { + && mb_strnicmp(word, pat, (size_t)depth) != 0) { depth--; } } @@ -3341,15 +3356,15 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg) } } -// Dumps one word: apply case modifications and append a line to the buffer. -// When "lnum" is zero add insert mode completion. -static void dump_word(slang_T *slang, char_u *word, char_u *pat, Direction *dir, int dumpflags, +/// Dumps one word: apply case modifications and append a line to the buffer. +/// When "lnum" is zero add insert mode completion. +static void dump_word(slang_T *slang, char *word, char *pat, Direction *dir, int dumpflags, int wordflags, linenr_T lnum) { bool keepcap = false; - char_u *p; - char_u cword[MAXWLEN]; - char_u badword[MAXWLEN + 10]; + char *p; + char cword[MAXWLEN]; + char badword[MAXWLEN + 10]; int flags = wordflags; if (dumpflags & DUMPFLAG_ONECAP) { @@ -3371,7 +3386,7 @@ static void dump_word(slang_T *slang, char_u *word, char_u *pat, Direction *dir, keepcap = true; } } - char_u *tw = p; + char *tw = p; if (pat == NULL) { // Add flags and regions after a slash. @@ -3389,8 +3404,8 @@ static void dump_word(slang_T *slang, char_u *word, char_u *pat, Direction *dir, if (flags & WF_REGION) { for (int i = 0; i < 7; i++) { if (flags & (0x10000 << i)) { - const size_t badword_len = STRLEN(badword); - snprintf((char *)badword + badword_len, + const size_t badword_len = strlen(badword); + snprintf(badword + badword_len, sizeof(badword) - badword_len, "%d", i + 1); } @@ -3403,19 +3418,19 @@ static void dump_word(slang_T *slang, char_u *word, char_u *pat, Direction *dir, hashitem_T *hi; // Include the word count for ":spelldump!". - hi = hash_find(&slang->sl_wordcount, (char *)tw); + hi = hash_find(&slang->sl_wordcount, tw); if (!HASHITEM_EMPTY(hi)) { - vim_snprintf((char *)IObuff, IOSIZE, "%s\t%d", + vim_snprintf(IObuff, IOSIZE, "%s\t%d", tw, HI2WC(hi)->wc_count); - p = (char_u *)IObuff; + p = IObuff; } } - ml_append(lnum, (char *)p, (colnr_T)0, false); + ml_append(lnum, p, (colnr_T)0, false); } else if (((dumpflags & DUMPFLAG_ICASE) - ? mb_strnicmp((char *)p, (char *)pat, STRLEN(pat)) == 0 - : STRNCMP(p, pat, STRLEN(pat)) == 0) - && ins_compl_add_infercase(p, (int)STRLEN(p), + ? mb_strnicmp(p, pat, strlen(pat)) == 0 + : strncmp(p, pat, strlen(pat)) == 0) + && ins_compl_add_infercase(p, (int)strlen(p), p_ic, NULL, *dir, false) == OK) { // if dir was BACKWARD then honor it just once *dir = FORWARD; @@ -3430,25 +3445,25 @@ static void dump_word(slang_T *slang, char_u *word, char_u *pat, Direction *dir, /// @param flags flags with prefix ID /// /// @return the updated line number. -static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Direction *dir, - int dumpflags, int flags, linenr_T startlnum) +static linenr_T dump_prefixes(slang_T *slang, char *word, char *pat, Direction *dir, int dumpflags, + int flags, linenr_T startlnum) { idx_T arridx[MAXWLEN]; int curi[MAXWLEN]; - char_u prefix[MAXWLEN]; - char_u word_up[MAXWLEN]; + char prefix[MAXWLEN]; + char word_up[MAXWLEN]; bool has_word_up = false; linenr_T lnum = startlnum; // If the word starts with a lower-case letter make the word with an // upper-case letter in word_up[]. - int c = utf_ptr2char((char *)word); + int c = utf_ptr2char(word); if (SPELL_TOUPPER(c) != c) { onecap_copy(word, word_up, true); has_word_up = true; } - char_u *byts = slang->sl_pbyts; + char_u *byts = (char_u *)slang->sl_pbyts; idx_T *idxs = slang->sl_pidxs; if (byts != NULL) { // array not is empty // Loop over all prefixes, building them byte-by-byte in prefix[]. @@ -3480,7 +3495,7 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi c = valid_word_prefix(i, n, flags, word, slang, false); if (c != 0) { - STRLCPY(prefix + depth, word, MAXWLEN - depth); + xstrlcpy(prefix + depth, word, (size_t)(MAXWLEN - depth)); dump_word(slang, prefix, pat, dir, dumpflags, (c & WF_RAREPFX) ? (flags | WF_RARE) : flags, lnum); if (lnum != 0) { @@ -3492,10 +3507,9 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi // first letter is upper-case, but only if the prefix has // a condition. if (has_word_up) { - c = valid_word_prefix(i, n, flags, word_up, slang, - true); + c = valid_word_prefix(i, n, flags, word_up, slang, true); if (c != 0) { - STRLCPY(prefix + depth, word_up, MAXWLEN - depth); + xstrlcpy(prefix + depth, word_up, (size_t)(MAXWLEN - depth)); dump_word(slang, prefix, pat, dir, dumpflags, (c & WF_RAREPFX) ? (flags | WF_RARE) : flags, lnum); if (lnum != 0) { @@ -3505,7 +3519,7 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi } } else { // Normal char, go one level deeper. - prefix[depth++] = (char_u)c; + prefix[depth++] = (char)c; arridx[depth] = idxs[n]; curi[depth] = 1; } @@ -3518,9 +3532,9 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi // Move "p" to the end of word "start". // Uses the spell-checking word characters. -char_u *spell_to_word_end(char_u *start, win_T *win) +char *spell_to_word_end(char *start, win_T *win) { - char_u *p = start; + char *p = start; while (*p != NUL && spell_iswordp(p, win)) { MB_PTR_ADV(p); @@ -3539,8 +3553,8 @@ int spell_word_start(int startcol) return startcol; } - char_u *line = (char_u *)get_cursor_line_ptr(); - char_u *p; + char *line = get_cursor_line_ptr(); + char *p; // Find a word character before "startcol". for (p = line + startcol; p > line;) { @@ -3578,7 +3592,7 @@ void spell_expand_check_cap(colnr_T col) // Used for Insert mode completion CTRL-X ?. // Returns the number of matches. The matches are in "matchp[]", array of // allocated strings. -int expand_spelling(linenr_T lnum, char_u *pat, char ***matchp) +int expand_spelling(linenr_T lnum, char *pat, char ***matchp) { garray_T ga; @@ -3598,8 +3612,8 @@ bool valid_spelllang(const char *val) bool valid_spellfile(const char *val) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - for (const char_u *s = (char_u *)val; *s != NUL; s++) { - if (!vim_isfilec(*s) && *s != ',' && *s != ' ') { + for (const char *s = val; *s != NUL; s++) { + if (!vim_isfilec((uint8_t)(*s)) && *s != ',' && *s != ' ') { return false; } } @@ -3618,15 +3632,16 @@ char *did_set_spell_option(bool is_spellfile) } } - if (errmsg == NULL) { - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == curbuf && wp->w_p_spell) { - errmsg = did_set_spelllang(wp); - break; - } - } + if (errmsg != NULL) { + return errmsg; } + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == curbuf && wp->w_p_spell) { + errmsg = did_set_spelllang(wp); + break; + } + } return errmsg; } diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h index 2c4aebe420..726af7d698 100644 --- a/src/nvim/spell_defs.h +++ b/src/nvim/spell_defs.h @@ -72,19 +72,19 @@ typedef int idx_T; // si_repsal, sl_rep, and si_sal. Not for sl_sal! // One replacement: from "ft_from" to "ft_to". typedef struct fromto_S { - char_u *ft_from; - char_u *ft_to; + char *ft_from; + char *ft_to; } fromto_T; // Info from "SAL" entries in ".aff" file used in sl_sal. // The info is split for quick processing by spell_soundfold(). // Note that "sm_oneof" and "sm_rules" point into sm_lead. typedef struct salitem_S { - char_u *sm_lead; // leading letters - int sm_leadlen; // length of "sm_lead" - char_u *sm_oneof; // letters from () or NULL - char_u *sm_rules; // rules like ^, $, priority - char_u *sm_to; // replacement. + char *sm_lead; // leading letters + int sm_leadlen; // length of "sm_lead" + char_u *sm_oneof; // letters from () or NULL + char *sm_rules; // rules like ^, $, priority + char *sm_to; // replacement. int *sm_lead_w; // wide character copy of "sm_lead" int *sm_oneof_w; // wide character copy of "sm_oneof" int *sm_to_w; // wide character copy of "sm_to" @@ -119,17 +119,17 @@ struct slang_S { char *sl_fname; // name of .spl file bool sl_add; // true if it's a .add file. - char_u *sl_fbyts; // case-folded word bytes + char *sl_fbyts; // case-folded word bytes long sl_fbyts_len; // length of sl_fbyts idx_T *sl_fidxs; // case-folded word indexes - char_u *sl_kbyts; // keep-case word bytes + char *sl_kbyts; // keep-case word bytes idx_T *sl_kidxs; // keep-case word indexes - char_u *sl_pbyts; // prefix tree word bytes + char *sl_pbyts; // prefix tree word bytes idx_T *sl_pidxs; // prefix tree word indexes char_u *sl_info; // infotext string or NULL - char_u sl_regions[MAXREGIONS * 2 + 1]; + char sl_regions[MAXREGIONS * 2 + 1]; // table with up to 8 region names plus NUL char_u *sl_midword; // MIDWORD string or NULL @@ -143,9 +143,9 @@ struct slang_S { garray_T sl_comppat; // CHECKCOMPOUNDPATTERN items regprog_T *sl_compprog; // COMPOUNDRULE turned into a regexp progrm // (NULL when no compounding) - char_u *sl_comprules; // all COMPOUNDRULE concatenated (or NULL) - char_u *sl_compstartflags; // flags for first compound word - char_u *sl_compallflags; // all flags for compound words + uint8_t *sl_comprules; // all COMPOUNDRULE concatenated (or NULL) + uint8_t *sl_compstartflags; // flags for first compound word + uint8_t *sl_compallflags; // all flags for compound words bool sl_nobreak; // When true: no spaces between words char_u *sl_syllable; // SYLLABLE repeatable chars or NULL garray_T sl_syl_items; // syllable items @@ -172,7 +172,7 @@ struct slang_S { // Info from the .sug file. Loaded on demand. time_t sl_sugtime; // timestamp for .sug file - char_u *sl_sbyts; // soundfolded word bytes + char *sl_sbyts; // soundfolded word bytes idx_T *sl_sidxs; // soundfolded word indexes buf_T *sl_sugbuf; // buffer with word number table bool sl_sugloaded; // true when .sug file was loaded or failed to @@ -218,8 +218,7 @@ typedef struct { // the "w" library function for characters above 255. #define SPELL_TOFOLD(c) ((c) >= 128 ? utf_fold(c) : (int)spelltab.st_fold[c]) -#define SPELL_TOUPPER(c) ((c) >= 128 ? mb_toupper(c) \ - : (int)spelltab.st_upper[c]) +#define SPELL_TOUPPER(c) ((c) >= 128 ? mb_toupper(c) : (int)spelltab.st_upper[c]) #define SPELL_ISUPPER(c) ((c) >= 128 ? mb_isupper(c) : spelltab.st_isu[c]) @@ -228,7 +227,7 @@ typedef struct { extern slang_T *first_lang; // file used for "zG" and "zW" -extern char_u *int_wordlist; +extern char *int_wordlist; extern spelltab_T spelltab; extern int did_set_spelltab; diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 611c43e85e..44414ca1a5 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -226,82 +226,101 @@ // stored as an offset to the previous number in as // few bytes as possible, see offset2bytes()) -#include <stdint.h> +#include <assert.h> +#include <ctype.h> +#include <inttypes.h> +#include <limits.h> +#include <stdbool.h> #include <stdio.h> -#include <wctype.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include "auto/config.h" #include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/drawscreen.h" -#include "nvim/ex_cmds2.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/fileio.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/hashtab.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/os/os.h" +#include "nvim/os/time.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/regexp.h" #include "nvim/runtime.h" #include "nvim/spell.h" #include "nvim/spell_defs.h" #include "nvim/spellfile.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" -#ifndef UNIX // it's in os/unix_defs.h for Unix -# include <time.h> // for time_t -#endif - // Special byte values for <byte>. Some are only used in the tree for // postponed prefixes, some only in the other trees. This is a bit messy... -#define BY_NOFLAGS 0 // end of word without flags or region; for - // postponed prefix: no <pflags> -#define BY_INDEX 1 // child is shared, index follows -#define BY_FLAGS 2 // end of word, <flags> byte follows; for - // postponed prefix: <pflags> follows -#define BY_FLAGS2 3 // end of word, <flags> and <flags2> bytes - // follow; never used in prefix tree -#define BY_SPECIAL BY_FLAGS2 // highest special byte value +enum { + BY_NOFLAGS = 0, // end of word without flags or region; for postponed prefix: no <pflags> + BY_INDEX = 1, // child is shared, index follows + BY_FLAGS = 2, // end of word, <flags> byte follows; for postponed prefix: <pflags> follows + BY_FLAGS2 = 3, // end of word, <flags> and <flags2> bytes follow; never used in prefix tree + BY_SPECIAL = BY_FLAGS2, // highest special byte value +}; #define ZERO_FLAG 65009 // used when flag is zero: "0" // Flags used in .spl file for soundsalike flags. -#define SAL_F0LLOWUP 1 -#define SAL_COLLAPSE 2 -#define SAL_REM_ACCENTS 4 +enum { + SAL_F0LLOWUP = 1, + SAL_COLLAPSE = 2, + SAL_REM_ACCENTS = 4, +}; #define VIMSPELLMAGIC "VIMspell" // string at start of Vim spell file #define VIMSPELLMAGICL (sizeof(VIMSPELLMAGIC) - 1) #define VIMSPELLVERSION 50 // Section IDs. Only renumber them when VIMSPELLVERSION changes! -#define SN_REGION 0 // <regionname> section -#define SN_CHARFLAGS 1 // charflags section -#define SN_MIDWORD 2 // <midword> section -#define SN_PREFCOND 3 // <prefcond> section -#define SN_REP 4 // REP items section -#define SN_SAL 5 // SAL items section -#define SN_SOFO 6 // soundfolding section -#define SN_MAP 7 // MAP items section -#define SN_COMPOUND 8 // compound words section -#define SN_SYLLABLE 9 // syllable section -#define SN_NOBREAK 10 // NOBREAK section -#define SN_SUGFILE 11 // timestamp for .sug file -#define SN_REPSAL 12 // REPSAL items section -#define SN_WORDS 13 // common words -#define SN_NOSPLITSUGS 14 // don't split word for suggestions -#define SN_INFO 15 // info section -#define SN_NOCOMPOUNDSUGS 16 // don't compound for suggestions -#define SN_END 255 // end of sections +enum { + SN_REGION = 0, // <regionname> section + SN_CHARFLAGS = 1, // charflags section + SN_MIDWORD = 2, // <midword> section + SN_PREFCOND = 3, // <prefcond> section + SN_REP = 4, // REP items section + SN_SAL = 5, // SAL items section + SN_SOFO = 6, // soundfolding section + SN_MAP = 7, // MAP items section + SN_COMPOUND = 8, // compound words section + SN_SYLLABLE = 9, // syllable section + SN_NOBREAK = 10, // NOBREAK section + SN_SUGFILE = 11, // timestamp for .sug file + SN_REPSAL = 12, // REPSAL items section + SN_WORDS = 13, // common words + SN_NOSPLITSUGS = 14, // don't split word for suggestions + SN_INFO = 15, // info section + SN_NOCOMPOUNDSUGS = 16, // don't compound for suggestions + SN_END = 255, // end of sections +}; #define SNF_REQUIRED 1 // <sectionflags>: required section -#define CF_WORD 0x01 -#define CF_UPPER 0x02 +enum { + CF_WORD = 0x01, + CF_UPPER = 0x02, +}; static char *e_spell_trunc = N_("E758: Truncated spell file"); static char *e_illegal_character_in_word = N_("E1280: Illegal character in word"); @@ -313,7 +332,7 @@ static char *msg_compressing = N_("Compressing word tree..."); // and .dic file. // Main structure to store the contents of a ".aff" file. typedef struct afffile_S { - char_u *af_enc; // "SET", normalized, alloc'ed string or NULL + char *af_enc; // "SET", normalized, alloc'ed string or NULL int af_flagtype; // AFT_CHAR, AFT_LONG, AFT_NUM or AFT_CAPLONG unsigned af_rare; // RARE ID for rare word unsigned af_keepcase; // KEEPCASE ID for keep-case word @@ -341,12 +360,12 @@ typedef struct afffile_S { typedef struct affentry_S affentry_T; // Affix entry from ".aff" file. Used for prefixes and suffixes. struct affentry_S { - affentry_T *ae_next; // next affix with same name/number - char_u *ae_chop; // text to chop off basic word (can be NULL) - char_u *ae_add; // text to add to basic word (can be NULL) - char_u *ae_flags; // flags on the affix (can be NULL) - char_u *ae_cond; // condition (NULL for ".") - regprog_T *ae_prog; // regexp program for ae_cond or NULL + affentry_T *ae_next; // next affix with same name/number + char *ae_chop; // text to chop off basic word (can be NULL) + char *ae_add; // text to add to basic word (can be NULL) + char *ae_flags; // flags on the affix (can be NULL) + char *ae_cond; // condition (NULL for ".") + regprog_T *ae_prog; // regexp program for ae_cond or NULL char ae_compforbid; // COMPOUNDFORBIDFLAG found char ae_comppermit; // COMPOUNDPERMITFLAG found }; @@ -415,7 +434,7 @@ struct wordnode_S { // "wn_region" the LSW of the wordnr. char_u wn_affixID; // supported/required prefix ID or 0 uint16_t wn_flags; // WF_ flags - short wn_region; // region mask + int16_t wn_region; // region mask #ifdef SPELL_PRINTTREE int wn_nr; // sequence nr for printing @@ -460,7 +479,7 @@ typedef struct spellinfo_S { int si_memtot; // runtime memory used int si_verbose; // verbose messages int si_msg_count; // number of words added since last message - char_u *si_info; // info text chars or NULL + char *si_info; // info text chars or NULL int si_region_count; // number of regions supported (1 when there // are no regions) char_u si_region_name[MAXREGIONS * 2 + 1]; @@ -470,8 +489,8 @@ typedef struct spellinfo_S { garray_T si_rep; // list of fromto_T entries from REP lines garray_T si_repsal; // list of fromto_T entries from REPSAL lines garray_T si_sal; // list of fromto_T entries from SAL lines - char_u *si_sofofr; // SOFOFROM text - char_u *si_sofoto; // SOFOTO text + char *si_sofofr; // SOFOFROM text + char *si_sofoto; // SOFOTO text int si_nosugfile; // NOSUGFILE item found int si_nosplitsugs; // NOSPLITSUGS item found int si_nocompoundsugs; // NOCOMPOUNDSUGS item found @@ -481,16 +500,16 @@ typedef struct spellinfo_S { time_t si_sugtime; // timestamp for .sug file int si_rem_accents; // soundsalike: remove accents garray_T si_map; // MAP info concatenated - char_u *si_midword; // MIDWORD chars or NULL + char *si_midword; // MIDWORD chars or NULL int si_compmax; // max nr of words for compounding int si_compminlen; // minimal length for compounding int si_compsylmax; // max nr of syllables for compounding int si_compoptions; // COMP_ flags garray_T si_comppat; // CHECKCOMPOUNDPATTERN items, each stored as // a string - char_u *si_compflags; // flags used for compounding + char *si_compflags; // flags used for compounding char_u si_nobreak; // NOBREAK - char_u *si_syllable; // syllable string + char *si_syllable; // syllable string garray_T si_prefcond; // table with conditions for postponed // prefixes, each stored as a string int si_newprefID; // current value for ah_newID @@ -552,7 +571,7 @@ static inline int spell_check_magic_string(FILE *const fd) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE { char buf[VIMSPELLMAGICL]; - SPELL_READ_BYTES(buf, VIMSPELLMAGICL, fd,; ); + SPELL_READ_BYTES(buf, VIMSPELLMAGICL, fd,; ); // NOLINT(whitespace/parens) if (memcmp(buf, VIMSPELLMAGIC, VIMSPELLMAGICL) != 0) { return SP_FORMERROR; } @@ -701,7 +720,7 @@ slang_T *spell_load_file(char *fname, char *lang, slang_T *old_lp, bool silent) if (p == NULL) { goto endFAIL; } - set_map_str(lp, (char_u *)p); + set_map_str(lp, p); xfree(p); break; @@ -819,7 +838,7 @@ endOK: // Fill in the wordcount fields for a trie. // Returns the total number of words. -static void tree_count_words(char_u *byts, idx_T *idxs) +static void tree_count_words(const char_u *byts, idx_T *idxs) { int depth; idx_T arridx[MAXWLEN]; @@ -876,7 +895,7 @@ void suggest_load_files(void) slang_T *slang; char *dotp; FILE *fd; - char_u buf[MAXWLEN]; + char buf[MAXWLEN]; int i; time_t timestamp; int wcount; @@ -906,9 +925,9 @@ void suggest_load_files(void) // <SUGHEADER>: <fileID> <versionnr> <timestamp> for (i = 0; i < VIMSUGMAGICL; i++) { - buf[i] = (char_u)getc(fd); // <fileID> + buf[i] = (char)getc(fd); // <fileID> } - if (STRNCMP(buf, VIMSUGMAGIC, VIMSUGMAGICL) != 0) { + if (strncmp(buf, VIMSUGMAGIC, VIMSUGMAGICL) != 0) { semsg(_("E778: This does not look like a .sug file: %s"), slang->sl_fname); goto nextone; @@ -981,8 +1000,8 @@ someerror: // Need to put word counts in the word tries, so that we can find // a word by its number. - tree_count_words(slang->sl_fbyts, slang->sl_fidxs); - tree_count_words(slang->sl_sbyts, slang->sl_sidxs); + tree_count_words((char_u *)slang->sl_fbyts, slang->sl_fidxs); + tree_count_words((char_u *)slang->sl_sbyts, slang->sl_sidxs); nextone: if (fd != NULL) { @@ -1031,7 +1050,7 @@ static int read_region_section(FILE *fd, slang_T *lp, int len) if (len > MAXREGIONS * 2) { return SP_FORMERROR; } - SPELL_READ_NONNUL_BYTES((char *)lp->sl_regions, (size_t)len, fd,; ); + SPELL_READ_NONNUL_BYTES(lp->sl_regions, (size_t)len, fd,; ); // NOLINT(whitespace/parens) lp->sl_regions[len] = NUL; return 0; } @@ -1041,12 +1060,12 @@ static int read_region_section(FILE *fd, slang_T *lp, int len) // Return SP_*ERROR flags. static int read_charflags_section(FILE *fd) { - char_u *flags; + char *flags; char_u *fol; int flagslen, follen; // <charflagslen> <charflags> - flags = read_cnt_string(fd, 1, &flagslen); + flags = (char *)read_cnt_string(fd, 1, &flagslen); if (flagslen < 0) { return flagslen; } @@ -1060,7 +1079,7 @@ static int read_charflags_section(FILE *fd) // Set the word-char flags and fill SPELL_ISUPPER() table. if (flags != NULL && fol != NULL) { - set_spell_charflags(flags, flagslen, fol); + set_spell_charflags((char_u *)flags, flagslen, (char *)fol); } xfree(flags); @@ -1098,7 +1117,7 @@ static int read_prefcond_section(FILE *fd, slang_T *lp) if (n > 0) { char buf[MAXWLEN + 1]; buf[0] = '^'; // always match at one position only - SPELL_READ_NONNUL_BYTES(buf + 1, (size_t)n, fd,; ); + SPELL_READ_NONNUL_BYTES(buf + 1, (size_t)n, fd,; ); // NOLINT(whitespace/parens) buf[n + 1] = NUL; lp->sl_prefprog[i] = vim_regcomp(buf, RE_MAGIC | RE_STRING); } @@ -1121,17 +1140,17 @@ static int read_rep_section(FILE *fd, garray_T *gap, int16_t *first) ga_grow(gap, cnt); // <rep> : <repfromlen> <repfrom> <reptolen> <repto> - for (; gap->ga_len < cnt; ++gap->ga_len) { + for (; gap->ga_len < cnt; gap->ga_len++) { int c; ftp = &((fromto_T *)gap->ga_data)[gap->ga_len]; - ftp->ft_from = read_cnt_string(fd, 1, &c); + ftp->ft_from = (char *)read_cnt_string(fd, 1, &c); if (c < 0) { return c; } if (c == 0) { return SP_FORMERROR; } - ftp->ft_to = read_cnt_string(fd, 1, &c); + ftp->ft_to = (char *)read_cnt_string(fd, 1, &c); if (c <= 0) { xfree(ftp->ft_from); if (c < 0) { @@ -1147,8 +1166,8 @@ static int read_rep_section(FILE *fd, garray_T *gap, int16_t *first) } for (int i = 0; i < gap->ga_len; i++) { ftp = &((fromto_T *)gap->ga_data)[i]; - if (first[*ftp->ft_from] == -1) { - first[*ftp->ft_from] = (int16_t)i; + if (first[(uint8_t)(*ftp->ft_from)] == -1) { + first[(uint8_t)(*ftp->ft_from)] = (int16_t)i; } } return 0; @@ -1196,7 +1215,7 @@ static int read_sal_section(FILE *fd, slang_T *slang) return SP_TRUNCERROR; } p = xmalloc((size_t)ccnt + 2); - smp->sm_lead = p; + smp->sm_lead = (char *)p; // Read up to the first special char into sm_lead. int i = 0; @@ -1207,7 +1226,7 @@ static int read_sal_section(FILE *fd, slang_T *slang) } *p++ = (char_u)c; } - smp->sm_leadlen = (int)(p - smp->sm_lead); + smp->sm_leadlen = (int)(p - (char_u *)smp->sm_lead); *p++ = NUL; // Put (abc) chars in sm_oneof, if any. @@ -1229,7 +1248,7 @@ static int read_sal_section(FILE *fd, slang_T *slang) } // Any following chars go in sm_rules. - smp->sm_rules = p; + smp->sm_rules = (char *)p; if (i < ccnt) { // store the char we got while checking for end of sm_lead *p++ = (char_u)c; @@ -1244,7 +1263,7 @@ static int read_sal_section(FILE *fd, slang_T *slang) *p++ = NUL; // <saltolen> <salto> - smp->sm_to = read_cnt_string(fd, 1, &ccnt); + smp->sm_to = (char *)read_cnt_string(fd, 1, &ccnt); if (ccnt < 0) { xfree(smp->sm_lead); return ccnt; @@ -1256,7 +1275,7 @@ static int read_sal_section(FILE *fd, slang_T *slang) if (smp->sm_oneof == NULL) { smp->sm_oneof_w = NULL; } else { - smp->sm_oneof_w = mb_str2wide(smp->sm_oneof); + smp->sm_oneof_w = mb_str2wide((char *)smp->sm_oneof); } if (smp->sm_to == NULL) { smp->sm_to_w = NULL; @@ -1271,12 +1290,12 @@ static int read_sal_section(FILE *fd, slang_T *slang) smp = &((salitem_T *)gap->ga_data)[gap->ga_len]; p = xmalloc(1); p[0] = NUL; - smp->sm_lead = p; + smp->sm_lead = (char *)p; smp->sm_lead_w = mb_str2wide(smp->sm_lead); smp->sm_leadlen = 0; smp->sm_oneof = NULL; smp->sm_oneof_w = NULL; - smp->sm_rules = p; + smp->sm_rules = (char *)p; smp->sm_to = NULL; smp->sm_to_w = NULL; gap->ga_len++; @@ -1314,7 +1333,7 @@ static int read_words_section(FILE *fd, slang_T *lp, int len) } // Init the count to 10. - count_common_word(lp, word, -1, 10); + count_common_word(lp, (char *)word, -1, 10); done += i + 1; } return 0; @@ -1325,19 +1344,19 @@ static int read_words_section(FILE *fd, slang_T *lp, int len) static int read_sofo_section(FILE *fd, slang_T *slang) { int cnt; - char_u *from, *to; + char *from, *to; int res; slang->sl_sofo = true; // <sofofromlen> <sofofrom> - from = read_cnt_string(fd, 2, &cnt); + from = (char *)read_cnt_string(fd, 2, &cnt); if (cnt < 0) { return cnt; } // <sofotolen> <sofoto> - to = read_cnt_string(fd, 2, &cnt); + to = (char *)read_cnt_string(fd, 2, &cnt); if (cnt < 0) { xfree(from); return cnt; @@ -1407,7 +1426,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len) return SP_TRUNCERROR; } todo -= 2; - ga_init(gap, sizeof(char_u *), c); + ga_init(gap, sizeof(char *), c); ga_grow(gap, c); while (--c >= 0) { ((char **)(gap->ga_data))[gap->ga_len++] = (char *)read_cnt_string(fd, 1, &cnt); @@ -1428,25 +1447,25 @@ static int read_compound(FILE *fd, slang_T *slang, int len) // Conversion to utf-8 may double the size. c = todo * 2 + 7; c += todo * 2; - char_u *pat = xmalloc((size_t)c); + char *pat = xmalloc((size_t)c); // We also need a list of all flags that can appear at the start and one // for all flags. - char_u *cp = xmalloc((size_t)todo + 1); + uint8_t *cp = xmalloc((size_t)todo + 1); slang->sl_compstartflags = cp; *cp = NUL; - char_u *ap = xmalloc((size_t)todo + 1); + uint8_t *ap = xmalloc((size_t)todo + 1); slang->sl_compallflags = ap; *ap = NUL; // And a list of all patterns in their original form, for checking whether // compounding may work in match_compoundrule(). This is freed when we // encounter a wildcard, the check doesn't work then. - char_u *crp = xmalloc((size_t)todo + 1); + uint8_t *crp = xmalloc((size_t)todo + 1); slang->sl_comprules = crp; - char_u *pp = pat; + char_u *pp = (char_u *)pat; *pp++ = '^'; *pp++ = '\\'; *pp++ = '('; @@ -1515,7 +1534,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len) *crp = NUL; } - slang->sl_compprog = vim_regcomp((char *)pat, RE_MAGIC + RE_STRING + RE_STRICT); + slang->sl_compprog = vim_regcomp(pat, RE_MAGIC + RE_STRING + RE_STRICT); xfree(pat); if (slang->sl_compprog == NULL) { return SP_FORMERROR; @@ -1526,10 +1545,10 @@ static int read_compound(FILE *fd, slang_T *slang, int len) // Set the SOFOFROM and SOFOTO items in language "lp". // Returns SP_*ERROR flags when there is something wrong. -static int set_sofo(slang_T *lp, char_u *from, char_u *to) +static int set_sofo(slang_T *lp, char *from, char *to) { - char_u *s; - char_u *p; + char *s; + char *p; // Use "sl_sal" as an array with 256 pointers to a list of wide // characters. The index is the low byte of the character. @@ -1544,7 +1563,7 @@ static int set_sofo(slang_T *lp, char_u *from, char_u *to) // First count the number of items for each list. Temporarily use // sl_sal_first[] for this. for (p = from, s = to; *p != NUL && *s != NUL;) { - const int c = mb_cptr2char_adv((const char_u **)&p); + const int c = mb_cptr2char_adv((const char **)&p); MB_CPTR_ADV(s); if (c >= 256) { lp->sl_sal_first[c & 0xff]++; @@ -1567,8 +1586,8 @@ static int set_sofo(slang_T *lp, char_u *from, char_u *to) // list. memset(lp->sl_sal_first, 0, sizeof(salfirst_T) * 256); for (p = from, s = to; *p != NUL && *s != NUL;) { - const int c = mb_cptr2char_adv((const char_u **)&p); - const int i = mb_cptr2char_adv((const char_u **)&s); + const int c = mb_cptr2char_adv((const char **)&p); + const int i = mb_cptr2char_adv((const char **)&s); if (c >= 256) { // Append the from-to chars at the end of the list with // the low byte. @@ -1637,13 +1656,13 @@ static void set_sal_first(slang_T *lp) // Turn a multi-byte string into a wide character string. // Return it in allocated memory. -static int *mb_str2wide(char_u *s) +static int *mb_str2wide(char *s) { int i = 0; int *res = xmalloc(((size_t)mb_charlen(s) + 1) * sizeof(int)); - for (char_u *p = s; *p != NUL;) { - res[i++] = mb_ptr2char_adv((const char_u **)&p); + for (char *p = s; *p != NUL;) { + res[i++] = mb_ptr2char_adv((const char **)&p); } res[i] = NUL; @@ -1658,12 +1677,12 @@ static int *mb_str2wide(char_u *s) /// @param prefixcnt when "prefixtree" is true: prefix count /// /// @return zero when OK, SP_ value for an error. -static int spell_read_tree(FILE *fd, char_u **bytsp, long *bytsp_len, idx_T **idxsp, - bool prefixtree, int prefixcnt) +static int spell_read_tree(FILE *fd, char **bytsp, long *bytsp_len, idx_T **idxsp, bool prefixtree, + int prefixcnt) FUNC_ATTR_NONNULL_ARG(1, 2, 4) { int idx; - char_u *bp; + char *bp; idx_T *ip; // The tree size was computed when writing the file, so that we can @@ -1676,23 +1695,25 @@ static int spell_read_tree(FILE *fd, char_u **bytsp, long *bytsp_len, idx_T **id // Invalid length, multiply with sizeof(int) would overflow. return SP_FORMERROR; } - if (len > 0) { - // Allocate the byte array. - bp = xmalloc((size_t)len); - *bytsp = bp; - if (bytsp_len != NULL) { - *bytsp_len = len; - } + if (len <= 0) { + return 0; + } - // Allocate the index array. - ip = xcalloc((size_t)len, sizeof(*ip)); - *idxsp = ip; + // Allocate the byte array. + bp = xmalloc((size_t)len); + *bytsp = bp; + if (bytsp_len != NULL) { + *bytsp_len = len; + } - // Recursively read the tree and store it in the array. - idx = read_tree_node(fd, bp, ip, (int)len, 0, prefixtree, prefixcnt); - if (idx < 0) { - return idx; - } + // Allocate the index array. + ip = xcalloc((size_t)len, sizeof(*ip)); + *idxsp = ip; + + // Recursively read the tree and store it in the array. + idx = read_tree_node(fd, (char_u *)bp, ip, (int)len, 0, prefixtree, prefixcnt); + if (idx < 0) { + return idx; } return 0; } @@ -1816,15 +1837,15 @@ static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx /// Reload the spell file "fname" if it's loaded. /// /// @param added_word invoked through "zg" -static void spell_reload_one(char_u *fname, bool added_word) +static void spell_reload_one(char *fname, bool added_word) { slang_T *slang; bool didit = false; for (slang = first_lang; slang != NULL; slang = slang->sl_next) { - if (path_full_compare((char *)fname, slang->sl_fname, false, true) == kEqualFiles) { + if (path_full_compare(fname, slang->sl_fname, false, true) == kEqualFiles) { slang_clear(slang); - if (spell_load_file((char *)fname, NULL, slang, false) == NULL) { + if (spell_load_file(fname, NULL, slang, false) == NULL) { // reloading failed, clear the language slang_clear(slang); } @@ -1984,26 +2005,27 @@ static void spell_print_node(wordnode_T *node, int depth) static void spell_print_tree(wordnode_T *root) { - if (root != NULL) { - // Clear the "wn_u1.index" fields, used to remember what has been - // done. - spell_clear_flags(root); - - // Recursively print the tree. - spell_print_node(root, 0); + if (root == NULL) { + return; } + + // Clear the "wn_u1.index" fields, used to remember what has been done. + spell_clear_flags(root); + + // Recursively print the tree. + spell_print_node(root, 0); } #endif // SPELL_PRINTTREE // Reads the affix file "fname". // Returns an afffile_T, NULL for complete failure. -static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) +static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname) { FILE *fd; - char_u rline[MAXLINELEN]; - char_u *line; - char_u *pc = NULL; + char rline[MAXLINELEN]; + char *line; + char *pc = NULL; #define MAXITEMCNT 30 char *(items[MAXITEMCNT]); int itemcnt; @@ -2023,26 +2045,26 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) bool found_map = false; hashitem_T *hi; int l; - int compminlen = 0; // COMPOUNDMIN value - int compsylmax = 0; // COMPOUNDSYLMAX value - int compoptions = 0; // COMP_ flags - int compmax = 0; // COMPOUNDWORDMAX value - char_u *compflags = NULL; // COMPOUNDFLAG and COMPOUNDRULE + int compminlen = 0; // COMPOUNDMIN value + int compsylmax = 0; // COMPOUNDSYLMAX value + int compoptions = 0; // COMP_ flags + int compmax = 0; // COMPOUNDWORDMAX value + char *compflags = NULL; // COMPOUNDFLAG and COMPOUNDRULE // concatenated - char_u *midword = NULL; // MIDWORD value - char_u *syllable = NULL; // SYLLABLE value - char_u *sofofrom = NULL; // SOFOFROM value - char_u *sofoto = NULL; // SOFOTO value + char *midword = NULL; // MIDWORD value + char *syllable = NULL; // SYLLABLE value + char *sofofrom = NULL; // SOFOFROM value + char *sofoto = NULL; // SOFOTO value // Open the file. - fd = os_fopen((char *)fname, "r"); + fd = os_fopen(fname, "r"); if (fd == NULL) { semsg(_(e_notopen), fname); return NULL; } - vim_snprintf((char *)IObuff, IOSIZE, _("Reading affix file %s..."), fname); - spell_message(spin, (char *)IObuff); + vim_snprintf(IObuff, IOSIZE, _("Reading affix file %s..."), fname); + spell_message(spin, IObuff); // Only do REP lines when not done in another .aff file already. do_rep = GA_EMPTY(&spin->si_rep); @@ -2075,7 +2097,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // Convert from "SET" to 'encoding' when needed. xfree(pc); if (spin->si_conv.vc_type != CONV_NONE) { - pc = (char_u *)string_convert(&spin->si_conv, (char *)rline, NULL); + pc = string_convert(&spin->si_conv, rline, NULL); if (pc == NULL) { smsg(_("Conversion failure for word in %s line %d: %s"), fname, lnum, rline); @@ -2090,7 +2112,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // Split the line up in white separated items. Put a NUL after each // item. itemcnt = 0; - for (p = (char *)line;;) { + for (p = line;;) { while (*p != NUL && (uint8_t)(*p) <= ' ') { // skip white space and CR/NL p++; } @@ -2121,9 +2143,9 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) if (itemcnt > 0) { if (is_aff_rule(items, itemcnt, "SET", 2) && aff->af_enc == NULL) { // Setup for conversion from "ENC" to 'encoding'. - aff->af_enc = (char_u *)enc_canonize((char *)items[1]); + aff->af_enc = enc_canonize((char *)items[1]); if (!spin->si_ascii - && convert_setup(&spin->si_conv, (char *)aff->af_enc, p_enc) == FAIL) { + && convert_setup(&spin->si_conv, aff->af_enc, p_enc) == FAIL) { smsg(_("Conversion in %s not supported: from %s to %s"), fname, aff->af_enc, p_enc); } @@ -2156,7 +2178,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) } } else if (spell_info_item(items[0]) && itemcnt > 1) { p = getroom(spin, - (spin->si_info == NULL ? 0 : STRLEN(spin->si_info)) + (spin->si_info == NULL ? 0 : strlen(spin->si_info)) + strlen(items[0]) + strlen(items[1]) + 3, false); if (spin->si_info != NULL) { @@ -2166,63 +2188,49 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) STRCAT(p, items[0]); STRCAT(p, " "); STRCAT(p, items[1]); - spin->si_info = (char_u *)p; + spin->si_info = p; } else if (is_aff_rule(items, itemcnt, "MIDWORD", 2) && midword == NULL) { - midword = (char_u *)getroom_save(spin, (char_u *)items[1]); + midword = getroom_save(spin, items[1]); } else if (is_aff_rule(items, itemcnt, "TRY", 2)) { // ignored, we look in the tree for what chars may appear - } - // TODO: remove "RAR" later - else if ((is_aff_rule(items, itemcnt, "RAR", 2) - || is_aff_rule(items, itemcnt, "RARE", 2)) - && aff->af_rare == 0) { - aff->af_rare = affitem2flag(aff->af_flagtype, (char_u *)items[1], - fname, lnum); - } - // TODO: remove "KEP" later - else if ((is_aff_rule(items, itemcnt, "KEP", 2) - || is_aff_rule(items, itemcnt, "KEEPCASE", 2)) - && aff->af_keepcase == 0) { - aff->af_keepcase = affitem2flag(aff->af_flagtype, (char_u *)items[1], - fname, lnum); + } else if ((is_aff_rule(items, itemcnt, "RAR", 2) // TODO(vim): remove "RAR" later + || is_aff_rule(items, itemcnt, "RARE", 2)) + && aff->af_rare == 0) { + aff->af_rare = affitem2flag(aff->af_flagtype, items[1], fname, lnum); + } else if ((is_aff_rule(items, itemcnt, "KEP", 2) // TODO(vim): remove "KEP" later + || is_aff_rule(items, itemcnt, "KEEPCASE", 2)) + && aff->af_keepcase == 0) { + aff->af_keepcase = affitem2flag(aff->af_flagtype, items[1], fname, lnum); } else if ((is_aff_rule(items, itemcnt, "BAD", 2) || is_aff_rule(items, itemcnt, "FORBIDDENWORD", 2)) && aff->af_bad == 0) { - aff->af_bad = affitem2flag(aff->af_flagtype, (char_u *)items[1], - fname, lnum); + aff->af_bad = affitem2flag(aff->af_flagtype, items[1], fname, lnum); } else if (is_aff_rule(items, itemcnt, "NEEDAFFIX", 2) && aff->af_needaffix == 0) { - aff->af_needaffix = affitem2flag(aff->af_flagtype, (char_u *)items[1], - fname, lnum); + aff->af_needaffix = affitem2flag(aff->af_flagtype, items[1], fname, lnum); } else if (is_aff_rule(items, itemcnt, "CIRCUMFIX", 2) && aff->af_circumfix == 0) { - aff->af_circumfix = affitem2flag(aff->af_flagtype, (char_u *)items[1], - fname, lnum); + aff->af_circumfix = affitem2flag(aff->af_flagtype, items[1], fname, lnum); } else if (is_aff_rule(items, itemcnt, "NOSUGGEST", 2) && aff->af_nosuggest == 0) { - aff->af_nosuggest = affitem2flag(aff->af_flagtype, (char_u *)items[1], - fname, lnum); + aff->af_nosuggest = affitem2flag(aff->af_flagtype, items[1], fname, lnum); } else if ((is_aff_rule(items, itemcnt, "NEEDCOMPOUND", 2) || is_aff_rule(items, itemcnt, "ONLYINCOMPOUND", 2)) && aff->af_needcomp == 0) { - aff->af_needcomp = affitem2flag(aff->af_flagtype, (char_u *)items[1], - fname, lnum); + aff->af_needcomp = affitem2flag(aff->af_flagtype, items[1], fname, lnum); } else if (is_aff_rule(items, itemcnt, "COMPOUNDROOT", 2) && aff->af_comproot == 0) { - aff->af_comproot = affitem2flag(aff->af_flagtype, (char_u *)items[1], - fname, lnum); + aff->af_comproot = affitem2flag(aff->af_flagtype, items[1], fname, lnum); } else if (is_aff_rule(items, itemcnt, "COMPOUNDFORBIDFLAG", 2) && aff->af_compforbid == 0) { - aff->af_compforbid = affitem2flag(aff->af_flagtype, (char_u *)items[1], - fname, lnum); + aff->af_compforbid = affitem2flag(aff->af_flagtype, items[1], fname, lnum); if (aff->af_pref.ht_used > 0) { smsg(_("Defining COMPOUNDFORBIDFLAG after PFX item may give wrong results in %s line %d"), fname, lnum); } } else if (is_aff_rule(items, itemcnt, "COMPOUNDPERMITFLAG", 2) && aff->af_comppermit == 0) { - aff->af_comppermit = affitem2flag(aff->af_flagtype, (char_u *)items[1], - fname, lnum); + aff->af_comppermit = affitem2flag(aff->af_flagtype, items[1], fname, lnum); if (aff->af_pref.ht_used > 0) { smsg(_("Defining COMPOUNDPERMITFLAG after PFX item may give wrong results in %s line %d"), fname, lnum); @@ -2234,7 +2242,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) p = getroom(spin, strlen(items[1]) + 2, false); STRCPY(p, items[1]); STRCAT(p, "+"); - compflags = (char_u *)p; + compflags = p; } else if (is_aff_rule(items, itemcnt, "COMPOUNDRULES", 2)) { // We don't use the count, but do check that it's a number and // not COMPOUNDRULE mistyped. @@ -2249,7 +2257,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // using a slash to separate them. l = (int)strlen(items[1]) + 1; if (compflags != NULL) { - l += (int)STRLEN(compflags) + 1; + l += (int)strlen(compflags) + 1; } p = getroom(spin, (size_t)l, false); if (compflags != NULL) { @@ -2257,7 +2265,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) STRCAT(p, "/"); } STRCAT(p, items[1]); - compflags = (char_u *)p; + compflags = p; } } else if (is_aff_rule(items, itemcnt, "COMPOUNDWORDMAX", 2) && compmax == 0) { @@ -2306,12 +2314,12 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) } if (i >= gap->ga_len) { ga_grow(gap, 2); - ((char **)(gap->ga_data))[gap->ga_len++] = getroom_save(spin, (char_u *)items[1]); - ((char **)(gap->ga_data))[gap->ga_len++] = getroom_save(spin, (char_u *)items[2]); + ((char **)(gap->ga_data))[gap->ga_len++] = getroom_save(spin, items[1]); + ((char **)(gap->ga_data))[gap->ga_len++] = getroom_save(spin, items[2]); } } else if (is_aff_rule(items, itemcnt, "SYLLABLE", 2) && syllable == NULL) { - syllable = (char_u *)getroom_save(spin, (char_u *)items[1]); + syllable = getroom_save(spin, items[1]); } else if (is_aff_rule(items, itemcnt, "NOBREAK", 1)) { spin->si_nobreak = true; } else if (is_aff_rule(items, itemcnt, "NOSPLITSUGS", 1)) { @@ -2329,7 +2337,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) && aff_todo == 0 && itemcnt >= 4) { int lasti = 4; - char_u key[AH_KEY_LEN]; + char key[AH_KEY_LEN]; if (*items[0] == 'P') { tp = &aff->af_pref; @@ -2341,7 +2349,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // times. The affix files that do this have an undocumented // "S" flag on all but the last block, thus we check for that // and store it in ah_follows. - STRLCPY(key, items[1], AH_KEY_LEN); + xstrlcpy(key, items[1], AH_KEY_LEN); hi = hash_find(tp, (char *)key); if (!HASHITEM_EMPTY(hi)) { cur_aff = HI2AH(hi); @@ -2356,8 +2364,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) } else { // New affix letter. cur_aff = getroom(spin, sizeof(*cur_aff), true); - cur_aff->ah_flag = affitem2flag(aff->af_flagtype, (char_u *)items[1], - fname, lnum); + cur_aff->ah_flag = affitem2flag(aff->af_flagtype, items[1], fname, lnum); if (cur_aff->ah_flag == 0 || strlen(items[1]) >= AH_KEY_LEN) { break; } @@ -2375,7 +2382,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) fname, lnum, items[1]); } STRCPY(cur_aff->ah_key, items[1]); - hash_add(tp, (char_u *)cur_aff->ah_key); + hash_add(tp, cur_aff->ah_key); cur_aff->ah_combine = (*items[2] == 'Y'); } @@ -2444,13 +2451,13 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) aff_entry = getroom(spin, sizeof(*aff_entry), true); if (strcmp(items[2], "0") != 0) { - aff_entry->ae_chop = (char_u *)getroom_save(spin, (char_u *)items[2]); + aff_entry->ae_chop = getroom_save(spin, items[2]); } if (strcmp(items[3], "0") != 0) { - aff_entry->ae_add = (char_u *)getroom_save(spin, (char_u *)items[3]); + aff_entry->ae_add = getroom_save(spin, items[3]); // Recognize flags on the affix: abcd/XYZ - aff_entry->ae_flags = (char_u *)vim_strchr((char *)aff_entry->ae_add, '/'); + aff_entry->ae_flags = vim_strchr(aff_entry->ae_add, '/'); if (aff_entry->ae_flags != NULL) { *aff_entry->ae_flags++ = NUL; aff_process_flags(aff, aff_entry); @@ -2465,15 +2472,15 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) cur_aff->ah_first = aff_entry; if (strcmp(items[4], ".") != 0) { - char_u buf[MAXLINELEN]; + char buf[MAXLINELEN]; - aff_entry->ae_cond = (char_u *)getroom_save(spin, (char_u *)items[4]); + aff_entry->ae_cond = getroom_save(spin, items[4]); if (*items[0] == 'P') { - sprintf((char *)buf, "^%s", items[4]); + sprintf(buf, "^%s", items[4]); // NOLINT(runtime/printf) } else { - sprintf((char *)buf, "%s$", items[4]); + sprintf(buf, "%s$", items[4]); // NOLINT(runtime/printf) } - aff_entry->ae_prog = vim_regcomp((char *)buf, RE_MAGIC + RE_STRING + RE_STRICT); + aff_entry->ae_prog = vim_regcomp(buf, RE_MAGIC + RE_STRING + RE_STRICT); if (aff_entry->ae_prog == NULL) { smsg(_("Broken condition in %s line %d: %s"), fname, lnum, items[4]); @@ -2493,17 +2500,16 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // be empty or start with the same letter. if (aff_entry->ae_chop != NULL && aff_entry->ae_add != NULL - && aff_entry->ae_chop[utfc_ptr2len((char *)aff_entry->ae_chop)] == + && aff_entry->ae_chop[utfc_ptr2len(aff_entry->ae_chop)] == NUL) { int c, c_up; - c = utf_ptr2char((char *)aff_entry->ae_chop); + c = utf_ptr2char(aff_entry->ae_chop); c_up = SPELL_TOUPPER(c); if (c_up != c && (aff_entry->ae_cond == NULL - || utf_ptr2char((char *)aff_entry->ae_cond) == c)) { - p = (char *)aff_entry->ae_add - + STRLEN(aff_entry->ae_add); + || utf_ptr2char(aff_entry->ae_cond) == c)) { + p = aff_entry->ae_add + strlen(aff_entry->ae_add); MB_PTR_BACK(aff_entry->ae_add, p); if (utf_ptr2char(p) == c_up) { upper = true; @@ -2514,14 +2520,13 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // actual word, thus must check for the // upper-case letter. if (aff_entry->ae_cond != NULL) { - char_u buf[MAXLINELEN]; - onecap_copy((char_u *)items[4], buf, true); - aff_entry->ae_cond = (char_u *)getroom_save(spin, buf); + char buf[MAXLINELEN]; + onecap_copy(items[4], buf, true); + aff_entry->ae_cond = getroom_save(spin, buf); if (aff_entry->ae_cond != NULL) { - sprintf((char *)buf, "^%s", - aff_entry->ae_cond); + sprintf(buf, "^%s", aff_entry->ae_cond); // NOLINT(runtime/printf) vim_regfree(aff_entry->ae_prog); - aff_entry->ae_prog = vim_regcomp((char *)buf, RE_MAGIC + RE_STRING); + aff_entry->ae_prog = vim_regcomp(buf, RE_MAGIC + RE_STRING); } } } @@ -2536,7 +2541,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // Find a previously used condition. for (idx = spin->si_prefcond.ga_len - 1; idx >= 0; idx--) { p = ((char **)spin->si_prefcond.ga_data)[idx]; - if (str_equal(p, (char *)aff_entry->ae_cond)) { + if (str_equal(p, aff_entry->ae_cond)) { break; } } @@ -2552,7 +2557,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) if (aff_entry->ae_add == NULL) { p = ""; } else { - p = (char *)aff_entry->ae_add; + p = aff_entry->ae_add; } // PFX_FLAGS is a negative number, so that @@ -2591,7 +2596,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) } else if (is_aff_rule(items, itemcnt, "REP", 2) || is_aff_rule(items, itemcnt, "REPSAL", 2)) { // Ignore REP/REPSAL count - if (!isdigit(*items[1])) { + if (!isdigit((uint8_t)(*items[1]))) { smsg(_("Expected REP(SAL) count in %s line %d"), fname, lnum); } @@ -2619,14 +2624,14 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) } add_fromto(spin, items[0][3] == 'S' ? &spin->si_repsal - : &spin->si_rep, (char_u *)items[1], (char_u *)items[2]); + : &spin->si_rep, items[1], items[2]); } } else if (is_aff_rule(items, itemcnt, "MAP", 2)) { // MAP item or count if (!found_map) { // First line contains the count. found_map = true; - if (!isdigit(*items[1])) { + if (!isdigit((uint8_t)(*items[1]))) { smsg(_("Expected MAP count in %s line %d"), fname, lnum); } @@ -2635,7 +2640,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // Check that every character appears only once. for (p = items[1]; *p != NUL;) { - c = mb_ptr2char_adv((const char_u **)&p); + c = mb_ptr2char_adv((const char **)&p); if ((!GA_EMPTY(&spin->si_map) && vim_strchr(spin->si_map.ga_data, c) != NULL) @@ -2664,24 +2669,24 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) spin->si_rem_accents = sal_to_bool(items[2]); } else { // when "to" is "_" it means empty - add_fromto(spin, &spin->si_sal, (char_u *)items[1], - strcmp(items[2], "_") == 0 ? (char_u *)"" - : (char_u *)items[2]); + add_fromto(spin, &spin->si_sal, items[1], + strcmp(items[2], "_") == 0 ? "" + : items[2]); } } } else if (is_aff_rule(items, itemcnt, "SOFOFROM", 2) && sofofrom == NULL) { - sofofrom = (char_u *)getroom_save(spin, (char_u *)items[1]); + sofofrom = getroom_save(spin, items[1]); } else if (is_aff_rule(items, itemcnt, "SOFOTO", 2) && sofoto == NULL) { - sofoto = (char_u *)getroom_save(spin, (char_u *)items[1]); + sofoto = getroom_save(spin, items[1]); } else if (strcmp(items[0], "COMMON") == 0) { int i; for (i = 1; i < itemcnt; i++) { if (HASHITEM_EMPTY(hash_find(&spin->si_commonwords, (char *)items[i]))) { p = xstrdup(items[i]); - hash_add(&spin->si_commonwords, (char_u *)p); + hash_add(&spin->si_commonwords, p); } } } else { @@ -2744,7 +2749,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) } if (syllable != NULL) { - aff_check_string((char *)spin->si_syllable, (char *)syllable, "SYLLABLE"); + aff_check_string(spin->si_syllable, syllable, "SYLLABLE"); spin->si_syllable = syllable; } @@ -2755,15 +2760,15 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) } else if (!GA_EMPTY(&spin->si_sal)) { smsg(_("Both SAL and SOFO lines in %s"), fname); } else { - aff_check_string((char *)spin->si_sofofr, (char *)sofofrom, "SOFOFROM"); - aff_check_string((char *)spin->si_sofoto, (char *)sofoto, "SOFOTO"); + aff_check_string(spin->si_sofofr, sofofrom, "SOFOFROM"); + aff_check_string(spin->si_sofoto, sofoto, "SOFOTO"); spin->si_sofofr = sofofrom; spin->si_sofoto = sofoto; } } if (midword != NULL) { - aff_check_string((char *)spin->si_midword, (char *)midword, "MIDWORD"); + aff_check_string(spin->si_midword, midword, "MIDWORD"); spin->si_midword = midword; } @@ -2785,18 +2790,18 @@ static bool is_aff_rule(char **items, int itemcnt, char *rulename, int mincount) // ae_flags to ae_comppermit and ae_compforbid. static void aff_process_flags(afffile_T *affile, affentry_T *entry) { - char_u *p; + char *p; char_u *prevp; unsigned flag; if (entry->ae_flags != NULL && (affile->af_compforbid != 0 || affile->af_comppermit != 0)) { for (p = entry->ae_flags; *p != NUL;) { - prevp = p; + prevp = (char_u *)p; flag = get_affitem(affile->af_flagtype, &p); if (flag == affile->af_comppermit || flag == affile->af_compforbid) { - STRMOVE(prevp, p); - p = prevp; + STRMOVE(prevp, (char *)p); + p = (char *)prevp; if (flag == affile->af_comppermit) { entry->ae_comppermit = true; } else { @@ -2826,10 +2831,10 @@ static bool spell_info_item(char *s) // Turn an affix flag name into a number, according to the FLAG type. // returns zero for failure. -static unsigned affitem2flag(int flagtype, char_u *item, char_u *fname, int lnum) +static unsigned affitem2flag(int flagtype, char *item, char *fname, int lnum) { unsigned res; - char_u *p = item; + char *p = item; res = get_affitem(flagtype, &p); if (res == 0) { @@ -2852,54 +2857,54 @@ static unsigned affitem2flag(int flagtype, char_u *item, char_u *fname, int lnum // Get one affix name from "*pp" and advance the pointer. // Returns ZERO_FLAG for "0". // Returns zero for an error, still advances the pointer then. -static unsigned get_affitem(int flagtype, char_u **pp) +static unsigned get_affitem(int flagtype, char **pp) { int res; if (flagtype == AFT_NUM) { if (!ascii_isdigit(**pp)) { - ++*pp; // always advance, avoid getting stuck + (*pp)++; // always advance, avoid getting stuck return 0; } - res = getdigits_int((char **)pp, true, 0); + res = getdigits_int(pp, true, 0); if (res == 0) { res = ZERO_FLAG; } } else { - res = mb_ptr2char_adv((const char_u **)pp); + res = mb_ptr2char_adv((const char **)pp); if (flagtype == AFT_LONG || (flagtype == AFT_CAPLONG && res >= 'A' && res <= 'Z')) { if (**pp == NUL) { return 0; } - res = mb_ptr2char_adv((const char_u **)pp) + (res << 16); + res = mb_ptr2char_adv((const char **)pp) + (res << 16); } } return (unsigned)res; } -// Process the "compflags" string used in an affix file and append it to -// spin->si_compflags. -// The processing involves changing the affix names to ID numbers, so that -// they fit in one byte. -static void process_compflags(spellinfo_T *spin, afffile_T *aff, char_u *compflags) +/// Process the "compflags" string used in an affix file and append it to +/// spin->si_compflags. +/// The processing involves changing the affix names to ID numbers, so that +/// they fit in one byte. +static void process_compflags(spellinfo_T *spin, afffile_T *aff, char *compflags) { - char_u *p; - char_u *prevp; + char *p; + char *prevp; unsigned flag; compitem_T *ci; int id; int len; char_u *tp; - char_u key[AH_KEY_LEN]; + char key[AH_KEY_LEN]; hashitem_T *hi; // Make room for the old and the new compflags, concatenated with a / in // between. Processing it makes it shorter, but we don't know by how // much, thus allocate the maximum. - len = (int)STRLEN(compflags) + 1; + len = (int)strlen(compflags) + 1; if (spin->si_compflags != NULL) { - len += (int)STRLEN(spin->si_compflags) + 1; + len += (int)strlen(spin->si_compflags) + 1; } p = getroom(spin, (size_t)len, false); if (spin->si_compflags != NULL) { @@ -2907,12 +2912,12 @@ static void process_compflags(spellinfo_T *spin, afffile_T *aff, char_u *compfla STRCAT(p, "/"); } spin->si_compflags = p; - tp = p + STRLEN(p); + tp = (char_u *)p + strlen(p); for (p = compflags; *p != NUL;) { - if (vim_strchr("/?*+[]", *p) != NULL) { + if (vim_strchr("/?*+[]", (uint8_t)(*p)) != NULL) { // Copy non-flag characters directly. - *tp++ = *p++; + *tp++ = (char_u)(*p++); } else { // First get the flag number, also checks validity. prevp = p; @@ -2920,7 +2925,7 @@ static void process_compflags(spellinfo_T *spin, afffile_T *aff, char_u *compfla if (flag != 0) { // Find the flag in the hashtable. If it was used before, use // the existing ID. Otherwise add a new entry. - STRLCPY(key, prevp, p - prevp + 1); + xstrlcpy(key, prevp, (size_t)(p - prevp) + 1); hi = hash_find(&aff->af_comp, (char *)key); if (!HASHITEM_EMPTY(hi)) { id = HI2CI(hi)->ci_newID; @@ -2935,7 +2940,7 @@ static void process_compflags(spellinfo_T *spin, afffile_T *aff, char_u *compfla id = spin->si_newcompID--; } while (vim_strchr("/?*+[]\\-^", id) != NULL); ci->ci_newID = id; - hash_add(&aff->af_comp, ci->ci_key); + hash_add(&aff->af_comp, (char *)ci->ci_key); } *tp++ = (char_u)id; } @@ -2961,22 +2966,22 @@ static void check_renumber(spellinfo_T *spin) } // Returns true if flag "flag" appears in affix list "afflist". -static bool flag_in_afflist(int flagtype, char_u *afflist, unsigned flag) +static bool flag_in_afflist(int flagtype, char *afflist, unsigned flag) { char *p; unsigned n; switch (flagtype) { case AFT_CHAR: - return vim_strchr((char *)afflist, (int)flag) != NULL; + return vim_strchr(afflist, (int)flag) != NULL; case AFT_CAPLONG: case AFT_LONG: - for (p = (char *)afflist; *p != NUL;) { - n = (unsigned)mb_ptr2char_adv((const char_u **)&p); + for (p = afflist; *p != NUL;) { + n = (unsigned)mb_ptr2char_adv((const char **)&p); if ((flagtype == AFT_LONG || (n >= 'A' && n <= 'Z')) && *p != NUL) { - n = (unsigned)mb_ptr2char_adv((const char_u **)&p) + (n << 16); + n = (unsigned)mb_ptr2char_adv((const char **)&p) + (n << 16); } if (n == flag) { return true; @@ -2985,7 +2990,7 @@ static bool flag_in_afflist(int flagtype, char_u *afflist, unsigned flag) break; case AFT_NUM: - for (p = (char *)afflist; *p != NUL;) { + for (p = afflist; *p != NUL;) { int digits = getdigits_int(&p, true, 0); assert(digits >= 0); n = (unsigned int)digits; @@ -3032,17 +3037,17 @@ static bool str_equal(char *s1, char *s2) return strcmp(s1, s2) == 0; } -// Add a from-to item to "gap". Used for REP and SAL items. -// They are stored case-folded. -static void add_fromto(spellinfo_T *spin, garray_T *gap, char_u *from, char_u *to) +/// Add a from-to item to "gap". Used for REP and SAL items. +/// They are stored case-folded. +static void add_fromto(spellinfo_T *spin, garray_T *gap, char *from, char *to) { - char_u word[MAXWLEN]; + char word[MAXWLEN]; fromto_T *ftp = GA_APPEND_VIA_PTR(fromto_T, gap); - (void)spell_casefold(curwin, from, (int)STRLEN(from), word, MAXWLEN); - ftp->ft_from = (char_u *)getroom_save(spin, word); - (void)spell_casefold(curwin, to, (int)STRLEN(to), word, MAXWLEN); - ftp->ft_to = (char_u *)getroom_save(spin, word); + (void)spell_casefold(curwin, from, (int)strlen(from), word, MAXWLEN); + ftp->ft_from = getroom_save(spin, word); + (void)spell_casefold(curwin, to, (int)strlen(to), word, MAXWLEN); + ftp->ft_to = getroom_save(spin, word); } /// Converts a boolean argument in a SAL line to true or false; @@ -3086,16 +3091,16 @@ static void spell_free_aff(afffile_T *aff) // Read dictionary file "fname". // Returns OK or FAIL; -static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) +static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile) { hashtab_T ht; - char_u line[MAXLINELEN]; + char line[MAXLINELEN]; char_u *p; char_u *afflist; char_u store_afflist[MAXWLEN]; int pfxlen; bool need_affix; - char_u *dw; + char *dw; char_u *pc; char_u *w; int l; @@ -3111,7 +3116,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) Timestamp last_msg_time = 0; // Open the file. - fd = os_fopen((char *)fname, "r"); + fd = os_fopen(fname, "r"); if (fd == NULL) { semsg(_(e_notopen), fname); return FAIL; @@ -3120,22 +3125,22 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) // The hashtable is only used to detect duplicated words. hash_init(&ht); - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Reading dictionary file %s..."), fname); - spell_message(spin, (char *)IObuff); + spell_message(spin, IObuff); // start with a message for the first line spin->si_msg_count = 999999; // Read and ignore the first line: word count. - if (vim_fgets(line, MAXLINELEN, fd) || !ascii_isdigit(*skipwhite((char *)line))) { + if (vim_fgets((char *)line, MAXLINELEN, fd) || !ascii_isdigit(*skipwhite((char *)line))) { semsg(_("E760: No word count in %s"), fname); } // Read all the lines in the file one by one. // The words are converted to 'encoding' here, before being added to // the hashtable. - while (!vim_fgets(line, MAXLINELEN, fd) && !got_int) { + while (!vim_fgets((char *)line, MAXLINELEN, fd) && !got_int) { line_breakcheck(); lnum++; if (line[0] == '#' || line[0] == '/') { @@ -3143,7 +3148,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) } // Remove CR, LF and white space from the end. White space halfway through // the word is kept to allow multi-word terms like "et al.". - l = (int)STRLEN(line); + l = (int)strlen(line); while (l > 0 && line[l - 1] <= ' ') { l--; } @@ -3163,7 +3168,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) w = pc; } else { pc = NULL; - w = line; + w = (char_u *)line; } // Truncate the word at the "/", set "afflist" to what follows. @@ -3171,7 +3176,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) afflist = NULL; for (p = w; *p != NUL; MB_PTR_ADV(p)) { if (*p == '\\' && (p[1] == '\\' || p[1] == '/')) { - STRMOVE(p, p + 1); + STRMOVE(p, (char *)p + 1); } else if (*p == '/') { *p = NUL; afflist = p + 1; @@ -3180,7 +3185,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) } // Skip non-ASCII words when "spin->si_ascii" is true. - if (spin->si_ascii && has_non_ascii(w)) { + if (spin->si_ascii && has_non_ascii((char *)w)) { non_ascii++; xfree(pc); continue; @@ -3205,7 +3210,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) } // Store the word in the hashtable to be able to find duplicates. - dw = (char_u *)getroom_save(spin, w); + dw = getroom_save(spin, (char *)w); if (dw == NULL) { retval = FAIL; xfree(pc); @@ -3213,7 +3218,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) } hash = hash_hash(dw); - hi = hash_lookup(&ht, (const char *)dw, STRLEN(dw), hash); + hi = hash_lookup(&ht, (const char *)dw, strlen(dw), hash); if (!HASHITEM_EMPTY(hi)) { if (p_verbose > 0) { smsg(_("Duplicate word in %s line %d: %s"), @@ -3233,45 +3238,45 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) need_affix = false; if (afflist != NULL) { // Extract flags from the affix list. - flags |= get_affix_flags(affile, afflist); + flags |= get_affix_flags(affile, (char *)afflist); if (affile->af_needaffix != 0 - && flag_in_afflist(affile->af_flagtype, afflist, + && flag_in_afflist(affile->af_flagtype, (char *)afflist, affile->af_needaffix)) { need_affix = true; } if (affile->af_pfxpostpone) { // Need to store the list of prefix IDs with the word. - pfxlen = get_pfxlist(affile, afflist, store_afflist); + pfxlen = get_pfxlist(affile, (char *)afflist, store_afflist); } if (spin->si_compflags != NULL) { // Need to store the list of compound flags with the word. // Concatenate them to the list of prefix IDs. - get_compflags(affile, afflist, store_afflist + pfxlen); + get_compflags(affile, (char *)afflist, store_afflist + pfxlen); } } // Add the word to the word tree(s). if (store_word(spin, dw, flags, spin->si_region, - store_afflist, need_affix) == FAIL) { + (char *)store_afflist, need_affix) == FAIL) { retval = FAIL; } if (afflist != NULL) { // Find all matching suffixes and add the resulting words. // Additionally do matching prefixes that combine. - if (store_aff_word(spin, dw, afflist, affile, + if (store_aff_word(spin, dw, (char *)afflist, affile, &affile->af_suff, &affile->af_pref, - CONDIT_SUF, flags, store_afflist, pfxlen) == FAIL) { + CONDIT_SUF, flags, (char *)store_afflist, pfxlen) == FAIL) { retval = FAIL; } // Find all matching prefixes and add the resulting words. - if (store_aff_word(spin, dw, afflist, affile, + if (store_aff_word(spin, dw, (char *)afflist, affile, &affile->af_pref, NULL, - CONDIT_SUF, flags, store_afflist, pfxlen) == FAIL) { + CONDIT_SUF, flags, (char *)store_afflist, pfxlen) == FAIL) { retval = FAIL; } } @@ -3294,7 +3299,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) // Check for affix flags in "afflist" that are turned into word flags. // Return WF_ flags. -static int get_affix_flags(afffile_T *affile, char_u *afflist) +static int get_affix_flags(afffile_T *affile, char *afflist) { int flags = 0; @@ -3333,13 +3338,13 @@ static int get_affix_flags(afffile_T *affile, char_u *afflist) // Used for PFXPOSTPONE. // Put the resulting flags in "store_afflist[MAXWLEN]" with a terminating NUL // and return the number of affixes. -static int get_pfxlist(afffile_T *affile, char_u *afflist, char_u *store_afflist) +static int get_pfxlist(afffile_T *affile, char *afflist, char_u *store_afflist) { - char_u *p; - char_u *prevp; + char *p; + char *prevp; int cnt = 0; int id; - char_u key[AH_KEY_LEN]; + char key[AH_KEY_LEN]; hashitem_T *hi; for (p = afflist; *p != NUL;) { @@ -3347,7 +3352,7 @@ static int get_pfxlist(afffile_T *affile, char_u *afflist, char_u *store_afflist if (get_affitem(affile->af_flagtype, &p) != 0) { // A flag is a postponed prefix flag if it appears in "af_pref" // and its ID is not zero. - STRLCPY(key, prevp, p - prevp + 1); + xstrlcpy(key, prevp, (size_t)(p - prevp) + 1); hi = hash_find(&affile->af_pref, (char *)key); if (!HASHITEM_EMPTY(hi)) { id = HI2AH(hi)->ah_newID; @@ -3368,19 +3373,19 @@ static int get_pfxlist(afffile_T *affile, char_u *afflist, char_u *store_afflist // Get the list of compound IDs from the affix list "afflist" that are used // for compound words. // Puts the flags in "store_afflist[]". -static void get_compflags(afffile_T *affile, char_u *afflist, char_u *store_afflist) +static void get_compflags(afffile_T *affile, char *afflist, char_u *store_afflist) { - char_u *p; - char_u *prevp; + char *p; + char *prevp; int cnt = 0; - char_u key[AH_KEY_LEN]; + char key[AH_KEY_LEN]; hashitem_T *hi; for (p = afflist; *p != NUL;) { prevp = p; if (get_affitem(affile->af_flagtype, &p) != 0) { // A flag is a compound flag if it appears in "af_comp". - STRLCPY(key, prevp, p - prevp + 1); + xstrlcpy(key, prevp, (size_t)(p - prevp) + 1); hi = hash_find(&affile->af_comp, (char *)key); if (!HASHITEM_EMPTY(hi)) { store_afflist[cnt++] = (char_u)HI2CI(hi)->ci_newID; @@ -3409,25 +3414,25 @@ static void get_compflags(afffile_T *affile, char_u *afflist, char_u *store_affl /// @param pfxlen nr of flags in "pfxlist" for prefixes, rest is compound flags /// /// @return FAIL when out of memory. -static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afffile_T *affile, - hashtab_T *ht, hashtab_T *xht, int condit, int flags, char_u *pfxlist, +static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_T *affile, + hashtab_T *ht, hashtab_T *xht, int condit, int flags, char *pfxlist, int pfxlen) { int todo; hashitem_T *hi; affheader_T *ah; affentry_T *ae; - char_u newword[MAXWLEN]; + char newword[MAXWLEN]; int retval = OK; int i, j; - char_u *p; + char *p; int use_flags; - char_u *use_pfxlist; + char *use_pfxlist; int use_pfxlen; bool need_affix; char_u store_afflist[MAXWLEN]; - char_u pfx_pfxlist[MAXWLEN]; - size_t wordlen = STRLEN(word); + char pfx_pfxlist[MAXWLEN]; + size_t wordlen = strlen(word); int use_condit; todo = (int)ht->ht_used; @@ -3457,7 +3462,7 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff || ae->ae_chop != NULL || ae->ae_flags != NULL) && (ae->ae_chop == NULL - || STRLEN(ae->ae_chop) < wordlen) + || strlen(ae->ae_chop) < wordlen) && (ae->ae_prog == NULL || vim_regexec_prog(&ae->ae_prog, false, word, (colnr_T)0)) && (((condit & CONDIT_CFIX) == 0) @@ -3471,7 +3476,7 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff if (ae->ae_add == NULL) { *newword = NUL; } else { - STRLCPY(newword, ae->ae_add, MAXWLEN); + xstrlcpy(newword, ae->ae_add, MAXWLEN); } p = word; if (ae->ae_chop != NULL) { @@ -3484,10 +3489,10 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff STRCAT(newword, p); } else { // suffix: chop/add at the end of the word - STRLCPY(newword, word, MAXWLEN); + xstrlcpy(newword, word, MAXWLEN); if (ae->ae_chop != NULL) { // Remove chop string. - p = newword + STRLEN(newword); + p = newword + strlen(newword); i = mb_charlen(ae->ae_chop); for (; i > 0; i--) { MB_PTR_BACK(newword, p); @@ -3508,16 +3513,18 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff // Extract flags from the affix list. use_flags |= get_affix_flags(affile, ae->ae_flags); - if (affile->af_needaffix != 0 && flag_in_afflist(affile->af_flagtype, ae->ae_flags, - affile->af_needaffix)) { + if (affile->af_needaffix != 0 + && flag_in_afflist(affile->af_flagtype, ae->ae_flags, + affile->af_needaffix)) { need_affix = true; } // When there is a CIRCUMFIX flag the other affix // must also have it and we don't add the word // with one affix. - if (affile->af_circumfix != 0 && flag_in_afflist(affile->af_flagtype, ae->ae_flags, - affile->af_circumfix)) { + if (affile->af_circumfix != 0 + && flag_in_afflist(affile->af_flagtype, ae->ae_flags, + affile->af_circumfix)) { use_condit |= CONDIT_CFIX; if ((condit & CONDIT_CFIX) == 0) { need_affix = true; @@ -3528,12 +3535,11 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff || spin->si_compflags != NULL) { if (affile->af_pfxpostpone) { // Get prefix IDS from the affix list. - use_pfxlen = get_pfxlist(affile, - ae->ae_flags, store_afflist); + use_pfxlen = get_pfxlist(affile, ae->ae_flags, store_afflist); } else { use_pfxlen = 0; } - use_pfxlist = store_afflist; + use_pfxlist = (char *)store_afflist; // Combine the prefix IDs. Avoid adding the // same ID twice. @@ -3551,7 +3557,7 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff if (spin->si_compflags != NULL) { // Get compound IDS from the affix list. get_compflags(affile, ae->ae_flags, - use_pfxlist + use_pfxlen); + (char_u *)use_pfxlist + use_pfxlen); } else { use_pfxlist[use_pfxlen] = NUL; } @@ -3576,7 +3582,7 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff // Obey a "COMPOUNDFORBIDFLAG" of the affix: don't // use the compound flags. if (use_pfxlist != NULL && ae->ae_compforbid) { - STRLCPY(pfx_pfxlist, use_pfxlist, use_pfxlen + 1); + xstrlcpy(pfx_pfxlist, use_pfxlist, (size_t)use_pfxlen + 1); use_pfxlist = pfx_pfxlist; } @@ -3618,7 +3624,7 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff if (store_aff_word(spin, newword, ae->ae_flags, affile, &affile->af_suff, xht, use_condit & (xht == NULL - ? ~0 : ~CONDIT_SUF), + ? ~0 : ~CONDIT_SUF), use_flags, use_pfxlist, pfxlen) == FAIL) { retval = FAIL; } @@ -3652,12 +3658,12 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff } // Read a file with a list of words. -static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) +static int spell_read_wordfile(spellinfo_T *spin, char *fname) { FILE *fd; long lnum = 0; - char_u rline[MAXLINELEN]; - char_u *line; + char rline[MAXLINELEN]; + char *line; char_u *pc = NULL; char_u *p; int l; @@ -3668,14 +3674,14 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) int regionmask; // Open the file. - fd = os_fopen((char *)fname, "r"); + fd = os_fopen(fname, "r"); if (fd == NULL) { semsg(_(e_notopen), fname); return FAIL; } - vim_snprintf((char *)IObuff, IOSIZE, _("Reading word file %s..."), fname); - spell_message(spin, (char *)IObuff); + vim_snprintf(IObuff, IOSIZE, _("Reading word file %s..."), fname); + spell_message(spin, IObuff); // Read all the lines in the file one by one. while (!vim_fgets(rline, MAXLINELEN, fd) && !got_int) { @@ -3688,8 +3694,8 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) } // Remove CR, LF and white space from the end. - l = (int)STRLEN(rline); - while (l > 0 && rline[l - 1] <= ' ') { + l = (int)strlen(rline); + while (l > 0 && (uint8_t)rline[l - 1] <= ' ') { l--; } if (l == 0) { @@ -3700,13 +3706,13 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) // Convert from "/encoding={encoding}" to 'encoding' when needed. xfree(pc); if (spin->si_conv.vc_type != CONV_NONE) { - pc = (char_u *)string_convert(&spin->si_conv, (char *)rline, NULL); + pc = (char_u *)string_convert(&spin->si_conv, rline, NULL); if (pc == NULL) { smsg(_("Conversion failure for word in %s line %ld: %s"), fname, lnum, rline); continue; } - line = pc; + line = (char *)pc; } else { pc = NULL; line = rline; @@ -3714,7 +3720,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) if (*line == '/') { line++; - if (STRNCMP(line, "encoding=", 9) == 0) { + if (strncmp(line, "encoding=", 9) == 0) { if (spin->si_conv.vc_type != CONV_NONE) { smsg(_("Duplicate /encoding= line ignored in %s line %ld: %s"), fname, lnum, line - 1); @@ -3726,7 +3732,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) // Setup for conversion to 'encoding'. line += 9; - enc = enc_canonize((char *)line); + enc = enc_canonize(line); if (!spin->si_ascii && convert_setup(&spin->si_conv, enc, p_enc) == FAIL) { smsg(_("Conversion in %s not supported: from %s to %s"), @@ -3738,17 +3744,17 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) continue; } - if (STRNCMP(line, "regions=", 8) == 0) { + if (strncmp(line, "regions=", 8) == 0) { if (spin->si_region_count > 1) { smsg(_("Duplicate /regions= line ignored in %s line %ld: %s"), fname, lnum, line); } else { line += 8; - if (STRLEN(line) > MAXREGIONS * 2) { + if (strlen(line) > MAXREGIONS * 2) { smsg(_("Too many regions in %s line %ld: %s"), fname, lnum, line); } else { - spin->si_region_count = (int)STRLEN(line) / 2; + spin->si_region_count = (int)strlen(line) / 2; STRCPY(spin->si_region_name, line); // Adjust the mask for a word valid in all regions. @@ -3767,7 +3773,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) regionmask = spin->si_region; // Check for flags and region after a slash. - p = (char_u *)vim_strchr((char *)line, '/'); + p = (char_u *)vim_strchr(line, '/'); if (p != NULL) { *p++ = NUL; while (*p != NUL) { @@ -3817,9 +3823,9 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) fclose(fd); if (spin->si_ascii && non_ascii > 0) { - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Ignored %d words with non-ASCII characters"), non_ascii); - spell_message(spin, (char *)IObuff); + spell_message(spin, IObuff); } return retval; @@ -3865,9 +3871,9 @@ static void *getroom(spellinfo_T *spin, size_t len, bool align) /// Make a copy of a string into memory allocated with getroom(). /// /// @return NULL when out of memory. -static char *getroom_save(spellinfo_T *spin, char_u *s) +static char *getroom_save(spellinfo_T *spin, char *s) { - const size_t s_size = STRLEN(s) + 1; + const size_t s_size = strlen(s) + 1; return memcpy(getroom(spin, s_size, false), s, s_size); } @@ -3918,21 +3924,21 @@ static bool valid_spell_word(const char *word, const char *end) /// @param region supported region(s) /// @param pfxlist list of prefix ids or null /// @param need_affix only store word with affix id -static int store_word(spellinfo_T *spin, char_u *word, int flags, int region, const char_u *pfxlist, +static int store_word(spellinfo_T *spin, char *word, int flags, int region, const char *pfxlist, bool need_affix) { - int len = (int)STRLEN(word); + int len = (int)strlen(word); int ct = captype(word, word + len); char_u foldword[MAXWLEN]; int res = OK; // Avoid adding illegal bytes to the word tree. - if (!valid_spell_word((char *)word, (char *)word + len)) { + if (!valid_spell_word(word, word + len)) { return FAIL; } - (void)spell_casefold(curwin, word, len, foldword, MAXWLEN); - for (const char_u *p = pfxlist; res == OK; p++) { + (void)spell_casefold(curwin, word, len, (char *)foldword, MAXWLEN); + for (const char_u *p = (char_u *)pfxlist; res == OK; p++) { if (!need_affix || (p != NULL && *p != NUL)) { res = tree_add_word(spin, foldword, spin->si_foldroot, ct | flags, region, p == NULL ? 0 : *p); @@ -3944,9 +3950,9 @@ static int store_word(spellinfo_T *spin, char_u *word, int flags, int region, co spin->si_foldwcount++; if (res == OK && (ct == WF_KEEPCAP || (flags & WF_KEEPCAP))) { - for (const char_u *p = pfxlist; res == OK; p++) { + for (const char_u *p = (char_u *)pfxlist; res == OK; p++) { if (!need_affix || (p != NULL && *p != NUL)) { - res = tree_add_word(spin, word, spin->si_keeproot, flags, + res = tree_add_word(spin, (char_u *)word, spin->si_keeproot, flags, region, p == NULL ? 0 : *p); } if (p == NULL || *p == NUL) { @@ -3962,8 +3968,8 @@ static int store_word(spellinfo_T *spin, char_u *word, int flags, int region, co // When "flags" < 0 we are adding to the prefix tree where "flags" is used for // "rare" and "region" is the condition nr. // Returns FAIL when out of memory. -static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int flags, int region, - int affixID) +static int tree_add_word(spellinfo_T *spin, const char_u *word, wordnode_T *root, int flags, + int region, int affixID) { wordnode_T *node = root; wordnode_T *np; @@ -4093,7 +4099,7 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int // 3. When compressed before, added "compress_added" words // (si_compress_cnt == 1) and the number of free nodes drops below the // maximum word length. -#ifndef SPELL_COMPRESS_ALLWAYS +#ifndef SPELL_COMPRESS_ALWAYS if (spin->si_compress_cnt == 1 // NOLINT(readability/braces) ? spin->si_free_count < MAXWLEN : spin->si_blocks_cnt >= compress_start) @@ -4194,31 +4200,33 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root, const char *n // Skip the root itself, it's not actually used. The first sibling is the // start of the tree. - if (root->wn_sibling != NULL) { - hash_init(&ht); - const long n = node_compress(spin, root->wn_sibling, &ht, &tot); + if (root->wn_sibling == NULL) { + return; + } + + hash_init(&ht); + const long n = node_compress(spin, root->wn_sibling, &ht, &tot); #ifndef SPELL_PRINTTREE - if (spin->si_verbose || p_verbose > 2) + if (spin->si_verbose || p_verbose > 2) #endif - { - if (tot > 1000000) { - perc = (tot - n) / (tot / 100); - } else if (tot == 0) { - perc = 0; - } else { - perc = (tot - n) * 100 / tot; - } - vim_snprintf((char *)IObuff, IOSIZE, - _("Compressed %s of %ld nodes; %ld (%ld%%) remaining"), - name, tot, tot - n, perc); - spell_message(spin, (char *)IObuff); + { + if (tot > 1000000) { + perc = (tot - n) / (tot / 100); + } else if (tot == 0) { + perc = 0; + } else { + perc = (tot - n) * 100 / tot; } + vim_snprintf(IObuff, IOSIZE, + _("Compressed %s of %ld nodes; %ld (%ld%%) remaining"), + name, tot, tot - n, perc); + spell_message(spin, IObuff); + } #ifdef SPELL_PRINTTREE - spell_print_tree(root->wn_sibling); + spell_print_tree(root->wn_sibling); #endif - hash_clear(&ht); - } + hash_clear(&ht); } /// Compress a node, its siblings and its children, depth first. @@ -4248,9 +4256,9 @@ static long node_compress(spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, lo compressed += node_compress(spin, child, ht, tot); // Try to find an identical child. - hash = hash_hash(child->wn_u1.hashkey); + hash = hash_hash((char *)child->wn_u1.hashkey); hi = hash_lookup(ht, (const char *)child->wn_u1.hashkey, - STRLEN(child->wn_u1.hashkey), hash); + strlen((char *)child->wn_u1.hashkey), hash); if (!HASHITEM_EMPTY(hi)) { // There are children we encountered before with a hash value // identical to the current child. Now check if there is one @@ -4277,7 +4285,7 @@ static long node_compress(spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, lo } else { // No other child has this hash value, add it to the // hashtable. - hash_add_item(ht, hi, child->wn_u1.hashkey, hash); + hash_add_item(ht, hi, (char *)child->wn_u1.hashkey, hash); } } } @@ -4343,7 +4351,7 @@ static int rep_compare(const void *s1, const void *s2) fromto_T *p1 = (fromto_T *)s1; fromto_T *p2 = (fromto_T *)s2; - return strcmp((char *)p1->ft_from, (char *)p2->ft_from); + return strcmp(p1->ft_from, p2->ft_from); } /// Write the Vim .spl file "fname". @@ -4376,7 +4384,7 @@ static int write_vim_spell(spellinfo_T *spin, char *fname) if (spin->si_info != NULL) { putc(SN_INFO, fd); // <sectionID> putc(0, fd); // <sectionflags> - size_t i = STRLEN(spin->si_info); + size_t i = strlen(spin->si_info); put_bytes(fd, i, 4); // <sectionlen> fwv &= fwrite(spin->si_info, i, 1, fd); // <infotext> } @@ -4439,7 +4447,7 @@ static int write_vim_spell(spellinfo_T *spin, char *fname) putc(SN_MIDWORD, fd); // <sectionID> putc(SNF_REQUIRED, fd); // <sectionflags> - size_t i = STRLEN(spin->si_midword); + size_t i = strlen(spin->si_midword); put_bytes(fd, i, 4); // <sectionlen> fwv &= fwrite(spin->si_midword, i, 1, fd); // <midword> @@ -4499,8 +4507,8 @@ static int write_vim_spell(spellinfo_T *spin, char *fname) assert(gap->ga_len >= 0); for (size_t i = 0; i < (size_t)gap->ga_len; i++) { fromto_T *ftp = &((fromto_T *)gap->ga_data)[i]; - l += 1 + STRLEN(ftp->ft_from); // count <*fromlen> and <*from> - l += 1 + STRLEN(ftp->ft_to); // count <*tolen> and <*to> + l += 1 + strlen(ftp->ft_from); // count <*fromlen> and <*from> + l += 1 + strlen(ftp->ft_to); // count <*tolen> and <*to> } if (round == 2) { l++; // count <salflags> @@ -4527,8 +4535,8 @@ static int write_vim_spell(spellinfo_T *spin, char *fname) // <sal> : <salfromlen> <salfrom> <saltolen> <salto> fromto_T *ftp = &((fromto_T *)gap->ga_data)[i]; for (unsigned int rr = 1; rr <= 2; rr++) { - char_u *p = rr == 1 ? ftp->ft_from : ftp->ft_to; - l = STRLEN(p); + char *p = rr == 1 ? ftp->ft_from : ftp->ft_to; + l = strlen(p); assert(l < INT_MAX); putc((int)l, fd); if (l > 0) { @@ -4544,13 +4552,13 @@ static int write_vim_spell(spellinfo_T *spin, char *fname) putc(SN_SOFO, fd); // <sectionID> putc(0, fd); // <sectionflags> - size_t l = STRLEN(spin->si_sofofr); - put_bytes(fd, l + STRLEN(spin->si_sofoto) + 4, 4); // <sectionlen> + size_t l = strlen(spin->si_sofofr); + put_bytes(fd, l + strlen(spin->si_sofoto) + 4, 4); // <sectionlen> put_bytes(fd, l, 2); // <sofofromlen> fwv &= fwrite(spin->si_sofofr, l, 1, fd); // <sofofrom> - l = STRLEN(spin->si_sofoto); + l = strlen(spin->si_sofoto); put_bytes(fd, l, 2); // <sofotolen> fwv &= fwrite(spin->si_sofoto, l, 1, fd); // <sofoto> } @@ -4571,7 +4579,7 @@ static int write_vim_spell(spellinfo_T *spin, char *fname) todo = spin->si_commonwords.ht_used; for (hi = spin->si_commonwords.ht_array; todo > 0; hi++) { if (!HASHITEM_EMPTY(hi)) { - size_t l = STRLEN(hi->hi_key) + 1; + size_t l = strlen(hi->hi_key) + 1; len += l; if (round == 2) { // <word> fwv &= fwrite(hi->hi_key, l, 1, fd); @@ -4637,7 +4645,7 @@ static int write_vim_spell(spellinfo_T *spin, char *fname) putc(SN_COMPOUND, fd); // <sectionID> putc(0, fd); // <sectionflags> - size_t l = STRLEN(spin->si_compflags); + size_t l = strlen(spin->si_compflags); assert(spin->si_comppat.ga_len >= 0); for (size_t i = 0; i < (size_t)spin->si_comppat.ga_len; i++) { l += strlen(((char **)(spin->si_comppat.ga_data))[i]) + 1; @@ -4657,7 +4665,7 @@ static int write_vim_spell(spellinfo_T *spin, char *fname) fwv &= fwrite(p, strlen(p), 1, fd); // <comppattext> } // <compflags> - fwv &= fwrite(spin->si_compflags, STRLEN(spin->si_compflags), 1, fd); + fwv &= fwrite(spin->si_compflags, strlen(spin->si_compflags), 1, fd); } // SN_NOBREAK: NOBREAK flag @@ -4676,7 +4684,7 @@ static int write_vim_spell(spellinfo_T *spin, char *fname) putc(SN_SYLLABLE, fd); // <sectionID> putc(0, fd); // <sectionflags> - size_t l = STRLEN(spin->si_syllable); + size_t l = strlen(spin->si_syllable); put_bytes(fd, l, 4); // <sectionlen> fwv &= fwrite(spin->si_syllable, l, 1, fd); // <syllable> } @@ -4878,16 +4886,18 @@ void ex_mkspell(exarg_T *eap) char *arg = eap->arg; bool ascii = false; - if (STRNCMP(arg, "-ascii", 6) == 0) { + if (strncmp(arg, "-ascii", 6) == 0) { ascii = true; arg = skipwhite(arg + 6); } // Expand all the remaining arguments (e.g., $VIMRUNTIME). - if (get_arglist_exp(arg, &fcount, &fnames, false) == OK) { - mkspell(fcount, fnames, ascii, eap->forceit, false); - FreeWild(fcount, fnames); + if (get_arglist_exp(arg, &fcount, &fnames, false) != OK) { + return; } + + mkspell(fcount, fnames, ascii, eap->forceit, false); + FreeWild(fcount, fnames); } // Create the .sug file. @@ -4895,7 +4905,7 @@ void ex_mkspell(exarg_T *eap) // Writes the file with the name "wfname", with ".spl" changed to ".sug". static void spell_make_sugfile(spellinfo_T *spin, char *wfname) { - char_u *fname = NULL; + char *fname = NULL; int len; slang_T *slang; bool free_slang = false; @@ -4953,8 +4963,8 @@ static void spell_make_sugfile(spellinfo_T *spin, char *wfname) // Write the .sug file. // Make the file name by changing ".spl" to ".sug". fname = xmalloc(MAXPATHL); - STRLCPY(fname, wfname, MAXPATHL); - len = (int)STRLEN(fname); + xstrlcpy(fname, wfname, MAXPATHL); + len = (int)strlen(fname); fname[len - 2] = 'u'; fname[len - 1] = 'g'; sug_write(spin, fname); @@ -4991,7 +5001,7 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang) // Go through the whole case-folded tree, soundfold each word and put it // in the trie. - byts = slang->sl_fbyts; + byts = (char_u *)slang->sl_fbyts; idxs = slang->sl_fidxs; arridx[0] = 0; @@ -5018,7 +5028,7 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang) if (c == 0) { // Sound-fold the word. tword[depth] = NUL; - spell_soundfold(slang, tword, true, tsalword); + spell_soundfold(slang, (char *)tword, true, (char *)tsalword); // We use the "flags" field for the MSB of the wordnr, // "region" for the LSB of the wordnr. @@ -5182,18 +5192,18 @@ static int offset2bytes(int nr, char_u *buf) } // Write the .sug file in "fname". -static void sug_write(spellinfo_T *spin, char_u *fname) +static void sug_write(spellinfo_T *spin, char *fname) { // Create the file. Note that an existing file is silently overwritten! - FILE *fd = os_fopen((char *)fname, "w"); + FILE *fd = os_fopen(fname, "w"); if (fd == NULL) { semsg(_(e_notopen), fname); return; } - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Writing suggestion file %s..."), fname); - spell_message(spin, (char *)IObuff); + spell_message(spin, IObuff); // <SUGHEADER>: <fileID> <versionnr> <timestamp> if (fwrite(VIMSUGMAGIC, VIMSUGMAGICL, (size_t)1, fd) != 1) { // <fileID> @@ -5232,8 +5242,8 @@ static void sug_write(spellinfo_T *spin, char_u *fname) for (linenr_T lnum = 1; lnum <= wcount; lnum++) { // <sugline>: <sugnr> ... NUL - char_u *line = (char_u *)ml_get_buf(spin->si_spellbuf, lnum, false); - size_t len = STRLEN(line) + 1; + char *line = ml_get_buf(spin->si_spellbuf, lnum, false); + size_t len = strlen(line) + 1; if (fwrite(line, len, 1, fd) == 0) { emsg(_(e_write)); goto theend; @@ -5247,9 +5257,9 @@ static void sug_write(spellinfo_T *spin, char_u *fname) emsg(_(e_write)); } - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Estimated runtime memory use: %d bytes"), spin->si_memtot); - spell_message(spin, (char *)IObuff); + spell_message(spin, IObuff); theend: // close the file @@ -5267,7 +5277,7 @@ theend: /// @param added_word invoked through "zg" static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool added_word) { - char_u *fname = NULL; + char *fname = NULL; char **innames; int incount; afffile_T *(afile[MAXREGIONS]); @@ -5284,9 +5294,9 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool ga_init(&spin.si_rep, (int)sizeof(fromto_T), 20); ga_init(&spin.si_repsal, (int)sizeof(fromto_T), 20); ga_init(&spin.si_sal, (int)sizeof(fromto_T), 20); - ga_init(&spin.si_map, (int)sizeof(char_u), 100); - ga_init(&spin.si_comppat, (int)sizeof(char_u *), 20); - ga_init(&spin.si_prefcond, (int)sizeof(char_u *), 50); + ga_init(&spin.si_map, (int)sizeof(char), 100); + ga_init(&spin.si_comppat, (int)sizeof(char *), 20); + ga_init(&spin.si_prefcond, (int)sizeof(char *), 50); hash_init(&spin.si_commonwords); spin.si_newcompID = 127; // start compound ID at first maximum @@ -5298,7 +5308,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool char *wfname = xmalloc(MAXPATHL); if (fcount >= 1) { - len = (int)STRLEN(fnames[0]); + len = (int)strlen(fnames[0]); if (fcount == 1 && len > 4 && strcmp(fnames[0] + len - 4, ".add") == 0) { // For ":mkspell path/en.latin1.add" output file is // "path/en.latin1.add.spl". @@ -5308,14 +5318,14 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool // For ":mkspell path/vim" output file is "path/vim.latin1.spl". incount = 1; vim_snprintf(wfname, MAXPATHL, SPL_FNAME_TMPL, - fnames[0], spin.si_ascii ? (char_u *)"ascii" : spell_enc()); + fnames[0], spin.si_ascii ? "ascii" : spell_enc()); } else if (len > 4 && strcmp(fnames[0] + len - 4, ".spl") == 0) { // Name ends in ".spl", use as the file name. - STRLCPY(wfname, fnames[0], MAXPATHL); + xstrlcpy(wfname, fnames[0], MAXPATHL); } else { // Name should be language, make the file name from it. vim_snprintf(wfname, MAXPATHL, SPL_FNAME_TMPL, - fnames[0], spin.si_ascii ? (char_u *)"ascii" : spell_enc()); + fnames[0], spin.si_ascii ? "ascii" : spell_enc()); } // Check for .ascii.spl. @@ -5387,8 +5397,8 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool spin.si_conv.vc_type = CONV_NONE; spin.si_region = 1 << i; - vim_snprintf((char *)fname, MAXPATHL, "%s.aff", innames[i]); - if (os_path_exists((char *)fname)) { + vim_snprintf(fname, MAXPATHL, "%s.aff", innames[i]); + if (os_path_exists(fname)) { // Read the .aff file. Will init "spin->si_conv" based on the // "SET" line. afile[i] = spell_read_aff(&spin, fname); @@ -5396,8 +5406,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool error = true; } else { // Read the .dic file and store the words in the trees. - vim_snprintf((char *)fname, MAXPATHL, "%s.dic", - innames[i]); + vim_snprintf(fname, MAXPATHL, "%s.dic", innames[i]); if (spell_read_dic(&spin, fname, afile[i]) == FAIL) { error = true; } @@ -5405,7 +5414,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool } else { // No .aff file, try reading the file as a word list. Store // the words in the trees. - if (spell_read_wordfile(&spin, (char_u *)innames[i]) == FAIL) { + if (spell_read_wordfile(&spin, innames[i]) == FAIL) { error = true; } } @@ -5428,20 +5437,20 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool if (!error && !got_int) { // Write the info in the spell file. - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Writing spell file %s..."), wfname); - spell_message(&spin, (char *)IObuff); + spell_message(&spin, IObuff); error = write_vim_spell(&spin, wfname) == FAIL; spell_message(&spin, _("Done!")); - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Estimated runtime memory use: %d bytes"), spin.si_memtot); - spell_message(&spin, (char *)IObuff); + spell_message(&spin, IObuff); // If the file is loaded need to reload it. if (!error) { - spell_reload_one((char_u *)wfname, added_word); + spell_reload_one(wfname, added_word); } } @@ -5499,7 +5508,7 @@ static void spell_message(const spellinfo_T *spin, char *str) // ":[count]spellrare {word}" void ex_spell(exarg_T *eap) { - spell_add_word((char_u *)eap->arg, (int)strlen(eap->arg), + spell_add_word(eap->arg, (int)strlen(eap->arg), eap->cmdidx == CMD_spellwrong ? SPELL_ADD_BAD : eap->cmdidx == CMD_spellrare ? SPELL_ADD_RARE : SPELL_ADD_GOOD, eap->forceit ? 0 : (int)eap->line2, @@ -5511,31 +5520,31 @@ void ex_spell(exarg_T *eap) /// @param what SPELL_ADD_ values /// @param idx "zG" and "zW": zero, otherwise index in 'spellfile' /// @param bool // true for "zug", "zuG", "zuw" and "zuW" -void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo) +void spell_add_word(char *word, int len, SpellAddType what, int idx, bool undo) { FILE *fd = NULL; buf_T *buf = NULL; bool new_spf = false; char *fname; - char_u *fnamebuf = NULL; - char_u line[MAXWLEN * 2]; + char *fnamebuf = NULL; + char line[MAXWLEN * 2]; long fpos, fpos_next = 0; int i; - char_u *spf; + char *spf; - if (!valid_spell_word((char *)word, (char *)word + len)) { + if (!valid_spell_word(word, word + len)) { emsg(_(e_illegal_character_in_word)); return; } if (idx == 0) { // use internal wordlist if (int_wordlist == NULL) { - int_wordlist = (char_u *)vim_tempname(); + int_wordlist = vim_tempname(); if (int_wordlist == NULL) { return; } } - fname = (char *)int_wordlist; + fname = int_wordlist; } else { // If 'spellfile' isn't set figure out a good default value. if (*curwin->w_s->b_p_spf == NUL) { @@ -5549,8 +5558,8 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo } fnamebuf = xmalloc(MAXPATHL); - for (spf = (char_u *)curwin->w_s->b_p_spf, i = 1; *spf != NUL; i++) { - copy_option_part((char **)&spf, (char *)fnamebuf, MAXPATHL, ","); + for (spf = curwin->w_s->b_p_spf, i = 1; *spf != NUL; i++) { + copy_option_part(&spf, fnamebuf, MAXPATHL, ","); if (i == idx) { break; } @@ -5562,7 +5571,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo } // Check that the user isn't editing the .add file somewhere. - buf = buflist_findname_exp((char *)fnamebuf); + buf = buflist_findname_exp(fnamebuf); if (buf != NULL && buf->b_ml.ml_mfp == NULL) { buf = NULL; } @@ -5572,7 +5581,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo return; } - fname = (char *)fnamebuf; + fname = fnamebuf; } if (what == SPELL_ADD_BAD || undo) { @@ -5580,14 +5589,14 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo // since its flags sort before the one with WF_BANNED. fd = os_fopen(fname, "r"); if (fd != NULL) { - while (!vim_fgets(line, MAXWLEN * 2, fd)) { + while (!vim_fgets((char *)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] < ' ')) { + if (strncmp(word, line, (size_t)len) == 0 + && (line[len] == '/' || (uint8_t)line[len] < ' ')) { // Found duplicate word. Remove it by writing a '#' at // the start of the line. Mixing reading and writing // doesn't work for all systems, close the file first. @@ -5599,7 +5608,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo if (fseek(fd, fpos, SEEK_SET) == 0) { fputc('#', fd); if (undo) { - home_replace(NULL, fname, (char *)NameBuff, MAXPATHL, true); + home_replace(NULL, fname, NameBuff, MAXPATHL, true); smsg(_("Word '%.*s' removed from %s"), len, word, NameBuff); } } @@ -5624,7 +5633,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo // file. We may need to create the "spell" directory first. We // already checked the runtime directory is writable in // init_spellfile(). - if (!dir_of_file_exists((char_u *)fname) + if (!dir_of_file_exists(fname) && (p = (char_u *)path_tail_with_sep(fname)) != (char_u *)fname) { int c = *p; @@ -5649,7 +5658,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo } fclose(fd); - home_replace(NULL, fname, (char *)NameBuff, MAXPATHL, true); + home_replace(NULL, fname, NameBuff, MAXPATHL, true); smsg(_("Word '%.*s' added to %s"), len, word, NameBuff); } } @@ -5671,84 +5680,86 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo // Initialize 'spellfile' for the current buffer. static void init_spellfile(void) { - char_u *buf; + char *buf; int l; - char_u *fname; - char_u *rtp; - char_u *lend; + char *fname; + char *rtp; + char *lend; bool aspath = false; - char_u *lstart = (char_u *)curbuf->b_s.b_p_spl; - - if (*curwin->w_s->b_p_spl != NUL && !GA_EMPTY(&curwin->w_s->b_langp)) { - buf = xmalloc(MAXPATHL); - - // Find the end of the language name. Exclude the region. If there - // is a path separator remember the start of the tail. - for (lend = (char_u *)curwin->w_s->b_p_spl; *lend != NUL - && vim_strchr(",._", *lend) == NULL; lend++) { - if (vim_ispathsep(*lend)) { - aspath = true; - lstart = lend + 1; - } + char *lstart = curbuf->b_s.b_p_spl; + + if (*curwin->w_s->b_p_spl == NUL || GA_EMPTY(&curwin->w_s->b_langp)) { + return; + } + + buf = xmalloc(MAXPATHL); + + // Find the end of the language name. Exclude the region. If there + // is a path separator remember the start of the tail. + for (lend = curwin->w_s->b_p_spl; *lend != NUL + && vim_strchr(",._", (uint8_t)(*lend)) == NULL; lend++) { + if (vim_ispathsep(*lend)) { + aspath = true; + lstart = lend + 1; } + } - // Loop over all entries in 'runtimepath'. Use the first one where we - // are allowed to write. - rtp = (char_u *)p_rtp; - while (*rtp != NUL) { + // Loop over all entries in 'runtimepath'. Use the first one where we + // are allowed to write. + rtp = p_rtp; + while (*rtp != NUL) { + if (aspath) { + // Use directory of an entry with path, e.g., for + // "/dir/lg.utf-8.spl" use "/dir". + xstrlcpy(buf, curbuf->b_s.b_p_spl, (size_t)(lstart - curbuf->b_s.b_p_spl)); + } else { + // Copy the path from 'runtimepath' to buf[]. + copy_option_part(&rtp, buf, MAXPATHL, ","); + } + if (os_file_is_writable(buf) == 2) { + // Use the first language name from 'spelllang' and the + // encoding used in the first loaded .spl file. if (aspath) { - // Use directory of an entry with path, e.g., for - // "/dir/lg.utf-8.spl" use "/dir". - STRLCPY(buf, curbuf->b_s.b_p_spl, lstart - (char_u *)curbuf->b_s.b_p_spl); + xstrlcpy(buf, curbuf->b_s.b_p_spl, (size_t)(lend - curbuf->b_s.b_p_spl + 1)); } else { - // Copy the path from 'runtimepath' to buf[]. - copy_option_part((char **)&rtp, (char *)buf, MAXPATHL, ","); - } - if (os_file_is_writable((char *)buf) == 2) { - // Use the first language name from 'spelllang' and the - // encoding used in the first loaded .spl file. - if (aspath) { - STRLCPY(buf, curbuf->b_s.b_p_spl, lend - (char_u *)curbuf->b_s.b_p_spl + 1); - } else { - // Create the "spell" directory if it doesn't exist yet. - l = (int)STRLEN(buf); - vim_snprintf((char *)buf + l, MAXPATHL - (size_t)l, "/spell"); - if (os_file_is_writable((char *)buf) != 2) { - os_mkdir((char *)buf, 0755); - } - - l = (int)STRLEN(buf); - vim_snprintf((char *)buf + l, MAXPATHL - (size_t)l, - "/%.*s", (int)(lend - lstart), lstart); + // Create the "spell" directory if it doesn't exist yet. + l = (int)strlen(buf); + vim_snprintf(buf + l, MAXPATHL - (size_t)l, "/spell"); + if (os_file_is_writable(buf) != 2) { + os_mkdir(buf, 0755); } - l = (int)STRLEN(buf); - fname = (char_u *)LANGP_ENTRY(curwin->w_s->b_langp, 0) - ->lp_slang->sl_fname; - vim_snprintf((char *)buf + l, MAXPATHL - (size_t)l, ".%s.add", - ((fname != NULL - && strstr(path_tail((char *)fname), ".ascii.") != NULL) - ? "ascii" - : (const char *)spell_enc())); - set_option_value_give_err("spellfile", 0L, (const char *)buf, OPT_LOCAL); - break; + + l = (int)strlen(buf); + vim_snprintf(buf + l, MAXPATHL - (size_t)l, + "/%.*s", (int)(lend - lstart), lstart); } - aspath = false; + l = (int)strlen(buf); + fname = LANGP_ENTRY(curwin->w_s->b_langp, 0) + ->lp_slang->sl_fname; + vim_snprintf(buf + l, MAXPATHL - (size_t)l, ".%s.add", + ((fname != NULL + && strstr(path_tail(fname), ".ascii.") != NULL) + ? "ascii" + : (const char *)spell_enc())); + set_option_value_give_err("spellfile", 0L, buf, OPT_LOCAL); + break; } - - xfree(buf); + aspath = false; } + + xfree(buf); } /// Set the spell character tables from strings in the .spl file. /// /// @param cnt length of "flags" -static void set_spell_charflags(char_u *flags, int cnt, char_u *fol) +static void set_spell_charflags(const char_u *flags, int cnt, char *fol) { // We build the new tables here first, so that we can compare with the // previous one. spelltab_T new_st; int i; - char_u *p = fol; + char *p = fol; int c; clear_spell_chartab(&new_st); @@ -5760,7 +5771,7 @@ static void set_spell_charflags(char_u *flags, int cnt, char_u *fol) } if (*p != NUL) { - c = mb_ptr2char_adv((const char_u **)&p); + c = mb_ptr2char_adv((const char **)&p); new_st.st_fold[i + 128] = (char_u)c; if (i + 128 != c && new_st.st_isu[i + 128] && c < 256) { new_st.st_upper[c] = (char_u)(i + 128); @@ -5826,9 +5837,9 @@ static int write_spell_prefcond(FILE *fd, garray_T *gap, size_t *fwv) } // Use map string "map" for languages "lp". -static void set_map_str(slang_T *lp, char_u *map) +static void set_map_str(slang_T *lp, char *map) { - char_u *p; + char *p; int headc = 0; int c; int i; @@ -5849,7 +5860,7 @@ static void set_map_str(slang_T *lp, char_u *map) // "aaa/bbb/ccc/". Fill sl_map_array[c] with the character before c and // before the same slash. For characters above 255 sl_map_hash is used. for (p = map; *p != NUL;) { - c = mb_cptr2char_adv((const char_u **)&p); + c = mb_cptr2char_adv((const char **)&p); if (c == '/') { headc = 0; } else { @@ -5871,10 +5882,10 @@ static void set_map_str(slang_T *lp, char_u *map) b[cl] = NUL; utf_char2bytes(headc, b + cl + 1); b[cl + 1 + headcl] = NUL; - hash = hash_hash((char_u *)b); + hash = hash_hash(b); hi = hash_lookup(&lp->sl_map_hash, (const char *)b, strlen(b), hash); if (HASHITEM_EMPTY(hi)) { - hash_add_item(&lp->sl_map_hash, hi, (char_u *)b, hash); + hash_add_item(&lp->sl_map_hash, hi, b, hash); } else { // This should have been checked when generating the .spl // file. diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c index f2a0da188e..22add418a0 100644 --- a/src/nvim/spellsuggest.c +++ b/src/nvim/spellsuggest.c @@ -3,30 +3,48 @@ // spellsuggest.c: functions for spelling suggestions +#include <assert.h> +#include <inttypes.h> +#include <limits.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/fileio.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/hashtab.h" +#include "nvim/highlight_defs.h" #include "nvim/input.h" +#include "nvim/macros.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/normal.h" #include "nvim/option.h" #include "nvim/os/fs.h" #include "nvim/os/input.h" +#include "nvim/os/os_defs.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/screen.h" #include "nvim/spell.h" -#include "nvim/spell_defs.h" #include "nvim/spellfile.h" #include "nvim/spellsuggest.h" #include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -53,11 +71,11 @@ typedef struct suginfo_S { int su_maxscore; ///< maximum score for adding to su_ga int su_sfmaxscore; ///< idem, for when doing soundfold words garray_T su_sga; ///< like su_ga, sound-folded scoring - char_u *su_badptr; ///< start of bad word in line + char *su_badptr; ///< start of bad word in line int su_badlen; ///< length of detected bad word in line int su_badflags; ///< caps flags for bad word char_u su_badword[MAXWLEN]; ///< bad word truncated at su_badlen - char_u su_fbadword[MAXWLEN]; ///< su_badword case-folded + char su_fbadword[MAXWLEN]; ///< su_badword case-folded char_u su_sal_badword[MAXWLEN]; ///< su_badword soundfolded hashtab_T su_banned; ///< table with banned words slang_T *su_sallang; ///< default language for sound folding @@ -91,45 +109,55 @@ typedef struct { #define SUG_MAX_COUNT(su) (SUG_CLEAN_COUNT(su) + 50) // score for various changes -#define SCORE_SPLIT 149 // split bad word -#define SCORE_SPLIT_NO 249 // split bad word with NOSPLITSUGS -#define SCORE_ICASE 52 // slightly different case -#define SCORE_REGION 200 // word is for different region -#define SCORE_RARE 180 // rare word -#define SCORE_SWAP 75 // swap two characters -#define SCORE_SWAP3 110 // swap two characters in three -#define SCORE_REP 65 // REP replacement -#define SCORE_SUBST 93 // substitute a character -#define SCORE_SIMILAR 33 // substitute a similar character -#define SCORE_SUBCOMP 33 // substitute a composing character -#define SCORE_DEL 94 // delete a character -#define SCORE_DELDUP 66 // delete a duplicated character -#define SCORE_DELCOMP 28 // delete a composing character -#define SCORE_INS 96 // insert a character -#define SCORE_INSDUP 67 // insert a duplicate character -#define SCORE_INSCOMP 30 // insert a composing character -#define SCORE_NONWORD 103 // change non-word to word char - -#define SCORE_FILE 30 // suggestion from a file -#define SCORE_MAXINIT 350 // Initial maximum score: higher == slower. - // 350 allows for about three changes. - -#define SCORE_COMMON1 30 // subtracted for words seen before -#define SCORE_COMMON2 40 // subtracted for words often seen -#define SCORE_COMMON3 50 // subtracted for words very often seen -#define SCORE_THRES2 10 // word count threshold for COMMON2 -#define SCORE_THRES3 100 // word count threshold for COMMON3 +enum { + SCORE_SPLIT = 149, // split bad word + SCORE_SPLIT_NO = 249, // split bad word with NOSPLITSUGS + SCORE_ICASE = 52, // slightly different case + SCORE_REGION = 200, // word is for different region + SCORE_RARE = 180, // rare word + SCORE_SWAP = 75, // swap two characters + SCORE_SWAP3 = 110, // swap two characters in three + SCORE_REP = 65, // REP replacement + SCORE_SUBST = 93, // substitute a character + SCORE_SIMILAR = 33, // substitute a similar character + SCORE_SUBCOMP = 33, // substitute a composing character + SCORE_DEL = 94, // delete a character + SCORE_DELDUP = 66, // delete a duplicated character + SCORE_DELCOMP = 28, // delete a composing character + SCORE_INS = 96, // insert a character + SCORE_INSDUP = 67, // insert a duplicate character + SCORE_INSCOMP = 30, // insert a composing character + SCORE_NONWORD = 103, // change non-word to word char +}; + +enum { + SCORE_FILE = 30, // suggestion from a file + SCORE_MAXINIT = 350, // Initial maximum score: higher == slower. + // 350 allows for about three changes. +}; + +enum { + SCORE_COMMON1 = 30, // subtracted for words seen before + SCORE_COMMON2 = 40, // subtracted for words often seen + SCORE_COMMON3 = 50, // subtracted for words very often seen + SCORE_THRES2 = 10, // word count threshold for COMMON2 + SCORE_THRES3 = 100, // word count threshold for COMMON3 +}; // When trying changed soundfold words it becomes slow when trying more than // 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 +enum { + SCORE_SFMAX1 = 200, // maximum score for first try + SCORE_SFMAX2 = 300, // maximum score for second try + SCORE_SFMAX3 = 400, // maximum score for third try +}; #define SCORE_BIG (SCORE_INS * 3) // big difference -#define SCORE_MAXMAX 999999 // accept any score -#define SCORE_LIMITMAX 350 // for spell_edit_score_limit() +enum { + SCORE_MAXMAX = 999999, // accept any score + SCORE_LIMITMAX = 350, // for spell_edit_score_limit() +}; // for spell_edit_score_limit() we need to know the minimum value of // SCORE_ICASE, SCORE_SWAP, SCORE_DEL, SCORE_SIMILAR and SCORE_INS @@ -186,19 +214,25 @@ typedef struct trystate_S { } trystate_T; // values for ts_isdiff -#define DIFF_NONE 0 // no different byte (yet) -#define DIFF_YES 1 // different byte found -#define DIFF_INSERT 2 // inserting character +enum { + DIFF_NONE = 0, // no different byte (yet) + DIFF_YES = 1, // different byte found + DIFF_INSERT = 2, // inserting character +}; // values for ts_flags -#define TSF_PREFIXOK 1 // already checked that prefix is OK -#define TSF_DIDSPLIT 2 // tried split at this point -#define TSF_DIDDEL 4 // did a delete, "ts_delidx" has index +enum { + TSF_PREFIXOK = 1, // already checked that prefix is OK + TSF_DIDSPLIT = 2, // tried split at this point + TSF_DIDDEL = 4, // did a delete, "ts_delidx" has index +}; // special values ts_prefixdepth -#define PFD_NOPREFIX 0xff // not using prefixes -#define PFD_PREFIXTREE 0xfe // walking through the prefix tree -#define PFD_NOTSPECIAL 0xfd // highest value that's not special +enum { + PFD_NOPREFIX = 0xff, // not using prefixes + PFD_PREFIXTREE = 0xfe, // walking through the prefix tree + PFD_NOTSPECIAL = 0xfd, // highest value that's not special +}; static long spell_suggest_timeout = 5000; @@ -242,26 +276,27 @@ static int score_wordcount_adj(slang_T *slang, int score, char_u *word, bool spl int newscore; hashitem_T *hi = hash_find(&slang->sl_wordcount, (char *)word); - if (!HASHITEM_EMPTY(hi)) { - wc = HI2WC(hi); - if (wc->wc_count < SCORE_THRES2) { - bonus = SCORE_COMMON1; - } else if (wc->wc_count < SCORE_THRES3) { - bonus = SCORE_COMMON2; - } else { - bonus = SCORE_COMMON3; - } - if (split) { - newscore = score - bonus / 2; - } else { - newscore = score - bonus; - } - if (newscore < 0) { - return 0; - } - return newscore; + if (HASHITEM_EMPTY(hi)) { + return score; + } + + wc = HI2WC(hi); + if (wc->wc_count < SCORE_THRES2) { + bonus = SCORE_COMMON1; + } else if (wc->wc_count < SCORE_THRES3) { + bonus = SCORE_COMMON2; + } else { + bonus = SCORE_COMMON3; } - return score; + if (split) { + newscore = score - bonus / 2; + } else { + newscore = score - bonus; + } + if (newscore < 0) { + return 0; + } + return newscore; } /// Like captype() but for a KEEPCAP word add ONECAP if the word starts with a @@ -270,42 +305,45 @@ static int score_wordcount_adj(slang_T *slang, int score, char_u *word, bool spl static int badword_captype(char_u *word, char_u *end) FUNC_ATTR_NONNULL_ALL { - int flags = captype(word, end); + int flags = captype((char *)word, (char *)end); int c; int l, u; bool first; char_u *p; - if (flags & WF_KEEPCAP) { - // Count the number of UPPER and lower case letters. - l = u = 0; - first = false; - for (p = word; p < end; MB_PTR_ADV(p)) { - c = utf_ptr2char((char *)p); - if (SPELL_ISUPPER(c)) { - u++; - if (p == word) { - first = true; - } - } else { - l++; + if (!(flags & WF_KEEPCAP)) { + return flags; + } + + // Count the number of UPPER and lower case letters. + l = u = 0; + first = false; + for (p = word; p < end; MB_PTR_ADV(p)) { + c = utf_ptr2char((char *)p); + if (SPELL_ISUPPER(c)) { + u++; + if (p == word) { + first = true; } + } else { + l++; } + } - // If there are more UPPER than lower case letters suggest an - // ALLCAP word. Otherwise, if the first letter is UPPER then - // suggest ONECAP. Exception: "ALl" most likely should be "All", - // require three upper case letters. - if (u > l && u > 2) { - flags |= WF_ALLCAP; - } else if (first) { - flags |= WF_ONECAP; - } + // If there are more UPPER than lower case letters suggest an + // ALLCAP word. Otherwise, if the first letter is UPPER then + // suggest ONECAP. Exception: "ALl" most likely should be "All", + // require three upper case letters. + if (u > l && u > 2) { + flags |= WF_ALLCAP; + } else if (first) { + flags |= WF_ONECAP; + } - if (u >= 2 && l >= 2) { // maCARONI maCAroni - flags |= WF_MIXCAP; - } + if (u >= 2 && l >= 2) { // maCARONI maCAroni + flags |= WF_MIXCAP; } + return flags; } @@ -341,9 +379,11 @@ static int bytes2offset(char_u **pp) } // values for sps_flags -#define SPS_BEST 1 -#define SPS_FAST 2 -#define SPS_DOUBLE 4 +enum { + SPS_BEST = 1, + SPS_FAST = 2, + SPS_DOUBLE = 4, +}; static int sps_flags = SPS_BEST; ///< flags from 'spellsuggest' static int sps_limit = 9999; ///< max nr of suggestions given @@ -376,9 +416,9 @@ int spell_check_sps(void) f = SPS_FAST; } else if (strcmp(buf, "double") == 0) { f = SPS_DOUBLE; - } else if (STRNCMP(buf, "expr:", 5) != 0 - && STRNCMP(buf, "file:", 5) != 0 - && (STRNCMP(buf, "timeout:", 8) != 0 + } else if (strncmp(buf, "expr:", 5) != 0 + && strncmp(buf, "file:", 5) != 0 + && (strncmp(buf, "timeout:", 8) != 0 || (!ascii_isdigit(buf[8]) && !(buf[8] == '-' && ascii_isdigit(buf[9]))))) { f = -1; @@ -409,7 +449,7 @@ void spell_suggest(int count) { char *line; pos_T prev_cursor = curwin->w_cursor; - char_u wcopy[MAXWLEN + 2]; + char wcopy[MAXWLEN + 2]; char_u *p; int c; suginfo_T sug; @@ -447,6 +487,11 @@ void spell_suggest(int count) } badlen++; end_visual_mode(); + // make sure we don't include the NUL at the end of the line + line = get_cursor_line_ptr(); + if (badlen > (int)strlen(line) - (int)curwin->w_cursor.col) { + badlen = (int)strlen(line) - (int)curwin->w_cursor.col; + } // Find the start of the badly spelled word. } else if (spell_move_to(curwin, FORWARD, true, true, NULL) == 0 || curwin->w_cursor.col > prev_cursor.col) { @@ -456,15 +501,15 @@ void spell_suggest(int count) line = get_cursor_line_ptr(); p = (char_u *)line + curwin->w_cursor.col; // Backup to before start of word. - while (p > (char_u *)line && spell_iswordp_nmw(p, curwin)) { + while (p > (char_u *)line && spell_iswordp_nmw((char *)p, curwin)) { MB_PTR_BACK(line, p); } // Forward to start of word. - while (*p != NUL && !spell_iswordp_nmw(p, curwin)) { + while (*p != NUL && !spell_iswordp_nmw((char *)p, curwin)) { MB_PTR_ADV(p); } - if (!spell_iswordp_nmw(p, curwin)) { // No word found. + if (!spell_iswordp_nmw((char *)p, curwin)) { // No word found. beep_flush(); return; } @@ -508,12 +553,12 @@ void spell_suggest(int count) msg_start(); msg_row = Rows - 1; // for when 'cmdheight' > 1 lines_left = Rows; // avoid more prompt - vim_snprintf((char *)IObuff, IOSIZE, _("Change \"%.*s\" to:"), + vim_snprintf(IObuff, IOSIZE, _("Change \"%.*s\" to:"), sug.su_badlen, sug.su_badptr); - if (cmdmsg_rl && STRNCMP(IObuff, "Change", 6) == 0) { + if (cmdmsg_rl && strncmp(IObuff, "Change", 6) == 0) { // And now the rabbit from the high hat: Avoid showing the // untranslated message rightleft. - vim_snprintf((char *)IObuff, IOSIZE, ":ot \"%.*s\" egnahC", + vim_snprintf(IObuff, IOSIZE, ":ot \"%.*s\" egnahC", sug.su_badlen, sug.su_badptr); } msg_puts((const char *)IObuff); @@ -526,23 +571,23 @@ void spell_suggest(int count) // The suggested word may replace only part of the bad word, add // the not replaced part. But only when it's not getting too long. - STRLCPY(wcopy, stp->st_word, MAXWLEN + 1); + xstrlcpy(wcopy, stp->st_word, MAXWLEN + 1); int el = sug.su_badlen - stp->st_orglen; if (el > 0 && stp->st_wordlen + el <= MAXWLEN) { - STRLCPY(wcopy + stp->st_wordlen, sug.su_badptr + stp->st_orglen, el + 1); + xstrlcpy(wcopy + stp->st_wordlen, sug.su_badptr + stp->st_orglen, (size_t)el + 1); } - vim_snprintf((char *)IObuff, IOSIZE, "%2d", i + 1); + vim_snprintf(IObuff, IOSIZE, "%2d", i + 1); if (cmdmsg_rl) { - rl_mirror((char_u *)IObuff); + rl_mirror(IObuff); } msg_puts((const char *)IObuff); - vim_snprintf((char *)IObuff, IOSIZE, " \"%s\"", wcopy); + vim_snprintf(IObuff, IOSIZE, " \"%s\"", wcopy); msg_puts((const char *)IObuff); // The word may replace more than "su_badlen". if (sug.su_badlen < stp->st_orglen) { - vim_snprintf((char *)IObuff, IOSIZE, _(" < \"%.*s\""), + vim_snprintf(IObuff, IOSIZE, _(" < \"%.*s\""), stp->st_orglen, sug.su_badptr); msg_puts((const char *)IObuff); } @@ -550,16 +595,16 @@ void spell_suggest(int count) if (p_verbose > 0) { // Add the score. if (sps_flags & (SPS_DOUBLE | SPS_BEST)) { - vim_snprintf((char *)IObuff, IOSIZE, " (%s%d - %d)", + vim_snprintf(IObuff, IOSIZE, " (%s%d - %d)", stp->st_salscore ? "s " : "", stp->st_score, stp->st_altscore); } else { - vim_snprintf((char *)IObuff, IOSIZE, " (%d)", + vim_snprintf(IObuff, IOSIZE, " (%d)", stp->st_score); } if (cmdmsg_rl) { // Mirror the numbers, but keep the leading space. - rl_mirror((char_u *)IObuff + 1); + rl_mirror(IObuff + 1); } msg_advance(30); msg_puts((const char *)IObuff); @@ -593,20 +638,20 @@ void spell_suggest(int count) if (sug.su_badlen > stp->st_orglen) { // Replacing less than "su_badlen", append the remainder to // repl_to. - repl_from = xstrnsave((char *)sug.su_badptr, (size_t)sug.su_badlen); - vim_snprintf((char *)IObuff, IOSIZE, "%s%.*s", stp->st_word, + repl_from = xstrnsave(sug.su_badptr, (size_t)sug.su_badlen); + vim_snprintf(IObuff, IOSIZE, "%s%.*s", stp->st_word, sug.su_badlen - stp->st_orglen, sug.su_badptr + stp->st_orglen); - repl_to = xstrdup((char *)IObuff); + repl_to = xstrdup(IObuff); } else { // Replacing su_badlen or more, use the whole word. - repl_from = xstrnsave((char *)sug.su_badptr, (size_t)stp->st_orglen); + repl_from = xstrnsave(sug.su_badptr, (size_t)stp->st_orglen); repl_to = xstrdup(stp->st_word); } // Replace the word. - p = xmalloc(STRLEN(line) - (size_t)stp->st_orglen + (size_t)stp->st_wordlen + 1); - c = (int)(sug.su_badptr - (char_u *)line); + p = xmalloc(strlen(line) - (size_t)stp->st_orglen + (size_t)stp->st_wordlen + 1); + c = (int)(sug.su_badptr - line); memmove(p, line, (size_t)c); STRCPY(p + c, stp->st_word); STRCAT(p, sug.su_badptr + stp->st_orglen); @@ -637,13 +682,13 @@ void spell_suggest(int count) /// /// @param maxcount maximum nr of suggestions /// @param need_cap 'spellcapcheck' matched -void spell_suggest_list(garray_T *gap, char_u *word, int maxcount, bool need_cap, bool interactive) +void spell_suggest_list(garray_T *gap, char *word, int maxcount, bool need_cap, bool interactive) { suginfo_T sug; suggest_T *stp; char_u *wcopy; - spell_find_suggest(word, 0, &sug, maxcount, false, need_cap, interactive); + spell_find_suggest((char_u *)word, 0, &sug, maxcount, false, need_cap, interactive); // Make room in "gap". ga_init(gap, sizeof(char_u *), sug.su_ga.ga_len + 1); @@ -653,7 +698,7 @@ void spell_suggest_list(garray_T *gap, char_u *word, int maxcount, bool need_cap // The suggested word may replace only part of "word", add the not // replaced part. - wcopy = xmalloc((size_t)stp->st_wordlen + STRLEN(sug.su_badptr + stp->st_orglen) + 1); + wcopy = xmalloc((size_t)stp->st_wordlen + strlen(sug.su_badptr + stp->st_orglen) + 1); STRCPY(wcopy, stp->st_word); STRCPY(wcopy + stp->st_wordlen, sug.su_badptr + stp->st_orglen); ((char_u **)gap->ga_data)[gap->ga_len++] = wcopy; @@ -675,7 +720,7 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma bool banbadword, bool need_cap, bool interactive) { hlf_T attr = HLF_COUNT; - char_u buf[MAXPATHL]; + char buf[MAXPATHL]; char *p; bool do_combine = false; char *sps_copy; @@ -693,7 +738,7 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma } hash_init(&su->su_banned); - su->su_badptr = badptr; + su->su_badptr = (char *)badptr; if (badlen != 0) { su->su_badlen = badlen; } else { @@ -707,7 +752,7 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma if (su->su_badlen >= MAXWLEN) { su->su_badlen = MAXWLEN - 1; // just in case } - STRLCPY(su->su_badword, su->su_badptr, su->su_badlen + 1); + xstrlcpy((char *)su->su_badword, su->su_badptr, (size_t)su->su_badlen + 1); (void)spell_casefold(curwin, su->su_badptr, su->su_badlen, su->su_fbadword, MAXWLEN); @@ -717,8 +762,8 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma su->su_fbadword[su->su_badlen] = NUL; // get caps flags for bad word - su->su_badflags = badword_captype(su->su_badptr, - su->su_badptr + su->su_badlen); + su->su_badflags = badword_captype((char_u *)su->su_badptr, + (char_u *)su->su_badptr + su->su_badlen); if (need_cap) { su->su_badflags |= WF_ONECAP; } @@ -738,23 +783,23 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma // Soundfold the bad word with the default sound folding, so that we don't // have to do this many times. if (su->su_sallang != NULL) { - spell_soundfold(su->su_sallang, su->su_fbadword, true, - su->su_sal_badword); + spell_soundfold(su->su_sallang, (char *)su->su_fbadword, true, + (char *)su->su_sal_badword); } // If the word is not capitalised and spell_check() doesn't consider the // word to be bad then it might need to be capitalised. Add a suggestion // for that. - c = utf_ptr2char((char *)su->su_badptr); + c = utf_ptr2char(su->su_badptr); if (!SPELL_ISUPPER(c) && attr == HLF_COUNT) { - make_case_word(su->su_badword, buf, WF_ONECAP); + make_case_word((char *)su->su_badword, buf, WF_ONECAP); add_suggestion(su, &su->su_ga, (char *)buf, su->su_badlen, SCORE_ICASE, 0, true, su->su_sallang, false); } // Ban the bad word itself. It may appear in another region. if (banbadword) { - add_banned(su, su->su_badword); + add_banned(su, (char *)su->su_badword); } // Make a copy of 'spellsuggest', because the expression may change it. @@ -764,18 +809,18 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma for (p = sps_copy; *p != NUL;) { copy_option_part(&p, (char *)buf, MAXPATHL, ","); - if (STRNCMP(buf, "expr:", 5) == 0) { + if (strncmp(buf, "expr:", 5) == 0) { // Evaluate an expression. Skip this when called recursively, // when using spellsuggest() in the expression. if (!expr_busy) { expr_busy = true; - spell_suggest_expr(su, buf + 5); + spell_suggest_expr(su, (char_u *)buf + 5); expr_busy = false; } - } else if (STRNCMP(buf, "file:", 5) == 0) { + } else if (strncmp(buf, "file:", 5) == 0) { // Use list of suggestions in a file. - spell_suggest_file(su, buf + 5); - } else if (STRNCMP(buf, "timeout:", 8) == 0) { + spell_suggest_file(su, (char_u *)buf + 5); + } else if (strncmp(buf, "timeout:", 8) == 0) { // Limit the time searching for suggestions. spell_suggest_timeout = atol((char *)buf + 8); } else if (!did_intern) { @@ -844,7 +889,7 @@ static void spell_suggest_file(suginfo_T *su, char_u *fname) } // Read it line by line. - while (!vim_fgets(line, MAXWLEN * 2, fd) && !got_int) { + while (!vim_fgets((char *)line, MAXWLEN * 2, fd) && !got_int) { line_breakcheck(); p = (char_u *)vim_strchr((char *)line, '/'); @@ -859,8 +904,8 @@ static void spell_suggest_file(suginfo_T *su, char_u *fname) // If the suggestion doesn't have specific case duplicate the case // of the bad word. - if (captype(p, NULL) == 0) { - make_case_word(p, cword, su->su_badflags); + if (captype((char *)p, NULL) == 0) { + make_case_word((char *)p, (char *)cword, su->su_badflags); p = cword; } @@ -968,20 +1013,20 @@ static void spell_find_cleanup(suginfo_T *su) /// Try finding suggestions by recognizing specific situations. static void suggest_try_special(suginfo_T *su) { - int c; + char c; char_u word[MAXWLEN]; // Recognize a word that is repeated: "the the". - char_u *p = (char_u *)skiptowhite((char *)su->su_fbadword); - size_t len = (size_t)(p - su->su_fbadword); - p = (char_u *)skipwhite((char *)p); - if (STRLEN(p) == len && STRNCMP(su->su_fbadword, p, len) == 0) { + char *p = skiptowhite((char *)su->su_fbadword); + size_t len = (size_t)(p - (char *)su->su_fbadword); + p = skipwhite(p); + if (strlen(p) == len && strncmp(su->su_fbadword, p, len) == 0) { // Include badflags: if the badword is onecap or allcap // use that for the goodword too: "The the" -> "The". c = su->su_fbadword[len]; su->su_fbadword[len] = NUL; - make_case_word(su->su_fbadword, word, su->su_badflags); - su->su_fbadword[len] = (char_u)c; + make_case_word(su->su_fbadword, (char *)word, su->su_badflags); + su->su_fbadword[len] = c; // Give a soundalike score of 0, compute the score as if deleting one // character. @@ -1038,21 +1083,21 @@ static void prof_report(char *name) /// Try finding suggestions by adding/removing/swapping letters. static void suggest_try_change(suginfo_T *su) { - char_u fword[MAXWLEN]; // copy of the bad word, case-folded + char fword[MAXWLEN]; // copy of the bad word, case-folded int n; - char_u *p; + char *p; langp_T *lp; // We make a copy of the case-folded bad word, so that we can modify it // to find matches (esp. REP items). Append some more text, changing // chars after the bad word may help. STRCPY(fword, su->su_fbadword); - n = (int)STRLEN(fword); + n = (int)strlen(fword); p = su->su_badptr + su->su_badlen; - (void)spell_casefold(curwin, p, (int)STRLEN(p), fword + n, MAXWLEN - n); + (void)spell_casefold(curwin, p, (int)strlen(p), fword + n, MAXWLEN - n); // Make sure the resulting text is not longer than the original text. - n = (int)STRLEN(su->su_badptr); + n = (int)strlen(su->su_badptr); if (n < MAXWLEN) { fword[n] = NUL; } @@ -1110,9 +1155,9 @@ static void suggest_try_change(suginfo_T *su) /// word splitting for now /// "similar_chars()" /// use "slang->sl_repsal" instead of "lp->lp_replang->sl_rep" -static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool soundfold) +static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soundfold) { - char_u tword[MAXWLEN]; // good word collected so far + char tword[MAXWLEN]; // good word collected so far trystate_T stack[MAXWLEN]; char preword[MAXWLEN * 3] = { 0 }; // word found with proper case; // concatenation of prefix compound @@ -1132,7 +1177,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so garray_T *gap; idx_T arridx; int len; - char_u *p; + char *p; fromto_T *ftp; int fl = 0, tl; int repextra = 0; // extra bytes in fword[] from REP item @@ -1157,7 +1202,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so if (soundfold) { // Going through the soundfold tree. - byts = fbyts = slang->sl_sbyts; + byts = fbyts = (char_u *)slang->sl_sbyts; idxs = fidxs = slang->sl_sidxs; pbyts = NULL; pidxs = NULL; @@ -1166,9 +1211,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so } else { // When there are postponed prefixes we need to use these first. At // the end of the prefix we continue in the case-fold tree. - fbyts = slang->sl_fbyts; + fbyts = (char_u *)slang->sl_fbyts; fidxs = slang->sl_fidxs; - pbyts = slang->sl_pbyts; + pbyts = (char_u *)slang->sl_pbyts; pidxs = slang->sl_pidxs; if (pbyts != NULL) { byts = pbyts; @@ -1223,9 +1268,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // 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); - flags = badword_captype(su->su_badptr, su->su_badptr + n); - su->su_badflags = badword_captype(su->su_badptr + n, - su->su_badptr + su->su_badlen); + flags = badword_captype((char_u *)su->su_badptr, (char_u *)su->su_badptr + n); + su->su_badflags = badword_captype((char_u *)su->su_badptr + n, + (char_u *)su->su_badptr + su->su_badlen); #ifdef DEBUG_TRIEWALK sprintf(changename[depth], "prefix"); // NOLINT(runtime/printf) #endif @@ -1241,8 +1286,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // and make find_keepcap_word() works. tword[sp->ts_twordlen] = NUL; make_case_word(tword + sp->ts_splitoff, - (char_u *)preword + sp->ts_prewordlen, flags); - sp->ts_prewordlen = (char_u)STRLEN(preword); + preword + sp->ts_prewordlen, flags); + sp->ts_prewordlen = (char_u)strlen(preword); sp->ts_splitoff = sp->ts_twordlen; } break; @@ -1321,9 +1366,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // need to check if a correct word follows. if (sp->ts_fidx - sp->ts_splitfidx == sp->ts_twordlen - sp->ts_splitoff - && STRNCMP(fword + sp->ts_splitfidx, + && strncmp(fword + sp->ts_splitfidx, tword + sp->ts_splitoff, - sp->ts_fidx - sp->ts_splitfidx) == 0) { + (size_t)(sp->ts_fidx - sp->ts_splitfidx)) == 0) { preword[sp->ts_prewordlen] = NUL; newscore = score_wordcount_adj(slang, sp->ts_score, (char_u *)preword + sp->ts_prewordlen, @@ -1357,23 +1402,22 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so compflags[sp->ts_complen] = (char_u)((unsigned)flags >> 24); compflags[sp->ts_complen + 1] = NUL; - STRLCPY(preword + sp->ts_prewordlen, - tword + sp->ts_splitoff, - sp->ts_twordlen - sp->ts_splitoff + 1); + xstrlcpy(preword + sp->ts_prewordlen, + tword + sp->ts_splitoff, + (size_t)(sp->ts_twordlen - sp->ts_splitoff) + 1); // Verify CHECKCOMPOUNDPATTERN rules. - if (match_checkcompoundpattern((char_u *)preword, sp->ts_prewordlen, + if (match_checkcompoundpattern(preword, sp->ts_prewordlen, &slang->sl_comppat)) { compound_ok = false; } if (compound_ok) { - p = (char_u *)preword; - while (*skiptowhite((char *)p) != NUL) { - p = (char_u *)skipwhite(skiptowhite((char *)p)); + p = preword; + while (*skiptowhite(p) != NUL) { + p = skipwhite(skiptowhite(p)); } - if (fword_ends && !can_compound(slang, p, - compflags + sp->ts_compsplit)) { + if (fword_ends && !can_compound(slang, p, compflags + sp->ts_compsplit)) { // Compound is not allowed. But it may still be // possible if we add another (short) word. compound_ok = false; @@ -1381,7 +1425,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so } // Get pointer to last char of previous word. - p = (char_u *)preword + sp->ts_prewordlen; + p = preword + sp->ts_prewordlen; MB_PTR_BACK(preword, p); } } @@ -1393,16 +1437,13 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so STRCPY(preword + sp->ts_prewordlen, tword + sp->ts_splitoff); } else if (flags & WF_KEEPCAP) { // Must find the word in the keep-case tree. - find_keepcap_word(slang, tword + sp->ts_splitoff, - (char_u *)preword + sp->ts_prewordlen); + find_keepcap_word(slang, (char *)tword + sp->ts_splitoff, preword + sp->ts_prewordlen); } else { // Include badflags: If the badword is onecap or allcap // use that for the goodword too. But if the badword is // allcap and it's only one char long use onecap. c = su->su_badflags; - if ((c & WF_ALLCAP) - && su->su_badlen == - utfc_ptr2len((char *)su->su_badptr)) { + if ((c & WF_ALLCAP) && su->su_badlen == utfc_ptr2len(su->su_badptr)) { c = WF_ONECAP; } c |= flags; @@ -1413,14 +1454,14 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so c &= ~WF_ONECAP; } make_case_word(tword + sp->ts_splitoff, - (char_u *)preword + sp->ts_prewordlen, c); + preword + sp->ts_prewordlen, c); } if (!soundfold) { // Don't use a banned word. It may appear again as a good // word, thus remember it. if (flags & WF_BANNED) { - add_banned(su, (char_u *)preword + sp->ts_prewordlen); + add_banned(su, preword + sp->ts_prewordlen); break; } if ((sp->ts_complen == sp->ts_compsplit @@ -1445,7 +1486,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so } if (!spell_valid_case(su->su_badflags, - captype((char_u *)preword + sp->ts_prewordlen, NULL))) { + captype(preword + sp->ts_prewordlen, NULL))) { newscore += SCORE_ICASE; } } @@ -1470,14 +1511,14 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so if (soundfold) { // For soundfolded words we need to find the original // words, the edit distance and then add them. - add_sound_suggest(su, (char_u *)preword, sp->ts_score, lp); + add_sound_suggest(su, preword, sp->ts_score, lp); } else if (sp->ts_fidx > 0) { // Give a penalty when changing non-word char to word // char, e.g., "thes," -> "these". p = fword + sp->ts_fidx; MB_PTR_BACK(fword, p); if (!spell_iswordp(p, curwin) && *preword != NUL) { - p = (char_u *)preword + STRLEN(preword); + p = preword + strlen(preword); MB_PTR_BACK(preword, p); if (spell_iswordp(p, curwin)) { newscore += SCORE_NONWORD; @@ -1499,10 +1540,10 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so if (su->su_badflags & WF_MIXCAP) { // We really don't know if the word should be // upper or lower case, add both. - c = captype((char_u *)preword, NULL); + c = captype(preword, NULL); if (c == 0 || c == WF_ALLCAP) { make_case_word(tword + sp->ts_splitoff, - (char_u *)preword + sp->ts_prewordlen, + preword + sp->ts_prewordlen, c == 0 ? WF_ALLCAP : 0); add_suggestion(su, &su->su_ga, (char *)preword, @@ -1589,13 +1630,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so && (flags & WF_NEEDCOMP)) { break; } - p = (char_u *)preword; - while (*skiptowhite((char *)p) != NUL) { - p = (char_u *)skipwhite(skiptowhite((char *)p)); + p = preword; + while (*skiptowhite(p) != NUL) { + p = skipwhite(skiptowhite(p)); } if (sp->ts_complen > sp->ts_compsplit - && !can_compound(slang, p, - compflags + sp->ts_compsplit)) { + && !can_compound(slang, p, compflags + sp->ts_compsplit)) { break; } @@ -1650,7 +1690,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so && goodword_ends) { int l; - l = utfc_ptr2len((char *)fword + sp->ts_fidx); + l = utfc_ptr2len(fword + sp->ts_fidx); if (fword_ends) { // Copy the skipped character to preword. memmove(preword + sp->ts_prewordlen, fword + sp->ts_fidx, (size_t)l); @@ -1675,8 +1715,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // set su->su_badflags to the caps type at this // position n = nofold_len(fword, sp->ts_fidx, su->su_badptr); - su->su_badflags = badword_captype(su->su_badptr + n, - su->su_badptr + su->su_badlen); + su->su_badflags = badword_captype((char_u *)su->su_badptr + n, + (char_u *)su->su_badptr + su->su_badlen); // Restart at top of the tree. sp->ts_arridx = 0; @@ -1743,7 +1783,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // when the byte was already changed. And don't try when we // just deleted this byte, accepting it is always cheaper than // delete + substitute. - if (c == fword[sp->ts_fidx] + if (c == (uint8_t)fword[sp->ts_fidx] || (sp->ts_tcharlen > 0 && sp->ts_isdiff != DIFF_NONE)) { newscore = 0; @@ -1753,7 +1793,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so if ((newscore == 0 || (sp->ts_fidx >= sp->ts_fidxtry && ((sp->ts_flags & TSF_DIDDEL) == 0 - || c != fword[sp->ts_delidx]))) + || c != (uint8_t)fword[sp->ts_delidx]))) && TRY_DEEPER(su, stack, depth, newscore)) { go_deeper(stack, depth, newscore); #ifdef DEBUG_TRIEWALK @@ -1772,7 +1812,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so if (fword[sp->ts_fidx] != NUL) { sp->ts_fidx++; } - tword[sp->ts_twordlen++] = (char_u)c; + tword[sp->ts_twordlen++] = (char)c; sp->ts_arridx = idxs[arridx]; if (newscore == SCORE_SUBST) { sp->ts_isdiff = DIFF_YES; @@ -1798,14 +1838,14 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Correct ts_fidx for the byte length of the // character (we didn't check that before). sp->ts_fidx = (char_u)(sp->ts_fcharstart - + utfc_ptr2len((char *)fword + sp->ts_fcharstart)); + + utfc_ptr2len(fword + sp->ts_fcharstart)); // For changing a composing character adjust // the score from SCORE_SUBST to // SCORE_SUBCOMP. if (utf_iscomposing(utf_ptr2char((char *)tword + sp->ts_twordlen - sp->ts_tcharlen)) - && utf_iscomposing(utf_ptr2char((char *)fword + && utf_iscomposing(utf_ptr2char(fword + sp->ts_fcharstart))) { sp->ts_score -= SCORE_SUBST - SCORE_SUBCOMP; } else if (!soundfold @@ -1813,15 +1853,15 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so && similar_chars(slang, utf_ptr2char((char *)tword + sp->ts_twordlen - sp->ts_tcharlen), - utf_ptr2char((char *)fword + sp->ts_fcharstart))) { + utf_ptr2char(fword + sp->ts_fcharstart))) { // For a similar character adjust score from // SCORE_SUBST to SCORE_SIMILAR. sp->ts_score -= SCORE_SUBST - SCORE_SIMILAR; } } else if (sp->ts_isdiff == DIFF_INSERT && sp->ts_twordlen > sp->ts_tcharlen) { - p = tword + sp->ts_twordlen - sp->ts_tcharlen; - c = utf_ptr2char((char *)p); + p = (char *)tword + sp->ts_twordlen - sp->ts_tcharlen; + c = utf_ptr2char(p); if (utf_iscomposing(c)) { // Inserting a composing char doesn't // count that much. @@ -1833,7 +1873,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // tree (might seem illogical but does // give better scores). MB_PTR_BACK(tword, p); - if (c == utf_ptr2char((char *)p)) { + if (c == utf_ptr2char(p)) { sp->ts_score -= SCORE_INS - SCORE_INSDUP; } } @@ -1884,12 +1924,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // score if the same character is following "nn" -> "n". It's // a bit illogical for soundfold tree but it does give better // results. - c = utf_ptr2char((char *)fword + sp->ts_fidx); + c = utf_ptr2char(fword + sp->ts_fidx); stack[depth].ts_fidx = - (char_u)(stack[depth].ts_fidx + utfc_ptr2len((char *)fword + sp->ts_fidx)); + (char_u)(stack[depth].ts_fidx + utfc_ptr2len(fword + sp->ts_fidx)); if (utf_iscomposing(c)) { stack[depth].ts_score -= SCORE_DEL - SCORE_DELCOMP; - } else if (c == utf_ptr2char((char *)fword + stack[depth].ts_fidx)) { + } else if (c == utf_ptr2char(fword + stack[depth].ts_fidx)) { stack[depth].ts_score -= SCORE_DEL - SCORE_DELDUP; } @@ -1949,7 +1989,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so } else { newscore = SCORE_INS; } - if (c != fword[sp->ts_fidx] + if (c != (uint8_t)fword[sp->ts_fidx] && TRY_DEEPER(su, stack, depth, newscore)) { go_deeper(stack, depth, newscore); #ifdef DEBUG_TRIEWALK @@ -1959,7 +1999,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so #endif depth++; sp = &stack[depth]; - tword[sp->ts_twordlen++] = (char_u)c; + tword[sp->ts_twordlen++] = (char)c; sp->ts_arridx = idxs[n]; fl = MB_BYTE2LEN(c); if (fl > 1) { @@ -1976,7 +2016,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // soundfold words (illogical but does give a better // score). if (sp->ts_twordlen >= 2 - && tword[sp->ts_twordlen - 2] == c) { + && (uint8_t)tword[sp->ts_twordlen - 2] == c) { sp->ts_score -= SCORE_INS - SCORE_INSDUP; } } @@ -1988,7 +2028,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // We change "fword" here, it's changed back afterwards at // STATE_UNSWAP. p = fword + sp->ts_fidx; - c = *p; + c = (uint8_t)(*p); if (c == NUL) { // End of word, can't swap or replace. PROF_STORE(sp->ts_state) @@ -2004,14 +2044,14 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so break; } - n = utf_ptr2len((char *)p); - c = utf_ptr2char((char *)p); + n = utf_ptr2len(p); + c = utf_ptr2char(p); if (p[n] == NUL) { c2 = NUL; } else if (!soundfold && !spell_iswordp(p + n, curwin)) { c2 = c; // don't swap non-word char } else { - c2 = utf_ptr2char((char *)p + n); + c2 = utf_ptr2char(p + n); } // When the second character is NUL we can't swap. @@ -2041,7 +2081,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so depth++; fl = utf_char2len(c2); memmove(p, p + n, (size_t)fl); - utf_char2bytes(c, (char *)p + fl); + utf_char2bytes(c, p + fl); stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl); } else { // If this swap doesn't work then SWAP3 won't either. @@ -2053,10 +2093,10 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so case STATE_UNSWAP: // Undo the STATE_SWAP swap: "21" -> "12". p = fword + sp->ts_fidx; - n = utfc_ptr2len((char *)p); - c = utf_ptr2char((char *)p + n); - memmove(p + utfc_ptr2len((char *)p + n), p, (size_t)n); - utf_char2bytes(c, (char *)p); + n = utfc_ptr2len(p); + c = utf_ptr2char(p + n); + memmove(p + utfc_ptr2len(p + n), p, (size_t)n); + utf_char2bytes(c, p); FALLTHROUGH; @@ -2064,14 +2104,14 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Swap two bytes, skipping one: "123" -> "321". We change // "fword" here, it's changed back afterwards at STATE_UNSWAP3. p = fword + sp->ts_fidx; - n = utf_ptr2len((char *)p); - c = utf_ptr2char((char *)p); - fl = utf_ptr2len((char *)p + n); - c2 = utf_ptr2char((char *)p + n); + n = utf_ptr2len(p); + c = utf_ptr2char(p); + fl = utf_ptr2len(p + n); + c2 = utf_ptr2char(p + n); if (!soundfold && !spell_iswordp(p + n + fl, curwin)) { c3 = c; // don't swap non-word char } else { - c3 = utf_ptr2char((char *)p + n + fl); + c3 = utf_ptr2char(p + n + fl); } // When characters are identical: "121" then SWAP3 result is @@ -2097,8 +2137,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so depth++; tl = utf_char2len(c3); memmove(p, p + n + fl, (size_t)tl); - utf_char2bytes(c2, (char *)p + tl); - utf_char2bytes(c, (char *)p + fl + tl); + utf_char2bytes(c2, p + tl); + utf_char2bytes(c, p + fl + tl); stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl + tl); } else { PROF_STORE(sp->ts_state) @@ -2109,14 +2149,14 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so case STATE_UNSWAP3: // Undo STATE_SWAP3: "321" -> "123" p = fword + sp->ts_fidx; - n = utfc_ptr2len((char *)p); - c2 = utf_ptr2char((char *)p + n); - fl = utfc_ptr2len((char *)p + n); - c = utf_ptr2char((char *)p + n + fl); - tl = utfc_ptr2len((char *)p + n + fl); + n = utfc_ptr2len(p); + c2 = utf_ptr2char(p + n); + fl = utfc_ptr2len(p + n); + c = utf_ptr2char(p + n + fl); + tl = utfc_ptr2len(p + n + fl); memmove(p + fl + tl, p, (size_t)n); - utf_char2bytes(c, (char *)p); - utf_char2bytes(c2, (char *)p + tl); + utf_char2bytes(c, p); + utf_char2bytes(c2, p + tl); p = p + tl; if (!soundfold && !spell_iswordp(p, curwin)) { @@ -2141,12 +2181,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so sp->ts_state = STATE_UNROT3L; depth++; p = fword + sp->ts_fidx; - n = utf_ptr2len((char *)p); - c = utf_ptr2char((char *)p); - fl = utf_ptr2len((char *)p + n); - fl += utf_ptr2len((char *)p + n + fl); + n = utf_ptr2len(p); + c = utf_ptr2char(p); + fl = utf_ptr2len(p + n); + fl += utf_ptr2len(p + n + fl); memmove(p, p + n, (size_t)fl); - utf_char2bytes(c, (char *)p + fl); + utf_char2bytes(c, p + fl); stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl); } else { PROF_STORE(sp->ts_state) @@ -2157,12 +2197,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so case STATE_UNROT3L: // Undo ROT3L: "231" -> "123" p = fword + sp->ts_fidx; - n = utfc_ptr2len((char *)p); - n += utfc_ptr2len((char *)p + n); - c = utf_ptr2char((char *)p + n); - tl = utfc_ptr2len((char *)p + n); + n = utfc_ptr2len(p); + n += utfc_ptr2len(p + n); + c = utf_ptr2char(p + n); + tl = utfc_ptr2len(p + n); memmove(p + tl, p, (size_t)n); - utf_char2bytes(c, (char *)p); + utf_char2bytes(c, p); // Rotate three bytes right: "123" -> "312". We change "fword" // here, it's changed back afterwards at STATE_UNROT3R. @@ -2178,12 +2218,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so sp->ts_state = STATE_UNROT3R; depth++; p = fword + sp->ts_fidx; - n = utf_ptr2len((char *)p); - n += utf_ptr2len((char *)p + n); - c = utf_ptr2char((char *)p + n); - tl = utf_ptr2len((char *)p + n); + n = utf_ptr2len(p); + n += utf_ptr2len(p + n); + c = utf_ptr2char(p + n); + tl = utf_ptr2len(p + n); memmove(p + tl, p, (size_t)n); - utf_char2bytes(c, (char *)p); + utf_char2bytes(c, p); stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + tl); } else { PROF_STORE(sp->ts_state) @@ -2194,12 +2234,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so case STATE_UNROT3R: // Undo ROT3R: "312" -> "123" p = fword + sp->ts_fidx; - c = utf_ptr2char((char *)p); - tl = utfc_ptr2len((char *)p); - n = utfc_ptr2len((char *)p + tl); - n += utfc_ptr2len((char *)p + tl + n); + c = utf_ptr2char(p); + tl = utfc_ptr2len(p); + n = utfc_ptr2len(p + tl); + n += utfc_ptr2len(p + tl + n); memmove(p, p + tl, (size_t)n); - utf_char2bytes(c, (char *)p + n); + utf_char2bytes(c, p + n); FALLTHROUGH; @@ -2220,9 +2260,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Use the first byte to quickly find the first entry that may // match. If the index is -1 there is none. if (soundfold) { - sp->ts_curi = slang->sl_repsal_first[fword[sp->ts_fidx]]; + sp->ts_curi = slang->sl_repsal_first[(uint8_t)fword[sp->ts_fidx]]; } else { - sp->ts_curi = lp->lp_replang->sl_rep_first[fword[sp->ts_fidx]]; + sp->ts_curi = lp->lp_replang->sl_rep_first[(uint8_t)fword[sp->ts_fidx]]; } if (sp->ts_curi < 0) { @@ -2250,10 +2290,10 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so ftp = (fromto_T *)gap->ga_data + sp->ts_curi++; if (*ftp->ft_from != *p) { // past possible matching entries - sp->ts_curi = (char_u)gap->ga_len; + sp->ts_curi = (int16_t)gap->ga_len; break; } - if (STRNCMP(ftp->ft_from, p, STRLEN(ftp->ft_from)) == 0 + if (strncmp(ftp->ft_from, p, strlen(ftp->ft_from)) == 0 && TRY_DEEPER(su, stack, depth, SCORE_REP)) { go_deeper(stack, depth, SCORE_REP); #ifdef DEBUG_TRIEWALK @@ -2267,10 +2307,10 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Change the "from" to the "to" string. depth++; - fl = (int)STRLEN(ftp->ft_from); - tl = (int)STRLEN(ftp->ft_to); + fl = (int)strlen(ftp->ft_from); + tl = (int)strlen(ftp->ft_to); if (fl != tl) { - STRMOVE(p + tl, p + fl); + STRMOVE(p + tl, (char *)p + fl); repextra += tl - fl; } memmove(p, ftp->ft_to, (size_t)tl); @@ -2296,11 +2336,11 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so gap = &lp->lp_replang->sl_rep; } ftp = (fromto_T *)gap->ga_data + sp->ts_curi - 1; - fl = (int)STRLEN(ftp->ft_from); - tl = (int)STRLEN(ftp->ft_to); + fl = (int)strlen(ftp->ft_from); + tl = (int)strlen(ftp->ft_to); p = fword + sp->ts_fidx; if (fl != tl) { - STRMOVE(p + fl, p + tl); + STRMOVE(p + fl, (char *)p + tl); repextra -= tl - fl; } memmove(p, ftp->ft_from, (size_t)fl); @@ -2344,9 +2384,9 @@ static void go_deeper(trystate_T *stack, int depth, int score_add) /// words and put it in "kword". /// Theoretically there could be several keep-case words that result in the /// same case-folded word, but we only find one... -static void find_keepcap_word(slang_T *slang, char_u *fword, char_u *kword) +static void find_keepcap_word(slang_T *slang, char *fword, char *kword) { - char_u uword[MAXWLEN]; // "fword" in upper-case + char uword[MAXWLEN]; // "fword" in upper-case int depth; idx_T tryidx; @@ -2363,7 +2403,7 @@ static void find_keepcap_word(slang_T *slang, char_u *fword, char_u *kword) int c; idx_T lo, hi, m; char_u *p; - char_u *byts = slang->sl_kbyts; // array with bytes of the words + char_u *byts = (char_u *)slang->sl_kbyts; // array with bytes of the words idx_T *idxs = slang->sl_kidxs; // array with indexes if (byts == NULL) { @@ -2402,13 +2442,13 @@ static void find_keepcap_word(slang_T *slang, char_u *fword, char_u *kword) } else { // round[depth] == 1: Try using the folded-case character. // round[depth] == 2: Try using the upper-case character. - flen = utf_ptr2len((char *)fword + fwordidx[depth]); + flen = utf_ptr2len(fword + fwordidx[depth]); ulen = utf_ptr2len((char *)uword + uwordidx[depth]); if (round[depth] == 1) { - p = fword + fwordidx[depth]; + p = (char_u *)fword + fwordidx[depth]; l = flen; } else { - p = uword + uwordidx[depth]; + p = (char_u *)uword + uwordidx[depth]; l = ulen; } @@ -2443,12 +2483,14 @@ static void find_keepcap_word(slang_T *slang, char_u *fword, char_u *kword) // Found the matching char. Copy it to "kword" and go a // level deeper. if (round[depth] == 1) { - STRNCPY(kword + kwordlen[depth], fword + fwordidx[depth], // NOLINT(runtime/printf) - flen); + strncpy(kword + kwordlen[depth], // NOLINT(runtime/printf) + fword + fwordidx[depth], + (size_t)flen); kwordlen[depth + 1] = kwordlen[depth] + flen; } else { - STRNCPY(kword + kwordlen[depth], uword + uwordidx[depth], // NOLINT(runtime/printf) - ulen); + strncpy(kword + kwordlen[depth], // NOLINT(runtime/printf) + uword + uwordidx[depth], + (size_t)ulen); kwordlen[depth + 1] = kwordlen[depth] + ulen; } fwordidx[depth + 1] = fwordidx[depth] + flen; @@ -2483,7 +2525,7 @@ static void score_comp_sal(suginfo_T *su) lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); if (!GA_EMPTY(&lp->lp_slang->sl_sal)) { // soundfold the bad word - spell_soundfold(lp->lp_slang, su->su_fbadword, true, badsound); + spell_soundfold(lp->lp_slang, (char *)su->su_fbadword, true, (char *)badsound); for (i = 0; i < su->su_ga.ga_len; i++) { stp = &SUG(su->su_ga, i); @@ -2526,7 +2568,7 @@ static void score_combine(suginfo_T *su) if (!GA_EMPTY(&lp->lp_slang->sl_sal)) { // soundfold the bad word slang = lp->lp_slang; - spell_soundfold(slang, su->su_fbadword, true, (char_u *)badsound); + spell_soundfold(slang, (char *)su->su_fbadword, true, badsound); for (int i = 0; i < su->su_ga.ga_len; i++) { stp = &SUG(su->su_ga, i); @@ -2551,7 +2593,7 @@ static void score_combine(suginfo_T *su) // Add the alternate score to su_sga. for (int i = 0; i < su->su_sga.ga_len; i++) { stp = &SUG(su->su_sga, i); - stp->st_altscore = spell_edit_score(slang, su->su_badword, (char_u *)stp->st_word); + stp->st_altscore = spell_edit_score(slang, (char_u *)su->su_badword, (char_u *)stp->st_word); if (stp->st_score == SCORE_MAXMAX) { stp->st_score = (SCORE_BIG * 7 + stp->st_altscore) / 8; } else { @@ -2620,7 +2662,7 @@ static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u * char_u badsound2[MAXWLEN]; char_u fword[MAXWLEN]; char_u goodsound[MAXWLEN]; - char_u goodword[MAXWLEN]; + char goodword[MAXWLEN]; int lendiff; lendiff = su->su_badlen - stp->st_orglen; @@ -2628,7 +2670,7 @@ static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u * pbad = badsound; } else { // soundfold the bad word with more characters following - (void)spell_casefold(curwin, su->su_badptr, stp->st_orglen, fword, MAXWLEN); + (void)spell_casefold(curwin, su->su_badptr, stp->st_orglen, (char *)fword, MAXWLEN); // When joining two words the sound often changes a lot. E.g., "t he" // sounds like "t h" while "the" sounds like "@". Avoid that by @@ -2637,11 +2679,11 @@ static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u * if (ascii_iswhite(su->su_badptr[su->su_badlen]) && *skiptowhite(stp->st_word) == NUL) { for (p = fword; *(p = (char_u *)skiptowhite((char *)p)) != NUL;) { - STRMOVE(p, p + 1); + STRMOVE(p, (char *)p + 1); } } - spell_soundfold(slang, fword, true, badsound2); + spell_soundfold(slang, (char *)fword, true, (char *)badsound2); pbad = badsound2; } @@ -2649,15 +2691,15 @@ static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u * // Add part of the bad word to the good word, so that we soundfold // what replaces the bad word. STRCPY(goodword, stp->st_word); - STRLCPY(goodword + stp->st_wordlen, - su->su_badptr + su->su_badlen - lendiff, lendiff + 1); - pgood = goodword; + xstrlcpy(goodword + stp->st_wordlen, + su->su_badptr + su->su_badlen - lendiff, (size_t)lendiff + 1); + pgood = (char_u *)goodword; } else { pgood = (char_u *)stp->st_word; } // Sound-fold the word and compute the score for the difference. - spell_soundfold(slang, pgood, false, goodsound); + spell_soundfold(slang, (char *)pgood, false, (char *)goodsound); return soundalike_score((char *)goodsound, (char *)pbad); } @@ -2706,7 +2748,7 @@ static void suggest_try_soundalike(suginfo_T *su) slang = lp->lp_slang; if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) { // soundfold the bad word - spell_soundfold(slang, su->su_fbadword, true, salword); + spell_soundfold(slang, (char *)su->su_fbadword, true, (char *)salword); // try all kinds of inserts/deletes/swaps/etc. // TODO(vim): also soundfold the next words, so that we can try joining @@ -2714,7 +2756,7 @@ static void suggest_try_soundalike(suginfo_T *su) #ifdef SUGGEST_PROFILE prof_init(); #endif - suggest_trie_walk(su, lp, salword, true); + suggest_trie_walk(su, lp, (char *)salword, true); #ifdef SUGGEST_PROFILE prof_report("soundalike"); #endif @@ -2756,7 +2798,7 @@ static void suggest_try_soundalike_finish(void) /// produce this soundfolded word. /// /// @param score soundfold score -static void add_sound_suggest(suginfo_T *su, char_u *goodword, int score, langp_T *lp) +static void add_sound_suggest(suginfo_T *su, char *goodword, int score, langp_T *lp) { slang_T *slang = lp->lp_slang; // language for sound folding int sfwordnr; @@ -2782,14 +2824,14 @@ static void add_sound_suggest(suginfo_T *su, char_u *goodword, int score, langp_ // the words that have a better score than before. Use a hashtable to // remember the words that have been done. hash = hash_hash(goodword); - const size_t goodword_len = STRLEN(goodword); + const size_t goodword_len = strlen(goodword); hi = hash_lookup(&slang->sl_sounddone, (const char *)goodword, goodword_len, hash); if (HASHITEM_EMPTY(hi)) { sft = xmalloc(sizeof(sftword_T) + goodword_len); sft->sft_score = (int16_t)score; memcpy(sft->sft_word, goodword, goodword_len + 1); - hash_add_item(&slang->sl_sounddone, hi, sft->sft_word, hash); + hash_add_item(&slang->sl_sounddone, hi, (char *)sft->sft_word, hash); } else { sft = HI2SFT(hi); if (score >= sft->sft_score) { @@ -2799,7 +2841,7 @@ static void add_sound_suggest(suginfo_T *su, char_u *goodword, int score, langp_ } // Find the word nr in the soundfold tree. - sfwordnr = soundfold_find(slang, goodword); + sfwordnr = soundfold_find(slang, (char_u *)goodword); if (sfwordnr < 0) { internal_error("add_sound_suggest()"); return; @@ -2813,7 +2855,7 @@ static void add_sound_suggest(suginfo_T *su, char_u *goodword, int score, langp_ // previous wordnr. orgnr += bytes2offset(&nrline); - byts = slang->sl_fbyts; + byts = (char_u *)slang->sl_fbyts; idxs = slang->sl_fidxs; // Lookup the word "orgnr" one of the two tries. @@ -2865,13 +2907,13 @@ badword: if (flags & WF_KEEPCAP) { // Must find the word in the keep-case tree. - find_keepcap_word(slang, theword, cword); + find_keepcap_word(slang, (char *)theword, (char *)cword); p = cword; } else { flags |= su->su_badflags; if ((flags & WF_CAPMASK) != 0) { // Need to fix case according to "flags". - make_case_word(theword, cword, flags); + make_case_word((char *)theword, (char *)cword, flags); p = cword; } else { p = theword; @@ -2916,9 +2958,9 @@ badword: // inefficient, using an array is quicker. limit = MAXSCORE(su->su_sfmaxscore - goodscore, score); if (limit > SCORE_LIMITMAX) { - goodscore += spell_edit_score(slang, su->su_badword, p); + goodscore += spell_edit_score(slang, (char_u *)su->su_badword, p); } else { - goodscore += spell_edit_score_limit(slang, su->su_badword, + goodscore += spell_edit_score_limit(slang, (char_u *)su->su_badword, p, limit); } @@ -2951,7 +2993,7 @@ static int soundfold_find(slang_T *slang, char_u *word) idx_T *idxs; int wordnr = 0; - byts = slang->sl_sbyts; + byts = (char_u *)slang->sl_sbyts; idxs = slang->sl_sidxs; for (;;) { @@ -3028,7 +3070,7 @@ static bool similar_chars(slang_T *slang, int c1, int c2) if (HASHITEM_EMPTY(hi)) { m1 = 0; } else { - m1 = utf_ptr2char((char *)hi->hi_key + STRLEN(hi->hi_key) + 1); + m1 = utf_ptr2char(hi->hi_key + strlen(hi->hi_key) + 1); } } else { m1 = slang->sl_map_array[c1]; @@ -3043,7 +3085,7 @@ static bool similar_chars(slang_T *slang, int c1, int c2) if (HASHITEM_EMPTY(hi)) { m2 = 0; } else { - m2 = utf_ptr2char((char *)hi->hi_key + STRLEN(hi->hi_key) + 1); + m2 = utf_ptr2char(hi->hi_key + strlen(hi->hi_key) + 1); } } else { m2 = slang->sl_map_array[c2]; @@ -3071,7 +3113,7 @@ static void add_suggestion(suginfo_T *su, garray_T *gap, const char *goodword, i // Minimize "badlen" for consistency. Avoids that changing "the the" to // "thee the" is added next to changing the first "the" the "thee". const char *pgood = goodword + strlen(goodword); - char_u *pbad = su->su_badptr + badlenarg; + char *pbad = su->su_badptr + badlenarg; for (;;) { goodlen = (int)(pgood - goodword); badlen = (int)(pbad - su->su_badptr); @@ -3080,7 +3122,7 @@ static void add_suggestion(suginfo_T *su, garray_T *gap, const char *goodword, i } MB_PTR_BACK(goodword, pgood); MB_PTR_BACK(su->su_badptr, pbad); - if (utf_ptr2char((char *)pgood) != utf_ptr2char((char *)pbad)) { + if (utf_ptr2char(pgood) != utf_ptr2char(pbad)) { break; } } @@ -3102,7 +3144,7 @@ static void add_suggestion(suginfo_T *su, garray_T *gap, const char *goodword, i for (i = gap->ga_len; --i >= 0; stp++) { if (stp->st_wordlen == goodlen && stp->st_orglen == badlen - && STRNCMP(stp->st_word, goodword, goodlen) == 0) { + && strncmp(stp->st_word, goodword, (size_t)goodlen) == 0) { // Found it. Remember the word with the lowest score. if (stp->st_slang == NULL) { stp->st_slang = slang; @@ -3172,7 +3214,7 @@ static void add_suggestion(suginfo_T *su, garray_T *gap, const char *goodword, i static void check_suggestions(suginfo_T *su, garray_T *gap) { suggest_T *stp; - char_u longword[MAXWLEN + 1]; + char longword[MAXWLEN + 1]; int len; hlf_T attr; @@ -3182,10 +3224,10 @@ static void check_suggestions(suginfo_T *su, garray_T *gap) stp = &SUG(*gap, 0); for (int i = gap->ga_len - 1; i >= 0; i--) { // Need to append what follows to check for "the the". - STRLCPY(longword, stp[i].st_word, MAXWLEN + 1); + xstrlcpy(longword, stp[i].st_word, MAXWLEN + 1); len = stp[i].st_wordlen; - STRLCPY(longword + len, su->su_badptr + stp[i].st_orglen, - MAXWLEN - len + 1); + xstrlcpy(longword + len, su->su_badptr + stp[i].st_orglen, + (size_t)(MAXWLEN - len + 1)); attr = HLF_COUNT; (void)spell_check(curwin, longword, &attr, NULL, false); if (attr != HLF_COUNT) { @@ -3200,19 +3242,20 @@ static void check_suggestions(suginfo_T *su, garray_T *gap) } /// Add a word to be banned. -static void add_banned(suginfo_T *su, char_u *word) +static void add_banned(suginfo_T *su, char *word) { char_u *s; hash_T hash; hashitem_T *hi; hash = hash_hash(word); - const size_t word_len = STRLEN(word); - hi = hash_lookup(&su->su_banned, (const char *)word, word_len, hash); - if (HASHITEM_EMPTY(hi)) { - s = xmemdupz(word, word_len); - hash_add_item(&su->su_banned, hi, s, hash); + const size_t word_len = strlen(word); + hi = hash_lookup(&su->su_banned, word, word_len, hash); + if (!HASHITEM_EMPTY(hi)) { // already present + return; } + s = xmemdupz(word, word_len); + hash_add_item(&su->su_banned, hi, (char *)s, hash); } /// Recompute the score for all suggestions if sound-folding is possible. This @@ -3239,7 +3282,7 @@ static void rescore_one(suginfo_T *su, suggest_T *stp) if (slang == su->su_sallang) { p = su->su_sal_badword; } else { - spell_soundfold(slang, su->su_fbadword, true, sal_badword); + spell_soundfold(slang, (char *)su->su_fbadword, true, (char *)sal_badword); p = sal_badword; } @@ -3279,21 +3322,23 @@ static int sug_compare(const void *s1, const void *s2) static int cleanup_suggestions(garray_T *gap, int maxscore, int keep) FUNC_ATTR_NONNULL_ALL { - if (gap->ga_len > 0) { - // Sort the list. - qsort(gap->ga_data, (size_t)gap->ga_len, sizeof(suggest_T), sug_compare); + if (gap->ga_len <= 0) { + return maxscore; + } - // Truncate the list to the number of suggestions that will be displayed. - if (gap->ga_len > keep) { - suggest_T *const stp = &SUG(*gap, 0); + // Sort the list. + qsort(gap->ga_data, (size_t)gap->ga_len, sizeof(suggest_T), sug_compare); - for (int i = keep; i < gap->ga_len; i++) { - xfree(stp[i].st_word); - } - gap->ga_len = keep; - if (keep >= 1) { - return stp[keep - 1].st_score; - } + // Truncate the list to the number of suggestions that will be displayed. + if (gap->ga_len > keep) { + suggest_T *const stp = &SUG(*gap, 0); + + for (int i = keep; i < gap->ga_len; i++) { + xfree(stp[i].st_word); + } + gap->ga_len = keep; + if (keep >= 1) { + return stp[keep - 1].st_score; } } return maxscore; @@ -3530,7 +3575,7 @@ static int soundalike_score(char *goodstart, char *badstart) /// The implementation of the algorithm comes from Aspell editdist.cpp, /// edit_distance(). It has been converted from C++ to C and modified to /// support multi-byte characters. -static int spell_edit_score(slang_T *slang, char_u *badword, char_u *goodword) +static int spell_edit_score(slang_T *slang, const char_u *badword, const char_u *goodword) { int *cnt; int j, i; @@ -3547,12 +3592,12 @@ static int spell_edit_score(slang_T *slang, char_u *badword, char_u *goodword) // Get the characters from the multi-byte strings and put them in an // int array for easy access. badlen = 0; - for (const char_u *p = badword; *p != NUL;) { + for (const char *p = (char *)badword; *p != NUL;) { wbadword[badlen++] = mb_cptr2char_adv(&p); } wbadword[badlen++] = 0; goodlen = 0; - for (const char_u *p = goodword; *p != NUL;) { + for (const char *p = (char *)goodword; *p != NUL;) { wgoodword[goodlen++] = mb_cptr2char_adv(&p); } wgoodword[goodlen++] = 0; @@ -3635,7 +3680,8 @@ static int spell_edit_score_limit(slang_T *slang, char_u *badword, char_u *goodw /// Multi-byte version of spell_edit_score_limit(). /// Keep it in sync with the above! -static int spell_edit_score_limit_w(slang_T *slang, char_u *badword, char_u *goodword, int limit) +static int spell_edit_score_limit_w(slang_T *slang, const char_u *badword, const char_u *goodword, + int limit) { limitscore_T stack[10]; // allow for over 3 * 2 edits int stackidx; @@ -3652,12 +3698,12 @@ static int spell_edit_score_limit_w(slang_T *slang, char_u *badword, char_u *goo // Get the characters from the multi-byte strings and put them in an // int array for easy access. bi = 0; - for (const char_u *p = badword; *p != NUL;) { + for (const char *p = (char *)badword; *p != NUL;) { wbadword[bi++] = mb_cptr2char_adv(&p); } wbadword[bi++] = 0; gi = 0; - for (const char_u *p = goodword; *p != NUL;) { + for (const char *p = (char *)goodword; *p != NUL;) { wgoodword[gi++] = mb_cptr2char_adv(&p); } wgoodword[gi++] = 0; diff --git a/src/nvim/state.c b/src/nvim/state.c index 7712fcd39a..9ba5f81776 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -1,27 +1,38 @@ // 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 <assert.h> +#include <stdbool.h> +#include <stddef.h> +#include <string.h> -#include "klib/kvec.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" -#include "nvim/ex_docmd.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/event/defs.h" +#include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" #include "nvim/getchar.h" +#include "nvim/globals.h" #include "nvim/insexpand.h" +#include "nvim/keycodes.h" #include "nvim/log.h" +#include "nvim/macros.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/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "state.c.generated.h" +# include "state.c.generated.h" // IWYU pragma: export #endif void state_enter(VimState *s) @@ -229,7 +240,9 @@ void get_mode(char *buf) /// Fires a ModeChanged autocmd if appropriate. void may_trigger_modechanged(void) { - if (!has_event(EVENT_MODECHANGED)) { + // Skip this when got_int is set, the autocommand will not be executed. + // Better trigger it next time. + if (!has_event(EVENT_MODECHANGED) || got_int) { return; } diff --git a/src/nvim/state.h b/src/nvim/state.h index b93d57d035..76a38b0dab 100644 --- a/src/nvim/state.h +++ b/src/nvim/state.h @@ -3,6 +3,8 @@ #include <stddef.h> +struct vim_state; + typedef struct vim_state VimState; typedef int (*state_check_callback)(VimState *state); diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index 33c5c3a347..6ad1f31143 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -5,22 +5,43 @@ #include <assert.h> #include <inttypes.h> #include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> -#include "nvim/assert.h" -#include "nvim/autocmd.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" +#include "nvim/ascii.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/vars.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/move.h" +#include "nvim/normal.h" #include "nvim/option.h" #include "nvim/optionstr.h" +#include "nvim/os/os.h" +#include "nvim/path.h" +#include "nvim/pos.h" +#include "nvim/screen.h" +#include "nvim/sign_defs.h" #include "nvim/statusline.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -43,11 +64,10 @@ void win_redr_status(win_T *wp) { int row; int col; - char_u *p; + char *p; int len; int fillchar; int attr; - int width; int this_ru_col; bool is_stl_global = global_stl_height() > 0; static bool busy = false; @@ -74,11 +94,11 @@ 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; + const int stl_width = is_stl_global ? Columns : wp->w_width; get_trans_bufname(wp->w_buffer); - p = (char_u *)NameBuff; - len = (int)STRLEN(p); + p = NameBuff; + len = (int)strlen(p); if ((bt_help(wp->w_buffer) || wp->w_p_pvw @@ -88,40 +108,40 @@ void win_redr_status(win_T *wp) *(p + len++) = ' '; } if (bt_help(wp->w_buffer)) { - snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Help]")); - len += (int)STRLEN(p + len); + snprintf(p + len, MAXPATHL - (size_t)len, "%s", _("[Help]")); + len += (int)strlen(p + len); } if (wp->w_p_pvw) { - snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Preview]")); - len += (int)STRLEN(p + len); + snprintf(p + len, MAXPATHL - (size_t)len, "%s", _("[Preview]")); + len += (int)strlen(p + len); } if (bufIsChanged(wp->w_buffer)) { - snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", "[+]"); - len += (int)STRLEN(p + len); + snprintf(p + len, MAXPATHL - (size_t)len, "%s", "[+]"); + len += (int)strlen(p + len); } if (wp->w_buffer->b_p_ro) { - snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[RO]")); + snprintf(p + len, MAXPATHL - (size_t)len, "%s", _("[RO]")); // len += (int)strlen(p + len); // dead assignment } - this_ru_col = ru_col - (Columns - width); - if (this_ru_col < (width + 1) / 2) { - this_ru_col = (width + 1) / 2; + this_ru_col = ru_col - (Columns - stl_width); + if (this_ru_col < (stl_width + 1) / 2) { + this_ru_col = (stl_width + 1) / 2; } if (this_ru_col <= 1) { - p = (char_u *)"<"; // No room for file name! + p = "<"; // No room for file name! len = 1; } else { int clen = 0, i; // Count total number of display cells. - clen = (int)mb_string2cells((char *)p); + clen = (int)mb_string2cells(p); // Find first character that will fit. // Going from start to end is much faster for DBCS. for (i = 0; p[i] != NUL && clen >= this_ru_col - 1; - i += utfc_ptr2len((char *)p + i)) { - clen -= utf_ptr2cells((char *)p + i); + i += utfc_ptr2len(p + i)) { + clen -= utf_ptr2cells(p + i); } len = clen; if (i > 0) { @@ -133,17 +153,27 @@ void win_redr_status(win_T *wp) row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp); col = is_stl_global ? 0 : wp->w_wincol; - grid_puts(&default_grid, (char *)p, row, col, attr); + 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, "<%s>", (char *)NameBuff, MAXPATHL) + if (get_keymap_str(wp, "<%s>", NameBuff, MAXPATHL) && this_ru_col - len > (int)(strlen(NameBuff) + 1)) { grid_puts(&default_grid, NameBuff, row, (int)((size_t)this_ru_col - strlen(NameBuff) - 1), attr); } win_redr_ruler(wp, true); + + // Draw the 'showcmd' information if 'showcmdloc' == "statusline". + if (p_sc && *p_sloc == 's') { + const int sc_width = MIN(10, this_ru_col - len - 2); + + if (sc_width > 0) { + grid_puts_len(&default_grid, showcmd_buf, sc_width, row, + wp->w_wincol + this_ru_col - sc_width - 1, attr); + } + } } // May need to draw the character below the vertical separator. @@ -158,6 +188,252 @@ void win_redr_status(win_T *wp) busy = false; } +/// Clear status line, window bar or tab page line click definition table +/// +/// @param[out] tpcd Table to clear. +/// @param[in] tpcd_size Size of the table. +void stl_clear_click_defs(StlClickDefinition *const click_defs, const size_t click_defs_size) +{ + if (click_defs != NULL) { + for (size_t i = 0; i < click_defs_size; i++) { + if (i == 0 || click_defs[i].func != click_defs[i - 1].func) { + xfree(click_defs[i].func); + } + } + memset(click_defs, 0, click_defs_size * sizeof(click_defs[0])); + } +} + +/// Allocate or resize the click definitions array if needed. +StlClickDefinition *stl_alloc_click_defs(StlClickDefinition *cdp, long width, size_t *size) +{ + if (*size < (size_t)width) { + xfree(cdp); + *size = (size_t)width; + cdp = xcalloc(*size, sizeof(StlClickDefinition)); + } + return cdp; +} + +/// Fill the click definitions array if needed. +void stl_fill_click_defs(StlClickDefinition *click_defs, StlClickRecord *click_recs, char *buf, + int width, bool tabline) +{ + if (click_defs == NULL) { + return; + } + + int col = 0; + int len = 0; + + StlClickDefinition cur_click_def = { + .type = kStlClickDisabled, + }; + for (int i = 0; click_recs[i].start != NULL; i++) { + len += vim_strnsize(buf, (int)(click_recs[i].start - buf)); + if (col < len) { + while (col < len) { + click_defs[col++] = cur_click_def; + } + } else { + xfree(cur_click_def.func); + } + buf = (char *)click_recs[i].start; + cur_click_def = click_recs[i].def; + if (!tabline && !(cur_click_def.type == kStlClickDisabled + || cur_click_def.type == kStlClickFuncRun)) { + // window bar and status line only support click functions + cur_click_def.type = kStlClickDisabled; + } + } + if (col < width) { + while (col < width) { + click_defs[col++] = cur_click_def; + } + } else { + xfree(cur_click_def.func); + } +} + +/// Redraw the status line, window bar or ruler of window "wp". +/// When "wp" is NULL redraw the tab pages line from 'tabline'. +static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) +{ + static bool entered = false; + int attr; + int curattr; + int row; + int col = 0; + int maxwidth; + int width; + int n; + int len; + int fillchar; + char buf[MAXPATHL]; + char *stl; + char *p; + char *opt_name; + int opt_scope = 0; + stl_hlrec_t *hltab; + StlClickRecord *tabtab; + win_T *ewp; + int p_crb_save; + bool is_stl_global = global_stl_height() > 0; + + ScreenGrid *grid = &default_grid; + + // There is a tiny chance that this gets called recursively: When + // redrawing a status line triggers redrawing the ruler or tabline. + // Avoid trouble by not allowing recursion. + if (entered) { + return; + } + entered = true; + + // setup environment for the task at hand + if (wp == NULL) { + // Use 'tabline'. Always at the first line of the screen. + stl = p_tal; + row = 0; + fillchar = ' '; + attr = HL_ATTR(HLF_TPF); + maxwidth = Columns; + opt_name = "tabline"; + } else if (draw_winbar) { + opt_name = "winbar"; + stl = ((*wp->w_p_wbr != NUL) ? wp->w_p_wbr : p_wbr); + opt_scope = ((*wp->w_p_wbr != NUL) ? OPT_LOCAL : 0); + row = -1; // row zero is first row of text + col = 0; + grid = &wp->w_grid; + grid_adjust(&grid, &row, &col); + + if (row < 0) { + return; + } + + fillchar = wp->w_p_fcs_chars.wbr; + attr = (wp == curwin) ? win_hl_attr(wp, HLF_WBR) : win_hl_attr(wp, HLF_WBRNC); + maxwidth = wp->w_width_inner; + stl_clear_click_defs(wp->w_winbar_click_defs, wp->w_winbar_click_defs_size); + wp->w_winbar_click_defs = stl_alloc_click_defs(wp->w_winbar_click_defs, maxwidth, + &wp->w_winbar_click_defs_size); + } else { + row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp); + fillchar = fillchar_status(&attr, wp); + maxwidth = is_stl_global ? Columns : wp->w_width; + stl_clear_click_defs(wp->w_status_click_defs, wp->w_status_click_defs_size); + wp->w_status_click_defs = stl_alloc_click_defs(wp->w_status_click_defs, maxwidth, + &wp->w_status_click_defs_size); + + if (draw_ruler) { + stl = p_ruf; + opt_name = "rulerformat"; + // advance past any leading group spec - implicit in ru_col + if (*stl == '%') { + if (*++stl == '-') { + stl++; + } + if (atoi(stl)) { + while (ascii_isdigit(*stl)) { + stl++; + } + } + if (*stl++ != '(') { + stl = p_ruf; + } + } + col = ru_col - (Columns - maxwidth); + if (col < (maxwidth + 1) / 2) { + col = (maxwidth + 1) / 2; + } + 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 + fillchar = ' '; + attr = HL_ATTR(HLF_MSG); + } + } else { + opt_name = "statusline"; + stl = ((*wp->w_p_stl != NUL) ? wp->w_p_stl : p_stl); + opt_scope = ((*wp->w_p_stl != NUL) ? OPT_LOCAL : 0); + } + + col += is_stl_global ? 0 : wp->w_wincol; + } + + if (maxwidth <= 0) { + goto theend; + } + + // Temporarily reset 'cursorbind', we don't want a side effect from moving + // the cursor away and back. + ewp = wp == NULL ? curwin : wp; + p_crb_save = ewp->w_p_crb; + ewp->w_p_crb = false; + + // Make a copy, because the statusline may include a function call that + // might change the option value and free the memory. + stl = xstrdup(stl); + width = build_stl_str_hl(ewp, buf, sizeof(buf), stl, opt_name, opt_scope, + fillchar, maxwidth, &hltab, &tabtab, NULL); + + xfree(stl); + ewp->w_p_crb = p_crb_save; + + // Make all characters printable. + p = transstr(buf, true); + len = (int)xstrlcpy(buf, p, sizeof(buf)); + len = (size_t)len < sizeof(buf) ? len : (int)sizeof(buf) - 1; + xfree(p); + + // fill up with "fillchar" + while (width < maxwidth && len < (int)sizeof(buf) - 1) { + len += utf_char2bytes(fillchar, buf + len); + width++; + } + buf[len] = NUL; + + // Draw each snippet with the specified highlighting. + grid_puts_line_start(grid, row); + + curattr = attr; + p = buf; + for (n = 0; hltab[n].start != NULL; n++) { + int textlen = (int)(hltab[n].start - p); + grid_puts_len(grid, p, textlen, row, col, curattr); + col += vim_strnsize(p, textlen); + p = hltab[n].start; + + if (hltab[n].userhl == 0) { + curattr = attr; + } else if (hltab[n].userhl < 0) { + curattr = syn_id2attr(-hltab[n].userhl); + } else if (wp != NULL && wp != curwin && wp->w_status_height != 0) { + curattr = highlight_stlnc[hltab[n].userhl - 1]; + } else { + curattr = highlight_user[hltab[n].userhl - 1]; + } + } + // Make sure to use an empty string instead of p, if p is beyond buf + len. + grid_puts(grid, p >= buf + len ? "" : p, row, col, curattr); + + grid_puts_line_flush(false); + + // Fill the tab_page_click_defs, w_status_click_defs or w_winbar_click_defs array for clicking + // in the tab page line, status line or window bar + StlClickDefinition *click_defs = (wp == NULL) ? tab_page_click_defs + : draw_winbar ? wp->w_winbar_click_defs + : wp->w_status_click_defs; + + stl_fill_click_defs(click_defs, tabtab, buf, maxwidth, wp == NULL); + +theend: + entered = false; +} + void win_redr_winbar(win_T *wp) { static bool entered = false; @@ -172,19 +448,7 @@ void win_redr_winbar(win_T *wp) if (wp->w_winbar_height == 0 || !redrawing()) { // Do nothing. } else if (*p_wbr != NUL || *wp->w_p_wbr != NUL) { - int saved_did_emsg = did_emsg; - - did_emsg = false; win_redr_custom(wp, true, false); - if (did_emsg) { - // When there is an error disable the winbar, otherwise the - // display is messed up with errors and a redraw triggers the problem - // again and again. - set_string_option_direct("winbar", -1, "", - OPT_FREE | (*wp->w_p_stl != NUL - ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR); - } - did_emsg |= saved_did_emsg; } entered = false; } @@ -214,11 +478,7 @@ void win_redr_ruler(win_T *wp, bool always) } if (*p_ruf && p_ch > 0 && !ui_has(kUIMessages)) { - const int called_emsg_before = called_emsg; win_redr_custom(wp, false, true); - if (called_emsg > called_emsg_before) { - set_string_option_direct("rulerformat", -1, "", OPT_FREE, SID_ERROR); - } return; } @@ -389,7 +649,6 @@ int fillchar_status(int *attr, win_T *wp) void redraw_custom_statusline(win_T *wp) { static bool entered = false; - int saved_did_emsg = did_emsg; // When called recursively return. This can happen when the statusline // contains an expression that triggers a redraw. @@ -398,234 +657,262 @@ void redraw_custom_statusline(win_T *wp) } entered = true; - did_emsg = false; win_redr_custom(wp, false, false); - if (did_emsg) { - // When there is an error disable the statusline, otherwise the - // display is messed up with errors and a redraw triggers the problem - // again and again. - set_string_option_direct("statusline", -1, "", - OPT_FREE | (*wp->w_p_stl != NUL - ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR); - } - did_emsg |= saved_did_emsg; entered = false; } -/// Redraw the status line, window bar or ruler of window "wp". -/// When "wp" is NULL redraw the tab pages line from 'tabline'. -void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) +static void ui_ext_tabline_update(void) { - static bool entered = false; - int attr; - int curattr; - int row; + Arena arena = ARENA_EMPTY; + + size_t n_tabs = 0; + FOR_ALL_TABS(tp) { + n_tabs++; + } + + Array tabs = arena_array(&arena, n_tabs); + FOR_ALL_TABS(tp) { + Dictionary tab_info = arena_dict(&arena, 2); + PUT_C(tab_info, "tab", TABPAGE_OBJ(tp->handle)); + + win_T *cwp = (tp == curtab) ? curwin : tp->tp_curwin; + get_trans_bufname(cwp->w_buffer); + PUT_C(tab_info, "name", STRING_OBJ(arena_string(&arena, cstr_as_string(NameBuff)))); + + ADD_C(tabs, DICTIONARY_OBJ(tab_info)); + } + + size_t n_buffers = 0; + FOR_ALL_BUFFERS(buf) { + n_buffers += buf->b_p_bl ? 1 : 0; + } + + Array buffers = arena_array(&arena, n_buffers); + FOR_ALL_BUFFERS(buf) { + // Do not include unlisted buffers + if (!buf->b_p_bl) { + continue; + } + + Dictionary buffer_info = arena_dict(&arena, 2); + PUT_C(buffer_info, "buffer", BUFFER_OBJ(buf->handle)); + + get_trans_bufname(buf); + PUT_C(buffer_info, "name", STRING_OBJ(arena_string(&arena, cstr_as_string(NameBuff)))); + + ADD_C(buffers, DICTIONARY_OBJ(buffer_info)); + } + + ui_call_tabline_update(curtab->handle, tabs, curbuf->handle, buffers); + arena_mem_free(arena_finish(&arena)); +} + +/// Draw the tab pages line at the top of the Vim window. +void draw_tabline(void) +{ + int tabcount = 0; + int tabwidth = 0; int col = 0; - int maxwidth; - int width; - int n; + int scol = 0; + int attr; + win_T *wp; + win_T *cwp; + int wincount; + int modified; + int c; int len; - int fillchar; - char buf[MAXPATHL]; - char *stl; + int attr_nosel = HL_ATTR(HLF_TP); + int attr_fill = HL_ATTR(HLF_TPF); char *p; - stl_hlrec_t *hltab; - StlClickRecord *tabtab; - int use_sandbox = false; - win_T *ewp; - int p_crb_save; - bool is_stl_global = global_stl_height() > 0; + int room; + int use_sep_chars = (t_colors < 8); - ScreenGrid *grid = &default_grid; + if (default_grid.chars == NULL) { + return; + } + redraw_tabline = false; - // There is a tiny chance that this gets called recursively: When - // redrawing a status line triggers redrawing the ruler or tabline. - // Avoid trouble by not allowing recursion. - if (entered) { + if (ui_has(kUITabline)) { + ui_ext_tabline_update(); return; } - entered = true; - // setup environment for the task at hand - if (wp == NULL) { - // Use 'tabline'. Always at the first line of the screen. - stl = p_tal; - row = 0; - fillchar = ' '; - attr = HL_ATTR(HLF_TPF); - maxwidth = Columns; - use_sandbox = was_set_insecurely(wp, "tabline", 0); - } else if (draw_winbar) { - stl = ((*wp->w_p_wbr != NUL) ? wp->w_p_wbr : p_wbr); - row = -1; // row zero is first row of text - col = 0; - grid = &wp->w_grid; - grid_adjust(&grid, &row, &col); + if (tabline_height() < 1) { + return; + } - if (row < 0) { - return; - } + // Clear tab_page_click_defs: Clicking outside of tabs has no effect. + assert(tab_page_click_defs_size >= (size_t)Columns); + stl_clear_click_defs(tab_page_click_defs, tab_page_click_defs_size); - fillchar = wp->w_p_fcs_chars.wbr; - attr = (wp == curwin) ? win_hl_attr(wp, HLF_WBR) : win_hl_attr(wp, HLF_WBRNC); - maxwidth = wp->w_width_inner; - use_sandbox = was_set_insecurely(wp, "winbar", 0); + // Use the 'tabline' option if it's set. + if (*p_tal != NUL) { + win_redr_custom(NULL, false, false); + } else { + FOR_ALL_TABS(tp) { + tabcount++; + } - stl_clear_click_defs(wp->w_winbar_click_defs, (long)wp->w_winbar_click_defs_size); - // Allocate / resize the click definitions array for winbar if needed. - if (wp->w_winbar_height && wp->w_winbar_click_defs_size < (size_t)maxwidth) { - xfree(wp->w_winbar_click_defs); - wp->w_winbar_click_defs_size = (size_t)maxwidth; - wp->w_winbar_click_defs = xcalloc(wp->w_winbar_click_defs_size, sizeof(StlClickRecord)); + if (tabcount > 0) { + tabwidth = (Columns - 1 + tabcount / 2) / tabcount; } - } else { - row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp); - fillchar = fillchar_status(&attr, wp); - maxwidth = is_stl_global ? Columns : wp->w_width; - stl_clear_click_defs(wp->w_status_click_defs, (long)wp->w_status_click_defs_size); - // Allocate / resize the click definitions array for statusline if needed. - if (wp->w_status_click_defs_size < (size_t)maxwidth) { - xfree(wp->w_status_click_defs); - wp->w_status_click_defs_size = (size_t)maxwidth; - wp->w_status_click_defs = xcalloc(wp->w_status_click_defs_size, sizeof(StlClickRecord)); + if (tabwidth < 6) { + tabwidth = 6; } - if (draw_ruler) { - stl = p_ruf; - // advance past any leading group spec - implicit in ru_col - if (*stl == '%') { - if (*++stl == '-') { - stl++; - } - if (atoi(stl)) { - while (ascii_isdigit(*stl)) { - stl++; - } - } - if (*stl++ != '(') { - stl = p_ruf; - } - } - col = ru_col - (Columns - maxwidth); - if (col < (maxwidth + 1) / 2) { - col = (maxwidth + 1) / 2; - } - 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 - fillchar = ' '; - attr = HL_ATTR(HLF_MSG); + attr = attr_nosel; + tabcount = 0; + + FOR_ALL_TABS(tp) { + if (col >= Columns - 4) { + break; } - use_sandbox = was_set_insecurely(wp, "rulerformat", 0); - } else { - if (*wp->w_p_stl != NUL) { - stl = wp->w_p_stl; + scol = col; + + if (tp == curtab) { + cwp = curwin; + wp = firstwin; } else { - stl = p_stl; + cwp = tp->tp_curwin; + wp = tp->tp_firstwin; } - use_sandbox = was_set_insecurely(wp, "statusline", *wp->w_p_stl == NUL ? 0 : OPT_LOCAL); - } - col += is_stl_global ? 0 : wp->w_wincol; - } + if (tp->tp_topframe == topframe) { + attr = win_hl_attr(cwp, HLF_TPS); + } + if (use_sep_chars && col > 0) { + grid_putchar(&default_grid, '|', 0, col++, attr); + } - if (maxwidth <= 0) { - goto theend; - } + if (tp->tp_topframe != topframe) { + attr = win_hl_attr(cwp, HLF_TP); + } - // Temporarily reset 'cursorbind', we don't want a side effect from moving - // the cursor away and back. - ewp = wp == NULL ? curwin : wp; - p_crb_save = ewp->w_p_crb; - ewp->w_p_crb = false; + grid_putchar(&default_grid, ' ', 0, col++, attr); - // Make a copy, because the statusline may include a function call that - // might change the option value and free the memory. - stl = xstrdup(stl); - width = - build_stl_str_hl(ewp, buf, sizeof(buf), stl, use_sandbox, - fillchar, maxwidth, &hltab, &tabtab); - xfree(stl); - ewp->w_p_crb = p_crb_save; + modified = false; - // Make all characters printable. - p = transstr(buf, true); - len = (int)STRLCPY(buf, p, sizeof(buf)); - len = (size_t)len < sizeof(buf) ? len : (int)sizeof(buf) - 1; - xfree(p); + for (wincount = 0; wp != NULL; wp = wp->w_next, wincount++) { + if (bufIsChanged(wp->w_buffer)) { + modified = true; + } + } - // fill up with "fillchar" - while (width < maxwidth && len < (int)sizeof(buf) - 1) { - len += utf_char2bytes(fillchar, buf + len); - width++; - } - buf[len] = NUL; + if (modified || wincount > 1) { + if (wincount > 1) { + vim_snprintf(NameBuff, MAXPATHL, "%d", wincount); + len = (int)strlen(NameBuff); + if (col + len >= Columns - 3) { + break; + } + grid_puts_len(&default_grid, NameBuff, len, 0, col, + hl_combine_attr(attr, win_hl_attr(cwp, HLF_T))); + col += len; + } + if (modified) { + grid_puts_len(&default_grid, "+", 1, 0, col++, attr); + } + grid_putchar(&default_grid, ' ', 0, col++, attr); + } - // Draw each snippet with the specified highlighting. - grid_puts_line_start(grid, row); + room = scol - col + tabwidth - 1; + if (room > 0) { + // Get buffer name in NameBuff[] + get_trans_bufname(cwp->w_buffer); + shorten_dir(NameBuff); + len = vim_strsize(NameBuff); + p = NameBuff; + while (len > room) { + len -= ptr2cells(p); + MB_PTR_ADV(p); + } + if (len > Columns - col - 1) { + len = Columns - col - 1; + } - curattr = attr; - p = buf; - for (n = 0; hltab[n].start != NULL; n++) { - int textlen = (int)(hltab[n].start - p); - grid_puts_len(grid, p, textlen, row, col, curattr); - col += vim_strnsize(p, textlen); - p = hltab[n].start; + grid_puts_len(&default_grid, p, (int)strlen(p), 0, col, attr); + col += len; + } + grid_putchar(&default_grid, ' ', 0, col++, attr); + + // Store the tab page number in tab_page_click_defs[], so that + // jump_to_mouse() knows where each one is. + tabcount++; + while (scol < col) { + tab_page_click_defs[scol++] = (StlClickDefinition) { + .type = kStlClickTabSwitch, + .tabnr = tabcount, + .func = NULL, + }; + } + } - if (hltab[n].userhl == 0) { - curattr = attr; - } else if (hltab[n].userhl < 0) { - curattr = syn_id2attr(-hltab[n].userhl); - } else if (wp != NULL && wp != curwin && wp->w_status_height != 0) { - curattr = highlight_stlnc[hltab[n].userhl - 1]; + if (use_sep_chars) { + c = '_'; } else { - curattr = highlight_user[hltab[n].userhl - 1]; + c = ' '; } - } - // Make sure to use an empty string instead of p, if p is beyond buf + len. - grid_puts(grid, p >= buf + len ? "" : p, row, col, curattr); + grid_fill(&default_grid, 0, 1, col, Columns, c, c, attr_fill); - grid_puts_line_flush(false); + // Draw the 'showcmd' information if 'showcmdloc' == "tabline". + if (p_sc && *p_sloc == 't') { + const int sc_width = MIN(10, (int)Columns - col - (tabcount > 1) * 3); - // Fill the tab_page_click_defs, w_status_click_defs or w_winbar_click_defs array for clicking - // in the tab page line, status line or window bar - StlClickDefinition *click_defs = (wp == NULL) ? tab_page_click_defs - : draw_winbar ? wp->w_winbar_click_defs - : wp->w_status_click_defs; + if (sc_width > 0) { + grid_puts_len(&default_grid, showcmd_buf, sc_width, 0, + Columns - sc_width - (tabcount > 1) * 2, attr_nosel); + } + } - if (click_defs == NULL) { - goto theend; + // Put an "X" for closing the current tab if there are several. + if (tabcount > 1) { + grid_putchar(&default_grid, 'X', 0, Columns - 1, attr_nosel); + tab_page_click_defs[Columns - 1] = (StlClickDefinition) { + .type = kStlClickTabClose, + .tabnr = 999, + .func = NULL, + }; + } } - col = 0; - len = 0; - p = buf; - StlClickDefinition cur_click_def = { - .type = kStlClickDisabled, - }; - for (n = 0; tabtab[n].start != NULL; n++) { - len += vim_strnsize(p, (int)(tabtab[n].start - p)); - while (col < len) { - click_defs[col++] = cur_click_def; - } - p = (char *)tabtab[n].start; - cur_click_def = tabtab[n].def; - if ((wp != NULL) && !(cur_click_def.type == kStlClickDisabled - || cur_click_def.type == kStlClickFuncRun)) { - // window bar and status line only support click functions - cur_click_def.type = kStlClickDisabled; - } + // Reset the flag here again, in case evaluating 'tabline' causes it to be + // set. + redraw_tabline = false; +} + +/// Build the 'statuscolumn' string for line "lnum". When "relnum" == -1, +/// the v:lnum and v:relnum variables don't have to be updated. +/// +/// @param hlrec HL attributes (can be NULL) +/// @param stcp Status column attributes (can be NULL) +/// @return The width of the built status column string for line "lnum" +int build_statuscol_str(win_T *wp, linenr_T lnum, long relnum, int maxwidth, int fillchar, + char *buf, stl_hlrec_t **hlrec, statuscol_T *stcp) +{ + bool fillclick = relnum >= 0 && lnum == wp->w_topline; + + if (relnum >= 0) { + set_vim_var_nr(VV_LNUM, lnum); + set_vim_var_nr(VV_RELNUM, relnum); } - while (col < maxwidth) { - click_defs[col++] = cur_click_def; + + StlClickRecord *clickrec; + char *stc = xstrdup(wp->w_p_stc); + int width = build_stl_str_hl(wp, buf, MAXPATHL, stc, "statuscolumn", OPT_LOCAL, fillchar, + maxwidth, hlrec, fillclick ? &clickrec : NULL, stcp); + xfree(stc); + + // Only update click definitions once per window per redraw + if (fillclick) { + stl_clear_click_defs(wp->w_statuscol_click_defs, wp->w_statuscol_click_defs_size); + wp->w_statuscol_click_defs = stl_alloc_click_defs(wp->w_statuscol_click_defs, width, + &wp->w_statuscol_click_defs_size); + stl_fill_click_defs(wp->w_statuscol_click_defs, clickrec, buf, width, false); } -theend: - entered = false; + return width; } /// Build a string from the status line items in "fmt". @@ -646,15 +933,18 @@ theend: /// 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 opt_name The option name corresponding to "fmt" +/// @param opt_scope The scope corresponding to "opt_name" /// @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 tabtab Tab clicks definition (can be NULL) +/// @param stcp Status column attributes (can be NULL) /// /// @return The final width of the statusline -int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_sandbox, int fillchar, - int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab) +int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_name, int opt_scope, + int fillchar, int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab, + statuscol_T *stcp) { static size_t stl_items_len = 20; // Initial value, grows as needed. static stl_item_t *stl_items = NULL; @@ -669,6 +959,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san char *usefmt = fmt; const int save_must_redraw = must_redraw; const int save_redr_type = curwin->w_redr_type; + const bool save_KeyTyped = KeyTyped; + // TODO(Bram): find out why using called_emsg_before makes tests fail, does it + // matter? + // const int called_emsg_before = called_emsg; + const int did_emsg_before = did_emsg; if (stl_items == NULL) { stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len); @@ -682,6 +977,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san stl_separator_locations = xmalloc(sizeof(int) * stl_items_len); } + // if "fmt" was set insecurely it needs to be evaluated in the sandbox + // "opt_name" will be NULL when caller is nvim_eval_statusline() + const int use_sandbox = opt_name ? was_set_insecurely(wp, opt_name, opt_scope) + : false; + // When the format starts with "%!" then evaluate it as an expression and // use the result as the actual format string. if (fmt[0] == '%' && fmt[1] == '!') { @@ -829,7 +1129,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // so `vim_strsize` will work. char *t = stl_items[stl_groupitems[groupdepth]].start; *out_p = NUL; - long group_len = vim_strsize(t); + ptrdiff_t group_len = vim_strsize(t); // If the group contained internal items // and the group did not have a minimum width, @@ -915,7 +1215,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san } // If the group is shorter than the minimum width, add padding characters. } else if (abs(stl_items[stl_groupitems[groupdepth]].minwid) > group_len) { - long min_group_width = stl_items[stl_groupitems[groupdepth]].minwid; + ptrdiff_t min_group_width = stl_items[stl_groupitems[groupdepth]].minwid; // If the group is left-aligned, add characters to the right. if (min_group_width < 0) { min_group_width = 0 - min_group_width; @@ -929,7 +1229,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san group_len = (min_group_width - group_len) * utf_char2len(fillchar); memmove(t + group_len, t, (size_t)(out_p - t)); if (out_p + group_len >= (out_end_p + 1)) { - group_len = (long)(out_end_p - out_p); + group_len = out_end_p - out_p; } out_p += group_len; // } @@ -1037,7 +1337,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san } stl_items[curitem].type = ClickFunc; stl_items[curitem].start = out_p; - stl_items[curitem].cmd = xmemdupz(t, (size_t)(fmt_p - t)); + stl_items[curitem].cmd = tabtab ? xmemdupz(t, (size_t)(fmt_p - t)) : NULL; stl_items[curitem].minwid = minwid; fmt_p++; curitem++; @@ -1078,7 +1378,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // An invalid item was specified. // Continue processing on the next character of the format string. - if (vim_strchr(STL_ALL, *fmt_p) == NULL) { + if (vim_strchr(STL_ALL, (uint8_t)(*fmt_p)) == NULL) { fmt_p++; continue; } @@ -1100,17 +1400,17 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // get replaced with the fillchar fillable = false; if (buf_spname(wp->w_buffer) != NULL) { - STRLCPY(NameBuff, buf_spname(wp->w_buffer), MAXPATHL); + xstrlcpy(NameBuff, buf_spname(wp->w_buffer), MAXPATHL); } else { char *t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname : wp->w_buffer->b_fname; - home_replace(wp->w_buffer, t, (char *)NameBuff, MAXPATHL, true); + home_replace(wp->w_buffer, t, NameBuff, MAXPATHL, true); } - trans_characters((char *)NameBuff, MAXPATHL); + trans_characters(NameBuff, MAXPATHL); if (opt != STL_FILENAME) { - str = (char *)NameBuff; + str = NameBuff; } else { - str = path_tail((char *)NameBuff); + str = path_tail(NameBuff); } break; case STL_VIM_EXPR: // '{' @@ -1217,8 +1517,14 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san } case STL_LINE: - num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) - ? 0L : (long)(wp->w_cursor.lnum); + // Overload %l with v:lnum for 'statuscolumn' + if (opt_name != NULL && strcmp(opt_name, "statuscolumn") == 0) { + if (wp->w_p_nu && !get_vim_var_nr(VV_VIRTNUM)) { + num = get_vim_var_nr(VV_LNUM); + } + } else { + num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? 0L : (long)(wp->w_cursor.lnum); + } break; case STL_NUMLINES: @@ -1255,6 +1561,12 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san str = buf_tmp; break; + case STL_SHOWCMD: + if (p_sc && (opt_name == NULL || strcmp(opt_name, p_sloc) == 0)) { + str = showcmd_buf; + } + break; + case STL_ARGLISTSTAT: fillable = false; @@ -1278,7 +1590,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san } break; case STL_PAGENUM: - num = printer_page_num; + num = 0; break; case STL_BUFNO: @@ -1310,9 +1622,16 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san case STL_ROFLAG: case STL_ROFLAG_ALT: - itemisflag = true; - if (wp->w_buffer->b_p_ro) { - str = (opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]"); + // Overload %r with v:relnum for 'statuscolumn' + if (opt_name != NULL && strcmp(opt_name, "statuscolumn") == 0) { + if (wp->w_p_rnu && !get_vim_var_nr(VV_VIRTNUM)) { + num = get_vim_var_nr(VV_RELNUM); + } + } else { + itemisflag = true; + if (wp->w_buffer->b_p_ro) { + str = (opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]"); + } } break; @@ -1324,6 +1643,33 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san } break; + case STL_FOLDCOL: // 'C' for 'statuscolumn' + case STL_SIGNCOL: { // 's' for 'statuscolumn' + if (stcp == NULL) { + break; + } + + bool fold = opt == STL_FOLDCOL; + *buf_tmp = NUL; + for (int i = 0; i <= SIGN_SHOW_MAX; i++) { + char *p = fold ? stcp->fold_text : stcp->sign_text[i]; + if ((!p || !*p) && *buf_tmp == NUL) { + break; + } + stl_items[curitem].type = Highlight; + stl_items[curitem].start = out_p + strlen(buf_tmp); + stl_items[curitem].minwid = !p || (fold && i) ? 0 : fold ? stcp->fold_attr + : stcp->sign_attr[i]; + curitem++; + if (!p || (fold && i)) { + str = buf_tmp; + break; + } + STRCAT(buf_tmp, p); + } + break; + } + case STL_FILETYPE: // Copy the filetype if it is not null and the formatted string will fit // in the temporary buffer @@ -1346,7 +1692,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san vim_snprintf(buf_tmp, sizeof(buf_tmp), ",%s", wp->w_buffer->b_p_ft); // Uppercase the file extension for (char *t = buf_tmp; *t != 0; t++) { - *t = (char)TOUPPER_LOC(*t); + *t = (char)TOUPPER_LOC((uint8_t)(*t)); } str = buf_tmp; } @@ -1595,6 +1941,10 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // What follows is post-processing to handle alignment and highlighting. int width = vim_strsize(out); + // Return truncated width for 'statuscolumn' + if (stcp != NULL && width > maxwidth) { + stcp->truncate = width - maxwidth; + } if (maxwidth > 0 && width > maxwidth) { // Result is too long, must truncate somewhere. int item_idx = 0; @@ -1642,6 +1992,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // the truncation point for (int i = 0; i < itemcnt; i++) { if (stl_items[i].start > trunc_p) { + for (int j = i; j < itemcnt; j++) { + if (stl_items[j].type == ClickFunc) { + XFREE_CLEAR(stl_items[j].cmd); + } + } itemcnt = i; break; } @@ -1804,5 +2159,18 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san curwin->w_redr_type = save_redr_type; } + // Check for an error. If there is one the display will be messed up and + // might loop redrawing. Avoid that by making the corresponding option + // empty. + // TODO(Bram): find out why using called_emsg_before makes tests fail, does it + // matter? + // if (called_emsg > called_emsg_before) + if (opt_name && did_emsg > did_emsg_before) { + set_string_option_direct(opt_name, -1, "", OPT_FREE | opt_scope, SID_ERROR); + } + + // A user function may reset KeyTyped, restore it. + KeyTyped = save_KeyTyped; + return width; } diff --git a/src/nvim/statusline.h b/src/nvim/statusline.h index 357a9a821f..f7e36f138c 100644 --- a/src/nvim/statusline.h +++ b/src/nvim/statusline.h @@ -1,7 +1,16 @@ #ifndef NVIM_STATUSLINE_H #define NVIM_STATUSLINE_H +#include <stddef.h> + #include "nvim/buffer_defs.h" +#include "nvim/macros.h" +#include "nvim/statusline_defs.h" + +/// Array defining what should be done when tabline is clicked +EXTERN StlClickDefinition *tab_page_click_defs INIT(= NULL); +/// Size of the tab_page_click_defs array +EXTERN size_t tab_page_click_defs_size INIT(= 0); #ifdef INCLUDE_GENERATED_DECLARATIONS # include "statusline.h.generated.h" diff --git a/src/nvim/statusline_defs.h b/src/nvim/statusline_defs.h new file mode 100644 index 0000000000..eac9dfd690 --- /dev/null +++ b/src/nvim/statusline_defs.h @@ -0,0 +1,26 @@ +#ifndef NVIM_STATUSLINE_DEFS_H +#define NVIM_STATUSLINE_DEFS_H + +#include <stddef.h> + +#include "nvim/macros.h" + +/// Status line click definition +typedef struct { + enum { + kStlClickDisabled = 0, ///< Clicks to this area are ignored. + kStlClickTabSwitch, ///< Switch to the given tab. + kStlClickTabClose, ///< Close given tab. + kStlClickFuncRun, ///< Run user function. + } type; ///< Type of the click. + int tabnr; ///< Tab page number. + char *func; ///< Function to run. +} StlClickDefinition; + +/// Used for tabline clicks +typedef struct { + StlClickDefinition def; ///< Click definition. + const char *start; ///< Location where region starts. +} StlClickRecord; + +#endif // NVIM_STATUSLINE_DEFS_H diff --git a/src/nvim/strings.c b/src/nvim/strings.c index 10173fac1d..34b3c38103 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -6,48 +6,29 @@ #include <math.h> #include <stdarg.h> #include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> +#include "auto/config.h" #include "nvim/ascii.h" #include "nvim/assert.h" -#include "nvim/buffer.h" #include "nvim/charset.h" -#include "nvim/diff.h" -#include "nvim/edit.h" -#include "nvim/eval.h" #include "nvim/eval/encode.h" -#include "nvim/ex_cmds.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_docmd.h" -#include "nvim/ex_getln.h" -#include "nvim/file_search.h" -#include "nvim/fileio.h" -#include "nvim/fold.h" -#include "nvim/func_attr.h" -#include "nvim/getchar.h" -#include "nvim/mark.h" +#include "nvim/gettext.h" +#include "nvim/macros.h" #include "nvim/math.h" #include "nvim/mbyte.h" -#include "nvim/memfile.h" -#include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/move.h" -#include "nvim/ops.h" #include "nvim/option.h" -#include "nvim/os/os.h" -#include "nvim/os/shell.h" -#include "nvim/os_unix.h" -#include "nvim/path.h" -#include "nvim/quickfix.h" -#include "nvim/regexp.h" -#include "nvim/screen.h" -#include "nvim/search.h" -#include "nvim/spell.h" #include "nvim/strings.h" -#include "nvim/syntax.h" -#include "nvim/tag.h" +#include "nvim/types.h" #include "nvim/vim.h" -#include "nvim/window.h" /// Copy up to `len` bytes of `string` into newly allocated memory and /// terminate with a NUL. The allocated memory always has size `len + 1`, even @@ -60,7 +41,7 @@ char *xstrnsave(const char *string, size_t len) // Same as vim_strsave(), but any characters found in esc_chars are preceded // by a backslash. -char_u *vim_strsave_escaped(const char_u *string, const char_u *esc_chars) +char *vim_strsave_escaped(const char *string, const char *esc_chars) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { return vim_strsave_escaped_ext(string, esc_chars, '\\', false); @@ -69,36 +50,36 @@ char_u *vim_strsave_escaped(const char_u *string, const char_u *esc_chars) // Same as vim_strsave_escaped(), but when "bsl" is true also escape // characters where rem_backslash() would remove the backslash. // Escape the characters with "cc". -char_u *vim_strsave_escaped_ext(const char_u *string, const char_u *esc_chars, char_u cc, bool bsl) +char *vim_strsave_escaped_ext(const char *string, const char *esc_chars, char cc, bool bsl) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { // First count the number of backslashes required. // Then allocate the memory and insert them. size_t length = 1; // count the trailing NUL - for (const char_u *p = string; *p; p++) { - const size_t l = (size_t)(utfc_ptr2len((char *)p)); + for (const char *p = string; *p; p++) { + const size_t l = (size_t)(utfc_ptr2len(p)); if (l > 1) { length += l; // count a multibyte char p += l - 1; continue; } - if (vim_strchr((char *)esc_chars, *p) != NULL || (bsl && rem_backslash((char *)p))) { + if (vim_strchr(esc_chars, (uint8_t)(*p)) != NULL || (bsl && rem_backslash(p))) { length++; // count a backslash } length++; // count an ordinary char } - char_u *escaped_string = xmalloc(length); - char_u *p2 = escaped_string; - for (const char_u *p = string; *p; p++) { - const size_t l = (size_t)(utfc_ptr2len((char *)p)); + char *escaped_string = xmalloc(length); + char *p2 = escaped_string; + for (const char *p = string; *p; p++) { + const size_t l = (size_t)(utfc_ptr2len(p)); if (l > 1) { memcpy(p2, p, l); p2 += l; p += l - 1; // skip multibyte char continue; } - if (vim_strchr((char *)esc_chars, *p) != NULL || (bsl && rem_backslash((char *)p))) { + if (vim_strchr(esc_chars, (uint8_t)(*p)) != NULL || (bsl && rem_backslash(p))) { *p2++ = cc; } *p2++ = *p; @@ -156,19 +137,20 @@ char *vim_strnsave_unquoted(const char *const string, const size_t length) return ret; } -// Escape "string" for use as a shell argument with system(). -// This uses single quotes, except when we know we need to use double quotes -// (MS-Windows without 'shellslash' set). -// Escape a newline, depending on the 'shell' option. -// When "do_special" is true also replace "!", "%", "#" and things starting -// with "<" like "<cfile>". -// When "do_newline" is false do not escape newline unless it is csh shell. -// Returns the result in allocated memory. -char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_newline) +/// Escape "string" for use as a shell argument with system(). +/// This uses single quotes, except when we know we need to use double quotes +/// (MS-Windows without 'shellslash' set). +/// Escape a newline, depending on the 'shell' option. +/// When "do_special" is true also replace "!", "%", "#" and things starting +/// with "<" like "<cfile>". +/// When "do_newline" is false do not escape newline unless it is csh shell. +/// +/// @return the result in allocated memory. +char *vim_strsave_shellescape(const char *string, bool do_special, bool do_newline) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { char *d; - char_u *escaped_string; + char *escaped_string; size_t l; int csh_like; bool fish_like; @@ -184,8 +166,8 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n fish_like = fish_like_shell(); // First count the number of extra bytes required. - size_t length = STRLEN(string) + 3; // two quotes and a trailing NUL - for (const char_u *p = string; *p != NUL; MB_PTR_ADV(p)) { + size_t length = strlen(string) + 3; // two quotes and a trailing NUL + for (const char *p = string; *p != NUL; MB_PTR_ADV(p)) { #ifdef MSWIN if (!p_ssl) { if (*p == '"') { @@ -214,7 +196,7 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n // Allocate memory for the result and fill it. escaped_string = xmalloc(length); - d = (char *)escaped_string; + d = escaped_string; // add opening quote #ifdef MSWIN @@ -224,7 +206,7 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n #endif *d++ = '\''; - for (const char *p = (char *)string; *p != NUL;) { + for (const char *p = string; *p != NUL;) { #ifdef MSWIN if (!p_ssl) { if (*p == '"') { @@ -252,7 +234,7 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n *d++ = *p++; continue; } - if (do_special && find_cmdline_var((char_u *)p, &l) >= 0) { + if (do_special && find_cmdline_var(p, &l) >= 0) { *d++ = '\\'; // insert backslash while (--l != SIZE_MAX) { // copy the var *d++ = *p++; @@ -282,14 +264,14 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n // Like vim_strsave(), but make all characters uppercase. // This uses ASCII lower-to-upper case translation, language independent. -char_u *vim_strsave_up(const char_u *string) +char *vim_strsave_up(const char *string) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { char *p1; - p1 = xstrdup((char *)string); - vim_strup((char_u *)p1); - return (char_u *)p1; + p1 = xstrdup(string); + vim_strup(p1); + return p1; } /// Like xstrnsave(), but make all characters uppercase. @@ -298,17 +280,17 @@ char *vim_strnsave_up(const char *string, size_t len) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { char *p1 = xstrnsave(string, len); - vim_strup((char_u *)p1); + vim_strup(p1); return p1; } // ASCII lower-to-upper case translation, language independent. -void vim_strup(char_u *p) +void vim_strup(char *p) FUNC_ATTR_NONNULL_ALL { - char_u c; - while ((c = *p) != NUL) { - *p++ = (char_u)(c < 'a' || c > 'z' ? c : c - 0x20); + uint8_t c; + while ((c = (uint8_t)(*p)) != NUL) { + *p++ = (char)(uint8_t)(c < 'a' || c > 'z' ? c : c - 0x20); } } @@ -331,7 +313,7 @@ char *strcase_save(const char *const orig, bool upper) int l = utf_ptr2len(p); if (c == 0) { // overlong sequence, use only the first byte - c = (char_u)(*p); + c = (uint8_t)(*p); l = 1; } int uc = upper ? mb_toupper(c) : mb_tolower(c); @@ -357,12 +339,12 @@ char *strcase_save(const char *const orig, bool upper) } // delete spaces at the end of a string -void del_trailing_spaces(char_u *ptr) +void del_trailing_spaces(char *ptr) FUNC_ATTR_NONNULL_ALL { - char_u *q; + char *q; - q = ptr + STRLEN(ptr); + q = ptr + strlen(ptr); while (--q > ptr && ascii_iswhite(q[0]) && q[-1] != '\\' && q[-1] != Ctrl_V) { *q = NUL; } @@ -390,7 +372,7 @@ int vim_stricmp(const char *s1, const char *s2) int i; for (;;) { - i = (int)TOLOWER_LOC(*s1) - (int)TOLOWER_LOC(*s2); + i = (int)TOLOWER_LOC((uint8_t)(*s1)) - (int)TOLOWER_LOC((uint8_t)(*s2)); if (i != 0) { return i; // this character different } @@ -414,7 +396,7 @@ int vim_strnicmp(const char *s1, const char *s2, size_t len) int i; while (len > 0) { - i = (int)TOLOWER_LOC(*s1) - (int)TOLOWER_LOC(*s2); + i = (int)TOLOWER_LOC((uint8_t)(*s1)) - (int)TOLOWER_LOC((uint8_t)(*s2)); if (i != 0) { return i; // this character different } @@ -470,14 +452,14 @@ void sort_strings(char **files, int count) // Return true if string "s" contains a non-ASCII character (128 or higher). // When "s" is NULL false is returned. -bool has_non_ascii(const char_u *s) +bool has_non_ascii(const char *s) FUNC_ATTR_PURE { - const char_u *p; + const char *p; if (s != NULL) { for (p = s; *p != NUL; p++) { - if (*p >= 128) { + if ((uint8_t)(*p) >= 128) { return true; } } @@ -604,10 +586,9 @@ static const void *tv_ptr(const typval_T *const tvs, int *const idxp) if (tvs[idx].v_type == VAR_UNKNOWN) { emsg(_(e_printf)); return NULL; - } else { - (*idxp)++; - return tvs[idx].vval.v_string; } + (*idxp)++; + return tvs[idx].vval.v_string; } /// Get float argument from idxp entry in tvs @@ -965,18 +946,18 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t - str_arg); } if (fmt_spec == 'S') { - char_u *p1; + char *p1; size_t i; - for (i = 0, p1 = (char_u *)str_arg; *p1; p1 += utfc_ptr2len((char *)p1)) { - size_t cell = (size_t)utf_ptr2cells((char *)p1); + for (i = 0, p1 = (char *)str_arg; *p1; p1 += utfc_ptr2len(p1)) { + size_t cell = (size_t)utf_ptr2cells(p1); if (precision_specified && i + cell > precision) { break; } i += cell; } - str_arg_l = (size_t)(p1 - (char_u *)str_arg); + str_arg_l = (size_t)(p1 - str_arg); if (min_field_width != 0) { min_field_width += str_arg_l - i; } @@ -1035,13 +1016,11 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t : va_arg(ap, long long)); // NOLINT (runtime/int) break; case 'z': - arg = (tvs - ? (ptrdiff_t)tv_nr(tvs, &arg_idx) - : va_arg(ap, ptrdiff_t)); + arg = (tvs ? (ptrdiff_t)tv_nr(tvs, &arg_idx) : va_arg(ap, ptrdiff_t)); break; } if (arg > 0) { - arg_sign = 1; + arg_sign = 1; } else if (arg < 0) { arg_sign = -1; } @@ -1049,19 +1028,13 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t // unsigned switch (length_modifier) { case '\0': - uarg = (unsigned int)(tvs - ? tv_nr(tvs, &arg_idx) - : va_arg(ap, unsigned int)); + uarg = (unsigned int)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned int)); break; case 'h': - uarg = (uint16_t)(tvs - ? tv_nr(tvs, &arg_idx) - : va_arg(ap, unsigned int)); + uarg = (uint16_t)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned int)); break; case 'l': - uarg = (tvs - ? (unsigned long)tv_nr(tvs, &arg_idx) - : va_arg(ap, unsigned long)); + uarg = (tvs ? (unsigned long)tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned long)); break; case '2': uarg = (uintmax_t)(unsigned long long)( // NOLINT (runtime/int) @@ -1071,9 +1044,7 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t : va_arg(ap, unsigned long long)); // NOLINT (runtime/int) break; case 'z': - uarg = (tvs - ? (size_t)tv_nr(tvs, &arg_idx) - : va_arg(ap, size_t)); + uarg = (tvs ? (size_t)tv_nr(tvs, &arg_idx) : va_arg(ap, size_t)); break; } arg_sign = (uarg != 0); diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 575d475b87..05c570e52f 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -4,51 +4,46 @@ // syntax.c: code for syntax highlighting #include <assert.h> -#include <ctype.h> #include <inttypes.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> -#include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" -#include "nvim/cursor_shape.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/vars.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" -#include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/hashtab.h" -#include "nvim/highlight.h" +#include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" #include "nvim/indent_c.h" -#include "nvim/keycodes.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/option.h" +#include "nvim/option_defs.h" #include "nvim/optionstr.h" #include "nvim/os/input.h" -#include "nvim/os/os.h" -#include "nvim/os/time.h" -#include "nvim/os_unix.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/regexp.h" -#include "nvim/sign.h" +#include "nvim/runtime.h" #include "nvim/strings.h" #include "nvim/syntax.h" -#include "nvim/syntax_defs.h" -#include "nvim/terminal.h" -#include "nvim/ui.h" +#include "nvim/types.h" #include "nvim/vim.h" static bool did_syntax_onoff = false; @@ -97,7 +92,7 @@ typedef struct syn_pattern { } synpat_T; typedef struct syn_cluster_S { - char_u *scl_name; // syntax cluster name + char *scl_name; // syntax cluster name char *scl_name_u; // uppercase of scl_name int16_t *scl_list; // IDs in this syntax cluster } syn_cluster_T; @@ -149,7 +144,7 @@ typedef struct { proftime_T slowest; proftime_T average; int id; - char_u *pattern; + char *pattern; } time_entry_T; struct name_list { @@ -233,7 +228,7 @@ static int running_syn_inc_tag = 0; // HI2KE() converts a hashitem pointer to a var pointer. static keyentry_T dumkey; #define KE2HIKEY(kp) ((kp)->keyword) -#define HIKEY2KE(p) ((keyentry_T *)((p) - (dumkey.keyword - (char_u *)&dumkey))) +#define HIKEY2KE(p) ((keyentry_T *)((p) - (dumkey.keyword - (char *)&dumkey))) #define HI2KE(hi) HIKEY2KE((hi)->hi_key) // -V:HI2KE:782 @@ -504,7 +499,7 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) bool had_sync_point; stateitem_T *cur_si; synpat_T *spp; - char_u *line; + char *line; int found_flags = 0; int found_match_idx = 0; linenr_T found_current_lnum = 0; @@ -554,8 +549,8 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) // Skip lines that end in a backslash. for (; start_lnum > 1; start_lnum--) { - line = (char_u *)ml_get(start_lnum - 1); - if (*line == NUL || *(line + STRLEN(line) - 1) != '\\') { + line = ml_get(start_lnum - 1); + if (*line == NUL || *(line + strlen(line) - 1) != '\\') { break; } } @@ -726,10 +721,12 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) static void save_chartab(char *chartab) { - if (syn_block->b_syn_isk != empty_option) { - memmove(chartab, syn_buf->b_chartab, (size_t)32); - memmove(syn_buf->b_chartab, syn_win->w_s->b_syn_chartab, (size_t)32); + if (syn_block->b_syn_isk == empty_option) { + return; } + + memmove(chartab, syn_buf->b_chartab, (size_t)32); + memmove(syn_buf->b_chartab, syn_win->w_s->b_syn_chartab, (size_t)32); } static void restore_chartab(char *chartab) @@ -742,22 +739,23 @@ static void restore_chartab(char *chartab) /// Return true if the line-continuation pattern matches in line "lnum". static int syn_match_linecont(linenr_T lnum) { - if (syn_block->b_syn_linecont_prog != NULL) { - regmmatch_T regmatch; - // chartab array for syn iskeyword - char_u buf_chartab[32]; - save_chartab((char *)buf_chartab); - - regmatch.rmm_ic = syn_block->b_syn_linecont_ic; - regmatch.regprog = syn_block->b_syn_linecont_prog; - int r = syn_regexec(®match, lnum, (colnr_T)0, - IF_SYN_TIME(&syn_block->b_syn_linecont_time)); - syn_block->b_syn_linecont_prog = regmatch.regprog; - - restore_chartab((char *)buf_chartab); - return r; + if (syn_block->b_syn_linecont_prog == NULL) { + return false; } - return false; + + regmmatch_T regmatch; + // chartab array for syn iskeyword + char buf_chartab[32]; + save_chartab(buf_chartab); + + regmatch.rmm_ic = syn_block->b_syn_linecont_ic; + regmatch.regprog = syn_block->b_syn_linecont_prog; + int r = syn_regexec(®match, lnum, (colnr_T)0, + IF_SYN_TIME(&syn_block->b_syn_linecont_time)); + syn_block->b_syn_linecont_prog = regmatch.regprog; + + restore_chartab(buf_chartab); + return r; } // Prepare the current state for the start of a line. @@ -878,14 +876,16 @@ static void syn_stack_free_block(synblock_T *block) { synstate_T *p; - if (block->b_sst_array != NULL) { - for (p = block->b_sst_first; p != NULL; p = p->sst_next) { - clear_syn_state(p); - } - XFREE_CLEAR(block->b_sst_array); - block->b_sst_first = NULL; - block->b_sst_len = 0; + if (block->b_sst_array == NULL) { + return; + } + + for (p = block->b_sst_first; p != NULL; p = p->sst_next) { + clear_syn_state(p); } + XFREE_CLEAR(block->b_sst_array); + block->b_sst_first = NULL; + block->b_sst_len = 0; } // Free b_sst_array[] for buffer "buf". // Used when syntax items changed to force resyncing everywhere. @@ -1327,10 +1327,13 @@ static bool syn_stack_equal(synstate_T *sp) // displayed line // displayed line // lnum -> line below window -void syntax_end_parsing(linenr_T lnum) +void syntax_end_parsing(win_T *wp, linenr_T lnum) { synstate_T *sp; + if (syn_block != wp->w_s) { + return; // not the right window + } sp = syn_stack_find_entry(lnum); if (sp != NULL && sp->sst_lnum < lnum) { sp = sp->sst_next; @@ -1492,8 +1495,8 @@ int get_syntax_attr(const colnr_T col, bool *const can_spell, const bool keep_st static int syn_current_attr(const bool syncing, const bool displaying, bool *const can_spell, const bool keep_state) { - lpos_T endpos; // was: char_u *endp; - lpos_T hl_startpos; // was: int hl_startcol; + lpos_T endpos; + lpos_T hl_startpos; lpos_T hl_endpos; lpos_T eos_pos; // end-of-start match (start region) lpos_T eoe_pos; // end-of-end pattern @@ -1509,8 +1512,8 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con regmmatch_T regmatch; lpos_T pos; reg_extmatch_T *cur_extmatch = NULL; - char_u buf_chartab[32]; // chartab array for syn iskeyword - char_u *line; // current line. NOTE: becomes invalid after + char buf_chartab[32]; // chartab array for syn iskeyword + char *line; // current line. NOTE: becomes invalid after // looking for a pattern match! // variables for zero-width matches that have a "nextgroup" argument @@ -1520,7 +1523,7 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con // No character, no attributes! Past end of line? // Do try matching with an empty line (could be the start of a region). - line = (char_u *)syn_getcurline(); + line = syn_getcurline(); if (line[current_col] == NUL && current_col != 0) { // If we found a match after the last column, use it. if (next_match_idx >= 0 && next_match_col >= (int)current_col @@ -1557,7 +1560,7 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con ga_init(&zero_width_next_ga, (int)sizeof(int), 10); // use syntax iskeyword option - save_chartab((char *)buf_chartab); + save_chartab(buf_chartab); // Repeat matching keywords and patterns, to find contained items at the // same column. This stops when there are no extra matches at the current @@ -1583,14 +1586,14 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con // 2. Check for keywords, if on a keyword char after a non-keyword // char. Don't do this when syncing. if (do_keywords) { - line = (char_u *)syn_getcurline(); - const char_u *cur_pos = line + current_col; - if (vim_iswordp_buf((char *)cur_pos, syn_buf) + line = syn_getcurline(); + const char *cur_pos = line + current_col; + if (vim_iswordp_buf(cur_pos, syn_buf) && (current_col == 0 - || !vim_iswordp_buf((char *)cur_pos - 1 - - utf_head_off((char *)line, (char *)cur_pos - 1), + || !vim_iswordp_buf(cur_pos - 1 - + utf_head_off(line, cur_pos - 1), syn_buf))) { - syn_id = check_keyword_id((char *)line, (int)current_col, &endcol, &flags, + syn_id = check_keyword_id(line, (int)current_col, &endcol, &flags, &next_list, cur_si, &cchar); if (syn_id != 0) { push_current_state(KEYWORD_IDX); @@ -1652,13 +1655,12 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con && (spp->sp_type == SPTYPE_MATCH || spp->sp_type == SPTYPE_START) && (current_next_list != NULL - ? in_id_list(NULL, current_next_list, - &spp->sp_syn, 0) - : (cur_si == NULL - ? !(spp->sp_flags & HL_CONTAINED) - : in_id_list(cur_si, - cur_si->si_cont_list, &spp->sp_syn, - spp->sp_flags & HL_CONTAINED)))) { + ? in_id_list(NULL, current_next_list, &spp->sp_syn, 0) + : (cur_si == NULL + ? !(spp->sp_flags & HL_CONTAINED) + : in_id_list(cur_si, + cur_si->si_cont_list, &spp->sp_syn, + spp->sp_flags & HL_CONTAINED)))) { // If we already tried matching in this line, and // there isn't a match before next_match_col, skip // this item. @@ -1830,7 +1832,7 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con // - this is an empty line and the "skipempty" option was given // - we are on white space and the "skipwhite" option was given if (!found_match) { - line = (char_u *)syn_getcurline(); + line = syn_getcurline(); if (((current_next_flags & HL_SKIPWHITE) && ascii_iswhite(line[current_col])) || ((current_next_flags & HL_SKIPEMPTY) @@ -1853,7 +1855,7 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con } } while (found_match); - restore_chartab((char *)buf_chartab); + restore_chartab(buf_chartab); // Use attributes from the current state, if within its highlighting. // If not, use attributes from the current-but-one state, etc. @@ -1947,7 +1949,7 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con // nextgroup ends at end of line, unless "skipnl" or "skipempty" present if (current_next_list != NULL - && (line = (char_u *)syn_getcurline())[current_col] != NUL + && (line = syn_getcurline())[current_col] != NUL && line[current_col + 1] == NUL && !(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY))) { current_next_list = NULL; @@ -2363,9 +2365,9 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_ regmmatch_T regmatch; regmmatch_T best_regmatch; // startpos/endpos of best match lpos_T pos; - char_u *line; + char *line; bool had_match = false; - char_u buf_chartab[32]; // chartab array for syn option iskeyword + char buf_chartab[32]; // chartab array for syn option iskeyword // just in case we are invoked for a keyword if (idx < 0) { @@ -2407,7 +2409,7 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_ best_regmatch.startpos[0].col = 0; // avoid compiler warning // use syntax iskeyword option - save_chartab((char *)buf_chartab); + save_chartab(buf_chartab); for (;;) { // Find end pattern that matches first after "matchcol". @@ -2468,8 +2470,8 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_ break; } - line = (char_u *)ml_get_buf(syn_buf, startpos->lnum, false); - int line_len = (int)STRLEN(line); + line = ml_get_buf(syn_buf, startpos->lnum, false); + int line_len = (int)strlen(line); // take care of an empty match or negative offset if (pos.col <= matchcol) { @@ -2548,7 +2550,7 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_ m_endpos->lnum = 0; } - restore_chartab((char *)buf_chartab); + restore_chartab(buf_chartab); // Remove external matches. unref_extmatch(re_extmatch_in); @@ -2587,8 +2589,8 @@ static void syn_add_end_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *spp { int col; int off; - char_u *base; - char_u *p; + char *base; + char *p; if (spp->sp_off_flags & (1 << idx)) { result->lnum = regmatch->startpos[0].lnum; @@ -2604,7 +2606,7 @@ static void syn_add_end_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *spp if (result->lnum > syn_buf->b_ml.ml_line_count) { col = 0; } else if (off != 0) { - base = (char_u *)ml_get_buf(syn_buf, result->lnum, false); + base = ml_get_buf(syn_buf, result->lnum, false); p = base + col; if (off > 0) { while (off-- > 0 && *p != NUL) { @@ -2631,8 +2633,8 @@ static void syn_add_start_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *s { int col; int off; - char_u *base; - char_u *p; + char *base; + char *p; if (spp->sp_off_flags & (1 << (idx + SPO_COUNT))) { result->lnum = regmatch->endpos[0].lnum; @@ -2649,7 +2651,7 @@ static void syn_add_start_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *s col = (int)strlen(ml_get_buf(syn_buf, result->lnum, false)); } if (off != 0) { - base = (char_u *)ml_get_buf(syn_buf, result->lnum, false); + base = ml_get_buf(syn_buf, result->lnum, false); p = base + col; if (off > 0) { while (off-- && *p != NUL) { @@ -2745,20 +2747,20 @@ static int check_keyword_id(char *const line, const int startcol, int *const end // Must make a copy of the keyword, so we can add a NUL and make it // lowercase. - char_u keyword[MAXKEYWLEN + 1]; // assume max. keyword len is 80 - STRLCPY(keyword, kwp, kwlen + 1); + char keyword[MAXKEYWLEN + 1]; // assume max. keyword len is 80 + xstrlcpy(keyword, kwp, (size_t)kwlen + 1); keyentry_T *kp = NULL; // matching case if (syn_block->b_keywtab.ht_used != 0) { - kp = match_keyword((char *)keyword, &syn_block->b_keywtab, cur_si); + kp = match_keyword(keyword, &syn_block->b_keywtab, cur_si); } // ignoring case if (kp == NULL && syn_block->b_keywtab_ic.ht_used != 0) { - str_foldcase((char_u *)kwp, kwlen, keyword, MAXKEYWLEN + 1); - kp = match_keyword((char *)keyword, &syn_block->b_keywtab_ic, cur_si); + str_foldcase(kwp, kwlen, keyword, MAXKEYWLEN + 1); + kp = match_keyword(keyword, &syn_block->b_keywtab_ic, cur_si); } if (kp != NULL) { @@ -2785,9 +2787,9 @@ static keyentry_T *match_keyword(char *keyword, hashtab_T *ht, stateitem_T *cur_ if (current_next_list != 0 ? in_id_list(NULL, current_next_list, &kp->k_syn, 0) : (cur_si == NULL - ? !(kp->flags & HL_CONTAINED) - : in_id_list(cur_si, cur_si->si_cont_list, - &kp->k_syn, kp->flags & HL_CONTAINED))) { + ? !(kp->flags & HL_CONTAINED) + : in_id_list(cur_si, cur_si->si_cont_list, + &kp->k_syn, kp->flags & HL_CONTAINED))) { return kp; } } @@ -2798,15 +2800,15 @@ static keyentry_T *match_keyword(char *keyword, hashtab_T *ht, stateitem_T *cur_ // Handle ":syntax conceal" command. static void syn_cmd_conceal(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *next; + char *arg = eap->arg; + char *next; - eap->nextcmd = find_nextcmd((char *)arg); + eap->nextcmd = find_nextcmd(arg); if (eap->skip) { return; } - next = (char_u *)skiptowhite((char *)arg); + next = skiptowhite(arg); if (*arg == NUL) { if (curwin->w_s->b_syn_conceal) { msg("syntax conceal on"); @@ -2852,10 +2854,10 @@ static void syn_cmd_case(exarg_T *eap, int syncing) /// Handle ":syntax foldlevel" command. static void syn_cmd_foldlevel(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *arg_end; + char *arg = eap->arg; + char *arg_end; - eap->nextcmd = find_nextcmd((char *)arg); + eap->nextcmd = find_nextcmd(arg); if (eap->skip) { return; } @@ -2872,7 +2874,7 @@ static void syn_cmd_foldlevel(exarg_T *eap, int syncing) return; } - arg_end = (char_u *)skiptowhite((char *)arg); + arg_end = skiptowhite(arg); if (STRNICMP(arg, "start", 5) == 0 && arg_end - arg == 5) { curwin->w_s->b_syn_foldlevel = SYNFLD_START; } else if (STRNICMP(arg, "minimum", 7) == 0 && arg_end - arg == 7) { @@ -2882,7 +2884,7 @@ static void syn_cmd_foldlevel(exarg_T *eap, int syncing) return; } - arg = (char_u *)skipwhite((char *)arg_end); + arg = skipwhite(arg_end); if (*arg != NUL) { semsg(_(e_illegal_arg), arg); } @@ -3120,22 +3122,20 @@ static void syn_cmd_clear(exarg_T *eap, int syncing) if (id == 0) { semsg(_("E391: No such syntax cluster: %s"), arg); break; - } else { - // We can't physically delete a cluster without changing - // the IDs of other clusters, so we do the next best thing - // and make it empty. - int scl_id = id - SYNID_CLUSTER; - - XFREE_CLEAR(SYN_CLSTR(curwin->w_s)[scl_id].scl_list); } + // We can't physically delete a cluster without changing + // the IDs of other clusters, so we do the next best thing + // and make it empty. + int scl_id = id - SYNID_CLUSTER; + + XFREE_CLEAR(SYN_CLSTR(curwin->w_s)[scl_id].scl_list); } else { id = syn_name2id_len(arg, (size_t)(arg_end - arg)); if (id == 0) { semsg(_(e_nogroup), arg); break; - } else { - syn_clear_one(id, syncing); } + syn_clear_one(id, syncing); } arg = skipwhite(arg_end); } @@ -3221,10 +3221,10 @@ void syn_maybe_enable(void) /// @param syncing when true: list syncing items static void syn_cmd_list(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *arg_end; + char *arg = eap->arg; + char *arg_end; - eap->nextcmd = find_nextcmd((char *)arg); + eap->nextcmd = find_nextcmd(arg); if (eap->skip) { return; } @@ -3277,26 +3277,26 @@ static void syn_cmd_list(exarg_T *eap, int syncing) } else { // List the group IDs and syntax clusters that are in the argument. while (!ends_excmd(*arg) && !got_int) { - arg_end = (char_u *)skiptowhite((char *)arg); + arg_end = skiptowhite(arg); if (*arg == '@') { - int id = syn_scl_namen2id((char *)arg + 1, (int)(arg_end - arg - 1)); + int id = syn_scl_namen2id(arg + 1, (int)(arg_end - arg - 1)); if (id == 0) { semsg(_("E392: No such syntax cluster: %s"), arg); } else { syn_list_cluster(id - SYNID_CLUSTER); } } else { - int id = syn_name2id_len((char *)arg, (size_t)(arg_end - arg)); + int id = syn_name2id_len(arg, (size_t)(arg_end - arg)); if (id == 0) { semsg(_(e_nogroup), arg); } else { syn_list_one(id, syncing, true); } } - arg = (char_u *)skipwhite((char *)arg_end); + arg = skipwhite(arg_end); } } - eap->nextcmd = check_nextcmd((char *)arg); + eap->nextcmd = check_nextcmd(arg); } static void syn_lines_msg(void) @@ -3341,8 +3341,7 @@ static int last_matchgroup; static void syn_list_one(const int id, const bool syncing, const bool link_only) { bool did_header = false; - static struct name_list namelist1[] = - { + static struct name_list namelist1[] = { { HL_DISPLAY, "display" }, { HL_CONTAINED, "contained" }, { HL_ONELINE, "oneline" }, @@ -3355,8 +3354,7 @@ static void syn_list_one(const int id, const bool syncing, const bool link_only) { HL_CONCEALENDS, "concealends" }, { 0, NULL } }; - static struct name_list namelist2[] = - { + static struct name_list namelist2[] = { { HL_SKIPWHITE, "skipwhite" }, { HL_SKIPNL, "skipnl" }, { HL_SKIPEMPTY, "skipempty" }, @@ -3423,8 +3421,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((char *)highlight_group_name(SYN_ITEMS(curwin->w_s) - [spp->sp_sync_idx].sp_syn.id - 1)); + msg_outtrans(highlight_group_name(SYN_ITEMS(curwin->w_s) + [spp->sp_sync_idx].sp_syn.id - 1)); } else { msg_puts("NONE"); } @@ -3437,7 +3435,7 @@ static void syn_list_one(const int id, const bool syncing, const bool link_only) (void)syn_list_header(did_header, 0, id, true); msg_puts_attr("links to", attr); msg_putchar(' '); - msg_outtrans((char *)highlight_group_name(highlight_link_id(id - 1) - 1)); + msg_outtrans(highlight_group_name(highlight_link_id(id - 1) - 1)); } } @@ -3460,7 +3458,7 @@ static void syn_list_cluster(int id) // slight hack: roughly duplicate the guts of syn_list_header() msg_putchar('\n'); - msg_outtrans((char *)SYN_CLSTR(curwin->w_s)[id].scl_name); + msg_outtrans(SYN_CLSTR(curwin->w_s)[id].scl_name); if (msg_col >= endcol) { // output at least one space endcol = msg_col + 1; @@ -3497,9 +3495,9 @@ static void put_id_list(const char *const name, const int16_t *const list, const int scl_id = *p - SYNID_CLUSTER; msg_putchar('@'); - msg_outtrans((char *)SYN_CLSTR(curwin->w_s)[scl_id].scl_name); + msg_outtrans(SYN_CLSTR(curwin->w_s)[scl_id].scl_name); } else { - msg_outtrans((char *)highlight_group_name(*p - 1)); + msg_outtrans(highlight_group_name(*p - 1)); } if (p[1]) { msg_putchar(','); @@ -3521,7 +3519,7 @@ static void put_pattern(const char *const s, const int c, const synpat_T *const if (last_matchgroup == 0) { msg_outtrans("NONE"); } else { - msg_outtrans((char *)highlight_group_name(last_matchgroup - 1)); + msg_outtrans(highlight_group_name(last_matchgroup - 1)); } msg_putchar(' '); } @@ -3531,7 +3529,7 @@ static void put_pattern(const char *const s, const int c, const synpat_T *const msg_putchar(c); // output the pattern, in between a char that is not in the pattern - for (i = 0; vim_strchr(spp->sp_pattern, sepchars[i]) != NULL;) { + for (i = 0; vim_strchr(spp->sp_pattern, (uint8_t)sepchars[i]) != NULL;) { if (sepchars[++i] == NUL) { i = 0; // no good char found, just use the first one break; @@ -3606,7 +3604,7 @@ static bool syn_list_keywords(const int id, const hashtab_T *const ht, bool did_ || prev_next_list != kp->next_list) { force_newline = true; } else { - outlen = (int)STRLEN(kp->keyword); + outlen = (int)strlen(kp->keyword); } // output "contained" and "nextgroup" on each line if (syn_list_header(did_header, outlen, id, force_newline)) { @@ -3679,7 +3677,7 @@ static void syn_clear_keyword(int id, hashtab_T *ht) if (kp_next == NULL) { hash_remove(ht, hi); } else { - hi->hi_key = KE2HIKEY(kp_next); + hi->hi_key = (char *)KE2HIKEY(kp_next); } } else { kp_prev->ke_next = kp_next; @@ -3734,8 +3732,8 @@ static void add_keyword(char *const name, const int id, const int flags, { char name_folded[MAXKEYWLEN + 1]; const char *const name_ic = (curwin->w_s->b_syn_ic) - ? (char *)str_foldcase((char_u *)name, (int)strlen(name), (char_u *)name_folded, - sizeof(name_folded)) + ? str_foldcase(name, (int)strlen(name), name_folded, + sizeof(name_folded)) : name; keyentry_T *const kp = xmalloc(sizeof(keyentry_T) + strlen(name_ic)); @@ -3755,7 +3753,7 @@ static void add_keyword(char *const name, const int id, const int flags, ? &curwin->w_s->b_keywtab_ic : &curwin->w_s->b_keywtab; hashitem_T *const hi = hash_lookup(ht, (const char *)kp->keyword, - STRLEN(kp->keyword), hash); + strlen(kp->keyword), hash); // even though it looks like only the kp->keyword member is // being used here, vim uses some pointer trickery to get the original @@ -3768,7 +3766,7 @@ static void add_keyword(char *const name, const int id, const int flags, } else { // keyword already exists, prepend to list kp->ke_next = HI2KE(hi); - hi->hi_key = KE2HIKEY(kp); + hi->hi_key = (char *)KE2HIKEY(kp); } } @@ -4007,7 +4005,7 @@ static void syn_cmd_include(exarg_T *eap, int syncing) // filename to include. eap->argt |= (EX_XFILE | EX_NOSPC); separate_nextcmd(eap); - if (*eap->arg == '<' || *eap->arg == '$' || path_is_absolute((char_u *)eap->arg)) { + if (*eap->arg == '<' || *eap->arg == '$' || path_is_absolute(eap->arg)) { // For an absolute path, "$VIM/..." or "<sfile>.." we ":source" the // file. Need to expand the file name first. In other cases // ":runtime!" is used. @@ -4335,7 +4333,7 @@ static void syn_cmd_region(exarg_T *eap, int syncing) if (item == ITEM_MATCHGROUP) { p = skiptowhite(rest); - if ((p - rest == 4 && STRNCMP(rest, "NONE", 4) == 0) || eap->skip) { + if ((p - rest == 4 && strncmp(rest, "NONE", 4) == 0) || eap->skip) { matchgroup_id = 0; } else { matchgroup_id = syn_check_group(rest, (size_t)(p - rest)); @@ -4580,7 +4578,7 @@ static void syn_combine_list(int16_t **const clstr1, int16_t **const clstr2, con static int syn_scl_name2id(char *name) { // Avoid using stricmp() too much, it's slow on some systems - char *name_u = (char *)vim_strsave_up((char_u *)name); + char *name_u = vim_strsave_up(name); int i; for (i = curwin->w_s->b_syn_clusters.ga_len; --i >= 0;) { if (SYN_CLSTR(curwin->w_s)[i].scl_name_u != NULL @@ -4641,8 +4639,8 @@ static int syn_add_cluster(char *name) syn_cluster_T *scp = GA_APPEND_VIA_PTR(syn_cluster_T, &curwin->w_s->b_syn_clusters); CLEAR_POINTER(scp); - scp->scl_name = (char_u *)name; - scp->scl_name_u = (char *)vim_strsave_up((char_u *)name); + scp->scl_name = name; + scp->scl_name_u = vim_strsave_up(name); scp->scl_list = NULL; if (STRICMP(name, "Spell") == 0) { @@ -4748,7 +4746,7 @@ static char *get_syn_pattern(char *arg, synpat_T *ci) return NULL; } - end = skip_regexp(arg + 1, *arg, true, NULL); + end = skip_regexp(arg + 1, *arg, true); if (*end != *arg) { // end delimiter not found semsg(_("E401: Pattern delimiter not found: %s"), arg); return NULL; @@ -4772,7 +4770,7 @@ static char *get_syn_pattern(char *arg, synpat_T *ci) end++; do { for (idx = SPO_COUNT; --idx >= 0;) { - if (STRNCMP(end, spo_name_tab[idx], 3) == 0) { + if (strncmp(end, spo_name_tab[idx], 3) == 0) { break; } } @@ -4861,10 +4859,10 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) } else if (!eap->skip) { curwin->w_s->b_syn_sync_id = (int16_t)syn_name2id("Comment"); } - } else if (STRNCMP(key, "LINES", 5) == 0 - || STRNCMP(key, "MINLINES", 8) == 0 - || STRNCMP(key, "MAXLINES", 8) == 0 - || STRNCMP(key, "LINEBREAKS", 10) == 0) { + } else if (strncmp(key, "LINES", 5) == 0 + || strncmp(key, "MINLINES", 8) == 0 + || strncmp(key, "MAXLINES", 8) == 0 + || strncmp(key, "LINEBREAKS", 10) == 0) { if (key[4] == 'S') { arg_end = key + 6; } else if (key[0] == 'L') { @@ -4901,7 +4899,7 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) finished = true; break; } - arg_end = skip_regexp(next_arg + 1, *next_arg, true, NULL); + arg_end = skip_regexp(next_arg + 1, *next_arg, true); if (*arg_end != *next_arg) { // end delimiter not found illegal = true; break; @@ -4996,7 +4994,7 @@ static int get_id_list(char **const arg, const int keylen, int16_t **const list, do { for (end = p; *end && !ascii_iswhite(*end) && *end != ','; end++) {} char *const name = xmalloc((size_t)(end - p) + 3); // leave room for "^$" - STRLCPY(name + 1, p, end - p + 1); + xstrlcpy(name + 1, p, (size_t)(end - p) + 1); if (strcmp(name + 1, "ALLBUT") == 0 || strcmp(name + 1, "ALL") == 0 || strcmp(name + 1, "TOP") == 0 @@ -5049,7 +5047,7 @@ static int get_id_list(char **const arg, const int keylen, int16_t **const list, regmatch.rm_ic = true; id = 0; for (int i = highlight_num_groups(); --i >= 0;) { - if (vim_regexec(®match, (char *)highlight_group_name(i), (colnr_T)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: @@ -5235,8 +5233,7 @@ struct subcommand { void (*func)(exarg_T *, int); // function to call }; -static struct subcommand subcommands[] = -{ +static struct subcommand subcommands[] = { { "case", syn_cmd_case }, { "clear", syn_cmd_clear }, { "cluster", syn_cmd_cluster }, @@ -5301,7 +5298,7 @@ void ex_ownsyntax(exarg_T *eap) curwin->w_s = xcalloc(1, sizeof(synblock_T)); hash_init(&curwin->w_s->b_keywtab); hash_init(&curwin->w_s->b_keywtab_ic); - // TODO: Keep the spell checking as it was. NOLINT(readability/todo) + // TODO(vim): Keep the spell checking as it was. curwin->w_p_spell = false; // No spell checking // make sure option values are "empty_option" instead of NULL clear_string_option(&curwin->w_s->b_p_spc); @@ -5312,7 +5309,7 @@ void ex_ownsyntax(exarg_T *eap) } // Save value of b:current_syntax. - old_value = (char *)get_var_value("b:current_syntax"); + old_value = get_var_value("b:current_syntax"); if (old_value != NULL) { old_value = xstrdup(old_value); } @@ -5321,7 +5318,7 @@ void ex_ownsyntax(exarg_T *eap) apply_autocmds(EVENT_SYNTAX, eap->arg, curbuf->b_fname, true, curbuf); // Move value of b:current_syntax to w:current_syntax. - new_value = (char *)get_var_value("b:current_syntax"); + new_value = get_var_value("b:current_syntax"); if (new_value != NULL) { set_internal_string_var("w:current_syntax", new_value); } @@ -5377,34 +5374,39 @@ void set_context_in_syntax_cmd(expand_T *xp, const char *arg) include_link = 0; include_default = 0; + if (*arg == NUL) { + return; + } + // (part of) subcommand already typed - if (*arg != NUL) { - const char *p = (const char *)skiptowhite(arg); - if (*p != NUL) { // Past first word. - xp->xp_pattern = skipwhite(p); - if (*skiptowhite(xp->xp_pattern) != NUL) { - xp->xp_context = EXPAND_NOTHING; - } else if (STRNICMP(arg, "case", p - arg) == 0) { - expand_what = EXP_CASE; - } else if (STRNICMP(arg, "spell", p - arg) == 0) { - expand_what = EXP_SPELL; - } else if (STRNICMP(arg, "sync", p - arg) == 0) { - expand_what = EXP_SYNC; - } else if (STRNICMP(arg, "list", p - arg) == 0) { - p = skipwhite(p); - if (*p == '@') { - expand_what = EXP_CLUSTER; - } else { - xp->xp_context = EXPAND_HIGHLIGHT; - } - } else if (STRNICMP(arg, "keyword", p - arg) == 0 - || STRNICMP(arg, "region", p - arg) == 0 - || STRNICMP(arg, "match", p - arg) == 0) { - xp->xp_context = EXPAND_HIGHLIGHT; - } else { - xp->xp_context = EXPAND_NOTHING; - } + const char *p = (const char *)skiptowhite(arg); + if (*p == NUL) { + return; + } + + // past first world + xp->xp_pattern = skipwhite(p); + if (*skiptowhite(xp->xp_pattern) != NUL) { + xp->xp_context = EXPAND_NOTHING; + } else if (STRNICMP(arg, "case", p - arg) == 0) { + expand_what = EXP_CASE; + } else if (STRNICMP(arg, "spell", p - arg) == 0) { + expand_what = EXP_SPELL; + } else if (STRNICMP(arg, "sync", p - arg) == 0) { + expand_what = EXP_SYNC; + } else if (STRNICMP(arg, "list", p - arg) == 0) { + p = skipwhite(p); + if (*p == '@') { + expand_what = EXP_CLUSTER; + } else { + xp->xp_context = EXPAND_HIGHLIGHT; } + } else if (STRNICMP(arg, "keyword", p - arg) == 0 + || STRNICMP(arg, "region", p - arg) == 0 + || STRNICMP(arg, "match", p - arg) == 0) { + xp->xp_context = EXPAND_HIGHLIGHT; + } else { + xp->xp_context = EXPAND_NOTHING; } } @@ -5654,7 +5656,7 @@ static void syntime_report(void) proftime_T tm = profile_divide(spp->sp_time.total, (int)spp->sp_time.count); p->average = tm; p->id = spp->sp_syn.id; - p->pattern = (char_u *)spp->sp_pattern; + p->pattern = spp->sp_pattern; } } @@ -5685,7 +5687,7 @@ static void syntime_report(void) msg_puts(profile_msg(p->average)); msg_puts(" "); msg_advance(50); - msg_outtrans((char *)highlight_group_name(p->id - 1)); + msg_outtrans(highlight_group_name(p->id - 1)); msg_puts(" "); msg_advance(69); @@ -5695,10 +5697,10 @@ static void syntime_report(void) } else { len = Columns - 70; } - if (len > (int)STRLEN(p->pattern)) { - len = (int)STRLEN(p->pattern); + if (len > (int)strlen(p->pattern)) { + len = (int)strlen(p->pattern); } - msg_outtrans_len((char *)p->pattern, len); + msg_outtrans_len(p->pattern, len); msg_puts("\n"); } ga_clear(&ga); diff --git a/src/nvim/syntax.h b/src/nvim/syntax.h index 0d890314c5..0a63392a04 100644 --- a/src/nvim/syntax.h +++ b/src/nvim/syntax.h @@ -6,6 +6,7 @@ #include "nvim/buffer_defs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/globals.h" +#include "nvim/macros.h" #define HL_CONTAINED 0x01 // not used on toplevel #define HL_TRANSP 0x02 // has no highlighting diff --git a/src/nvim/syntax_defs.h b/src/nvim/syntax_defs.h index c656f21181..3f3101f7e3 100644 --- a/src/nvim/syntax_defs.h +++ b/src/nvim/syntax_defs.h @@ -30,7 +30,7 @@ struct keyentry { int16_t *next_list; // ID list for next match (if non-zero) int flags; int k_char; // conceal substitute character - char_u keyword[1]; // actually longer + char keyword[1]; // actually longer }; // Struct used to store one state of the state stack. diff --git a/src/nvim/tag.c b/src/nvim/tag.c index a8d8eebb0d..197184c181 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -4,29 +4,38 @@ // Code to handle tags and the tag stack #include <assert.h> +#include <ctype.h> #include <inttypes.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" -#include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" -#include "nvim/ex_getln.h" #include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/hashtab.h" #include "nvim/help.h" -#include "nvim/if_cscope.h" +#include "nvim/highlight_defs.h" #include "nvim/input.h" #include "nvim/insexpand.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memory.h" @@ -36,15 +45,17 @@ #include "nvim/optionstr.h" #include "nvim/os/input.h" #include "nvim/os/os.h" +#include "nvim/os/os_defs.h" #include "nvim/os/time.h" -#include "nvim/os_unix.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/runtime.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/tag.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -52,29 +63,29 @@ // Structure to hold pointers to various items in a tag line. typedef struct tag_pointers { // filled in by parse_tag_line(): - char *tagname; // start of tag name (skip "file:") - char_u *tagname_end; // char after tag name - char_u *fname; // first char of file name - char_u *fname_end; // char after file name - char_u *command; // first char of command + char *tagname; // start of tag name (skip "file:") + char *tagname_end; // char after tag name + char *fname; // first char of file name + char *fname_end; // char after file name + char *command; // first char of command // filled in by parse_match(): - char_u *command_end; // first char after command - char_u *tag_fname; // file name of the tags file. This is used + char *command_end; // first char after command + char *tag_fname; // file name of the tags file. This is used // when 'tr' is set. - char_u *tagkind; // "kind:" value - char_u *tagkind_end; // end of tagkind + char *tagkind; // "kind:" value + char *tagkind_end; // end of tagkind char *user_data; // user_data string - char_u *user_data_end; // end of user_data + char *user_data_end; // end of user_data linenr_T tagline; // "line:" value } tagptrs_T; // Structure to hold info about the tag pattern being used. typedef struct { - char_u *pat; // the pattern - int len; // length of pat[] - char_u *head; // start of pattern head - int headlen; // length of head[] - regmatch_T regmatch; // regexp program, may be NULL + char *pat; // the pattern + int len; // length of pat[] + char *head; // start of pattern head + int headlen; // length of head[] + regmatch_T regmatch; // regexp program, may be NULL } pat_T; // The matching tags are first stored in one of the hash tables. In @@ -82,14 +93,16 @@ typedef struct { // ht_match[] is used to find duplicates, ga_match[] to keep them in sequence. // At the end, the matches from ga_match[] are concatenated, to make a list // sorted on priority. -#define MT_ST_CUR 0 // static match in current file -#define MT_GL_CUR 1 // global match in current file -#define MT_GL_OTH 2 // global match in other file -#define MT_ST_OTH 3 // static match in other file -#define MT_IC_OFF 4 // add for icase match -#define MT_RE_OFF 8 // add for regexp match -#define MT_MASK 7 // mask for printing priority -#define MT_COUNT 16 +enum { + MT_ST_CUR = 0, // static match in current file + MT_GL_CUR = 1, // global match in current file + MT_GL_OTH = 2, // global match in other file + MT_ST_OTH = 3, // static match in other file + MT_IC_OFF = 4, // add for icase match + MT_RE_OFF = 8, // add for regexp match + MT_MASK = 7, // mask for printing priority + MT_COUNT = 16, +}; static char *mt_names[MT_COUNT/2] = { "FSC", "F C", "F ", "FS ", " SC", " C", " ", " S " }; @@ -97,16 +110,90 @@ static char *mt_names[MT_COUNT/2] = #define NOTAGFILE 99 // return value for jumpto_tag static char *nofile_fname = NULL; // fname for NOTAGFILE error +/// Return values used when reading lines from a tags file. +typedef enum { + TAGS_READ_SUCCESS = 1, + TAGS_READ_EOF, + TAGS_READ_IGNORE, +} tags_read_status_T; + +/// States used during a tags search +typedef enum { + TS_START, ///< at start of file + TS_LINEAR, ///< linear searching forward, till EOF + TS_BINARY, ///< binary searching + TS_SKIP_BACK, ///< skipping backwards + TS_STEP_FORWARD, ///< stepping forwards +} tagsearch_state_T; + +/// Binary search file offsets in a tags file +typedef struct { + off_T low_offset; ///< offset for first char of first line that + ///< could match + off_T high_offset; ///< offset of char after last line that could + ///< match + off_T curr_offset; ///< Current file offset in search range + off_T curr_offset_used; ///< curr_offset used when skipping back + off_T match_offset; ///< Where the binary search found a tag + int low_char; ///< first char at low_offset + int high_char; ///< first char at high_offset +} tagsearch_info_T; + +/// Return values used when matching tags against a pattern. +typedef enum { + TAG_MATCH_SUCCESS = 1, + TAG_MATCH_FAIL, + TAG_MATCH_STOP, + TAG_MATCH_NEXT, +} tagmatch_status_T; + +/// Arguments used for matching tags read from a tags file against a pattern. +typedef struct { + int matchoff; ///< tag match offset + bool match_re; ///< true if the tag matches a regexp + bool match_no_ic; ///< true if the tag matches with case + bool has_re; ///< regular expression used + bool sortic; ///< tags file sorted ignoring case (foldcase) + bool sort_error; ///< tags file not sorted +} findtags_match_args_T; + +/// State information used during a tag search +typedef struct { + tagsearch_state_T state; ///< tag search state + bool stop_searching; ///< stop when match found or error + pat_T *orgpat; ///< holds unconverted pattern info + char *lbuf; ///< line buffer + int lbuf_size; ///< length of lbuf + char *tag_fname; ///< name of the tag file + FILE *fp; ///< current tags file pointer + int flags; ///< flags used for tag search + int tag_file_sorted; ///< !_TAG_FILE_SORTED value + bool get_searchpat; ///< used for 'showfulltag' + bool help_only; ///< only search for help tags + bool did_open; ///< did open a tag file + int mincount; ///< MAXCOL: find all matches + ///< other: minimal number of matches + bool linear; ///< do a linear search + vimconv_T vimconv; + char help_lang[3]; ///< lang of current tags file + int help_pri; ///< help language priority + char *help_lang_find; ///< lang to be found + bool is_txt; ///< flag of file extension + int match_count; ///< number of matches found + garray_T ga_match[MT_COUNT]; ///< stores matches in sequence + hashtab_T ht_match[MT_COUNT]; ///< stores matches by key +} findtags_state_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tag.c.generated.h" #endif -static char_u *bottommsg = (char_u *)N_("E555: at bottom of tag stack"); -static char_u *topmsg = (char_u *)N_("E556: at top of tag stack"); -static char_u *recurmsg - = (char_u *)N_("E986: cannot modify the tag stack within tagfunc"); -static char_u *tfu_inv_ret_msg - = (char_u *)N_("E987: invalid return value from tagfunc"); +static char *bottommsg = N_("E555: at bottom of tag stack"); +static char *topmsg = N_("E556: at top of tag stack"); +static char *recurmsg = N_("E986: cannot modify the tag stack within tagfunc"); +static char *tfu_inv_ret_msg = N_("E987: invalid return value from tagfunc"); +static char e_window_unexpectedly_close_while_searching_for_tags[] + = N_("E1299: Window unexpectedly closed while searching for tags"); static char *tagmatchname = NULL; // name of last used tag @@ -115,10 +202,54 @@ static char *tagmatchname = NULL; // name of last used tag static taggy_T ptag_entry = { NULL, INIT_FMARK, 0, 0, NULL }; static int tfu_in_use = false; // disallow recursive call of tagfunc +static Callback tfu_cb; // 'tagfunc' callback function // Used instead of NUL to separate tag fields in the growarrays. #define TAG_SEP 0x02 +/// Reads the 'tagfunc' option value and convert that to a callback value. +/// Invoked when the 'tagfunc' option is set. The option value can be a name of +/// a function (string), or function(<name>) or funcref(<name>) or a lambda. +void set_tagfunc_option(char **errmsg) +{ + callback_free(&tfu_cb); + callback_free(&curbuf->b_tfu_cb); + + if (*curbuf->b_p_tfu == NUL) { + return; + } + + if (option_set_callback_func(curbuf->b_p_tfu, &tfu_cb) == FAIL) { + *errmsg = e_invarg; + } + + callback_copy(&curbuf->b_tfu_cb, &tfu_cb); +} + +#if defined(EXITFREE) +void free_tagfunc_option(void) +{ + callback_free(&tfu_cb); +} +#endif + +/// Mark the global 'tagfunc' callback with "copyID" so that it is not garbage +/// collected. +bool set_ref_in_tagfunc(int copyID) +{ + return set_ref_in_callback(&tfu_cb, copyID, NULL, NULL); +} + +/// Copy the global 'tagfunc' callback function to the buffer-local 'tagfunc' +/// callback for 'buf'. +void set_buflocal_tfu_callback(buf_T *buf) +{ + callback_free(&buf->b_tfu_cb); + if (tfu_cb.type != kCallbackNone) { + callback_copy(&buf->b_tfu_cb, &tfu_cb); + } +} + /// Jump to tag; handling of tag commands and tag stack /// /// *tag != NUL: ":tag {tag}", jump to new tag, add to tag stack @@ -132,16 +263,13 @@ static int tfu_in_use = false; // disallow recursive call of tagfunc /// type == DT_LAST: jump to last match of same tag /// type == DT_SELECT: ":tselect [tag]", select tag from a list of all matches /// type == DT_JUMP: ":tjump [tag]", jump to tag or select tag from a list -/// type == DT_CSCOPE: use cscope to find the tag /// 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 -/// /// @param tag tag (pattern) to jump to /// @param forceit :ta with ! /// @param verbose print "tag not found" message -bool do_tag(char *tag, int type, int count, int forceit, int verbose) +void do_tag(char *tag, int type, int count, int forceit, int verbose) { taggy_T *tagstack = curwin->w_tagstack; int tagstackidx = curwin->w_tagstackidx; @@ -158,13 +286,13 @@ bool do_tag(char *tag, int type, int count, int forceit, int verbose) int error_cur_match = 0; int save_pos = false; fmark_T saved_fmark; - bool jumped_to_tag = false; int new_num_matches; char **new_matches; int use_tagstack; int skip_msg = false; - char_u *buf_ffname = (char_u *)curbuf->b_ffname; // name for priority computation + char *buf_ffname = curbuf->b_ffname; // name for priority computation int use_tfu = 1; + char *tofree = NULL; // remember the matches for the last used tag static int num_matches = 0; @@ -174,16 +302,15 @@ bool do_tag(char *tag, int type, int count, int forceit, int verbose) if (tfu_in_use) { emsg(_(recurmsg)); - return false; + return; } #ifdef EXITFREE if (type == DT_FREE) { // remove the list of matches FreeWild(num_matches, matches); - cs_free_tags(); num_matches = 0; - return false; + return; } #endif @@ -219,9 +346,7 @@ bool do_tag(char *tag, int type, int count, int forceit, int verbose) // new pattern, add to the tag stack if (*tag != NUL && (type == DT_TAG || type == DT_SELECT || type == DT_JUMP - || type == DT_LTAG - || type == DT_CSCOPE - )) { + || type == DT_LTAG)) { if (g_do_tagpreview != 0) { if (ptag_entry.tagname != NULL && strcmp(ptag_entry.tagname, tag) == 0) { @@ -260,8 +385,7 @@ bool do_tag(char *tag, int type, int count, int forceit, int verbose) new_tag = true; } else { - if ( - g_do_tagpreview != 0 ? ptag_entry.tagname == NULL : + if (g_do_tagpreview != 0 ? ptag_entry.tagname == NULL : tagstacklen == 0) { // empty stack emsg(_(e_tagstack)); @@ -312,7 +436,6 @@ bool do_tag(char *tag, int type, int count, int forceit, int verbose) // remove the old list of matches FreeWild(num_matches, matches); - cs_free_tags(); num_matches = 0; tag_freematch(); goto end_do_tag; @@ -361,7 +484,6 @@ bool do_tag(char *tag, int type, int count, int forceit, int verbose) cur_match = count - 1; break; case DT_SELECT: case DT_JUMP: - case DT_CSCOPE: case DT_LAST: cur_match = MAXCOL - 1; break; case DT_NEXT: @@ -411,7 +533,7 @@ bool do_tag(char *tag, int type, int count, int forceit, int verbose) buf_T *buf = buflist_findnr(cur_fnum); if (buf != NULL) { - buf_ffname = (char_u *)buf->b_ffname; + buf_ffname = buf->b_ffname; } } @@ -422,7 +544,10 @@ bool do_tag(char *tag, int type, int count, int forceit, int verbose) // When desired match not found yet, try to find it (and others). if (use_tagstack) { - name = tagstack[tagstackidx].tagname; + // make a copy, the tagstack may change in 'tagfunc' + name = xstrdup(tagstack[tagstackidx].tagname); + xfree(tofree); + tofree = name; } else if (g_do_tagpreview != 0) { name = ptag_entry.tagname; } else { @@ -455,9 +580,6 @@ bool do_tag(char *tag, int type, int count, int forceit, int verbose) flags = TAG_NOIC; } - if (type == DT_CSCOPE) { - flags = TAG_CSCOPE; - } if (verbose) { flags |= TAG_VERBOSE; } @@ -466,12 +588,21 @@ bool do_tag(char *tag, int type, int count, int forceit, int verbose) } if (find_tags(name, &new_num_matches, &new_matches, flags, - max_num_matches, (char *)buf_ffname) == OK + max_num_matches, buf_ffname) == OK && new_num_matches < max_num_matches) { max_num_matches = MAXCOL; // If less than max_num_matches // found: all matches found. } + // A tag function may do anything, which may cause various + // information to become invalid. At least check for the tagstack + // to still be the same. + if (tagstack != curwin->w_tagstack) { + emsg(_(e_window_unexpectedly_close_while_searching_for_tags)); + FreeWild(new_num_matches, new_matches); + break; + } + // If there already were some matches for the same name, move them // to the start. Avoids that the order changes when using // ":tnext" and jumping to another file. @@ -487,11 +618,11 @@ bool do_tag(char *tag, int type, int count, int forceit, int verbose) for (i = idx; i < new_num_matches; i++) { parse_match(new_matches[i], &tagp2); if (strcmp(tagp.tagname, tagp2.tagname) == 0) { - char_u *p = (char_u *)new_matches[i]; + char *p = new_matches[i]; for (k = i; k > idx; k--) { new_matches[k] = new_matches[k - 1]; } - new_matches[idx++] = (char *)p; + new_matches[idx++] = p; break; } } @@ -510,10 +641,7 @@ bool do_tag(char *tag, int type, int count, int forceit, int verbose) } else { bool ask_for_selection = false; - if (type == DT_CSCOPE && num_matches > 1) { - cs_print_tags(); - ask_for_selection = true; - } else if (type == DT_TAG && *tag != NUL) { + if (type == DT_TAG && *tag != NUL) { // If a count is supplied to the ":tag <name>" command, then // jump to count'th matching tag. cur_match = count > 0 ? count - 1 : 0; @@ -521,7 +649,7 @@ bool do_tag(char *tag, int type, int count, int forceit, int verbose) print_tag_list(new_tag, use_tagstack, num_matches, matches); ask_for_selection = true; } else if (type == DT_LTAG) { - if (add_llist_tags((char_u *)tag, num_matches, matches) == FAIL) { + if (add_llist_tags(tag, num_matches, matches) == FAIL) { goto end_do_tag; } @@ -537,8 +665,6 @@ bool do_tag(char *tag, int type, int count, int forceit, int verbose) tagstack[tagstackidx].fmark = saved_fmark; tagstackidx = prevtagstackidx; } - cs_free_tags(); - jumped_to_tag = true; break; } cur_match = i - 1; @@ -570,7 +696,7 @@ bool do_tag(char *tag, int type, int count, int forceit, int verbose) && tagp2.user_data) { XFREE_CLEAR(tagstack[tagstackidx].user_data); tagstack[tagstackidx].user_data = - xstrnsave(tagp2.user_data, (size_t)(tagp2.user_data_end - (char_u *)tagp2.user_data)); + xstrnsave(tagp2.user_data, (size_t)(tagp2.user_data_end - tagp2.user_data)); } tagstackidx++; @@ -587,11 +713,10 @@ bool do_tag(char *tag, int type, int count, int forceit, int verbose) ic = (matches[cur_match][0] & MT_IC_OFF); if (type != DT_TAG && type != DT_SELECT && type != DT_JUMP - && type != DT_CSCOPE && (num_matches > 1 || ic) && !skip_msg) { // Give an indication of the number of matching tags - snprintf((char *)IObuff, sizeof(IObuff), _("tag %d of %d%s"), + snprintf(IObuff, sizeof(IObuff), _("tag %d of %d%s"), cur_match + 1, num_matches, max_num_matches != MAXCOL ? _(" or more") : ""); @@ -603,11 +728,11 @@ bool do_tag(char *tag, int type, int count, int forceit, int verbose) if (ic) { msg_attr((const char *)IObuff, HL_ATTR(HLF_W)); } else { - msg((char *)IObuff); + msg(IObuff); } msg_scroll = true; // Don't overwrite this message. } else { - give_warning((char *)IObuff, ic); + give_warning(IObuff, ic); } if (ic && !msg_scrolled && msg_silent == 0) { ui_flush(); @@ -616,11 +741,11 @@ bool do_tag(char *tag, int type, int count, int forceit, int verbose) } // Let the SwapExists event know what tag we are jumping to. - vim_snprintf((char *)IObuff, IOSIZE, ":ta %s\r", name); - set_vim_var_string(VV_SWAPCOMMAND, (char *)IObuff, -1); + vim_snprintf(IObuff, IOSIZE, ":ta %s\r", name); + set_vim_var_string(VV_SWAPCOMMAND, IObuff, -1); // Jump to the desired match. - i = jumpto_tag((char_u *)matches[cur_match], forceit, type != DT_CSCOPE); + i = jumpto_tag(matches[cur_match], forceit, true); set_vim_var_string(VV_SWAPCOMMAND, NULL, -1); @@ -650,7 +775,6 @@ bool do_tag(char *tag, int type, int count, int forceit, int verbose) if (use_tagstack && tagstackidx > curwin->w_tagstacklen) { tagstackidx = curwin->w_tagstackidx; } - jumped_to_tag = true; } } break; @@ -663,8 +787,7 @@ end_do_tag: } postponed_split = 0; // don't split next time g_do_tagpreview = 0; // don't do tag preview next time - - return jumped_to_tag; + xfree(tofree); } // List all the matching tags. @@ -673,8 +796,8 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char taggy_T *tagstack = curwin->w_tagstack; int tagstackidx = curwin->w_tagstackidx; int i; - char_u *p; - char_u *command_end; + char *p; + char *command_end; tagptrs_T tagp; int taglen; int attr; @@ -682,7 +805,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char // Assume that the first match indicates how long the tags can // be, and align the file names to that. parse_match(matches[0], &tagp); - taglen = (int)(tagp.tagname_end - (char_u *)tagp.tagname + 2); + taglen = (int)(tagp.tagname_end - tagp.tagname + 2); if (taglen < 18) { taglen = 18; } @@ -709,17 +832,17 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char } else { *IObuff = ' '; } - vim_snprintf((char *)IObuff + 1, IOSIZE - 1, + vim_snprintf(IObuff + 1, IOSIZE - 1, "%2d %s ", i + 1, mt_names[matches[i][0] & MT_MASK]); - msg_puts((char *)IObuff); + msg_puts(IObuff); if (tagp.tagkind != NULL) { - msg_outtrans_len((char *)tagp.tagkind, + msg_outtrans_len(tagp.tagkind, (int)(tagp.tagkind_end - tagp.tagkind)); } msg_advance(13); msg_outtrans_len_attr(tagp.tagname, - (int)(tagp.tagname_end - (char_u *)tagp.tagname), + (int)(tagp.tagname_end - tagp.tagname), HL_ATTR(HLF_T)); msg_putchar(' '); taglen_advance(taglen); @@ -728,7 +851,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char // it and put "..." in the middle p = tag_full_fname(&tagp); if (p != NULL) { - msg_outtrans_attr((char *)p, HL_ATTR(HLF_D)); + msg_outtrans_attr(p, HL_ATTR(HLF_D)); XFREE_CLEAR(p); } if (msg_col > 0) { @@ -749,28 +872,28 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char } // skip "file:" without a value (static tag) - if (STRNCMP(p, "file:", 5) == 0 && ascii_isspace(p[5])) { + if (strncmp(p, "file:", 5) == 0 && ascii_isspace(p[5])) { p += 5; continue; } // skip "kind:<kind>" and "<kind>" if (p == tagp.tagkind || (p + 5 == tagp.tagkind - && STRNCMP(p, "kind:", 5) == 0)) { + && strncmp(p, "kind:", 5) == 0)) { p = tagp.tagkind_end; continue; } // print all other extra fields attr = HL_ATTR(HLF_CM); while (*p && *p != '\r' && *p != '\n') { - if (msg_col + ptr2cells((char *)p) >= Columns) { + if (msg_col + ptr2cells(p) >= Columns) { msg_putchar('\n'); if (got_int) { break; } msg_advance(15); } - p = (char_u *)msg_outtrans_one((char *)p, attr); + p = msg_outtrans_one(p, attr); if (*p == TAB) { msg_puts_attr(" ", attr); break; @@ -809,7 +932,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char } while (p != command_end) { - if (msg_col + (*p == TAB ? 1 : ptr2cells((char *)p)) > Columns) { + if (msg_col + (*p == TAB ? 1 : ptr2cells(p)) > Columns) { msg_putchar('\n'); } if (got_int) { @@ -828,7 +951,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char msg_putchar(' '); p++; } else { - p = (char_u *)msg_outtrans_one((char *)p, 0); + p = msg_outtrans_one(p, 0); } // don't display the "$/;\"" and "$?;\"" @@ -854,14 +977,14 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char /// Add the matching tags to the location list for the current /// window. -static int add_llist_tags(char_u *tag, int num_matches, char **matches) +static int add_llist_tags(char *tag, int num_matches, char **matches) { list_T *list; - char_u tag_name[128 + 1]; - char_u *fname; - char_u *cmd; + char tag_name[128 + 1]; + char *fname; + char *cmd; int i; - char_u *p; + char *p; tagptrs_T tagp; fname = xmalloc(MAXPATHL + 1); @@ -876,11 +999,11 @@ static int add_llist_tags(char_u *tag, int num_matches, char **matches) parse_match(matches[i], &tagp); // Save the tag name - len = (int)(tagp.tagname_end - (char_u *)tagp.tagname); + len = (int)(tagp.tagname_end - tagp.tagname); if (len > 128) { len = 128; } - STRLCPY(tag_name, tagp.tagname, len + 1); + xstrlcpy(tag_name, tagp.tagname, (size_t)len + 1); tag_name[len] = NUL; // Save the tag file name @@ -888,17 +1011,17 @@ static int add_llist_tags(char_u *tag, int num_matches, char **matches) if (p == NULL) { continue; } - STRLCPY(fname, p, MAXPATHL); + xstrlcpy(fname, p, MAXPATHL); XFREE_CLEAR(p); // Get the line number or the search pattern used to locate // the tag. lnum = 0; - if (isdigit(*tagp.command)) { + if (isdigit((uint8_t)(*tagp.command))) { // Line number is used to locate the tag - lnum = atol((char *)tagp.command); + lnum = atol(tagp.command); } else { - char_u *cmd_start, *cmd_end; + char *cmd_start, *cmd_end; // Search pattern is used to locate the tag @@ -946,7 +1069,7 @@ static int add_llist_tags(char_u *tag, int num_matches, char **matches) if (cmd_len > (CMDBUFFSIZE - 5)) { cmd_len = CMDBUFFSIZE - 5; } - snprintf((char *)cmd + len, (size_t)(CMDBUFFSIZE + 1 - len), + snprintf(cmd + len, (size_t)(CMDBUFFSIZE + 1 - len), "%.*s", cmd_len, cmd_start); len += cmd_len; @@ -964,16 +1087,16 @@ static int add_llist_tags(char_u *tag, int num_matches, char **matches) dict = tv_dict_alloc(); tv_list_append_dict(list, dict); - tv_dict_add_str(dict, S_LEN("text"), (const char *)tag_name); + tv_dict_add_str(dict, S_LEN("text"), tag_name); tv_dict_add_str(dict, S_LEN("filename"), (const char *)fname); tv_dict_add_nr(dict, S_LEN("lnum"), lnum); if (lnum == 0) { - tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cmd); + tv_dict_add_str(dict, S_LEN("pattern"), cmd); } } - vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag); - set_errorlist(curwin, list, ' ', (char *)IObuff, NULL); + vim_snprintf(IObuff, IOSIZE, "ltag %s", tag); + set_errorlist(curwin, list, ' ', IObuff, NULL); tv_list_free(list); XFREE_CLEAR(fname); @@ -1002,7 +1125,7 @@ static void taglen_advance(int l) void do_tags(exarg_T *eap) { int i; - char_u *name; + char *name; taggy_T *tagstack = curwin->w_tagstack; int tagstackidx = curwin->w_tagstackidx; int tagstacklen = curwin->w_tagstacklen; @@ -1017,14 +1140,14 @@ void do_tags(exarg_T *eap) } msg_putchar('\n'); - vim_snprintf((char *)IObuff, IOSIZE, "%c%2d %2d %-15s %5" PRIdLINENR " ", + vim_snprintf(IObuff, IOSIZE, "%c%2d %2d %-15s %5" PRIdLINENR " ", i == tagstackidx ? '>' : ' ', i + 1, tagstack[i].cur_match + 1, tagstack[i].tagname, tagstack[i].fmark.mark.lnum); - msg_outtrans((char *)IObuff); - msg_outtrans_attr((char *)name, tagstack[i].fmark.fnum == curbuf->b_fnum + msg_outtrans(IObuff); + msg_outtrans_attr(name, tagstack[i].fmark.fnum == curbuf->b_fnum ? HL_ATTR(HLF_D) : 0); xfree(name); } @@ -1037,12 +1160,12 @@ void do_tags(exarg_T *eap) // Compare two strings, for length "len", ignoring case the ASCII way. // return 0 for match, < 0 for smaller, > 0 for bigger // Make sure case is folded to uppercase in comparison (like for 'sort -f') -static int tag_strnicmp(char_u *s1, char_u *s2, size_t len) +static int tag_strnicmp(char *s1, char *s2, size_t len) { int i; while (len > 0) { - i = TOUPPER_ASC(*s1) - TOUPPER_ASC(*s2); + i = TOUPPER_ASC((uint8_t)(*s1)) - TOUPPER_ASC((uint8_t)(*s2)); if (i != 0) { return i; // this character different } @@ -1073,7 +1196,8 @@ static void prepare_pats(pat_T *pats, int has_re) pats->headlen = 0; } else { for (pats->headlen = 0; pats->head[pats->headlen] != NUL; pats->headlen++) { - if (vim_strchr((p_magic ? ".[~*\\$" : "\\$"), pats->head[pats->headlen]) != NULL) { + if (vim_strchr(magic_isset() ? ".[~*\\$" : "\\$", + (uint8_t)pats->head[pats->headlen]) != NULL) { break; } } @@ -1084,7 +1208,7 @@ static void prepare_pats(pat_T *pats, int has_re) } if (has_re) { - pats->regmatch.regprog = vim_regcomp((char *)pats->pat, p_magic ? RE_MAGIC : 0); + pats->regmatch.regprog = vim_regcomp(pats->pat, magic_isset() ? RE_MAGIC : 0); } else { pats->regmatch.regprog = NULL; } @@ -1101,8 +1225,7 @@ static void prepare_pats(pat_T *pats, int has_re) /// @param match_count here the number of tags found will be placed /// @param flags flags from find_tags (TAG_*) /// @param buf_ffname name of buffer for priority -static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int flags, - char_u *buf_ffname) +static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flags, char *buf_ffname) { pos_T save_pos; list_T *taglist; @@ -1110,7 +1233,7 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl int result = FAIL; typval_T args[4]; typval_T rettv; - char_u flagString[4]; + char flagString[4]; taggy_T *tag = NULL; if (curwin->w_tagstacklen > 0) { @@ -1121,14 +1244,14 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl } } - if (*curbuf->b_p_tfu == NUL) { + if (*curbuf->b_p_tfu == NUL || curbuf->b_tfu_cb.type == kCallbackNone) { return FAIL; } args[0].v_type = VAR_STRING; - args[0].vval.v_string = (char *)pat; + args[0].vval.v_string = pat; args[1].v_type = VAR_STRING; - args[1].vval.v_string = (char *)flagString; + args[1].vval.v_string = flagString; // create 'info' dict argument dict_T *const d = tv_dict_alloc_lock(VAR_FIXED); @@ -1145,14 +1268,14 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl args[3].v_type = VAR_UNKNOWN; - vim_snprintf((char *)flagString, sizeof(flagString), + vim_snprintf(flagString, sizeof(flagString), "%s%s%s", g_tag_at_cursor ? "c": "", flags & TAG_INS_COMP ? "i": "", flags & TAG_REGEXP ? "r": ""); save_pos = curwin->w_cursor; - result = call_vim_function(curbuf->b_p_tfu, 3, args, &rettv); + result = callback_call(&curbuf->b_tfu_cb, 3, args, &rettv); curwin->w_cursor = save_pos; // restore the cursor position d->dv_refcount--; @@ -1172,9 +1295,9 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl TV_LIST_ITER_CONST(taglist, li, { char *res_name; - char_u *res_fname; - char_u *res_cmd; - char_u *res_kind; + char *res_fname; + char *res_cmd; + char *res_kind; int has_extra = 0; int name_only = flags & TAG_NAMES; @@ -1203,16 +1326,16 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl continue; } if (!strcmp(dict_key, "filename")) { - res_fname = (char_u *)tv->vval.v_string; + res_fname = tv->vval.v_string; continue; } if (!strcmp(dict_key, "cmd")) { - res_cmd = (char_u *)tv->vval.v_string; + res_cmd = tv->vval.v_string; continue; } has_extra = 1; if (!strcmp(dict_key, "kind")) { - res_kind = (char_u *)tv->vval.v_string; + res_kind = tv->vval.v_string; continue; } // Other elements will be stored as "\tKEY:VALUE" @@ -1232,30 +1355,30 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl char *const mfp = name_only ? xstrdup(res_name) : xmalloc(len + 2); if (!name_only) { - char_u *p = (char_u *)mfp; + char *p = mfp; *p++ = MT_GL_OTH + 1; // mtt *p++ = TAG_SEP; // no tag file name STRCPY(p, res_name); - p += STRLEN(p); + p += strlen(p); *p++ = TAB; STRCPY(p, res_fname); - p += STRLEN(p); + p += strlen(p); *p++ = TAB; STRCPY(p, res_cmd); - p += STRLEN(p); + p += strlen(p); if (has_extra) { STRCPY(p, ";\""); - p += STRLEN(p); + p += strlen(p); if (res_kind) { *p++ = TAB; STRCPY(p, res_kind); - p += STRLEN(p); + p += strlen(p); } TV_DICT_ITER(TV_LIST_ITEM_TV(li)->vval.v_dict, di, { @@ -1280,11 +1403,11 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl *p++ = TAB; STRCPY(p, dict_key); - p += STRLEN(p); + p += strlen(p); STRCPY(p, ":"); - p += STRLEN(p); + p += strlen(p); STRCPY(p, tv->vval.v_string); - p += STRLEN(p); + p += strlen(p); }); } } @@ -1302,6 +1425,873 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl return result; } +/// Initialize the state used by find_tags() +static void findtags_state_init(findtags_state_T *st, char *pat, int flags, int mincount) +{ + st->tag_fname = xmalloc(MAXPATHL + 1); + st->fp = NULL; + st->orgpat = xmalloc(sizeof(pat_T)); + st->orgpat->pat = pat; + st->orgpat->len = (int)strlen(pat); + st->orgpat->regmatch.regprog = NULL; + st->flags = flags; + st->tag_file_sorted = NUL; + st->help_lang_find = NULL; + st->is_txt = false; + st->did_open = false; + st->help_only = (flags & TAG_HELP); + st->get_searchpat = false; + st->help_lang[0] = NUL; + st->help_pri = 0; + st->mincount = mincount; + st->lbuf_size = LSIZE; + st->lbuf = xmalloc((size_t)st->lbuf_size); + st->match_count = 0; + st->stop_searching = false; + + for (int mtt = 0; mtt < MT_COUNT; mtt++) { + ga_init(&st->ga_match[mtt], sizeof(char *), 100); + hash_init(&st->ht_match[mtt]); + } +} + +/// Free the state used by find_tags() +static void findtags_state_free(findtags_state_T *st) +{ + xfree(st->tag_fname); + xfree(st->lbuf); + vim_regfree(st->orgpat->regmatch.regprog); + xfree(st->orgpat); +} + +/// Initialize the language and priority used for searching tags in a Vim help +/// file. +/// Returns true to process the help file for tags and false to skip the file. +static bool findtags_in_help_init(findtags_state_T *st) +{ + int i; + + // Keep "en" as the language if the file extension is ".txt" + if (st->is_txt) { + STRCPY(st->help_lang, "en"); + } else { + // Prefer help tags according to 'helplang'. Put the two-letter + // language name in help_lang[]. + i = (int)strlen(st->tag_fname); + if (i > 3 && st->tag_fname[i - 3] == '-') { + xstrlcpy(st->help_lang, st->tag_fname + i - 2, 3); + } else { + STRCPY(st->help_lang, "en"); + } + } + // When searching for a specific language skip tags files for other + // languages. + if (st->help_lang_find != NULL + && STRICMP(st->help_lang, st->help_lang_find) != 0) { + return false; + } + + // For CTRL-] in a help file prefer a match with the same language. + if ((st->flags & TAG_KEEP_LANG) + && st->help_lang_find == NULL + && curbuf->b_fname != NULL + && (i = (int)strlen(curbuf->b_fname)) > 4 + && curbuf->b_fname[i - 1] == 'x' + && curbuf->b_fname[i - 4] == '.' + && STRNICMP(curbuf->b_fname + i - 3, st->help_lang, 2) == 0) { + st->help_pri = 0; + } else { + st->help_pri = 1; + char *s; + for (s = p_hlg; *s != NUL; s++) { + if (STRNICMP(s, st->help_lang, 2) == 0) { + break; + } + st->help_pri++; + if ((s = vim_strchr(s, ',')) == NULL) { + break; + } + } + if (s == NULL || *s == NUL) { + // Language not in 'helplang': use last, prefer English, unless + // found already. + st->help_pri++; + if (STRICMP(st->help_lang, "en") != 0) { + st->help_pri++; + } + } + } + + return true; +} + +/// Use the function set in 'tagfunc' (if configured and enabled) to get the +/// tags. +/// Return OK if at least 1 tag has been successfully found, NOTDONE if the +/// 'tagfunc' is not used or the 'tagfunc' returns v:null and FAIL otherwise. +static int findtags_apply_tfu(findtags_state_T *st, char *pat, char *buf_ffname) +{ + const bool use_tfu = ((st->flags & TAG_NO_TAGFUNC) == 0); + + if (!use_tfu || tfu_in_use || *curbuf->b_p_tfu == NUL) { + return NOTDONE; + } + + tfu_in_use = true; + int retval = find_tagfunc_tags(pat, st->ga_match, &st->match_count, + st->flags, buf_ffname); + tfu_in_use = false; + + return retval; +} + +/// Read the next line from a tags file. +/// Returns TAGS_READ_SUCCESS if a tags line is successfully read and should be +/// processed. +/// Returns TAGS_READ_EOF if the end of file is reached. +/// Returns TAGS_READ_IGNORE if the current line should be ignored (used when +/// reached end of a emacs included tags file) +static tags_read_status_T findtags_get_next_line(findtags_state_T *st, tagsearch_info_T *sinfo_p) +{ + int eof; + off_T offset; + + // For binary search: compute the next offset to use. + if (st->state == TS_BINARY) { + offset = sinfo_p->low_offset + ((sinfo_p->high_offset - sinfo_p->low_offset) / 2); + if (offset == sinfo_p->curr_offset) { + return TAGS_READ_EOF; // End the binary search without a match. + } else { + sinfo_p->curr_offset = offset; + } + } else if (st->state == TS_SKIP_BACK) { + // Skipping back (after a match during binary search). + sinfo_p->curr_offset -= st->lbuf_size * 2; + if (sinfo_p->curr_offset < 0) { + sinfo_p->curr_offset = 0; + rewind(st->fp); + st->state = TS_STEP_FORWARD; + } + } + + // When jumping around in the file, first read a line to find the + // start of the next line. + if (st->state == TS_BINARY || st->state == TS_SKIP_BACK) { + // Adjust the search file offset to the correct position + sinfo_p->curr_offset_used = sinfo_p->curr_offset; + vim_ignored = vim_fseek(st->fp, sinfo_p->curr_offset, SEEK_SET); + eof = vim_fgets(st->lbuf, st->lbuf_size, st->fp); + if (!eof && sinfo_p->curr_offset != 0) { + sinfo_p->curr_offset = vim_ftell(st->fp); + if (sinfo_p->curr_offset == sinfo_p->high_offset) { + // oops, gone a bit too far; try from low offset + vim_ignored = vim_fseek(st->fp, sinfo_p->low_offset, SEEK_SET); + sinfo_p->curr_offset = sinfo_p->low_offset; + } + eof = vim_fgets(st->lbuf, st->lbuf_size, st->fp); + } + // skip empty and blank lines + while (!eof && vim_isblankline(st->lbuf)) { + sinfo_p->curr_offset = vim_ftell(st->fp); + eof = vim_fgets(st->lbuf, st->lbuf_size, st->fp); + } + if (eof) { + // Hit end of file. Skip backwards. + st->state = TS_SKIP_BACK; + sinfo_p->match_offset = vim_ftell(st->fp); + sinfo_p->curr_offset = sinfo_p->curr_offset_used; + return TAGS_READ_IGNORE; + } + } else { + // Not jumping around in the file: Read the next line. + + // skip empty and blank lines + do { + eof = vim_fgets(st->lbuf, st->lbuf_size, st->fp); + } while (!eof && vim_isblankline(st->lbuf)); + + if (eof) { + return TAGS_READ_EOF; + } + } + + return TAGS_READ_SUCCESS; +} + +/// Parse a tags file header line in "st->lbuf". +/// Returns true if the current line in st->lbuf is not a tags header line and +/// should be parsed as a regular tag line. Returns false if the line is a +/// header line and the next header line should be read. +static bool findtags_hdr_parse(findtags_state_T *st) +{ + // Header lines in a tags file start with "!_TAG_" + if (strncmp(st->lbuf, "!_TAG_", 6) != 0) { + // Non-header item before the header, e.g. "!" itself. + return true; + } + + // Process the header line. + if (strncmp(st->lbuf, "!_TAG_FILE_SORTED\t", 18) == 0) { + st->tag_file_sorted = (uint8_t)st->lbuf[18]; + } + if (strncmp(st->lbuf, "!_TAG_FILE_ENCODING\t", 20) == 0) { + // Prepare to convert every line from the specified encoding to + // 'encoding'. + char *p; + for (p = st->lbuf + 20; *p > ' ' && *p < 127; p++) {} + *p = NUL; + convert_setup(&st->vimconv, st->lbuf + 20, p_enc); + } + + // Read the next line. Unrecognized flags are ignored. + return false; +} + +/// Handler to initialize the state when starting to process a new tags file. +/// Called in the TS_START state when finding tags from a tags file. +/// Returns true if the line read from the tags file should be parsed and +/// false if the line should be ignored. +static bool findtags_start_state_handler(findtags_state_T *st, bool *sortic, + tagsearch_info_T *sinfo_p) +{ + const bool noic = (st->flags & TAG_NOIC); + + // The header ends when the line sorts below "!_TAG_". When case is + // folded lower case letters sort before "_". + if (strncmp(st->lbuf, "!_TAG_", 6) <= 0 + || (st->lbuf[0] == '!' && ASCII_ISLOWER(st->lbuf[1]))) { + return findtags_hdr_parse(st); + } + + // Headers ends. + + // When there is no tag head, or ignoring case, need to do a + // linear search. + // When no "!_TAG_" is found, default to binary search. If + // the tag file isn't sorted, the second loop will find it. + // When "!_TAG_FILE_SORTED" found: start binary search if + // flag set. + if (st->linear) { + st->state = TS_LINEAR; + } else if (st->tag_file_sorted == NUL) { + st->state = TS_BINARY; + } else if (st->tag_file_sorted == '1') { + st->state = TS_BINARY; + } else if (st->tag_file_sorted == '2') { + st->state = TS_BINARY; + *sortic = true; + st->orgpat->regmatch.rm_ic = (p_ic || !noic); + } else { + st->state = TS_LINEAR; + } + + if (st->state == TS_BINARY && st->orgpat->regmatch.rm_ic && !*sortic) { + // Binary search won't work for ignoring case, use linear + // search. + st->linear = true; + st->state = TS_LINEAR; + } + + // When starting a binary search, get the size of the file and + // compute the first offset. + if (st->state == TS_BINARY) { + if (vim_fseek(st->fp, 0, SEEK_END) != 0) { + // can't seek, don't use binary search + st->state = TS_LINEAR; + } else { + // Get the tag file size. + // Don't use lseek(), it doesn't work + // properly on MacOS Catalina. + const off_T filesize = vim_ftell(st->fp); + vim_ignored = vim_fseek(st->fp, 0, SEEK_SET); + + // Calculate the first read offset in the file. Start + // the search in the middle of the file. + sinfo_p->low_offset = 0; + sinfo_p->low_char = 0; + sinfo_p->high_offset = filesize; + sinfo_p->curr_offset = 0; + sinfo_p->high_char = 0xff; + } + return false; + } + + return true; +} + +/// Parse a tag line read from a tags file. +/// Also compares the tag name in "tagpp->tagname" with a search pattern in +/// "st->orgpat->head" as a quick check if the tag may match. +/// Returns: +/// - TAG_MATCH_SUCCESS if the tag may match +/// - TAG_MATCH_FAIL if the tag doesn't match +/// - TAG_MATCH_NEXT to look for the next matching tag (used in a binary search) +/// - TAG_MATCH_STOP if all the tags are processed without a match. +/// Uses the values in "margs" for doing the comparison. +static tagmatch_status_T findtags_parse_line(findtags_state_T *st, tagptrs_T *tagpp, + findtags_match_args_T *margs, + tagsearch_info_T *sinfo_p) +{ + int status; + int i; + int cmplen; + int tagcmp; + + // Figure out where the different strings are in this line. + // For "normal" tags: Do a quick check if the tag matches. + // This speeds up tag searching a lot! + if (st->orgpat->headlen) { + CLEAR_FIELD(*tagpp); + tagpp->tagname = st->lbuf; + tagpp->tagname_end = vim_strchr(st->lbuf, TAB); + if (tagpp->tagname_end == NULL) { + // Corrupted tag line. + return TAG_MATCH_FAIL; + } + + // Skip this line if the length of the tag is different and + // there is no regexp, or the tag is too short. + cmplen = (int)(tagpp->tagname_end - tagpp->tagname); + if (p_tl != 0 && cmplen > p_tl) { // adjust for 'taglength' + cmplen = (int)p_tl; + } + if ((st->flags & TAG_REGEXP) && st->orgpat->headlen < cmplen) { + cmplen = st->orgpat->headlen; + } else if (st->state == TS_LINEAR && st->orgpat->headlen != cmplen) { + return TAG_MATCH_NEXT; + } + + if (st->state == TS_BINARY) { + // Simplistic check for unsorted tags file. + i = (int)tagpp->tagname[0]; + if (margs->sortic) { + i = TOUPPER_ASC(tagpp->tagname[0]); + } + if (i < sinfo_p->low_char || i > sinfo_p->high_char) { + margs->sort_error = true; + } + + // Compare the current tag with the searched tag. + if (margs->sortic) { + tagcmp = tag_strnicmp(tagpp->tagname, st->orgpat->head, + (size_t)cmplen); + } else { + tagcmp = strncmp(tagpp->tagname, st->orgpat->head, (size_t)cmplen); + } + + // A match with a shorter tag means to search forward. + // A match with a longer tag means to search backward. + if (tagcmp == 0) { + if (cmplen < st->orgpat->headlen) { + tagcmp = -1; + } else if (cmplen > st->orgpat->headlen) { + tagcmp = 1; + } + } + + if (tagcmp == 0) { + // We've located the tag, now skip back and search + // forward until the first matching tag is found. + st->state = TS_SKIP_BACK; + sinfo_p->match_offset = sinfo_p->curr_offset; + return TAG_MATCH_NEXT; + } + if (tagcmp < 0) { + sinfo_p->curr_offset = vim_ftell(st->fp); + if (sinfo_p->curr_offset < sinfo_p->high_offset) { + sinfo_p->low_offset = sinfo_p->curr_offset; + if (margs->sortic) { + sinfo_p->low_char = TOUPPER_ASC(tagpp->tagname[0]); + } else { + sinfo_p->low_char = (uint8_t)tagpp->tagname[0]; + } + return TAG_MATCH_NEXT; + } + } + if (tagcmp > 0 && sinfo_p->curr_offset != sinfo_p->high_offset) { + sinfo_p->high_offset = sinfo_p->curr_offset; + if (margs->sortic) { + sinfo_p->high_char = TOUPPER_ASC(tagpp->tagname[0]); + } else { + sinfo_p->high_char = (uint8_t)tagpp->tagname[0]; + } + return TAG_MATCH_NEXT; + } + + // No match yet and are at the end of the binary search. + return TAG_MATCH_STOP; + } else if (st->state == TS_SKIP_BACK) { + assert(cmplen >= 0); + if (mb_strnicmp(tagpp->tagname, st->orgpat->head, (size_t)cmplen) != 0) { + st->state = TS_STEP_FORWARD; + } else { + // Have to skip back more. Restore the curr_offset + // used, otherwise we get stuck at a long line. + sinfo_p->curr_offset = sinfo_p->curr_offset_used; + } + return TAG_MATCH_NEXT; + } else if (st->state == TS_STEP_FORWARD) { + assert(cmplen >= 0); + if (mb_strnicmp(tagpp->tagname, st->orgpat->head, (size_t)cmplen) != 0) { + if ((off_T)vim_ftell(st->fp) > sinfo_p->match_offset) { + return TAG_MATCH_STOP; // past last match + } else { + return TAG_MATCH_NEXT; // before first match + } + } + } else { + // skip this match if it can't match + assert(cmplen >= 0); + if (mb_strnicmp(tagpp->tagname, st->orgpat->head, (size_t)cmplen) != 0) { + return TAG_MATCH_NEXT; + } + } + + // Can be a matching tag, isolate the file name and command. + tagpp->fname = tagpp->tagname_end + 1; + tagpp->fname_end = vim_strchr(tagpp->fname, TAB); + if (tagpp->fname_end == NULL) { + status = FAIL; + } else { + tagpp->command = tagpp->fname_end + 1; + status = OK; + } + } else { + status = parse_tag_line(st->lbuf, tagpp); + } + + if (status == FAIL) { + return TAG_MATCH_FAIL; + } + + return TAG_MATCH_SUCCESS; +} + +/// Initialize the structure used for tag matching. +static void findtags_matchargs_init(findtags_match_args_T *margs, int flags) +{ + margs->matchoff = 0; // match offset + margs->match_re = false; // match with regexp + margs->match_no_ic = false; // matches with case + margs->has_re = (flags & TAG_REGEXP); // regexp used + margs->sortic = false; // tag file sorted in nocase + margs->sort_error = false; // tags file not sorted +} + +/// Compares the tag name in "tagpp->tagname" with a search pattern in +/// "st->orgpat->pat". +/// Returns true if the tag matches, false if the tag doesn't match. +/// Uses the values in "margs" for doing the comparison. +static bool findtags_match_tag(findtags_state_T *st, tagptrs_T *tagpp, findtags_match_args_T *margs) +{ + bool match = false; + + // First try matching with the pattern literally (also when it is + // a regexp). + int cmplen = (int)(tagpp->tagname_end - tagpp->tagname); + if (p_tl != 0 && cmplen > p_tl) { // adjust for 'taglength' + cmplen = (int)p_tl; + } + // if tag length does not match, don't try comparing + if (st->orgpat->len != cmplen) { + match = false; + } else { + if (st->orgpat->regmatch.rm_ic) { + assert(cmplen >= 0); + match = mb_strnicmp(tagpp->tagname, st->orgpat->pat, (size_t)cmplen) == 0; + if (match) { + margs->match_no_ic = strncmp(tagpp->tagname, st->orgpat->pat, (size_t)cmplen) == 0; + } + } else { + match = strncmp(tagpp->tagname, st->orgpat->pat, (size_t)cmplen) == 0; + } + } + + // Has a regexp: Also find tags matching regexp. + margs->match_re = false; + if (!match && st->orgpat->regmatch.regprog != NULL) { + char cc = *tagpp->tagname_end; + *tagpp->tagname_end = NUL; + match = vim_regexec(&st->orgpat->regmatch, tagpp->tagname, (colnr_T)0); + if (match) { + margs->matchoff = (int)(st->orgpat->regmatch.startp[0] - tagpp->tagname); + if (st->orgpat->regmatch.rm_ic) { + st->orgpat->regmatch.rm_ic = false; + margs->match_no_ic = vim_regexec(&st->orgpat->regmatch, + tagpp->tagname, (colnr_T)0); + st->orgpat->regmatch.rm_ic = true; + } + } + *tagpp->tagname_end = cc; + margs->match_re = true; + } + + return match; +} + +/// Convert the encoding of a line read from a tags file in "st->lbuf". +/// Converting the pattern from 'enc' to the tags file encoding doesn't work, +/// because characters are not recognized. The converted line is saved in +/// st->lbuf. +static void findtags_string_convert(findtags_state_T *st) +{ + char *conv_line = string_convert(&st->vimconv, st->lbuf, NULL); + if (conv_line == NULL) { + return; + } + + // Copy or swap lbuf and conv_line. + int len = (int)strlen(conv_line) + 1; + if (len > st->lbuf_size) { + xfree(st->lbuf); + st->lbuf = conv_line; + st->lbuf_size = len; + } else { + STRCPY(st->lbuf, conv_line); + xfree(conv_line); + } +} + +/// Add a matching tag found in a tags file to st->ht_match and st->ga_match. +static void findtags_add_match(findtags_state_T *st, tagptrs_T *tagpp, findtags_match_args_T *margs, + char *buf_ffname, hash_T *hash) +{ + const bool name_only = (st->flags & TAG_NAMES); + int mtt; + size_t len = 0; + bool is_current; // file name matches + bool is_static; // current tag line is static + char *mfp; + char *p; + char *s; + + // Decide in which array to store this match. + is_current = test_for_current(tagpp->fname, tagpp->fname_end, + st->tag_fname, buf_ffname); + is_static = test_for_static(tagpp); + + // Decide in which of the sixteen tables to store this match. + if (is_static) { + if (is_current) { + mtt = MT_ST_CUR; + } else { + mtt = MT_ST_OTH; + } + } else { + if (is_current) { + mtt = MT_GL_CUR; + } else { + mtt = MT_GL_OTH; + } + } + if (st->orgpat->regmatch.rm_ic && !margs->match_no_ic) { + mtt += MT_IC_OFF; + } + if (margs->match_re) { + mtt += MT_RE_OFF; + } + + // Add the found match in ht_match[mtt] and ga_match[mtt]. + // Store the info we need later, which depends on the kind of + // tags we are dealing with. + if (st->help_only) { +#define ML_EXTRA 3 + // Append the help-heuristic number after the tagname, for + // sorting it later. The heuristic is ignored for + // detecting duplicates. + // The format is {tagname}@{lang}NUL{heuristic}NUL + *tagpp->tagname_end = NUL; + len = (size_t)(tagpp->tagname_end - tagpp->tagname); + mfp = xmalloc(sizeof(char) + len + 10 + ML_EXTRA + 1); + + p = mfp; + STRCPY(p, tagpp->tagname); + p[len] = '@'; + STRCPY(p + len + 1, st->help_lang); + snprintf(p + len + 1 + ML_EXTRA, strlen(p) + len + 1 + ML_EXTRA, "%06d", + help_heuristic(tagpp->tagname, + margs->match_re ? margs->matchoff : 0, + !margs->match_no_ic) + st->help_pri); + + *tagpp->tagname_end = TAB; + } else if (name_only) { + if (st->get_searchpat) { + char *temp_end = tagpp->command; + + if (*temp_end == '/') { + while (*temp_end && *temp_end != '\r' + && *temp_end != '\n' + && *temp_end != '$') { + temp_end++; + } + } + + if (tagpp->command + 2 < temp_end) { + len = (size_t)(temp_end - tagpp->command - 2); + mfp = xmalloc(len + 2); + xstrlcpy(mfp, tagpp->command + 2, len + 1); + } else { + mfp = NULL; + } + st->get_searchpat = false; + } else { + len = (size_t)(tagpp->tagname_end - tagpp->tagname); + mfp = xmalloc(sizeof(char) + len + 1); + xstrlcpy(mfp, tagpp->tagname, len + 1); + + // if wanted, re-read line to get long form too + if (State & MODE_INSERT) { + st->get_searchpat = p_sft; + } + } + } else { + size_t tag_fname_len = strlen(st->tag_fname); + // Save the tag in a buffer. + // Use 0x02 to separate fields (Can't use NUL, because the + // hash key is terminated by NUL). + // Emacs tag: <mtt><tag_fname><0x02><ebuf><0x02><lbuf><NUL> + // other tag: <mtt><tag_fname><0x02><0x02><lbuf><NUL> + // without Emacs tags: <mtt><tag_fname><0x02><lbuf><NUL> + // Here <mtt> is the "mtt" value plus 1 to avoid NUL. + len = tag_fname_len + strlen(st->lbuf) + 3; + mfp = xmalloc(sizeof(char) + len + 1); + p = mfp; + p[0] = (char)(mtt + 1); + STRCPY(p + 1, st->tag_fname); +#ifdef BACKSLASH_IN_FILENAME + // Ignore differences in slashes, avoid adding + // both path/file and path\file. + slash_adjust(p + 1); +#endif + p[tag_fname_len + 1] = TAG_SEP; + s = p + 1 + tag_fname_len + 1; + STRCPY(s, st->lbuf); + } + + if (mfp != NULL) { + hashitem_T *hi; + + // Don't add identical matches. + // "mfp" is used as a hash key, there is a NUL byte to end + // the part that matters for comparing, more bytes may + // follow after it. E.g. help tags store the priority + // after the NUL. + *hash = hash_hash(mfp); + hi = hash_lookup(&st->ht_match[mtt], (const char *)mfp, strlen(mfp), *hash); + if (HASHITEM_EMPTY(hi)) { + hash_add_item(&st->ht_match[mtt], hi, mfp, *hash); + GA_APPEND(char *, &st->ga_match[mtt], mfp); + st->match_count++; + } else { + // duplicate tag, drop it + xfree(mfp); + } + } +} + +/// Read and get all the tags from file st->tag_fname. +/// Sets "st->stop_searching" to true to stop searching for additional tags. +static void findtags_get_all_tags(findtags_state_T *st, findtags_match_args_T *margs, + char *buf_ffname) +{ + tagptrs_T tagp; + tagsearch_info_T search_info; + int retval; + hash_T hash = 0; + + // This is only to avoid a compiler warning for using search_info + // uninitialised. + CLEAR_FIELD(search_info); + + // Read and parse the lines in the file one by one + for (;;) { + // check for CTRL-C typed, more often when jumping around + if (st->state == TS_BINARY || st->state == TS_SKIP_BACK) { + line_breakcheck(); + } else { + fast_breakcheck(); + } + if ((st->flags & TAG_INS_COMP)) { // Double brackets for gcc + ins_compl_check_keys(30, false); + } + if (got_int || ins_compl_interrupted()) { + st->stop_searching = true; + break; + } + // When mincount is TAG_MANY, stop when enough matches have been + // found (for completion). + if (st->mincount == TAG_MANY && st->match_count >= TAG_MANY) { + st->stop_searching = true; + break; + } + if (st->get_searchpat) { + goto line_read_in; + } + + retval = (int)findtags_get_next_line(st, &search_info); + if (retval == TAGS_READ_IGNORE) { + continue; + } + if (retval == TAGS_READ_EOF) { + break; + } + +line_read_in: + + if (st->vimconv.vc_type != CONV_NONE) { + findtags_string_convert(st); + } + + // When still at the start of the file, check for Emacs tags file + // format, and for "not sorted" flag. + if (st->state == TS_START) { + if (!findtags_start_state_handler(st, &margs->sortic, &search_info)) { + continue; + } + } + + // When the line is too long the NUL will not be in the + // last-but-one byte (see vim_fgets()). + // Has been reported for Mozilla JS with extremely long names. + // In that case we need to increase lbuf_size. + if (st->lbuf[st->lbuf_size - 2] != NUL) { + st->lbuf_size *= 2; + xfree(st->lbuf); + st->lbuf = xmalloc((size_t)st->lbuf_size); + + if (st->state == TS_STEP_FORWARD || st->state == TS_LINEAR) { + // Seek to the same position to read the same line again + vim_ignored = vim_fseek(st->fp, search_info.curr_offset, SEEK_SET); + } + // this will try the same thing again, make sure the offset is + // different + search_info.curr_offset = 0; + continue; + } + + retval = (int)findtags_parse_line(st, &tagp, margs, &search_info); + if (retval == TAG_MATCH_NEXT) { + continue; + } + if (retval == TAG_MATCH_STOP) { + break; + } + if (retval == TAG_MATCH_FAIL) { + semsg(_("E431: Format error in tags file \"%s\""), st->tag_fname); + semsg(_("Before byte %" PRId64), (int64_t)vim_ftell(st->fp)); + st->stop_searching = true; + return; + } + + // If a match is found, add it to ht_match[] and ga_match[]. + if (findtags_match_tag(st, &tagp, margs)) { + findtags_add_match(st, &tagp, margs, buf_ffname, &hash); + } + } // forever +} + +/// Search for tags matching "st->orgpat.pat" in the "st->tag_fname" tags file. +/// Information needed to search for the tags is in the "st" state structure. +/// The matching tags are returned in "st". If an error is encountered, then +/// "st->stop_searching" is set to true. +static void findtags_in_file(findtags_state_T *st, int flags, char *buf_ffname) +{ + findtags_match_args_T margs; + + st->vimconv.vc_type = CONV_NONE; + st->tag_file_sorted = NUL; + st->fp = NULL; + findtags_matchargs_init(&margs, st->flags); + + // A file that doesn't exist is silently ignored. Only when not a + // single file is found, an error message is given (further on). + if (curbuf->b_help) { + if (!findtags_in_help_init(st)) { + return; + } + } + + st->fp = os_fopen(st->tag_fname, "r"); + if (st->fp == NULL) { + return; + } + + if (p_verbose >= 5) { + verbose_enter(); + smsg(_("Searching tags file %s"), st->tag_fname); + verbose_leave(); + } + st->did_open = true; // remember that we found at least one file + + st->state = TS_START; // we're at the start of the file + + // Read and parse the lines in the file one by one + findtags_get_all_tags(st, &margs, buf_ffname); + + if (st->fp != NULL) { + fclose(st->fp); + st->fp = NULL; + } + if (st->vimconv.vc_type != CONV_NONE) { + convert_setup(&st->vimconv, NULL, NULL); + } + + if (margs.sort_error) { + semsg(_("E432: Tags file not sorted: %s"), st->tag_fname); + } + + // Stop searching if sufficient tags have been found. + if (st->match_count >= st->mincount) { + st->stop_searching = true; + } +} + +/// Copy the tags found by find_tags() to "matchesp". +/// Returns the number of matches copied. +static int findtags_copy_matches(findtags_state_T *st, char ***matchesp) +{ + const bool name_only = (st->flags & TAG_NAMES); + char **matches; + int mtt; + int i; + char *mfp; + char *p; + + if (st->match_count > 0) { + matches = xmalloc((size_t)st->match_count * sizeof(char *)); + } else { + matches = NULL; + } + st->match_count = 0; + for (mtt = 0; mtt < MT_COUNT; mtt++) { + for (i = 0; i < st->ga_match[mtt].ga_len; i++) { + mfp = ((char **)(st->ga_match[mtt].ga_data))[i]; + if (matches == NULL) { + xfree(mfp); + } else { + if (!name_only) { + // Change mtt back to zero-based. + *mfp = (char)(*mfp - 1); + + // change the TAG_SEP back to NUL + for (p = mfp + 1; *p != NUL; p++) { + if (*p == TAG_SEP) { + *p = NUL; + } + } + } + matches[st->match_count++] = mfp; + } + } + + ga_clear(&st->ga_match[mtt]); + hash_clear(&st->ht_match[mtt]); + } + + *matchesp = matches; + return st->match_count; +} + /// find_tags() - search for tags in tags files /// /// Return FAIL if search completely failed (*num_matches will be 0, *matchesp @@ -1325,94 +2315,33 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl /// TAG_REGEXP use "pat" as a regexp /// TAG_NOIC don't always ignore case /// TAG_KEEP_LANG keep language -/// TAG_CSCOPE use cscope results for tags /// TAG_NO_TAGFUNC do not call the 'tagfunc' function /// /// @param pat pattern to search for /// @param num_matches return: number of matches found /// @param matchesp return: array of matches found -/// @param mincount MAXCOL: find all matches other: minimal number of matches */ +/// @param mincount MAXCOL: find all matches +/// other: minimal number of matches /// @param buf_ffname name of buffer for priority int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int mincount, char *buf_ffname) { - FILE *fp; - char_u *lbuf; // line buffer - int lbuf_size = LSIZE; // length of lbuf - char_u *tag_fname; // name of tag file + findtags_state_T st; tagname_T tn; // info for get_tagfname() int first_file; // trying first tag file - tagptrs_T tagp; - bool did_open = false; // did open a tag file - bool stop_searching = false; // stop when match found or error int retval = FAIL; // return value - int is_static; // current tag line is static - int is_current; // file name matches - bool eof = false; // found end-of-file - char_u *p; - char_u *s; - int i; - int tag_file_sorted = NUL; // !_TAG_FILE_SORTED value - struct tag_search_info { // Binary search file offsets - off_T low_offset; // offset for first char of first line that - // could match - off_T high_offset; // offset of char after last line that could - // match - off_T curr_offset; // Current file offset in search range - off_T curr_offset_used; // curr_offset used when skipping back - off_T match_offset; // Where the binary search found a tag - int low_char; // first char at low_offset - int high_char; // first char at high_offset - } search_info; - int tagcmp; - off_T offset; int round; - enum { - TS_START, // at start of file - TS_LINEAR, // linear searching forward, till EOF - TS_BINARY, // binary searching - TS_SKIP_BACK, // skipping backwards - TS_STEP_FORWARD, // stepping forwards - } state; // Current search state - int cmplen; - int match; // matches - int match_no_ic = 0; // matches with rm_ic == false - int match_re; // match with regexp - int matchoff = 0; int save_emsg_off; - char *mfp; - garray_T ga_match[MT_COUNT]; // stores matches in sequence - hashtab_T ht_match[MT_COUNT]; // stores matches by key - hash_T hash = 0; - int match_count = 0; // number of matches found - char **matches; - int mtt; int help_save; - int help_pri = 0; - char_u *help_lang_find = NULL; // lang to be found - char_u help_lang[3]; // lang of current tags file + int i; char *saved_pat = NULL; // copy of pat[] - bool is_txt = false; - - pat_T orgpat; // holds unconverted pattern info - vimconv_T vimconv; - int findall = (mincount == MAXCOL || mincount == TAG_MANY); - // find all matching tags - bool sort_error = false; // tags file not sorted - int linear; // do a linear search - bool sortic = false; // tag file sorted in nocase - bool line_error = false; // syntax error + int findall = (mincount == MAXCOL || mincount == TAG_MANY); // find all matching tags int has_re = (flags & TAG_REGEXP); // regexp used - int help_only = (flags & TAG_HELP); - int name_only = (flags & TAG_NAMES); int noic = (flags & TAG_NOIC); - int get_it_again = false; - int use_cscope = (flags & TAG_CSCOPE); int verbose = (flags & TAG_VERBOSE); - int use_tfu = ((flags & TAG_NO_TAGFUNC) == 0); int save_p_ic = p_ic; // Change the value of 'ignorecase' according to 'tagcase' for the @@ -1427,76 +2356,63 @@ int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int minc p_ic = false; break; case TC_FOLLOWSCS: - p_ic = ignorecase((char_u *)pat); + p_ic = ignorecase(pat); break; case TC_SMART: - p_ic = ignorecase_opt((char_u *)pat, true, true); + p_ic = ignorecase_opt(pat, true, true); break; default: abort(); } help_save = curbuf->b_help; - orgpat.pat = (char_u *)pat; - orgpat.regmatch.regprog = NULL; - vimconv.vc_type = CONV_NONE; - // Allocate memory for the buffers that are used - lbuf = xmalloc((size_t)lbuf_size); - tag_fname = xmalloc(MAXPATHL + 1); - for (mtt = 0; mtt < MT_COUNT; mtt++) { - ga_init(&ga_match[mtt], sizeof(char *), 100); - hash_init(&ht_match[mtt]); - } - - STRCPY(tag_fname, "from cscope"); // for error messages + findtags_state_init(&st, pat, flags, mincount); // Initialize a few variables - if (help_only) { // want tags from help file + if (st.help_only) { // want tags from help file curbuf->b_help = true; // will be restored later - } else if (use_cscope) { - // Make sure we don't mix help and cscope, confuses Coverity. - help_only = false; - curbuf->b_help = false; } - orgpat.len = (int)strlen(pat); if (curbuf->b_help) { // When "@ab" is specified use only the "ab" language, otherwise // search all languages. - if (orgpat.len > 3 && pat[orgpat.len - 3] == '@' - && ASCII_ISALPHA(pat[orgpat.len - 2]) - && ASCII_ISALPHA(pat[orgpat.len - 1])) { - saved_pat = xstrnsave(pat, (size_t)orgpat.len - 3); - help_lang_find = (char_u *)&pat[orgpat.len - 2]; - orgpat.pat = (char_u *)saved_pat; - orgpat.len -= 3; + if (st.orgpat->len > 3 && pat[st.orgpat->len - 3] == '@' + && ASCII_ISALPHA(pat[st.orgpat->len - 2]) + && ASCII_ISALPHA(pat[st.orgpat->len - 1])) { + saved_pat = xstrnsave(pat, (size_t)st.orgpat->len - 3); + st.help_lang_find = &pat[st.orgpat->len - 2]; + st.orgpat->pat = saved_pat; + st.orgpat->len -= 3; } } - if (p_tl != 0 && orgpat.len > p_tl) { // adjust for 'taglength' - orgpat.len = (int)p_tl; + if (p_tl != 0 && st.orgpat->len > p_tl) { // adjust for 'taglength' + st.orgpat->len = (int)p_tl; } save_emsg_off = emsg_off; emsg_off = true; // don't want error for invalid RE here - prepare_pats(&orgpat, has_re); + prepare_pats(st.orgpat, has_re); emsg_off = save_emsg_off; - if (has_re && orgpat.regmatch.regprog == NULL) { + if (has_re && st.orgpat->regmatch.regprog == NULL) { goto findtag_end; } - // This is only to avoid a compiler warning for using search_info - // uninitialised. - CLEAR_FIELD(search_info); + retval = findtags_apply_tfu(&st, pat, buf_ffname); + if (retval != NOTDONE) { + goto findtag_end; + } - if (*curbuf->b_p_tfu != NUL && use_tfu && !tfu_in_use) { - tfu_in_use = true; - retval = find_tagfunc_tags((char_u *)pat, &ga_match[0], &match_count, flags, - (char_u *)buf_ffname); - tfu_in_use = false; - if (retval != NOTDONE) { - goto findtag_end; - } + // re-initialize the default return value + retval = FAIL; + + // Set a flag if the file extension is .txt + if ((flags & TAG_KEEP_LANG) + && st.help_lang_find == NULL + && curbuf->b_fname != NULL + && (i = (int)strlen(curbuf->b_fname)) > 4 + && STRICMP(curbuf->b_fname + i - 4, ".txt") == 0) { + st.is_txt = true; } // When finding a specified number of matches, first try with matching @@ -1507,725 +2423,52 @@ int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int minc // tags files twice. // When the tag file is case-fold sorted, it is either one or the other. // Only ignore case when TAG_NOIC not used or 'ignorecase' set. - - // Set a flag if the file extension is .txt - if ((flags & TAG_KEEP_LANG) - && help_lang_find == NULL - && curbuf->b_fname != NULL - && (i = (int)strlen(curbuf->b_fname)) > 4 - && STRICMP(curbuf->b_fname + i - 4, ".txt") == 0) { - is_txt = true; - } - orgpat.regmatch.rm_ic = ((p_ic || !noic) - && (findall || orgpat.headlen == 0 || !p_tbs)); + st.orgpat->regmatch.rm_ic = ((p_ic || !noic) + && (findall || st.orgpat->headlen == 0 || !p_tbs)); for (round = 1; round <= 2; round++) { - linear = (orgpat.headlen == 0 || !p_tbs || round == 2); + st.linear = (st.orgpat->headlen == 0 || !p_tbs || round == 2); // Try tag file names from tags option one by one. for (first_file = true; - use_cscope || get_tagfname(&tn, first_file, (char *)tag_fname) == OK; + get_tagfname(&tn, first_file, st.tag_fname) == OK; first_file = false) { - // A file that doesn't exist is silently ignored. Only when not a - // single file is found, an error message is given (further on). - if (use_cscope) { - fp = NULL; // avoid GCC warning - } else { - if (curbuf->b_help) { - // Keep en if the file extension is .txt - if (is_txt) { - STRCPY(help_lang, "en"); - } else { - // Prefer help tags according to 'helplang'. Put the - // two-letter language name in help_lang[]. - i = (int)STRLEN(tag_fname); - if (i > 3 && tag_fname[i - 3] == '-') { - STRCPY(help_lang, tag_fname + i - 2); - } else { - STRCPY(help_lang, "en"); - } - } - - // When searching for a specific language skip tags files - // for other languages. - if (help_lang_find != NULL - && STRICMP(help_lang, help_lang_find) != 0) { - continue; - } - - // For CTRL-] in a help file prefer a match with the same - // language. - if ((flags & TAG_KEEP_LANG) - && help_lang_find == NULL - && curbuf->b_fname != NULL - && (i = (int)strlen(curbuf->b_fname)) > 4 - && curbuf->b_fname[i - 1] == 'x' - && curbuf->b_fname[i - 4] == '.' - && STRNICMP(curbuf->b_fname + i - 3, help_lang, 2) == 0) { - help_pri = 0; - } else { - help_pri = 1; - for (s = p_hlg; *s != NUL; s++) { - if (STRNICMP(s, help_lang, 2) == 0) { - break; - } - help_pri++; - if ((s = (char_u *)vim_strchr((char *)s, ',')) == NULL) { - break; - } - } - if (s == NULL || *s == NUL) { - // Language not in 'helplang': use last, prefer English, - // unless found already. - help_pri++; - if (STRICMP(help_lang, "en") != 0) { - help_pri++; - } - } - } - } - - if ((fp = os_fopen((char *)tag_fname, "r")) == NULL) { - continue; - } - - if (p_verbose >= 5) { - verbose_enter(); - smsg(_("Searching tags file %s"), tag_fname); - verbose_leave(); - } - } - did_open = true; // remember that we found at least one file - - state = TS_START; // we're at the start of the file - - // Read and parse the lines in the file one by one - for (;;) { - // check for CTRL-C typed, more often when jumping around - if (state == TS_BINARY || state == TS_SKIP_BACK) { - line_breakcheck(); - } else { - fast_breakcheck(); - } - if ((flags & TAG_INS_COMP)) { // Double brackets for gcc - ins_compl_check_keys(30, false); - } - if (got_int || ins_compl_interrupted()) { - stop_searching = true; - break; - } - // When mincount is TAG_MANY, stop when enough matches have been - // found (for completion). - if (mincount == TAG_MANY && match_count >= TAG_MANY) { - stop_searching = true; - retval = OK; - break; - } - if (get_it_again) { - goto line_read_in; - } - // For binary search: compute the next offset to use. - if (state == TS_BINARY) { - offset = search_info.low_offset + ((search_info.high_offset - - search_info.low_offset) / 2); - if (offset == search_info.curr_offset) { - break; // End the binary search without a match. - } else { - search_info.curr_offset = offset; - } - } else if (state == TS_SKIP_BACK) { - // Skipping back (after a match during binary search). - search_info.curr_offset -= lbuf_size * 2; - if (search_info.curr_offset < 0) { - search_info.curr_offset = 0; - rewind(fp); - state = TS_STEP_FORWARD; - } - } - - // When jumping around in the file, first read a line to find the - // start of the next line. - if (state == TS_BINARY || state == TS_SKIP_BACK) { - // Adjust the search file offset to the correct position - search_info.curr_offset_used = search_info.curr_offset; - vim_fseek(fp, search_info.curr_offset, SEEK_SET); - eof = vim_fgets(lbuf, lbuf_size, fp); - if (!eof && search_info.curr_offset != 0) { - // The explicit cast is to work around a bug in gcc 3.4.2 - // (repeated below). - search_info.curr_offset = vim_ftell(fp); - if (search_info.curr_offset == search_info.high_offset) { - // oops, gone a bit too far; try from low offset - vim_fseek(fp, search_info.low_offset, SEEK_SET); - search_info.curr_offset = search_info.low_offset; - } - eof = vim_fgets(lbuf, lbuf_size, fp); - } - // skip empty and blank lines - while (!eof && vim_isblankline((char *)lbuf)) { - search_info.curr_offset = vim_ftell(fp); - eof = vim_fgets(lbuf, lbuf_size, fp); - } - if (eof) { - // Hit end of file. Skip backwards. - state = TS_SKIP_BACK; - search_info.match_offset = vim_ftell(fp); - search_info.curr_offset = search_info.curr_offset_used; - continue; - } - } else { - // Not jumping around in the file: Read the next line. - - // skip empty and blank lines - do { - eof = use_cscope - ? cs_fgets(lbuf, lbuf_size) - : vim_fgets(lbuf, lbuf_size, fp); - } while (!eof && vim_isblankline((char *)lbuf)); - - if (eof) { - break; // end of file - } - } -line_read_in: - - if (vimconv.vc_type != CONV_NONE) { - char_u *conv_line; - int len; - - // Convert every line. Converting the pattern from 'enc' to - // the tags file encoding doesn't work, because characters are - // not recognized. - conv_line = (char_u *)string_convert(&vimconv, (char *)lbuf, NULL); - if (conv_line != NULL) { - // Copy or swap lbuf and conv_line. - len = (int)STRLEN(conv_line) + 1; - if (len > lbuf_size) { - xfree(lbuf); - lbuf = conv_line; - lbuf_size = len; - } else { - STRCPY(lbuf, conv_line); - xfree(conv_line); - } - } - } - - // When still at the start of the file, check for Emacs tags file - // format, and for "not sorted" flag. - if (state == TS_START) { - // The header ends when the line sorts below "!_TAG_". When - // case is folded lower case letters sort before "_". - if (STRNCMP(lbuf, "!_TAG_", 6) <= 0 - || (lbuf[0] == '!' && ASCII_ISLOWER(lbuf[1]))) { - if (STRNCMP(lbuf, "!_TAG_", 6) != 0) { - // Non-header item before the header, e.g. "!" itself. - goto parse_line; - } - - // Read header line. - if (STRNCMP(lbuf, "!_TAG_FILE_SORTED\t", 18) == 0) { - tag_file_sorted = lbuf[18]; - } - if (STRNCMP(lbuf, "!_TAG_FILE_ENCODING\t", 20) == 0) { - // Prepare to convert every line from the specified - // encoding to 'encoding'. - for (p = lbuf + 20; *p > ' ' && *p < 127; p++) {} - *p = NUL; - convert_setup(&vimconv, (char *)lbuf + 20, p_enc); - } - - // Read the next line. Unrecognized flags are ignored. - continue; - } - - // Headers ends. - - // When there is no tag head, or ignoring case, need to do a - // linear search. - // When no "!_TAG_" is found, default to binary search. If - // the tag file isn't sorted, the second loop will find it. - // When "!_TAG_FILE_SORTED" found: start binary search if - // flag set. - // For cscope, it's always linear. - if (linear || use_cscope) { - state = TS_LINEAR; - } else if (tag_file_sorted == NUL) { - state = TS_BINARY; - } else if (tag_file_sorted == '1') { - state = TS_BINARY; - } else if (tag_file_sorted == '2') { - state = TS_BINARY; - sortic = true; - orgpat.regmatch.rm_ic = (p_ic || !noic); - } else { - state = TS_LINEAR; - } - - if (state == TS_BINARY && orgpat.regmatch.rm_ic && !sortic) { - // Binary search won't work for ignoring case, use linear - // search. - linear = true; - state = TS_LINEAR; - } - - // When starting a binary search, get the size of the file and - // compute the first offset. - if (state == TS_BINARY) { - if (vim_fseek(fp, 0, SEEK_END) != 0) { - // can't seek, don't use binary search - state = TS_LINEAR; - } else { - // Get the tag file size. - // Don't use lseek(), it doesn't work - // properly on MacOS Catalina. - const off_T filesize = vim_ftell(fp); - vim_fseek(fp, 0, SEEK_SET); - - // Calculate the first read offset in the file. Start - // the search in the middle of the file. - search_info.low_offset = 0; - search_info.low_char = 0; - search_info.high_offset = filesize; - search_info.curr_offset = 0; - search_info.high_char = 0xff; - } - continue; - } - } - -parse_line: - // When the line is too long the NUL will not be in the - // last-but-one byte (see vim_fgets()). - // Has been reported for Mozilla JS with extremely long names. - // In that case we need to increase lbuf_size. - if (lbuf[lbuf_size - 2] != NUL && !use_cscope) { - lbuf_size *= 2; - xfree(lbuf); - lbuf = xmalloc((size_t)lbuf_size); - // this will try the same thing again, make sure the offset is - // different - search_info.curr_offset = 0; - continue; - } - - // Figure out where the different strings are in this line. - // For "normal" tags: Do a quick check if the tag matches. - // This speeds up tag searching a lot! - if (orgpat.headlen) { - CLEAR_FIELD(tagp); - tagp.tagname = (char *)lbuf; - tagp.tagname_end = (char_u *)vim_strchr((char *)lbuf, TAB); - if (tagp.tagname_end == NULL) { - // Corrupted tag line. - line_error = true; - break; - } - - // Skip this line if the length of the tag is different and - // there is no regexp, or the tag is too short. - cmplen = (int)(tagp.tagname_end - (char_u *)tagp.tagname); - if (p_tl != 0 && cmplen > p_tl) { // adjust for 'taglength' - cmplen = (int)p_tl; - } - if (has_re && orgpat.headlen < cmplen) { - cmplen = orgpat.headlen; - } else if (state == TS_LINEAR && orgpat.headlen != cmplen) { - continue; - } - - if (state == TS_BINARY) { - // Simplistic check for unsorted tags file. - i = (int)tagp.tagname[0]; - if (sortic) { - i = TOUPPER_ASC(tagp.tagname[0]); - } - if (i < search_info.low_char || i > search_info.high_char) { - sort_error = true; - } - - // Compare the current tag with the searched tag. - if (sortic) { - tagcmp = tag_strnicmp((char_u *)tagp.tagname, orgpat.head, - (size_t)cmplen); - } else { - tagcmp = STRNCMP(tagp.tagname, orgpat.head, cmplen); - } - - // A match with a shorter tag means to search forward. - // A match with a longer tag means to search backward. - if (tagcmp == 0) { - if (cmplen < orgpat.headlen) { - tagcmp = -1; - } else if (cmplen > orgpat.headlen) { - tagcmp = 1; - } - } - - if (tagcmp == 0) { - // We've located the tag, now skip back and search - // forward until the first matching tag is found. - state = TS_SKIP_BACK; - search_info.match_offset = search_info.curr_offset; - continue; - } - if (tagcmp < 0) { - search_info.curr_offset = vim_ftell(fp); - if (search_info.curr_offset < search_info.high_offset) { - search_info.low_offset = search_info.curr_offset; - if (sortic) { - search_info.low_char = - TOUPPER_ASC(tagp.tagname[0]); - } else { - search_info.low_char = (uint8_t)tagp.tagname[0]; - } - continue; - } - } - if (tagcmp > 0 - && search_info.curr_offset != search_info.high_offset) { - search_info.high_offset = search_info.curr_offset; - if (sortic) { - search_info.high_char = - TOUPPER_ASC(tagp.tagname[0]); - } else { - search_info.high_char = (uint8_t)tagp.tagname[0]; - } - continue; - } - - // No match yet and are at the end of the binary search. - break; - } else if (state == TS_SKIP_BACK) { - assert(cmplen >= 0); - if (mb_strnicmp(tagp.tagname, (char *)orgpat.head, (size_t)cmplen) != 0) { - state = TS_STEP_FORWARD; - } else { - // Have to skip back more. Restore the curr_offset - // used, otherwise we get stuck at a long line. - search_info.curr_offset = search_info.curr_offset_used; - } - continue; - } else if (state == TS_STEP_FORWARD) { - assert(cmplen >= 0); - if (mb_strnicmp(tagp.tagname, (char *)orgpat.head, (size_t)cmplen) != 0) { - if ((off_T)vim_ftell(fp) > search_info.match_offset) { - break; // past last match - } else { - continue; // before first match - } - } - } else { - // skip this match if it can't match - assert(cmplen >= 0); - } - if (mb_strnicmp(tagp.tagname, (char *)orgpat.head, (size_t)cmplen) != 0) { - continue; - } - - // Can be a matching tag, isolate the file name and command. - tagp.fname = tagp.tagname_end + 1; - tagp.fname_end = (char_u *)vim_strchr((char *)tagp.fname, TAB); - tagp.command = tagp.fname_end + 1; - if (tagp.fname_end == NULL) { - i = FAIL; - } else { - i = OK; - } - } else { - i = parse_tag_line(lbuf, - &tagp); - } - if (i == FAIL) { - line_error = true; - break; - } - - // First try matching with the pattern literally (also when it is - // a regexp). - cmplen = (int)(tagp.tagname_end - (char_u *)tagp.tagname); - if (p_tl != 0 && cmplen > p_tl) { // adjust for 'taglength' - cmplen = (int)p_tl; - } - // if tag length does not match, don't try comparing - if (orgpat.len != cmplen) { - match = false; - } else { - if (orgpat.regmatch.rm_ic) { - assert(cmplen >= 0); - match = mb_strnicmp(tagp.tagname, (char *)orgpat.pat, (size_t)cmplen) == 0; - if (match) { - match_no_ic = (STRNCMP(tagp.tagname, orgpat.pat, - cmplen) == 0); - } - } else { - match = (STRNCMP(tagp.tagname, orgpat.pat, cmplen) == 0); - } - } - - // Has a regexp: Also find tags matching regexp. - match_re = false; - if (!match && orgpat.regmatch.regprog != NULL) { - int cc; - - cc = *tagp.tagname_end; - *tagp.tagname_end = NUL; - match = vim_regexec(&orgpat.regmatch, tagp.tagname, (colnr_T)0); - if (match) { - matchoff = (int)(orgpat.regmatch.startp[0] - tagp.tagname); - if (orgpat.regmatch.rm_ic) { - orgpat.regmatch.rm_ic = false; - match_no_ic = vim_regexec(&orgpat.regmatch, tagp.tagname, (colnr_T)0); - orgpat.regmatch.rm_ic = true; - } - } - *tagp.tagname_end = (char_u)cc; - match_re = true; - } - - // If a match is found, add it to ht_match[] and ga_match[]. - if (match) { - size_t len = 0; - - if (use_cscope) { - // Don't change the ordering, always use the same table. - mtt = MT_GL_OTH; - } else { - // Decide in which array to store this match. - is_current = test_for_current((char *)tagp.fname, (char *)tagp.fname_end, - (char *)tag_fname, - buf_ffname); - is_static = test_for_static(&tagp); - - // Decide in which of the sixteen tables to store this match. - if (is_static) { - if (is_current) { - mtt = MT_ST_CUR; - } else { - mtt = MT_ST_OTH; - } - } else { - if (is_current) { - mtt = MT_GL_CUR; - } else { - mtt = MT_GL_OTH; - } - } - if (orgpat.regmatch.rm_ic && !match_no_ic) { - mtt += MT_IC_OFF; - } - if (match_re) { - mtt += MT_RE_OFF; - } - } - - // Add the found match in ht_match[mtt] and ga_match[mtt]. - // Store the info we need later, which depends on the kind of - // tags we are dealing with. - if (help_only) { -#define ML_EXTRA 3 - // Append the help-heuristic number after the tagname, for - // sorting it later. The heuristic is ignored for - // detecting duplicates. - // The format is {tagname}@{lang}NUL{heuristic}NUL - *tagp.tagname_end = NUL; - len = (size_t)(tagp.tagname_end - (char_u *)tagp.tagname); - mfp = xmalloc(sizeof(char) + len + 10 + ML_EXTRA + 1); - - p = (char_u *)mfp; - STRCPY(p, tagp.tagname); - p[len] = '@'; - STRCPY(p + len + 1, help_lang); - snprintf((char *)p + len + 1 + ML_EXTRA, STRLEN(p) + len + 1 + ML_EXTRA, "%06d", - help_heuristic(tagp.tagname, - match_re ? matchoff : 0, !match_no_ic) - + help_pri); - - *tagp.tagname_end = TAB; - } else if (name_only) { - if (get_it_again) { - char_u *temp_end = tagp.command; - - if (*temp_end == '/') { - while (*temp_end && *temp_end != '\r' - && *temp_end != '\n' - && *temp_end != '$') { - temp_end++; - } - } - - if (tagp.command + 2 < temp_end) { - len = (size_t)(temp_end - tagp.command - 2); - mfp = xmalloc(len + 2); - STRLCPY(mfp, tagp.command + 2, len + 1); - } else { - mfp = NULL; - } - get_it_again = false; - } else { - len = (size_t)(tagp.tagname_end - (char_u *)tagp.tagname); - mfp = xmalloc(sizeof(char) + len + 1); - STRLCPY(mfp, tagp.tagname, len + 1); - - // if wanted, re-read line to get long form too - if (State & MODE_INSERT) { - get_it_again = p_sft; - } - } - } else { - size_t tag_fname_len = STRLEN(tag_fname); - // Save the tag in a buffer. - // Use 0x02 to separate fields (Can't use NUL, because the - // hash key is terminated by NUL). - // Emacs tag: <mtt><tag_fname><0x02><ebuf><0x02><lbuf><NUL> - // other tag: <mtt><tag_fname><0x02><0x02><lbuf><NUL> - // without Emacs tags: <mtt><tag_fname><0x02><lbuf><NUL> - // Here <mtt> is the "mtt" value plus 1 to avoid NUL. - len = tag_fname_len + STRLEN(lbuf) + 3; - mfp = xmalloc(sizeof(char) + len + 1); - p = (char_u *)mfp; - p[0] = (char_u)(mtt + 1); - STRCPY(p + 1, tag_fname); -#ifdef BACKSLASH_IN_FILENAME - // Ignore differences in slashes, avoid adding - // both path/file and path\file. - slash_adjust(p + 1); -#endif - p[tag_fname_len + 1] = TAG_SEP; - s = p + 1 + tag_fname_len + 1; - STRCPY(s, lbuf); - } - - if (mfp != NULL) { - hashitem_T *hi; - - // Don't add identical matches. - // Add all cscope tags, because they are all listed. - // "mfp" is used as a hash key, there is a NUL byte to end - // the part that matters for comparing, more bytes may - // follow after it. E.g. help tags store the priority - // after the NUL. - if (use_cscope) { - hash++; - } else { - hash = hash_hash((char_u *)mfp); - } - hi = hash_lookup(&ht_match[mtt], (const char *)mfp, - strlen(mfp), hash); - if (HASHITEM_EMPTY(hi)) { - hash_add_item(&ht_match[mtt], hi, (char_u *)mfp, hash); - ga_grow(&ga_match[mtt], 1); - ((char **)(ga_match[mtt].ga_data))[ga_match[mtt].ga_len++] = mfp; - match_count++; - } else { - // duplicate tag, drop it - xfree(mfp); - } - } - } - if (use_cscope && eof) { - break; - } - } // forever - - if (line_error) { - semsg(_("E431: Format error in tags file \"%s\""), tag_fname); - if (!use_cscope) { - semsg(_("Before byte %" PRId64), (int64_t)vim_ftell(fp)); - } - stop_searching = true; - line_error = false; - } - - if (!use_cscope) { - fclose(fp); - } - if (vimconv.vc_type != CONV_NONE) { - convert_setup(&vimconv, NULL, NULL); - } - - tag_file_sorted = NUL; - if (sort_error) { - semsg(_("E432: Tags file not sorted: %s"), tag_fname); - sort_error = false; - } - - // Stop searching if sufficient tags have been found. - if (match_count >= mincount) { + findtags_in_file(&st, flags, buf_ffname); + if (st.stop_searching) { retval = OK; - stop_searching = true; - } - - if (stop_searching || use_cscope) { break; } } // end of for-each-file loop - if (!use_cscope) { - tagname_free(&tn); - } + tagname_free(&tn); // stop searching when already did a linear search, or when TAG_NOIC // used, and 'ignorecase' not set or already did case-ignore search - if (stop_searching || linear || (!p_ic && noic) || orgpat.regmatch.rm_ic) { - break; - } - if (use_cscope) { + if (st.stop_searching || st.linear || (!p_ic && noic) + || st.orgpat->regmatch.rm_ic) { break; } - orgpat.regmatch.rm_ic = true; // try another time while ignoring case + + // try another time while ignoring case + st.orgpat->regmatch.rm_ic = true; } - if (!stop_searching) { - if (!did_open && verbose) { // never opened any tags file + if (!st.stop_searching) { + if (!st.did_open && verbose) { // never opened any tags file emsg(_("E433: No tags file")); } retval = OK; // It's OK even when no tag found } findtag_end: - xfree(lbuf); - vim_regfree(orgpat.regmatch.regprog); - xfree(tag_fname); + findtags_state_free(&st); // Move the matches from the ga_match[] arrays into one list of // matches. When retval == FAIL, free the matches. if (retval == FAIL) { - match_count = 0; - } - - if (match_count > 0) { - matches = xmalloc((size_t)match_count * sizeof(char *)); - } else { - matches = NULL; - } - match_count = 0; - for (mtt = 0; mtt < MT_COUNT; mtt++) { - for (i = 0; i < ga_match[mtt].ga_len; i++) { - mfp = ((char **)(ga_match[mtt].ga_data))[i]; - if (matches == NULL) { - xfree(mfp); - } else { - if (!name_only) { - // Change mtt back to zero-based. - *mfp = (char)(*mfp - 1); - - // change the TAG_SEP back to NUL - for (p = (char_u *)mfp + 1; *p != NUL; p++) { - if (*p == TAG_SEP) { - *p = NUL; - } - } - } - matches[match_count++] = mfp; - } - } - - ga_clear(&ga_match[mtt]); - hash_clear(&ht_match[mtt]); + st.match_count = 0; } - *matchesp = matches; - *num_matches = match_count; + *num_matches = findtags_copy_matches(&st, matchesp); curbuf->b_help = help_save; xfree(saved_pat); @@ -2246,7 +2489,7 @@ static void found_tagfile_cb(char *fname, void *cookie) #ifdef BACKSLASH_IN_FILENAME slash_adjust(tag_fname); #endif - simplify_filename((char_u *)tag_fname); + simplify_filename(tag_fname); GA_APPEND(char *, &tag_fnames, tag_fname); } @@ -2302,7 +2545,7 @@ int get_tagfname(tagname_T *tnp, int first, char *buf) #ifdef BACKSLASH_IN_FILENAME slash_adjust(buf); #endif - simplify_filename((char_u *)buf); + simplify_filename(buf); for (int i = 0; i < tag_fnames.ga_len; i++) { if (strcmp(buf, ((char **)(tag_fnames.ga_data))[i]) == 0) { @@ -2310,7 +2553,7 @@ int get_tagfname(tagname_T *tnp, int first, char *buf) } } } else { - STRLCPY(buf, ((char **)(tag_fnames.ga_data))[tnp->tn_hf_idx++], MAXPATHL); + xstrlcpy(buf, ((char **)(tag_fnames.ga_data))[tnp->tn_hf_idx++], MAXPATHL); } return OK; } @@ -2318,7 +2561,7 @@ int get_tagfname(tagname_T *tnp, int first, char *buf) if (first) { // Init. We make a copy of 'tags', because autocommands may change // the value without notifying us. - tnp->tn_tags = xstrdup((*curbuf->b_p_tags != NUL) ? curbuf->b_p_tags : (char *)p_tags); + tnp->tn_tags = xstrdup((*curbuf->b_p_tags != NUL) ? curbuf->b_p_tags : p_tags); tnp->tn_np = tnp->tn_tags; } @@ -2328,14 +2571,14 @@ int get_tagfname(tagname_T *tnp, int first, char *buf) // tnp->tn_did_filefind_init == true: find next file in this part. for (;;) { if (tnp->tn_did_filefind_init) { - fname = (char *)vim_findfile(tnp->tn_search_ctx); + fname = vim_findfile(tnp->tn_search_ctx); if (fname != NULL) { break; } tnp->tn_did_filefind_init = false; } else { - char_u *filename = NULL; + char *filename = NULL; // Stop when used all parts of 'tags'. if (*tnp->tn_np == NUL) { @@ -2348,14 +2591,14 @@ int get_tagfname(tagname_T *tnp, int first, char *buf) buf[0] = NUL; (void)copy_option_part(&tnp->tn_np, buf, MAXPATHL - 1, " ,"); - r_ptr = (char *)vim_findfile_stopdir((char_u *)buf); + r_ptr = vim_findfile_stopdir(buf); // move the filename one char forward and truncate the // filepath with a NUL - filename = (char_u *)path_tail(buf); + filename = path_tail(buf); STRMOVE(filename + 1, filename); *filename++ = NUL; - tnp->tn_search_ctx = vim_findfile_init(buf, (char *)filename, + tnp->tn_search_ctx = vim_findfile_init(buf, filename, r_ptr, 100, false, // don't free visited list FINDFILE_FILE, // we search for a file @@ -2388,13 +2631,13 @@ void tagname_free(tagname_T *tnp) /// @param lbuf line to be parsed /// /// @return FAIL if there is a format error in this line, OK otherwise. -static int parse_tag_line(char_u *lbuf, tagptrs_T *tagp) +static int parse_tag_line(char *lbuf, tagptrs_T *tagp) { - char_u *p; + char *p; // Isolate the tagname, from lbuf up to the first white - tagp->tagname = (char *)lbuf; - p = (char_u *)vim_strchr((char *)lbuf, TAB); + tagp->tagname = lbuf; + p = vim_strchr(lbuf, TAB); if (p == NULL) { return FAIL; } @@ -2405,7 +2648,7 @@ static int parse_tag_line(char_u *lbuf, tagptrs_T *tagp) p++; } tagp->fname = p; - p = (char_u *)vim_strchr((char *)p, TAB); + p = vim_strchr(p, TAB); if (p == NULL) { return FAIL; } @@ -2437,13 +2680,13 @@ static int parse_tag_line(char_u *lbuf, tagptrs_T *tagp) // Return false if it is not a static tag. static bool test_for_static(tagptrs_T *tagp) { - char_u *p; + char *p; // Check for new style static tag ":...<Tab>file:[<Tab>...]" p = tagp->command; - while ((p = (char_u *)vim_strchr((char *)p, '\t')) != NULL) { + while ((p = vim_strchr(p, '\t')) != NULL) { p++; - if (STRNCMP(p, "file:", 5) == 0) { + if (strncmp(p, "file:", 5) == 0) { return true; } } @@ -2451,14 +2694,14 @@ static bool test_for_static(tagptrs_T *tagp) return false; } -// Returns the length of a matching tag line. -static size_t matching_line_len(const char_u *const lbuf) +/// @return the length of a matching tag line. +static size_t matching_line_len(const char *const lbuf) { - const char_u *p = lbuf + 1; + const char *p = lbuf + 1; // does the same thing as parse_match() - p += STRLEN(p) + 1; - return (size_t)(p - lbuf) + STRLEN(p); + p += strlen(p) + 1; + return (size_t)(p - lbuf) + strlen(p); } /// Parse a line from a matching tag. Does not change the line itself. @@ -2478,11 +2721,11 @@ static int parse_match(char *lbuf, tagptrs_T *tagp) char *p; char *pc, *pt; - tagp->tag_fname = (char_u *)lbuf + 1; - lbuf += STRLEN(tagp->tag_fname) + 2; + tagp->tag_fname = lbuf + 1; + lbuf += strlen(tagp->tag_fname) + 2; // Find search pattern and the file name for non-etags. - retval = parse_tag_line((char_u *)lbuf, tagp); + retval = parse_tag_line(lbuf, tagp); tagp->tagkind = NULL; tagp->user_data = NULL; @@ -2491,22 +2734,22 @@ static int parse_match(char *lbuf, tagptrs_T *tagp) if (retval == OK) { // Try to find a kind field: "kind:<kind>" or just "<kind>" - p = (char *)tagp->command; - if (find_extra((char_u **)&p) == OK) { - tagp->command_end = (char_u *)p; - if (p > (char *)tagp->command && p[-1] == '|') { - tagp->command_end = (char_u *)p - 1; // drop trailing bar + p = tagp->command; + if (find_extra(&p) == OK) { + tagp->command_end = p; + if (p > tagp->command && p[-1] == '|') { + tagp->command_end = p - 1; // drop trailing bar } p += 2; // skip ";\"" if (*p++ == TAB) { // Accept ASCII alphabetic kind characters and any multi-byte // character. while (ASCII_ISALPHA(*p) || utfc_ptr2len(p) > 1) { - if (STRNCMP(p, "kind:", 5) == 0) { - tagp->tagkind = (char_u *)p + 5; - } else if (STRNCMP(p, "user_data:", 10) == 0) { + if (strncmp(p, "kind:", 5) == 0) { + tagp->tagkind = p + 5; + } else if (strncmp(p, "user_data:", 10) == 0) { tagp->user_data = p + 10; - } else if (STRNCMP(p, "line:", 5) == 0) { + } else if (strncmp(p, "line:", 5) == 0) { tagp->tagline = atoi(p + 5); } if (tagp->tagkind != NULL && tagp->user_data != NULL) { @@ -2516,7 +2759,7 @@ static int parse_match(char *lbuf, tagptrs_T *tagp) pc = vim_strchr(p, ':'); pt = vim_strchr(p, '\t'); if (pc == NULL || (pt != NULL && pc > pt)) { - tagp->tagkind = (char_u *)p; + tagp->tagkind = p; } if (pt == NULL) { break; @@ -2527,16 +2770,16 @@ static int parse_match(char *lbuf, tagptrs_T *tagp) } } if (tagp->tagkind != NULL) { - for (p = (char *)tagp->tagkind; + for (p = tagp->tagkind; *p && *p != '\t' && *p != '\r' && *p != '\n'; MB_PTR_ADV(p)) {} - tagp->tagkind_end = (char_u *)p; + tagp->tagkind_end = p; } if (tagp->user_data != NULL) { for (p = tagp->user_data; *p && *p != '\t' && *p != '\r' && *p != '\n'; MB_PTR_ADV(p)) {} - tagp->user_data_end = (char_u *)p; + tagp->user_data_end = p; } } return retval; @@ -2545,13 +2788,12 @@ static int parse_match(char *lbuf, tagptrs_T *tagp) // Find out the actual file name of a tag. Concatenate the tags file name // with the matching tag file name. // Returns an allocated string. -static char_u *tag_full_fname(tagptrs_T *tagp) +static char *tag_full_fname(tagptrs_T *tagp) { - int c = *tagp->fname_end; + char c = *tagp->fname_end; *tagp->fname_end = NUL; - char_u *fullname = - (char_u *)expand_tag_fname((char *)tagp->fname, (char *)tagp->tag_fname, false); - *tagp->fname_end = (char_u)c; + char *fullname = expand_tag_fname(tagp->fname, tagp->tag_fname, false); + *tagp->fname_end = c; return fullname; } @@ -2560,43 +2802,42 @@ static char_u *tag_full_fname(tagptrs_T *tagp) /// /// @param lbuf_arg line from the tags file for this tag /// @param forceit :ta with ! -/// @param keep_help keep help flag (false for cscope) +/// @param keep_help keep help flag /// /// @return OK for success, NOTAGFILE when file not found, FAIL otherwise. -static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) +static int jumpto_tag(const char *lbuf_arg, int forceit, int keep_help) { - int save_magic; bool save_p_ws; int save_p_scs, save_p_ic; linenr_T save_lnum; - char_u *str; - char_u *pbuf; // search pattern buffer - char_u *pbuf_end; - char_u *tofree_fname = NULL; + char *str; + char *pbuf; // search pattern buffer + char *pbuf_end; + char *tofree_fname = NULL; char *fname; tagptrs_T tagp; int retval = FAIL; int getfile_result = GETFILE_UNUSED; int search_options; win_T *curwin_save = NULL; - char_u *full_fname = NULL; + char *full_fname = NULL; const bool old_KeyTyped = KeyTyped; // getting the file may reset it const int l_g_do_tagpreview = g_do_tagpreview; const size_t len = matching_line_len(lbuf_arg) + 1; - char_u *lbuf = xmalloc(len); + char *lbuf = xmalloc(len); memmove(lbuf, lbuf_arg, len); pbuf = xmalloc(LSIZE); // parse the match line into the tagp structure - if (parse_match((char *)lbuf, &tagp) == FAIL) { + if (parse_match(lbuf, &tagp) == FAIL) { tagp.fname_end = NULL; goto erret; } // truncate the file name, so it can be used as a string *tagp.fname_end = NUL; - fname = (char *)tagp.fname; + fname = tagp.fname; // copy the command to pbuf[], remove trailing CR/NL str = tagp.command; @@ -2619,8 +2860,8 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) // Expand file name, when needed (for environment variables). // If 'tagrelative' option set, may change file name. - fname = expand_tag_fname(fname, (char *)tagp.tag_fname, true); - tofree_fname = (char_u *)fname; // free() it later + fname = expand_tag_fname(fname, tagp.tag_fname, true); + tofree_fname = fname; // free() it later // Check if the file with the tag exists before abandoning the current // file. Also accept a file name for which there is a matching BufReadCmd @@ -2643,8 +2884,8 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) // entering it (autocommands) so turn the tag filename // into a fullpath if (!curwin->w_p_pvw) { - full_fname = (char_u *)FullName_save(fname, false); - fname = (char *)full_fname; + full_fname = FullName_save(fname, false); + fname = full_fname; // Make the preview window the current window. // Open a preview window when needed. @@ -2708,8 +2949,8 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) curwin->w_set_curswant = true; postponed_split = 0; - save_magic = p_magic; - p_magic = false; // always execute with 'nomagic' + const optmagic_T save_magic_overruled = magic_overruled; + magic_overruled = OPTION_MAGIC_OFF; // always execute with 'nomagic' // Save value of no_hlsearch, jumping to a tag is not a real search const bool save_no_hlsearch = no_hlsearch; @@ -2730,7 +2971,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) // anything following. str = pbuf; if (pbuf[0] == '/' || pbuf[0] == '?') { - str = (char_u *)skip_regexp((char *)pbuf + 1, pbuf[0], false, NULL) + 1; + str = skip_regexp(pbuf + 1, pbuf[0], false) + 1; } if (str > pbuf_end - 1) { // search command with nothing following save_p_ws = p_ws; @@ -2752,7 +2993,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) retval = OK; } else { int found = 1; - int cc; + char cc; // try again, ignore case now p_ic = true; @@ -2763,17 +3004,16 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) (void)test_for_static(&tagp); cc = *tagp.tagname_end; *tagp.tagname_end = NUL; - snprintf((char *)pbuf, LSIZE, "^%s\\s\\*(", tagp.tagname); + snprintf(pbuf, LSIZE, "^%s\\s\\*(", tagp.tagname); if (!do_search(NULL, '/', '/', pbuf, (long)1, search_options, NULL)) { // Guess again: "^char * \<func (" - snprintf((char *)pbuf, LSIZE, "^\\[#a-zA-Z_]\\.\\*\\<%s\\s\\*(", + snprintf(pbuf, LSIZE, "^\\[#a-zA-Z_]\\.\\*\\<%s\\s\\*(", tagp.tagname); - if (!do_search(NULL, '/', '/', pbuf, (long)1, - search_options, NULL)) { + if (!do_search(NULL, '/', '/', pbuf, (long)1, search_options, NULL)) { found = 0; } } - *tagp.tagname_end = (char_u)cc; + *tagp.tagname_end = cc; } if (found == 0) { emsg(_("E434: Can't find tag pattern")); @@ -2805,7 +3045,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) secure = 1; sandbox++; curwin->w_cursor.lnum = 1; // start command in line 1 - do_cmdline_cmd((char *)pbuf); + do_cmdline_cmd(pbuf); retval = OK; // When the command has done something that is not allowed make sure @@ -2817,7 +3057,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) sandbox--; } - p_magic = save_magic; + magic_overruled = save_magic_overruled; // restore no_hlsearch when keeping the old search pattern if (search_options) { set_no_hlsearch(save_no_hlsearch); @@ -2890,14 +3130,13 @@ static char *expand_tag_fname(char *fname, char *const tag_fname, const bool exp char *retval; if ((p_tr || curbuf->b_help) - && !vim_isAbsName((char_u *)fname) + && !vim_isAbsName(fname) && (p = path_tail(tag_fname)) != tag_fname) { retval = xmalloc(MAXPATHL); STRCPY(retval, tag_fname); - STRLCPY(retval + (p - tag_fname), fname, - MAXPATHL - (p - tag_fname)); + xstrlcpy(retval + (p - tag_fname), fname, (size_t)(MAXPATHL - (p - tag_fname))); // Translate names like "src/a/../b/file.c" into "src/b/file.c". - simplify_filename((char_u *)retval); + simplify_filename(retval); } else { retval = xstrdup(fname); } @@ -2914,18 +3153,18 @@ static char *expand_tag_fname(char *fname, char *const tag_fname, const bool exp /// file. static int test_for_current(char *fname, char *fname_end, char *tag_fname, char *buf_ffname) { - int c; + char c; int retval = false; if (buf_ffname != NULL) { // if the buffer has a name { - c = (unsigned char)(*fname_end); + c = *fname_end; *fname_end = NUL; } char *fullname = expand_tag_fname(fname, tag_fname, true); retval = (path_full_compare(fullname, buf_ffname, true, true) & kEqualFiles); xfree(fullname); - *fname_end = (char)c; + *fname_end = c; } return retval; @@ -2933,17 +3172,17 @@ static int test_for_current(char *fname, char *fname_end, char *tag_fname, char // Find the end of the tagaddress. // Return OK if ";\"" is following, FAIL otherwise. -static int find_extra(char_u **pp) +static int find_extra(char **pp) { - char_u *str = *pp; - char_u first_char = **pp; + char *str = *pp; + char first_char = **pp; // Repeat for addresses separated with ';' for (;;) { if (ascii_isdigit(*str)) { - str = (char_u *)skipdigits((char *)str + 1); + str = skipdigits(str + 1); } else if (*str == '/' || *str == '?') { - str = (char_u *)skip_regexp((char *)str + 1, *str, false, NULL); + str = skip_regexp(str + 1, *str, false); if (*str != first_char) { str = NULL; } else { @@ -2951,7 +3190,7 @@ static int find_extra(char_u **pp) } } else { // not a line number or search string, look for terminator. - str = (char_u *)strstr((char *)str, "|;\""); + str = strstr(str, "|;\""); if (str != NULL) { str++; break; @@ -2965,7 +3204,7 @@ static int find_extra(char_u **pp) first_char = *str; } - if (str != NULL && STRNCMP(str, ";\"", 2) == 0) { + if (str != NULL && strncmp(str, ";\"", 2) == 0) { *pp = str; return OK; } @@ -2982,11 +3221,11 @@ static void tagstack_clear_entry(taggy_T *item) } /// @param tagnames expand tag names -int expand_tags(int tagnames, char_u *pat, int *num_file, char ***file) +int expand_tags(int tagnames, char *pat, int *num_file, char ***file) { int i; int extra_flag; - char_u *name_buf; + char *name_buf; size_t name_buf_size = 100; tagptrs_T t_p; int ret; @@ -2999,11 +3238,11 @@ int expand_tags(int tagnames, char_u *pat, int *num_file, char ***file) extra_flag = 0; } if (pat[0] == '/') { - ret = find_tags((char *)pat + 1, num_file, file, + ret = find_tags(pat + 1, num_file, file, TAG_REGEXP | extra_flag | TAG_VERBOSE | TAG_NO_TAGFUNC, TAG_MANY, curbuf->b_ffname); } else { - ret = find_tags((char *)pat, num_file, file, + ret = find_tags(pat, num_file, file, TAG_REGEXP | extra_flag | TAG_VERBOSE | TAG_NO_TAGFUNC | TAG_NOIC, TAG_MANY, curbuf->b_ffname); } @@ -3014,9 +3253,9 @@ int expand_tags(int tagnames, char_u *pat, int *num_file, char ***file) size_t len; parse_match((*file)[i], &t_p); - len = (size_t)(t_p.tagname_end - (char_u *)t_p.tagname); + len = (size_t)(t_p.tagname_end - t_p.tagname); if (len > name_buf_size - 3) { - char_u *buf; + char *buf; name_buf_size = len + 3; buf = xrealloc(name_buf, name_buf_size); @@ -3057,7 +3296,7 @@ static int add_tag_field(dict_T *dict, const char *field_name, const char *start } return FAIL; } - char_u *buf = xmalloc(MAXPATHL); + char *buf = xmalloc(MAXPATHL); if (start != NULL) { if (end == NULL) { end = start + strlen(start); @@ -3069,28 +3308,27 @@ static int add_tag_field(dict_T *dict, const char *field_name, const char *start if (len > MAXPATHL - 1) { len = MAXPATHL - 1; } - STRLCPY(buf, start, len + 1); + xstrlcpy(buf, start, (size_t)len + 1); } buf[len] = NUL; - retval = tv_dict_add_str(dict, field_name, strlen(field_name), - (const char *)buf); + retval = tv_dict_add_str(dict, field_name, strlen(field_name), buf); xfree(buf); return retval; } /// Add the tags matching the specified pattern "pat" to the list "list" /// as a dictionary. Use "buf_fname" for priority, unless NULL. -int get_tags(list_T *list, char_u *pat, char_u *buf_fname) +int get_tags(list_T *list, char *pat, char *buf_fname) { int num_matches, i, ret; char **matches; - char_u *full_fname; + char *full_fname; dict_T *dict; tagptrs_T tp; bool is_static; - ret = find_tags((char *)pat, &num_matches, &matches, - TAG_REGEXP | TAG_NOIC, MAXCOL, (char *)buf_fname); + ret = find_tags(pat, &num_matches, &matches, + TAG_REGEXP | TAG_NOIC, MAXCOL, buf_fname); if (ret == OK && num_matches > 0) { for (i = 0; i < num_matches; i++) { int parse_result = parse_match(matches[i], &tp); @@ -3102,7 +3340,7 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) is_static = test_for_static(&tp); // Skip pseudo-tag lines. - if (STRNCMP(tp.tagname, "!_TAG_", 6) == 0) { + if (strncmp(tp.tagname, "!_TAG_", 6) == 0) { xfree(matches[i]); continue; } @@ -3111,11 +3349,11 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) tv_list_append_dict(list, dict); full_fname = tag_full_fname(&tp); - if (add_tag_field(dict, "name", tp.tagname, (char *)tp.tagname_end) == FAIL - || add_tag_field(dict, "filename", (char *)full_fname, NULL) == FAIL - || add_tag_field(dict, "cmd", (char *)tp.command, (char *)tp.command_end) == FAIL - || add_tag_field(dict, "kind", (char *)tp.tagkind, - tp.tagkind ? (char *)tp.tagkind_end : NULL) == FAIL + if (add_tag_field(dict, "name", tp.tagname, tp.tagname_end) == FAIL + || add_tag_field(dict, "filename", full_fname, NULL) == FAIL + || add_tag_field(dict, "cmd", tp.command, tp.command_end) == FAIL + || add_tag_field(dict, "kind", tp.tagkind, + tp.tagkind ? tp.tagkind_end : NULL) == FAIL || tv_dict_add_nr(dict, S_LEN("static"), is_static) == FAIL) { ret = FAIL; } @@ -3123,18 +3361,18 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) xfree(full_fname); if (tp.command_end != NULL) { - for (char_u *p = tp.command_end + 3; + for (char *p = tp.command_end + 3; *p != NUL && *p != '\n' && *p != '\r'; MB_PTR_ADV(p)) { if (p == tp.tagkind - || (p + 5 == tp.tagkind && STRNCMP(p, "kind:", 5) == 0)) { + || (p + 5 == tp.tagkind && strncmp(p, "kind:", 5) == 0)) { // skip "kind:<kind>" and "<kind>" p = tp.tagkind_end - 1; - } else if (STRNCMP(p, "file:", 5) == 0) { + } else if (strncmp(p, "file:", 5) == 0) { // skip "file:" (static tag) p += 4; } else if (!ascii_iswhite(*p)) { - char_u *s, *n; + char *s, *n; int len; // Add extra field as a dict entry. Fields are @@ -3146,17 +3384,17 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) len = (int)(p - n); if (*p == ':' && len > 0) { s = ++p; - while (*p != NUL && *p >= ' ') { + while (*p != NUL && (uint8_t)(*p) >= ' ') { p++; } n[len] = NUL; - if (add_tag_field(dict, (char *)n, (char *)s, (char *)p) == FAIL) { + if (add_tag_field(dict, n, s, p) == FAIL) { ret = FAIL; } n[len] = ':'; } else { // Skip field without colon. - while (*p != NUL && *p >= ' ') { + while (*p != NUL && (uint8_t)(*p) >= ' ') { p++; } } @@ -3349,10 +3587,12 @@ int set_tagstack(win_T *wp, const dict_T *d, int action) if ((di = tv_dict_find(d, "curidx", -1)) != NULL) { tagstack_set_curidx(wp, (int)tv_get_number(&di->di_tv) - 1); } + if (action == 't') { // truncate the stack taggy_T *const tagstack = wp->w_tagstack; const int tagstackidx = wp->w_tagstackidx; int tagstacklen = wp->w_tagstacklen; + // delete all the tag stack entries above the current entry while (tagstackidx < tagstacklen) { tagstack_clear_entry(&tagstack[--tagstacklen]); diff --git a/src/nvim/tag.h b/src/nvim/tag.h index 7f2ef8d6d7..e08145f727 100644 --- a/src/nvim/tag.h +++ b/src/nvim/tag.h @@ -14,7 +14,6 @@ #define DT_SELECT 7 // jump to selection from list #define DT_HELP 8 // like DT_TAG, but no wildcards #define DT_JUMP 9 // jump to new tag or selection from list -#define DT_CSCOPE 10 // cscope find command (like tjump) #define DT_LTAG 11 // tag using location list #define DT_FREE 99 // free cached matches @@ -23,7 +22,6 @@ #define TAG_NAMES 2 // only return name of tag #define TAG_REGEXP 4 // use tag pattern as regexp #define TAG_NOIC 8 // don't always ignore case -#define TAG_CSCOPE 16 // cscope tag #define TAG_VERBOSE 32 // message verbosity #define TAG_INS_COMP 64 // Currently doing insert completion #define TAG_KEEP_LANG 128 // keep current language diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 50574b292d..a52a34cd66 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -37,45 +37,57 @@ // Some code from pangoterm http://www.leonerd.org.uk/code/pangoterm #include <assert.h> +#include <limits.h> #include <stdbool.h> #include <stdint.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> #include <vterm.h> +#include <vterm_keycodes.h> +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/change.h" +#include "nvim/channel.h" #include "nvim/cursor.h" +#include "nvim/drawline.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" #include "nvim/event/time.h" -#include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/getchar.h" +#include "nvim/globals.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/keycodes.h" -#include "nvim/log.h" #include "nvim/macros.h" #include "nvim/main.h" #include "nvim/map.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" +#include "nvim/msgpack_rpc/channel_defs.h" +#include "nvim/normal.h" #include "nvim/option.h" #include "nvim/optionstr.h" -#include "nvim/os/input.h" +#include "nvim/pos.h" +#include "nvim/screen.h" #include "nvim/state.h" #include "nvim/terminal.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" -#include "nvim/window.h" typedef struct terminal_state { VimState state; @@ -375,7 +387,7 @@ void terminal_check_size(Terminal *term) // Check if there is a window that displays the terminal and find the maximum width and height. // Skip the autocommand window which isn't actually displayed. FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp == aucmd_win) { + if (is_aucmd_win(wp)) { continue; } if (wp->w_buffer && wp->w_buffer->terminal == term) { @@ -423,12 +435,12 @@ bool terminal_enter(void) handle_T save_curwin = curwin->handle; bool save_w_p_cul = curwin->w_p_cul; char *save_w_p_culopt = NULL; - char_u save_w_p_culopt_flags = curwin->w_p_culopt_flags; + uint8_t save_w_p_culopt_flags = curwin->w_p_culopt_flags; int save_w_p_cuc = curwin->w_p_cuc; long save_w_p_so = curwin->w_p_so; long save_w_p_siso = curwin->w_p_siso; if (curwin->w_p_cul && curwin->w_p_culopt_flags & CULOPT_NBR) { - if (strcmp(curwin->w_p_culopt, "number")) { + if (strcmp(curwin->w_p_culopt, "number") != 0) { save_w_p_culopt = curwin->w_p_culopt; curwin->w_p_culopt = xstrdup("number"); } @@ -523,6 +535,18 @@ static int terminal_check(VimState *state) if (must_redraw) { update_screen(); + + // Make sure an invoked autocmd doesn't delete the buffer (and the + // terminal) under our fingers. + curbuf->b_locked++; + + // save and restore curwin and curbuf, in case the autocmd changes them + aco_save_T aco; + aucmd_prepbuf(&aco, curbuf); + apply_autocmds(EVENT_TEXTCHANGEDT, NULL, NULL, false, curbuf); + aucmd_restbuf(&aco); + + curbuf->b_locked--; } if (need_maketitle) { // Update title in terminal-mode. #7248 @@ -694,7 +718,7 @@ void terminal_paste(long count, char **y_array, size_t y_size) } vterm_keyboard_start_paste(curbuf->terminal->vt); size_t buff_len = strlen(y_array[0]); - char_u *buff = xmalloc(buff_len); + char *buff = xmalloc(buff_len); for (int i = 0; i < count; i++) { // -V756 // feed the lines to the terminal for (size_t j = 0; j < y_size; j++) { @@ -707,18 +731,18 @@ void terminal_paste(long count, char **y_array, size_t y_size) buff = xrealloc(buff, len); buff_len = len; } - char_u *dst = buff; - char_u *src = (char_u *)y_array[j]; + char *dst = buff; + char *src = y_array[j]; while (*src != '\0') { - len = (size_t)utf_ptr2len((char *)src); - int c = utf_ptr2char((char *)src); + len = (size_t)utf_ptr2len(src); + int c = utf_ptr2char(src); if (!is_filter_char(c)) { memcpy(dst, src, len); dst += len; } src += len; } - terminal_send(curbuf->terminal, (char *)buff, (size_t)(dst - buff)); + terminal_send(curbuf->terminal, buff, (size_t)(dst - buff)); } } xfree(buff); @@ -1415,8 +1439,8 @@ static bool send_mouse_event(Terminal *term, int c) int direction = c == K_MOUSEDOWN ? MSCR_DOWN : MSCR_UP; if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { scroll_redraw(direction, curwin->w_botline - curwin->w_topline); - } else { - scroll_redraw(direction, 3L); + } else if (p_mousescroll_vert > 0) { + scroll_redraw(direction, p_mousescroll_vert); } curwin->w_redr_status = true; diff --git a/src/nvim/testdir/Make_all.mak b/src/nvim/testdir/Make_all.mak new file mode 100644 index 0000000000..b917877422 --- /dev/null +++ b/src/nvim/testdir/Make_all.mak @@ -0,0 +1 @@ +# Tests may depend on the existence of this file. diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 4641408069..9511b311e6 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -11,16 +11,7 @@ ROOT := ../../.. export SHELL := sh export NVIM_PRG := $(NVIM_PRG) -export TMPDIR := $(abspath Xtest-tmpdir) - -SCRIPTS_DEFAULT = \ - test42.out \ - -ifneq ($(OS),Windows_NT) - SCRIPTS_DEFAULTS := $(SCRIPTS_DEFAULT) \ - test49.out \ - -endif +export TMPDIR := $(abspath X-test-tmpdir) ifeq ($(OS),Windows_NT) FIXFF = fixff @@ -28,8 +19,6 @@ else FIXFF = endif -SCRIPTS ?= $(SCRIPTS_DEFAULT) - # Tests using runtest.vim. NEW_TESTS_ALOT := test_alot_utf8 test_alot test_alot_latin NEW_TESTS_IN_ALOT := $(shell sed -n '/^source/ s/^source //; s/\.vim$$//p' $(addsuffix .vim,$(NEW_TESTS_ALOT))) @@ -66,11 +55,7 @@ else endif endif -ifdef TESTNUM - SCRIPTS := test$(TESTNUM).out -endif - -nongui: nolog $(FIXFF) $(SCRIPTS) newtests report +nongui: nolog $(FIXFF) newtests report .gdbinit: @echo "[OLDTEST-PREP] Setting up .gdbinit" @@ -88,8 +73,6 @@ report: test1.out: $(NVIM_PRG) -$(SCRIPTS): $(NVIM_PRG) test1.out - NO_PLUGINS = --noplugin --headless # In vim, if the -u command line option is specified, compatible is turned on # and viminfo is not read. Unlike vim, neovim reads viminfo and requires the @@ -147,17 +130,6 @@ test1.out: .gdbinit test1.in @rm -f wrongtermsize @rm -rf X* viminfo -%.out: %.in .gdbinit - @echo "[OLDESTTEST] Running" $* - @rm -rf $*.failed test.ok $(RM_ON_RUN) - @mkdir -p $(TMPDIR) - @cp $*.ok test.ok - @/bin/sh runnvim.sh --oldesttest $(ROOT) $(NVIM_PRG) $* $(RUN_VIM) $*.in - @rm -rf X* test.ok viminfo - -# Explicit dependencies. -test49.out: test49.vim - nolog: @echo "[OLDTEST-PREP] Removing test.log and messages" @rm -f test.log messages @@ -179,4 +151,4 @@ newtestssilent: $(NEW_TESTS_RES) @echo "[OLDTEST] Running" $* @rm -rf $*.failed test.ok $(RM_ON_RUN) @mkdir -p $(TMPDIR) - @/bin/sh runnvim.sh $(ROOT) $(NVIM_PRG) $* $(RUN_VIMTEST) $(NO_INITS) -u NONE -S runtest.vim $*.vim + @/bin/sh runnvim.sh $(ROOT) $(NVIM_PRG) $* $(RUN_VIMTEST) $(NO_INITS) -u NONE --cmd "set shortmess-=F" -S runtest.vim $*.vim diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim index 4107df99d6..680a59006b 100644 --- a/src/nvim/testdir/check.vim +++ b/src/nvim/testdir/check.vim @@ -1,11 +1,16 @@ source shared.vim source term_util.vim +command -nargs=1 MissingFeature throw 'Skipped: ' .. <args> .. ' feature missing' + " Command to check for the presence of a feature. command -nargs=1 CheckFeature call CheckFeature(<f-args>) func CheckFeature(name) + " if !has(a:name, 1) + " throw 'Checking for non-existent feature ' .. a:name + " endif if !has(a:name) - throw 'Skipped: ' .. a:name .. ' feature missing' + MissingFeature a:name endif endfunc @@ -39,6 +44,22 @@ func CheckFunction(name) endif endfunc +" Command to check for the presence of an Ex command +command -nargs=1 CheckCommand call CheckCommand(<f-args>) +func CheckCommand(name) + if !exists(':' .. a:name) + throw 'Skipped: ' .. a:name .. ' command not supported' + endif +endfunc + +" Command to check for the presence of a shell command +command -nargs=1 CheckExecutable call CheckExecutable(<f-args>) +func CheckExecutable(name) + if !executable(a:name) + throw 'Skipped: ' .. a:name .. ' program not executable' + endif +endfunc + " Command to check for the presence of python. Argument should have been " obtained with PythonProg() func CheckPython(name) @@ -71,12 +92,11 @@ 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' +" Command to check for running on Linux +command CheckLinux call CheckLinux() +func CheckLinux() + if !has('linux') + throw 'Skipped: only works on Linux' endif endfunc @@ -105,6 +125,14 @@ func CheckCanRunGui() endif endfunc +" Command to Check for an environment variable +command -nargs=1 CheckEnv call CheckEnv(<f-args>) +func CheckEnv(name) + if empty(eval('$' .. a:name)) + throw 'Skipped: Environment variable ' .. a:name .. ' is not set' + endif +endfunc + " Command to check that we are using the GUI command CheckGui call CheckGui() func CheckGui() @@ -145,6 +173,22 @@ func CheckNotAsan() endif endfunc +" Command to check for not running under valgrind +command CheckNotValgrind call CheckNotValgrind() +func CheckNotValgrind() + if RunningWithValgrind() + throw 'Skipped: does not work well with valgrind' + endif +endfunc + +" Command to check for X11 based GUI +command CheckX11BasedGui call CheckX11BasedGui() +func CheckX11BasedGui() + if !g:x11_based_gui + throw 'Skipped: requires X11 based GUI' + 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/runtest.vim b/src/nvim/testdir/runtest.vim index ce23141c7a..3c5699af73 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -105,7 +105,7 @@ set nomore lang mess C " Nvim: append runtime from build dir, which contains the generated doc/tags. -let &runtimepath .= ','.expand($BUILD_DIR).'/runtime/' +let &runtimepath ..= ',' .. expand($BUILD_DIR) .. '/runtime/' let s:t_bold = &t_md let s:t_normal = &t_me @@ -171,16 +171,19 @@ func RunTheTest(test) endtry endif + au VimLeavePre * call EarlyExit(g:testfunc) if a:test =~ 'Test_nocatch_' " Function handles errors itself. This avoids skipping commands after the " error. + let g:skipped_reason = '' exe 'call ' . a:test + if g:skipped_reason != '' + call add(s:messages, ' Skipped') + call add(s:skipped, 'SKIPPED ' . a:test . ': ' . g:skipped_reason) + endif else try - let s:test = a:test - au VimLeavePre * call EarlyExit(s:test) exe 'call ' . a:test - au! VimLeavePre catch /^\cskipped/ call add(s:messages, ' Skipped') call add(s:skipped, 'SKIPPED ' . a:test . ': ' . substitute(v:exception, '^\S*\s\+', '', '')) @@ -188,6 +191,7 @@ func RunTheTest(test) call add(v:errors, 'Caught exception in ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint) endtry endif + au! VimLeavePre " In case 'insertmode' was set and something went wrong, make sure it is " reset to avoid trouble with anything else. @@ -250,11 +254,11 @@ func AfterTheTest(func_name) if len(v:errors) > 0 if match(s:may_fail_list, '^' .. a:func_name) >= 0 let s:fail_expected += 1 - call add(s:errors_expected, 'Found errors in ' . s:test . ':') + call add(s:errors_expected, 'Found errors in ' . g:testfunc . ':') call extend(s:errors_expected, v:errors) else let s:fail += 1 - call add(s:errors, 'Found errors in ' . s:test . ':') + call add(s:errors, 'Found errors in ' . g:testfunc . ':') call extend(s:errors, v:errors) endif let v:errors = [] @@ -415,12 +419,12 @@ endif let s:may_fail_list = [] if $TEST_MAY_FAIL != '' - " Split the list at commas and add () to make it match s:test. + " Split the list at commas and add () to make it match g:testfunc. let s:may_fail_list = split($TEST_MAY_FAIL, ',')->map({i, v -> v .. '()'}) endif " Execute the tests in alphabetical order. -for s:test in sort(s:tests) +for g:testfunc in sort(s:tests) " Silence, please! set belloff=all let prev_error = '' @@ -430,7 +434,7 @@ for s:test in sort(s:tests) " A test can set g:test_is_flaky to retry running the test. let g:test_is_flaky = 0 - call RunTheTest(s:test) + call RunTheTest(g:testfunc) " Repeat a flaky test. Give up when: " - $TEST_NO_RETRY is not empty @@ -438,10 +442,10 @@ for s:test in sort(s:tests) " - it fails five times (with a different message) if len(v:errors) > 0 \ && $TEST_NO_RETRY == '' - \ && (index(s:flaky_tests, s:test) >= 0 + \ && (index(s:flaky_tests, g:testfunc) >= 0 \ || g:test_is_flaky) while 1 - call add(s:messages, 'Found errors in ' . s:test . ':') + call add(s:messages, 'Found errors in ' . g:testfunc . ':') call extend(s:messages, v:errors) call add(total_errors, 'Run ' . g:run_nr . ':') @@ -464,7 +468,7 @@ for s:test in sort(s:tests) let v:errors = [] let g:run_nr += 1 - call RunTheTest(s:test) + call RunTheTest(g:testfunc) if len(v:errors) == 0 " Test passed on rerun. @@ -473,7 +477,7 @@ for s:test in sort(s:tests) endwhile endif - call AfterTheTest(s:test) + call AfterTheTest(g:testfunc) endfor call FinishTesting() diff --git a/test/benchmark/samples/re.freeze.txt b/src/nvim/testdir/samples/re.freeze.txt index d768c23c5e..d768c23c5e 100644 --- a/test/benchmark/samples/re.freeze.txt +++ b/src/nvim/testdir/samples/re.freeze.txt diff --git a/src/nvim/testdir/sautest/autoload/foo.vim b/src/nvim/testdir/sautest/autoload/foo.vim index 298e7275d8..21d33a0f4d 100644 --- a/src/nvim/testdir/sautest/autoload/foo.vim +++ b/src/nvim/testdir/sautest/autoload/foo.vim @@ -9,3 +9,7 @@ endfunc func foo#addFoo(head) return a:head .. 'foo' endfunc + +func foo#() + return 'empty' +endfunc diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index c2809844ac..33f6d9a2d0 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -9,7 +9,7 @@ source view_util.vim " {Nvim} " Filepath captured from output may be truncated, like this: -" /home/va...estdir/Xtest-tmpdir/nvimxbXN4i/10 +" /home/va...estdir/X-test-tmpdir/nvimxbXN4i/10 " Get last 2 segments, then combine with $TMPDIR. func! Fix_truncated_tmpfile(fname) " sanity check @@ -285,6 +285,12 @@ func GetVimCommand(...) return cmd endfunc +" Return one when it looks like the tests are run with valgrind, which means +" that everything is much slower. +func RunningWithValgrind() + return GetVimCommand() =~ '\<valgrind\>' +endfunc + " Get the command to run Vim, with --clean instead of "-u NONE". func GetVimCommandClean() let cmd = GetVimCommand() @@ -317,7 +323,6 @@ func RunVim(before, after, arguments) endfunc func RunVimPiped(before, after, arguments, pipecmd) - let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' let cmd = GetVimCommand() let args = '' if len(a:before) > 0 @@ -332,7 +337,9 @@ func RunVimPiped(before, after, arguments, pipecmd) " Optionally run Vim under valgrind " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd - exe "silent !" . a:pipecmd . cmd . args . ' ' . a:arguments + let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' + " Nvim does not support -Z flag, remove it. + exe "silent !" . a:pipecmd . cmd . args . ' ' . substitute(a:arguments, '-Z', '', 'g') if len(a:before) > 0 call delete('Xbefore.vim') @@ -364,4 +371,19 @@ func GetMessages() return msg_list endfunc +" Run the list of commands in 'cmds' and look for 'errstr' in exception. +" Note that assert_fails() cannot be used in some places and this function +" can be used. +func AssertException(cmds, errstr) + let save_exception = '' + try + for cmd in a:cmds + exe cmd + endfor + catch + let save_exception = v:exception + endtry + call assert_match(a:errstr, save_exception) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test42.in b/src/nvim/testdir/test42.in Binary files differdeleted file mode 100644 index 456f9ddb07..0000000000 --- a/src/nvim/testdir/test42.in +++ /dev/null diff --git a/src/nvim/testdir/test42.ok b/src/nvim/testdir/test42.ok Binary files differdeleted file mode 100644 index 183430d71c..0000000000 --- a/src/nvim/testdir/test42.ok +++ /dev/null diff --git a/src/nvim/testdir/test49.in b/src/nvim/testdir/test49.in deleted file mode 100644 index 435e62765b..0000000000 --- a/src/nvim/testdir/test49.in +++ /dev/null @@ -1,31 +0,0 @@ -This is a test of the script language. - -If after adding a new test, the test output doesn't appear properly in -test49.failed, try to add one or more "G"s at the line ending in "test.out" - -STARTTEST -:se nomore -:lang mess C -:so test49.vim -:" Go back to this file and append the results from register r. -:buf test49.in -G"rp:/^Results/,$w! test.out -:" -:" make valgrind happy -:redir => funclist -:silent func -:redir END -:for line in split(funclist, "\n") -: let name = matchstr(line, 'function \zs[A-Z]\w*\ze(') -: if name != '' -: exe "delfunc " . name -: endif -:endfor -:for v in keys(g:) -: silent! exe "unlet " . v -:endfor -:unlet v -:qa! -ENDTEST - -Results of test49.vim: diff --git a/src/nvim/testdir/test49.ok b/src/nvim/testdir/test49.ok deleted file mode 100644 index 9f283e808b..0000000000 --- a/src/nvim/testdir/test49.ok +++ /dev/null @@ -1,56 +0,0 @@ -Results of test49.vim: -*** Test 18: OK (67224583) -*** Test 19: OK (69275973) -*** Test 20: OK (1874575085) -*** Test 21: OK (147932225) -*** Test 22: OK (4161) -*** Test 23: OK (49) -*** Test 24: OK (41) -*** Test 27: OK (1996459) -*** Test 28: OK (1996459) -*** Test 29: OK (170428555) -*** Test 30: OK (190905173) -*** Test 31: OK (190905173) -*** Test 34: OK (2146584868) -*** Test 35: OK (2146584868) -*** Test 36: OK (1071644672) -*** Test 37: OK (1071644672) -*** Test 38: OK (357908480) -*** Test 39: OK (357908480) -*** Test 40: OK (357908480) -*** Test 49: OK (179000669) -*** Test 50: OK (363550045) -*** Test 52: OK (1247112011) -*** Test 53: OK (131071) -*** Test 54: OK (2047) -*** Test 55: OK (1023) -*** Test 56: OK (511) -*** Test 57: OK (2147450880) -*** Test 58: OK (624945) -*** Test 59: OK (2038431743) -*** Test 60: OK (311511339) -*** Test 61: OK (374889517) -*** Test 62: OK (286331153) -*** Test 63: OK (236978127) -*** Test 64: OK (1499645335) -*** Test 65: OK (70187) -*** Test 66: OK (5464) -*** Test 67: OK (212514423) -*** Test 68: OK (212514423) -*** Test 76: OK (1610087935) -*** Test 77: OK (1388671) -*** Test 78: OK (134217728) -*** Test 79: OK (70288929) -*** Test 80: OK (17895765) -*** Test 81: OK (387) -*** Test 82: OK (8454401) -*** Test 83: OK (2835) -*** Test 84: OK (934782101) -*** Test 85: OK (198689) ---- Test 86: No Crash for vimgrep on BufUnload -*** Test 86: OK (0) ---- Test 88: All tests were run with throwing exceptions on error. - The $VIMNOERRTHROW control is not configured. ---- Test 88: All tests were run with throwing exceptions on interrupt. - The $VIMNOINTTHROW control is not configured. -*** Test 88: OK (50443995) diff --git a/src/nvim/testdir/test49.vim b/src/nvim/testdir/test49.vim deleted file mode 100644 index 3ee5e9ff7c..0000000000 --- a/src/nvim/testdir/test49.vim +++ /dev/null @@ -1,6724 +0,0 @@ -" Vim script language tests -" Author: Servatius Brandt <Servatius.Brandt@fujitsu-siemens.com> -" Last Change: 2019 Nov 03 - -"------------------------------------------------------------------------------- -" Test environment {{{1 -"------------------------------------------------------------------------------- - - -" Adding new tests easily. {{{2 -" -" Writing new tests is eased considerably with the following functions and -" abbreviations (see "Commands for recording the execution path", "Automatic -" argument generation"). -" -" To get the abbreviations, execute the command -" -" :let test49_set_env = 1 | source test49.vim -" -" To get them always (from src/nvim/testdir), put a line -" -" au! BufRead test49.vim let test49_set_env = 1 | source test49.vim -" -" into the local .vimrc file in the src/nvim/testdir directory. -" -if exists("test49_set_env") && test49_set_env - - " Automatic argument generation for the test environment commands. - - function! Xsum() - let addend = substitute(getline("."), '^.*"\s*X:\s*\|^.*', '', "") - " Evaluate arithmetic expression. - if addend != "" - exec "let g:Xsum = g:Xsum + " . addend - endif - endfunction - - function! Xcheck() - let g:Xsum=0 - ?XpathINIT?,.call Xsum() - exec "norm A " - return g:Xsum - endfunction - - iab Xcheck Xcheck<Space><C-R>=Xcheck()<CR><C-O>x - - function! Xcomment(num) - let str = "" - let tabwidth = &sts ? &sts : &ts - let tabs = (48+tabwidth - a:num - virtcol(".")) / tabwidth - while tabs > 0 - let str = str . "\t" - let tabs = tabs - 1 - endwhile - let str = str . '" X:' - return str - endfunction - - function! Xloop() - let back = line(".") . "|norm" . virtcol(".") . "|" - norm 0 - let last = search('X\(loop\|path\)INIT\|Xloop\>', "bW") - exec back - let theline = getline(last) - if theline =~ 'X\(loop\|path\)INIT' - let num = 1 - else - let num = 2 * substitute(theline, '.*Xloop\s*\(\d\+\).*', '\1', "") - endif - ?X\(loop\|path\)INIT? - \s/\(XloopINIT!\=\s*\d\+\s\+\)\@<=\(\d\+\)/\=2*submatch(2)/ - exec back - exec "norm a " - return num . Xcomment(strlen(num)) - endfunction - - iab Xloop Xloop<Space><C-R>=Xloop()<CR><C-O>x - - function! Xpath(loopinit) - let back = line(".") . "|norm" . virtcol(".") . "|" - norm 0 - let last = search('XpathINIT\|Xpath\>\|XloopINIT', "bW") - exec back - let theline = getline(last) - if theline =~ 'XpathINIT' - let num = 1 - elseif theline =~ 'Xpath\>' - let num = 2 * substitute(theline, '.*Xpath\s*\(\d\+\).*', '\1', "") - else - let pattern = '.*XloopINIT!\=\s*\(\d\+\)\s*\(\d\+\).*' - let num = substitute(theline, pattern, '\1', "") - let factor = substitute(theline, pattern, '\2', "") - " The "<C-O>x" from the "Xpath" iab and the character triggering its - " expansion are in the input buffer. Save and clear typeahead so - " that it is not read away by the call to "input()" below. Restore - " afterwards. - call inputsave() - let loops = input("Number of iterations in previous loop? ") - call inputrestore() - while (loops > 0) - let num = num * factor - let loops = loops - 1 - endwhile - endif - exec "norm a " - if a:loopinit - return num . " 1" - endif - return num . Xcomment(strlen(num)) - endfunction - - iab Xpath Xpath<Space><C-R>=Xpath(0)<CR><C-O>x - iab XloopINIT XloopINIT<Space><C-R>=Xpath(1)<CR><C-O>x - - " Also useful (see ExtraVim below): - aug ExtraVim - au! - au BufEnter <sfile> syn region ExtraVim - \ start=+^if\s\+ExtraVim(.*)+ end=+^endif+ - \ transparent keepend - au BufEnter <sfile> syn match ExtraComment /^"/ - \ contained containedin=ExtraVim - au BufEnter <sfile> hi link ExtraComment vimComment - aug END - - aug Xpath - au BufEnter <sfile> syn keyword Xpath - \ XpathINIT Xpath XloopINIT Xloop XloopNEXT Xcheck Xout - au BufEnter <sfile> hi link Xpath Special - aug END - - do BufEnter <sfile> - - " Do not execute the tests when sourcing this file for getting the functions - " and abbreviations above, which are intended for easily adding new test - " cases; they are not needed for test execution. Unlet the variable - " controlling this so that an explicit ":source" command for this file will - " execute the tests. - unlet test49_set_env - finish - -endif - - -" Commands for recording the execution path. {{{2 -" -" The Xpath/Xloop commands can be used for computing the eXecution path by -" adding (different) powers of 2 from those script lines, for which the -" execution should be checked. Xloop provides different addends for each -" execution of a loop. Permitted values are 2^0 to 2^30, so that 31 execution -" points (multiply counted inside loops) can be tested. -" -" Note that the arguments of the following commands can be generated -" automatically, see below. -" -" Usage: {{{3 -" -" - Use XpathINIT at the beginning of the test. -" -" - Use Xpath to check if a line is executed. -" Argument: power of 2 (decimal). -" -" - To check multiple execution of loops use Xloop for automatically -" computing Xpath values: -" -" - Use XloopINIT before the loop. -" Two arguments: -" - the first Xpath value (power of 2) to be used (Xnext), -" - factor for computing a new Xnext value when reexecuting a loop -" (by a ":continue" or ":endwhile"); this should be 2^n where -" n is the number of Xloop commands inside the loop. -" If XloopINIT! is used, the first execution of XloopNEXT is -" a no-operation. -" -" - Use Xloop inside the loop: -" One argument: -" The argument and the Xnext value are multiplied to build the -" next Xpath value. No new Xnext value is prepared. The argument -" should be 2^(n-1) for the nth Xloop command inside the loop. -" If the loop has only one Xloop command, the argument can be -" omitted (default: 1). -" -" - Use XloopNEXT before ":continue" and ":endwhile". This computes a new -" Xnext value for the next execution of the loop by multiplying the old -" one with the factor specified in the XloopINIT command. No Argument. -" Alternatively, when XloopINIT! is used, a single XloopNEXT at the -" beginning of the loop can be used. -" -" Nested loops are not supported. -" -" - Use Xcheck at end of each test. It prints the test number, the expected -" execution path value, the test result ("OK" or "FAIL"), and, if the tests -" fails, the actual execution path. -" One argument: -" Expected Xpath/Xloop sum for the correct execution path. -" In order that this value can be computed automatically, do the -" following: For each line in the test with an Xpath and Xloop -" command, add a comment starting with "X:" and specifying an -" expression that evaluates to the value contributed by this line to -" the correct execution path. (For copying an Xpath argument of at -" least two digits into the comment, press <C-P>.) At the end of the -" test, just type "Xcheck" and press <Esc>. -" -" - In order to add additional information to the test output file, use the -" Xout command. Argument(s) like ":echo". -" -" Automatic argument generation: {{{3 -" -" The arguments of the Xpath, XloopINIT, Xloop, and Xcheck commands can be -" generated automatically, so that new tests can easily be written without -" mental arithmetic. The Xcheck argument is computed from the "X:" comments -" of the preceding Xpath and Xloop commands. See the commands and -" abbreviations at the beginning of this file. -" -" Implementation: {{{3 -" XpathINIT, Xpath, XloopINIT, Xloop, XloopNEXT, Xcheck, Xout. -" -" The variants for existing g:ExtraVimResult are needed when executing a script -" in an extra Vim process, see ExtraVim below. - -" EXTRA_VIM_START - do not change or remove this line. - -com! XpathINIT let g:Xpath = 0 - -if exists("g:ExtraVimResult") - com! -count -bar Xpath exec "!echo <count> >>" . g:ExtraVimResult -else - com! -count -bar Xpath let g:Xpath = g:Xpath + <count> -endif - -com! -count -nargs=1 -bang - \ XloopINIT let g:Xnext = <count> | - \ let g:Xfactor = <args> | - \ let g:Xskip = strlen("<bang>") - -if exists("g:ExtraVimResult") - com! -count=1 -bar Xloop exec "!echo " . (g:Xnext * <count>) . " >>" . - \ g:ExtraVimResult -else - com! -count=1 -bar Xloop let g:Xpath = g:Xpath + g:Xnext * <count> -endif - -com! XloopNEXT let g:Xnext = g:Xnext * - \ (g:Xskip ? 1 : g:Xfactor) | - \ let g:Xskip = 0 - -let @r = "" -let Xtest = 1 -com! -count Xcheck let Xresult = "*** Test " . - \ (Xtest<10?" ":Xtest<100?" ":"") . - \ Xtest . ": " . ( - \ (Xpath==<count>) ? "OK (".Xpath.")" : - \ "FAIL (".Xpath." instead of <count>)" - \ ) | - \ let @R = Xresult . "\n" | - \ echo Xresult | - \ let Xtest = Xtest + 1 - -if exists("g:ExtraVimResult") - com! -nargs=+ Xoutq exec "!echo @R:'" . - \ substitute(substitute(<q-args>, - \ "'", '&\\&&', "g"), "\n", "@NL@", "g") - \ . "' >>" . g:ExtraVimResult -else - com! -nargs=+ Xoutq let @R = "--- Test " . - \ (g:Xtest<10?" ":g:Xtest<100?" ":"") . - \ g:Xtest . ": " . substitute(<q-args>, - \ "\n", "&\t ", "g") . "\n" -endif -com! -nargs=+ Xout exec 'Xoutq' <args> - -" Switch off storing of lines for undoing changes. Speeds things up a little. -set undolevels=-1 - -" EXTRA_VIM_STOP - do not change or remove this line. - - -" ExtraVim() - Run a script file in an extra Vim process. {{{2 -" -" This is useful for testing immediate abortion of the script processing due to -" an error in a command dynamically enclosed by a :try/:tryend region or when an -" exception is thrown but not caught or when an interrupt occurs. It can also -" be used for testing :finish. -" -" An interrupt location can be specified by an "INTERRUPT" comment. A number -" telling how often this location is reached (in a loop or in several function -" calls) should be specified as argument. When missing, once per script -" invocation or function call is assumed. INTERRUPT locations are tested by -" setting a breakpoint in that line and using the ">quit" debug command when -" the breakpoint is reached. A function for which an INTERRUPT location is -" specified must be defined before calling it (or executing it as a script by -" using ExecAsScript below). -" -" This function is only called in normal modus ("g:ExtraVimResult" undefined). -" -" Tests to be executed as an extra script should be written as follows: -" -" column 1 column 1 -" | | -" v v -" -" XpathINIT XpathINIT -" if ExtraVim() if ExtraVim() -" ... " ... -" ... " ... -" endif endif -" Xcheck <number> Xcheck <number> -" -" Double quotes in column 1 are removed before the script is executed. -" They should be used if the test has unbalanced conditionals (:if/:endif, -" :while:/endwhile, :try/:endtry) or for a line with a syntax error. The -" extra script may use Xpath, XloopINIT, Xloop, XloopNEXT, and Xout as usual. -" -" A file name may be specified as argument. All messages of the extra Vim -" process are then redirected to the file. An existing file is overwritten. -" -let ExtraVimCount = 0 -let ExtraVimBase = expand("<sfile>") -let ExtraVimTestEnv = "" -" -function ExtraVim(...) - " Count how often this function is called. - let g:ExtraVimCount = g:ExtraVimCount + 1 - - " Disable folds to prevent that the ranges in the ":write" commands below - " are extended up to the end of a closed fold. This also speeds things up - " considerably. - set nofoldenable - - " Open a buffer for this test script and copy the test environment to - " a temporary file. Take account of parts relevant for the extra script - " execution only. - let current_buffnr = bufnr("%") - execute "view +1" g:ExtraVimBase - if g:ExtraVimCount == 1 - let g:ExtraVimTestEnv = tempname() - execute "/E" . "XTRA_VIM_START/+,/E" . "XTRA_VIM_STOP/-w" - \ g:ExtraVimTestEnv "|']+" - execute "/E" . "XTRA_VIM_START/+,/E" . "XTRA_VIM_STOP/-w >>" - \ g:ExtraVimTestEnv "|']+" - execute "/E" . "XTRA_VIM_START/+,/E" . "XTRA_VIM_STOP/-w >>" - \ g:ExtraVimTestEnv "|']+" - execute "/E" . "XTRA_VIM_START/+,/E" . "XTRA_VIM_STOP/-w >>" - \ g:ExtraVimTestEnv "|']+" - endif - - " Start the extra Vim script with a ":source" command for the test - " environment. The source line number where the extra script will be - " appended, needs to be passed as variable "ExtraVimBegin" to the script. - let extra_script = tempname() - exec "!echo 'source " . g:ExtraVimTestEnv . "' >" . extra_script - let extra_begin = 1 - - " Starting behind the test environment, skip over the first g:ExtraVimCount - " occurrences of "if ExtraVim()" and copy the following lines up to the - " matching "endif" to the extra Vim script. - execute "/E" . "ND_OF_TEST_ENVIRONMENT/" - exec 'norm ' . g:ExtraVimCount . '/^\s*if\s\+ExtraVim(.*)/+' . "\n" - execute ".,/^endif/-write >>" . extra_script - - " Open a buffer for the extra Vim script, delete all ^", and write the - " script if was actually modified. - execute "edit +" . (extra_begin + 1) extra_script - ,$s/^"//e - update - - " Count the INTERRUPTs and build the breakpoint and quit commands. - let breakpoints = "" - let debug_quits = "" - let in_func = 0 - exec extra_begin - while search( - \ '"\s*INTERRUPT\h\@!\|^\s*fu\%[nction]\>!\=\s*\%(\u\|s:\)\w*\s*(\|' - \ . '^\s*\\\|^\s*endf\%[unction]\>\|' - \ . '\%(^\s*fu\%[nction]!\=\s*\)\@<!\%(\u\|s:\)\w*\s*(\|' - \ . 'ExecAsScript\s\+\%(\u\|s:\)\w*', - \ "W") > 0 - let theline = getline(".") - if theline =~ '^\s*fu' - " Function definition. - let in_func = 1 - let func_start = line(".") - let func_name = substitute(theline, - \ '^\s*fu\%[nction]!\=\s*\(\%(\u\|s:\)\w*\).*', '\1', "") - elseif theline =~ '^\s*endf' - " End of function definition. - let in_func = 0 - else - let finding = substitute(theline, '.*\(\%' . col(".") . 'c.*\)', - \ '\1', "") - if finding =~ '^"\s*INTERRUPT\h\@!' - " Interrupt comment. Compose as many quit commands as - " specified. - let cnt = substitute(finding, - \ '^"\s*INTERRUPT\s*\(\d*\).*$', '\1', "") - let quits = "" - while cnt > 0 - " Use "\r" rather than "\n" to separate the quit commands. - " "\r" is not interpreted as command separator by the ":!" - " command below but works to separate commands in the - " external vim. - let quits = quits . "q\r" - let cnt = cnt - 1 - endwhile - if in_func - " Add the function breakpoint and note the number of quits - " to be used, if specified, or one for every call else. - let breakpoints = breakpoints . " -c 'breakadd func " . - \ (line(".") - func_start) . " " . - \ func_name . "'" - if quits != "" - let debug_quits = debug_quits . quits - elseif !exists("quits{func_name}") - let quits{func_name} = "q\r" - else - let quits{func_name} = quits{func_name} . "q\r" - endif - else - " Add the file breakpoint and the quits to be used for it. - let breakpoints = breakpoints . " -c 'breakadd file " . - \ line(".") . " " . extra_script . "'" - if quits == "" - let quits = "q\r" - endif - let debug_quits = debug_quits . quits - endif - else - " Add the quits to be used for calling the function or executing - " it as script file. - if finding =~ '^ExecAsScript' - " Sourcing function as script. - let finding = substitute(finding, - \ '^ExecAsScript\s\+\(\%(\u\|s:\)\w*\).*', '\1', "") - else - " Function call. - let finding = substitute(finding, - \ '^\(\%(\u\|s:\)\w*\).*', '\1', "") - endif - if exists("quits{finding}") - let debug_quits = debug_quits . quits{finding} - endif - endif - endif - endwhile - - " Close the buffer for the script and create an (empty) resultfile. - bwipeout - let resultfile = tempname() - exec "!>" . resultfile - - " Run the script in an extra vim. Switch to extra modus by passing the - " resultfile in ExtraVimResult. Redirect messages to the file specified as - " argument if any. Use ":debuggreedy" so that the commands provided on the - " pipe are consumed at the debug prompt. Use "-N" to enable command-line - " continuation ("C" in 'cpo'). Add "nviminfo" to 'viminfo' to avoid - " messing up the user's viminfo file. - let redirect = a:0 ? - \ " -c 'au VimLeave * redir END' -c 'redir\\! >" . a:1 . "'" : "" - exec "!echo '" . debug_quits . "q' | " .. v:progpath .. " -u NONE -N -es" . redirect . - \ " -c 'debuggreedy|set viminfo+=nviminfo'" . - \ " -c 'let ExtraVimBegin = " . extra_begin . "'" . - \ " -c 'let ExtraVimResult = \"" . resultfile . "\"'" . breakpoints . - \ " -S " . extra_script - - " Build the resulting sum for resultfile and add it to g:Xpath. Add Xout - " information provided by the extra Vim process to the test output. - let sum = 0 - exec "edit" resultfile - let line = 1 - while line <= line("$") - let theline = getline(line) - if theline =~ '^@R:' - exec 'Xout "' . substitute(substitute( - \ escape(escape(theline, '"'), '\"'), - \ '^@R:', '', ""), '@NL@', "\n", "g") . '"' - else - let sum = sum + getline(line) - endif - let line = line + 1 - endwhile - bwipeout - let g:Xpath = g:Xpath + sum - - " Delete the extra script and the resultfile. - call delete(extra_script) - call delete(resultfile) - - " Switch back to the buffer that was active when this function was entered. - exec "buffer" current_buffnr - - " Return 0. This protects extra scripts from being run in the main Vim - " process. - return 0 -endfunction - - -" ExtraVimThrowpoint() - Relative throwpoint in ExtraVim script {{{2 -" -" Evaluates v:throwpoint and returns the throwpoint relative to the beginning of -" an ExtraVim script as passed by ExtraVim() in ExtraVimBegin. -" -" EXTRA_VIM_START - do not change or remove this line. -function ExtraVimThrowpoint() - if !exists("g:ExtraVimBegin") - Xout "ExtraVimThrowpoint() used outside ExtraVim() script." - return v:throwpoint - endif - - if v:throwpoint =~ '^function\>' - return v:throwpoint - endif - - return "line " . - \ (substitute(v:throwpoint, '.*, line ', '', "") - g:ExtraVimBegin) . - \ " of ExtraVim() script" -endfunction -" EXTRA_VIM_STOP - do not change or remove this line. - - -" MakeScript() - Make a script file from a function. {{{2 -" -" Create a script that consists of the body of the function a:funcname. -" Replace any ":return" by a ":finish", any argument variable by a global -" variable, and every ":call" by a ":source" for the next following argument -" in the variable argument list. This function is useful if similar tests are -" to be made for a ":return" from a function call or a ":finish" in a script -" file. -" -" In order to execute a function specifying an INTERRUPT location (see ExtraVim) -" as a script file, use ExecAsScript below. -" -" EXTRA_VIM_START - do not change or remove this line. -function MakeScript(funcname, ...) - let script = tempname() - execute "redir! >" . script - execute "function" a:funcname - redir END - execute "edit" script - " Delete the "function" and the "endfunction" lines. Do not include the - " word "function" in the pattern since it might be translated if LANG is - " set. When MakeScript() is being debugged, this deletes also the debugging - " output of its line 3 and 4. - exec '1,/.*' . a:funcname . '(.*)/d' - /^\d*\s*endfunction\>/,$d - %s/^\d*//e - %s/return/finish/e - %s/\<a:\(\h\w*\)/g:\1/ge - normal gg0 - let cnt = 0 - while search('\<call\s*\%(\u\|s:\)\w*\s*(.*)', 'W') > 0 - let cnt = cnt + 1 - s/\<call\s*\%(\u\|s:\)\w*\s*(.*)/\='source ' . a:{cnt}/ - endwhile - g/^\s*$/d - write - bwipeout - return script -endfunction -" EXTRA_VIM_STOP - do not change or remove this line. - - -" ExecAsScript - Source a temporary script made from a function. {{{2 -" -" Make a temporary script file from the function a:funcname, ":source" it, and -" delete it afterwards. -" -" When inside ":if ExtraVim()", add a file breakpoint for each INTERRUPT -" location specified in the function. -" -" EXTRA_VIM_START - do not change or remove this line. -function ExecAsScript(funcname) - " Make a script from the function passed as argument. - let script = MakeScript(a:funcname) - - " When running in an extra Vim process, add a file breakpoint for each - " function breakpoint set when the extra Vim process was invoked by - " ExtraVim(). - if exists("g:ExtraVimResult") - let bplist = tempname() - execute "redir! >" . bplist - breaklist - redir END - execute "edit" bplist - " Get the line number from the function breakpoint. Works also when - " LANG is set. - execute 'v/^\s*\d\+\s\+func\s\+' . a:funcname . '\s.*/d' - %s/^\s*\d\+\s\+func\s\+\%(\u\|s:\)\w*\s\D*\(\d*\).*/\1/e - let cnt = 0 - while cnt < line("$") - let cnt = cnt + 1 - if getline(cnt) != "" - execute "breakadd file" getline(cnt) script - endif - endwhile - bwipeout! - call delete(bplist) - endif - - " Source and delete the script. - exec "source" script - call delete(script) -endfunction - -com! -nargs=1 -bar ExecAsScript call ExecAsScript(<f-args>) -" EXTRA_VIM_STOP - do not change or remove this line. - - -" END_OF_TEST_ENVIRONMENT - do not change or remove this line. - - -" Tests 1 to 17 were moved to test_vimscript.vim -let Xtest = 18 - -"------------------------------------------------------------------------------- -" Test 18: Interrupt (Ctrl-C pressed) {{{1 -" -" On an interrupt, the script processing is terminated immediately. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - if 1 - Xpath 1 " X: 1 - while 1 - Xpath 2 " X: 2 - if 1 - Xpath 4 " X: 4 - "INTERRUPT - Xpath 8 " X: 0 - break - finish - endif | Xpath 16 " X: 0 - Xpath 32 " X: 0 - endwhile | Xpath 64 " X: 0 - Xpath 128 " X: 0 - endif | Xpath 256 " X: 0 - Xpath 512 " X: 0 -endif - -if ExtraVim() - try - Xpath 1024 " X: 1024 - "INTERRUPT - Xpath 2048 " X: 0 - endtry | Xpath 4096 " X: 0 - Xpath 8192 " X: 0 -endif - -if ExtraVim() - function! F() - if 1 - Xpath 16384 " X: 16384 - while 1 - Xpath 32768 " X: 32768 - if 1 - Xpath 65536 " X: 65536 - "INTERRUPT - Xpath 131072 " X: 0 - break - return - endif | Xpath 262144 " X: 0 - Xpath Xpath 524288 " X: 0 - endwhile | Xpath 1048576 " X: 0 - Xpath Xpath 2097152 " X: 0 - endif | Xpath Xpath 4194304 " X: 0 - Xpath Xpath 8388608 " X: 0 - endfunction - - call F() | Xpath 16777216 " X: 0 - Xpath 33554432 " X: 0 -endif - -if ExtraVim() - function! G() - try - Xpath 67108864 " X: 67108864 - "INTERRUPT - Xpath 134217728 " X: 0 - endtry | Xpath 268435456 " X: 0 - Xpath 536870912 " X: 0 - endfunction - - call G() | Xpath 1073741824 " X: 0 - " The Xpath command does not accept 2^31 (negative); display explicitly: - exec "!echo 2147483648 >>" . g:ExtraVimResult - " X: 0 -endif - -Xcheck 67224583 - - -"------------------------------------------------------------------------------- -" Test 19: Aborting on errors inside :try/:endtry {{{1 -" -" An error in a command dynamically enclosed in a :try/:endtry region -" aborts script processing immediately. It does not matter whether -" the failing command is outside or inside a function and whether a -" function has an "abort" attribute. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - function! F() abort - Xpath 1 " X: 1 - asdf - Xpath 2 " X: 0 - endfunction - - try - Xpath 4 " X: 4 - call F() - Xpath 8 " X: 0 - endtry | Xpath 16 " X: 0 - Xpath 32 " X: 0 -endif - -if ExtraVim() - function! G() - Xpath 64 " X: 64 - asdf - Xpath 128 " X: 0 - endfunction - - try - Xpath 256 " X: 256 - call G() - Xpath 512 " X: 0 - endtry | Xpath 1024 " X: 0 - Xpath 2048 " X: 0 -endif - -if ExtraVim() - try - Xpath 4096 " X: 4096 - asdf - Xpath 8192 " X: 0 - endtry | Xpath 16384 " X: 0 - Xpath 32768 " X: 0 -endif - -if ExtraVim() - if 1 - try - Xpath 65536 " X: 65536 - asdf - Xpath 131072 " X: 0 - endtry | Xpath 262144 " X: 0 - endif | Xpath 524288 " X: 0 - Xpath 1048576 " X: 0 -endif - -if ExtraVim() - let p = 1 - while p - let p = 0 - try - Xpath 2097152 " X: 2097152 - asdf - Xpath 4194304 " X: 0 - endtry | Xpath 8388608 " X: 0 - endwhile | Xpath 16777216 " X: 0 - Xpath 33554432 " X: 0 -endif - -if ExtraVim() - let p = 1 - while p - let p = 0 -" try - Xpath 67108864 " X: 67108864 - endwhile | Xpath 134217728 " X: 0 - Xpath 268435456 " X: 0 -endif - -Xcheck 69275973 -"------------------------------------------------------------------------------- -" Test 20: Aborting on errors after :try/:endtry {{{1 -" -" When an error occurs after the last active :try/:endtry region has -" been left, termination behavior is as if no :try/:endtry has been -" seen. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - let p = 1 - while p - let p = 0 - try - Xpath 1 " X: 1 - endtry - asdf - endwhile | Xpath 2 " X: 0 - Xpath 4 " X: 4 -endif - -if ExtraVim() - while 1 - try - Xpath 8 " X: 8 - break - Xpath 16 " X: 0 - endtry - endwhile - Xpath 32 " X: 32 - asdf - Xpath 64 " X: 64 -endif - -if ExtraVim() - while 1 - try - Xpath 128 " X: 128 - break - Xpath 256 " X: 0 - finally - Xpath 512 " X: 512 - endtry - endwhile - Xpath 1024 " X: 1024 - asdf - Xpath 2048 " X: 2048 -endif - -if ExtraVim() - while 1 - try - Xpath 4096 " X: 4096 - finally - Xpath 8192 " X: 8192 - break - Xpath 16384 " X: 0 - endtry - endwhile - Xpath 32768 " X: 32768 - asdf - Xpath 65536 " X: 65536 -endif - -if ExtraVim() - let p = 1 - while p - let p = 0 - try - Xpath 131072 " X: 131072 - continue - Xpath 262144 " X: 0 - endtry - endwhile - Xpath 524288 " X: 524288 - asdf - Xpath 1048576 " X: 1048576 -endif - -if ExtraVim() - let p = 1 - while p - let p = 0 - try - Xpath 2097152 " X: 2097152 - continue - Xpath 4194304 " X: 0 - finally - Xpath 8388608 " X: 8388608 - endtry - endwhile - Xpath 16777216 " X: 16777216 - asdf - Xpath 33554432 " X: 33554432 -endif - -if ExtraVim() - let p = 1 - while p - let p = 0 - try - Xpath 67108864 " X: 67108864 - finally - Xpath 134217728 " X: 134217728 - continue - Xpath 268435456 " X: 0 - endtry - endwhile - Xpath 536870912 " X: 536870912 - asdf - Xpath 1073741824 " X: 1073741824 -endif - -Xcheck 1874575085 - - -"------------------------------------------------------------------------------- -" Test 21: :finally for :try after :continue/:break/:return/:finish {{{1 -" -" If a :try conditional stays inactive due to a preceding :continue, -" :break, :return, or :finish, its :finally clause should not be -" executed. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - function F() - let loops = 2 - XloopINIT! 1 256 - while loops > 0 - XloopNEXT - let loops = loops - 1 - try - if loops == 1 - Xloop 1 " X: 1 - continue - Xloop 2 " X: 0 - elseif loops == 0 - Xloop 4 " X: 4*256 - break - Xloop 8 " X: 0 - endif - - try " inactive - Xloop 16 " X: 0 - finally - Xloop 32 " X: 0 - endtry - finally - Xloop 64 " X: 64 + 64*256 - endtry - Xloop 128 " X: 0 - endwhile - - try - Xpath 65536 " X: 65536 - return - Xpath 131072 " X: 0 - try " inactive - Xpath 262144 " X: 0 - finally - Xpath 524288 " X: 0 - endtry - finally - Xpath 1048576 " X: 1048576 - endtry - Xpath 2097152 " X: 0 - endfunction - - try - Xpath 4194304 " X: 4194304 - call F() - Xpath 8388608 " X: 8388608 - finish - Xpath 16777216 " X: 0 - try " inactive - Xpath 33554432 " X: 0 - finally - Xpath 67108864 " X: 0 - endtry - finally - Xpath 134217728 " X: 134217728 - endtry - Xpath 268435456 " X: 0 -endif - -Xcheck 147932225 - - -"------------------------------------------------------------------------------- -" Test 22: :finally for a :try after an error/interrupt/:throw {{{1 -" -" If a :try conditional stays inactive due to a preceding error or -" interrupt or :throw, its :finally clause should not be executed. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - function! Error() - try - asdf " aborting error, triggering error exception - endtry - endfunction - - Xpath 1 " X: 1 - call Error() - Xpath 2 " X: 0 - - if 1 " not active due to error - try " not active since :if inactive - Xpath 4 " X: 0 - finally - Xpath 8 " X: 0 - endtry - endif - - try " not active due to error - Xpath 16 " X: 0 - finally - Xpath 32 " X: 0 - endtry -endif - -if ExtraVim() - function! Interrupt() - try - "INTERRUPT " triggering interrupt exception - endtry - endfunction - - Xpath 64 " X: 64 - call Interrupt() - Xpath 128 " X: 0 - - if 1 " not active due to interrupt - try " not active since :if inactive - Xpath 256 " X: 0 - finally - Xpath 512 " X: 0 - endtry - endif - - try " not active due to interrupt - Xpath 1024 " X: 0 - finally - Xpath 2048 " X: 0 - endtry -endif - -if ExtraVim() - function! Throw() - throw "xyz" - endfunction - - Xpath 4096 " X: 4096 - call Throw() - Xpath 8192 " X: 0 - - if 1 " not active due to :throw - try " not active since :if inactive - Xpath 16384 " X: 0 - finally - Xpath 32768 " X: 0 - endtry - endif - - try " not active due to :throw - Xpath 65536 " X: 0 - finally - Xpath 131072 " X: 0 - endtry -endif - -Xcheck 4161 - - -"------------------------------------------------------------------------------- -" Test 23: :catch clauses for a :try after a :throw {{{1 -" -" If a :try conditional stays inactive due to a preceding :throw, -" none of its :catch clauses should be executed. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - try - Xpath 1 " X: 1 - throw "xyz" - Xpath 2 " X: 0 - - if 1 " not active due to :throw - try " not active since :if inactive - Xpath 4 " X: 0 - catch /xyz/ - Xpath 8 " X: 0 - endtry - endif - catch /xyz/ - Xpath 16 " X: 16 - endtry - - Xpath 32 " X: 32 - throw "abc" - Xpath 64 " X: 0 - - try " not active due to :throw - Xpath 128 " X: 0 - catch /abc/ - Xpath 256 " X: 0 - endtry -endif - -Xcheck 49 - - -"------------------------------------------------------------------------------- -" Test 24: :endtry for a :try after a :throw {{{1 -" -" If a :try conditional stays inactive due to a preceding :throw, -" its :endtry should not rethrow the exception to the next surrounding -" active :try conditional. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - try " try 1 - try " try 2 - Xpath 1 " X: 1 - throw "xyz" " makes try 2 inactive - Xpath 2 " X: 0 - - try " try 3 - Xpath 4 " X: 0 - endtry " no rethrow to try 1 - catch /xyz/ " should catch although try 2 inactive - Xpath 8 " X: 8 - endtry - catch /xyz/ " try 1 active, but exception already caught - Xpath 16 " X: 0 - endtry - Xpath 32 " X: 32 -endif - -Xcheck 41 - -" Tests 25 and 26 were moved to test_trycatch.vim -let Xtest = 27 - - -"------------------------------------------------------------------------------- -" Test 27: Executing :finally clauses after :return {{{1 -" -" For a :return command dynamically enclosed in a :try/:endtry region, -" :finally clauses are executed and the called function is ended. -"------------------------------------------------------------------------------- - -XpathINIT - -function! F() - try - Xpath 1 " X: 1 - try - Xpath 2 " X: 2 - return - Xpath 4 " X: 0 - finally - Xpath 8 " X: 8 - endtry - Xpath 16 " X: 0 - finally - Xpath 32 " X: 32 - endtry - Xpath 64 " X: 0 -endfunction - -function! G() - try - Xpath 128 " X: 128 - return - Xpath 256 " X: 0 - finally - Xpath 512 " X: 512 - call F() - Xpath 1024 " X: 1024 - endtry - Xpath 2048 " X: 0 -endfunction - -function! H() - try - Xpath 4096 " X: 4096 - call G() - Xpath 8192 " X: 8192 - finally - Xpath 16384 " X: 16384 - return - Xpath 32768 " X: 0 - endtry - Xpath 65536 " X: 0 -endfunction - -try - Xpath 131072 " X: 131072 - call H() - Xpath 262144 " X: 262144 -finally - Xpath 524288 " X: 524288 -endtry -Xpath 1048576 " X: 1048576 - -Xcheck 1996459 - -" Leave F, G, and H for execution as scripts in the next test. - - -"------------------------------------------------------------------------------- -" Test 28: Executing :finally clauses after :finish {{{1 -" -" For a :finish command dynamically enclosed in a :try/:endtry region, -" :finally clauses are executed and the sourced file is finished. -" -" This test executes the bodies of the functions F, G, and H from the -" previous test as script files (:return replaced by :finish). -"------------------------------------------------------------------------------- - -XpathINIT - -let scriptF = MakeScript("F") " X: 1 + 2 + 8 + 32 -let scriptG = MakeScript("G", scriptF) " X: 128 + 512 + 1024 -let scriptH = MakeScript("H", scriptG) " X: 4096 + 8192 + 16384 - -try - Xpath 131072 " X: 131072 - exec "source" scriptH - Xpath 262144 " X: 262144 -finally - Xpath 524288 " X: 524288 -endtry -Xpath 1048576 " X: 1048576 - -call delete(scriptF) -call delete(scriptG) -call delete(scriptH) -unlet scriptF scriptG scriptH -delfunction F -delfunction G -delfunction H - -Xcheck 1996459 - - -"------------------------------------------------------------------------------- -" Test 29: Executing :finally clauses on errors {{{1 -" -" After an error in a command dynamically enclosed in a :try/:endtry -" region, :finally clauses are executed and the script processing is -" terminated. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - function! F() - while 1 - try - Xpath 1 " X: 1 - while 1 - try - Xpath 2 " X: 2 - asdf " error - Xpath 4 " X: 0 - finally - Xpath 8 " X: 8 - endtry | Xpath 16 " X: 0 - Xpath 32 " X: 0 - break - endwhile - Xpath 64 " X: 0 - finally - Xpath 128 " X: 128 - endtry | Xpath 256 " X: 0 - Xpath 512 " X: 0 - break - endwhile - Xpath 1024 " X: 0 - endfunction - - while 1 - try - Xpath 2048 " X: 2048 - while 1 - call F() - Xpath 4096 " X: 0 - break - endwhile | Xpath 8192 " X: 0 - Xpath 16384 " X: 0 - finally - Xpath 32768 " X: 32768 - endtry | Xpath 65536 " X: 0 - endwhile | Xpath 131072 " X: 0 - Xpath 262144 " X: 0 -endif - -if ExtraVim() - function! G() abort - if 1 - try - Xpath 524288 " X: 524288 - asdf " error - Xpath 1048576 " X: 0 - finally - Xpath 2097152 " X: 2097152 - endtry | Xpath 4194304 " X: 0 - endif | Xpath 8388608 " X: 0 - Xpath 16777216 " X: 0 - endfunction - - if 1 - try - Xpath 33554432 " X: 33554432 - call G() - Xpath 67108864 " X: 0 - finally - Xpath 134217728 " X: 134217728 - endtry | Xpath 268435456 " X: 0 - endif | Xpath 536870912 " X: 0 - Xpath 1073741824 " X: 0 -endif - -Xcheck 170428555 - - -"------------------------------------------------------------------------------- -" Test 30: Executing :finally clauses on interrupt {{{1 -" -" After an interrupt in a command dynamically enclosed in -" a :try/:endtry region, :finally clauses are executed and the -" script processing is terminated. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - XloopINIT 1 16 - - function! F() - try - Xloop 1 " X: 1 + 1*16 - "INTERRUPT - Xloop 2 " X: 0 - finally - Xloop 4 " X: 4 + 4*16 - endtry - Xloop 8 " X: 0 - endfunction - - try - Xpath 256 " X: 256 - try - Xpath 512 " X: 512 - "INTERRUPT - Xpath 1024 " X: 0 - finally - Xpath 2048 " X: 2048 - try - Xpath 4096 " X: 4096 - try - Xpath 8192 " X: 8192 - finally - Xpath 16384 " X: 16384 - try - Xpath 32768 " X: 32768 - "INTERRUPT - Xpath 65536 " X: 0 - endtry - Xpath 131072 " X: 0 - endtry - Xpath 262144 " X: 0 - endtry - Xpath 524288 " X: 0 - endtry - Xpath 1048576 " X: 0 - finally - Xpath 2097152 " X: 2097152 - try - Xpath 4194304 " X: 4194304 - call F() - Xpath 8388608 " X: 0 - finally - Xpath 16777216 " X: 16777216 - try - Xpath 33554432 " X: 33554432 - XloopNEXT - ExecAsScript F - Xpath 67108864 " X: 0 - finally - Xpath 134217728 " X: 134217728 - endtry - Xpath 268435456 " X: 0 - endtry - Xpath 536870912 " X: 0 - endtry - Xpath 1073741824 " X: 0 -endif - -Xcheck 190905173 - - -"------------------------------------------------------------------------------- -" Test 31: Executing :finally clauses after :throw {{{1 -" -" After a :throw dynamically enclosed in a :try/:endtry region, -" :finally clauses are executed and the script processing is -" terminated. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - XloopINIT 1 16 - - function! F() - try - Xloop 1 " X: 1 + 1*16 - throw "exception" - Xloop 2 " X: 0 - finally - Xloop 4 " X: 4 + 4*16 - endtry - Xloop 8 " X: 0 - endfunction - - try - Xpath 256 " X: 256 - try - Xpath 512 " X: 512 - throw "exception" - Xpath 1024 " X: 0 - finally - Xpath 2048 " X: 2048 - try - Xpath 4096 " X: 4096 - try - Xpath 8192 " X: 8192 - finally - Xpath 16384 " X: 16384 - try - Xpath 32768 " X: 32768 - throw "exception" - Xpath 65536 " X: 0 - endtry - Xpath 131072 " X: 0 - endtry - Xpath 262144 " X: 0 - endtry - Xpath 524288 " X: 0 - endtry - Xpath 1048576 " X: 0 - finally - Xpath 2097152 " X: 2097152 - try - Xpath 4194304 " X: 4194304 - call F() - Xpath 8388608 " X: 0 - finally - Xpath 16777216 " X: 16777216 - try - Xpath 33554432 " X: 33554432 - XloopNEXT - ExecAsScript F - Xpath 67108864 " X: 0 - finally - Xpath 134217728 " X: 134217728 - endtry - Xpath 268435456 " X: 0 - endtry - Xpath 536870912 " X: 0 - endtry - Xpath 1073741824 " X: 0 -endif - -Xcheck 190905173 - -" Tests 32 and 33 were moved to test_trycatch.vim -let Xtest = 34 - - -"------------------------------------------------------------------------------- -" Test 34: :finally reason discarded by :continue {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by a :continue in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 8 - - function! C(jump) - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "return" || a:jump == "finish" - return - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - continue " discards jump that caused the :finally - Xloop 1 " X: 0 - endtry - Xloop 2 " X: 0 - elseif loop == 2 - Xloop 4 " X: 4*(1+8+64+512+4096+32768+262144) - endif - endwhile - endfunction - - call C("continue") - Xpath 2097152 " X: 2097152 - call C("break") - Xpath 4194304 " X: 4194304 - call C("return") - Xpath 8388608 " X: 8388608 - let g:jump = "finish" - ExecAsScript C - unlet g:jump - Xpath 16777216 " X: 16777216 - try - call C("error") - Xpath 33554432 " X: 33554432 - finally - Xpath 67108864 " X: 67108864 - try - call C("interrupt") - Xpath 134217728 " X: 134217728 - finally - Xpath 268435456 " X: 268435456 - call C("throw") - Xpath 536870912 " X: 536870912 - endtry - endtry - Xpath 1073741824 " X: 1073741824 - - delfunction C - -endif - -Xcheck 2146584868 - - -"------------------------------------------------------------------------------- -" Test 35: :finally reason discarded by :break {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by a :break in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 8 - - function! B(jump) - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "return" || a:jump == "finish" - return - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - break " discards jump that caused the :finally - Xloop 1 " X: 0 - endtry - elseif loop == 2 - Xloop 2 " X: 0 - endif - endwhile - Xloop 4 " X: 4*(1+8+64+512+4096+32768+262144) - endfunction - - call B("continue") - Xpath 2097152 " X: 2097152 - call B("break") - Xpath 4194304 " X: 4194304 - call B("return") - Xpath 8388608 " X: 8388608 - let g:jump = "finish" - ExecAsScript B - unlet g:jump - Xpath 16777216 " X: 16777216 - try - call B("error") - Xpath 33554432 " X: 33554432 - finally - Xpath 67108864 " X: 67108864 - try - call B("interrupt") - Xpath 134217728 " X: 134217728 - finally - Xpath 268435456 " X: 268435456 - call B("throw") - Xpath 536870912 " X: 536870912 - endtry - endtry - Xpath 1073741824 " X: 1073741824 - - delfunction B - -endif - -Xcheck 2146584868 - - -"------------------------------------------------------------------------------- -" Test 36: :finally reason discarded by :return {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by a :return in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 8 - - function! R(jump, retval) abort - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "return" - return - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - return a:retval " discards jump that caused the :finally - Xloop 1 " X: 0 - endtry - elseif loop == 2 - Xloop 2 " X: 0 - endif - endwhile - Xloop 4 " X: 0 - endfunction - - let sum = -R("continue", -8) - Xpath 2097152 " X: 2097152 - let sum = sum - R("break", -16) - Xpath 4194304 " X: 4194304 - let sum = sum - R("return", -32) - Xpath 8388608 " X: 8388608 - try - let sum = sum - R("error", -64) - Xpath 16777216 " X: 16777216 - finally - Xpath 33554432 " X: 33554432 - try - let sum = sum - R("interrupt", -128) - Xpath 67108864 " X: 67108864 - finally - Xpath 134217728 " X: 134217728 - let sum = sum - R("throw", -256) - Xpath 268435456 " X: 268435456 - endtry - endtry - Xpath 536870912 " X: 536870912 - - let expected = 8 + 16 + 32 + 64 + 128 + 256 - if sum != expected - Xpath 1073741824 " X: 0 - Xout "sum =" . sum . ", expected: " . expected - endif - - unlet sum expected - delfunction R - -endif - -Xcheck 1071644672 - - -"------------------------------------------------------------------------------- -" Test 37: :finally reason discarded by :finish {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by a :finish in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 8 - - function! F(jump) " not executed as function, transformed to a script - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "finish" - finish - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - finish " discards jump that caused the :finally - Xloop 1 " X: 0 - endtry - elseif loop == 2 - Xloop 2 " X: 0 - endif - endwhile - Xloop 4 " X: 0 - endfunction - - let scriptF = MakeScript("F") - delfunction F - - let g:jump = "continue" - exec "source" scriptF - Xpath 2097152 " X: 2097152 - let g:jump = "break" - exec "source" scriptF - Xpath 4194304 " X: 4194304 - let g:jump = "finish" - exec "source" scriptF - Xpath 8388608 " X: 8388608 - try - let g:jump = "error" - exec "source" scriptF - Xpath 16777216 " X: 16777216 - finally - Xpath 33554432 " X: 33554432 - try - let g:jump = "interrupt" - exec "source" scriptF - Xpath 67108864 " X: 67108864 - finally - Xpath 134217728 " X: 134217728 - try - let g:jump = "throw" - exec "source" scriptF - Xpath 268435456 " X: 268435456 - finally - Xpath 536870912 " X: 536870912 - endtry - endtry - endtry - unlet g:jump - - call delete(scriptF) - unlet scriptF - -endif - -Xcheck 1071644672 - - -"------------------------------------------------------------------------------- -" Test 38: :finally reason discarded by an error {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by an error in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 4 - - function! E(jump) - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "return" || a:jump == "finish" - return - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - asdf " error; discards jump that caused the :finally - endtry - elseif loop == 2 - Xloop 1 " X: 0 - endif - endwhile - Xloop 2 " X: 0 - endfunction - - try - Xpath 16384 " X: 16384 - call E("continue") - Xpath 32768 " X: 0 - finally - try - Xpath 65536 " X: 65536 - call E("break") - Xpath 131072 " X: 0 - finally - try - Xpath 262144 " X: 262144 - call E("return") - Xpath 524288 " X: 0 - finally - try - Xpath 1048576 " X: 1048576 - let g:jump = "finish" - ExecAsScript E - Xpath 2097152 " X: 0 - finally - unlet g:jump - try - Xpath 4194304 " X: 4194304 - call E("error") - Xpath 8388608 " X: 0 - finally - try - Xpath 16777216 " X: 16777216 - call E("interrupt") - Xpath 33554432 " X: 0 - finally - try - Xpath 67108864 " X: 67108864 - call E("throw") - Xpath 134217728 " X: 0 - finally - Xpath 268435456 " X: 268435456 - delfunction E - endtry - endtry - endtry - endtry - endtry - endtry - endtry - Xpath 536870912 " X: 0 - -endif - -Xcheck 357908480 - - -"------------------------------------------------------------------------------- -" Test 39: :finally reason discarded by an interrupt {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by an interrupt in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 4 - - function! I(jump) - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "return" || a:jump == "finish" - return - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - "INTERRUPT - discards jump that caused the :finally - let dummy = 0 - endtry - elseif loop == 2 - Xloop 1 " X: 0 - endif - endwhile - Xloop 2 " X: 0 - endfunction - - try - Xpath 16384 " X: 16384 - call I("continue") - Xpath 32768 " X: 0 - finally - try - Xpath 65536 " X: 65536 - call I("break") - Xpath 131072 " X: 0 - finally - try - Xpath 262144 " X: 262144 - call I("return") - Xpath 524288 " X: 0 - finally - try - Xpath 1048576 " X: 1048576 - let g:jump = "finish" - ExecAsScript I - Xpath 2097152 " X: 0 - finally - unlet g:jump - try - Xpath 4194304 " X: 4194304 - call I("error") - Xpath 8388608 " X: 0 - finally - try - Xpath 16777216 " X: 16777216 - call I("interrupt") - Xpath 33554432 " X: 0 - finally - try - Xpath 67108864 " X: 67108864 - call I("throw") - Xpath 134217728 " X: 0 - finally - Xpath 268435456 " X: 268435456 - delfunction I - endtry - endtry - endtry - endtry - endtry - endtry - endtry - Xpath 536870912 " X: 0 - -endif - -Xcheck 357908480 - - -"------------------------------------------------------------------------------- -" Test 40: :finally reason discarded by :throw {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by a :throw in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 4 - - function! T(jump) - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "return" || a:jump == "finish" - return - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - throw "xyz" " discards jump that caused the :finally - endtry - elseif loop == 2 - Xloop 1 " X: 0 - endif - endwhile - Xloop 2 " X: 0 - endfunction - - try - Xpath 16384 " X: 16384 - call T("continue") - Xpath 32768 " X: 0 - finally - try - Xpath 65536 " X: 65536 - call T("break") - Xpath 131072 " X: 0 - finally - try - Xpath 262144 " X: 262144 - call T("return") - Xpath 524288 " X: 0 - finally - try - Xpath 1048576 " X: 1048576 - let g:jump = "finish" - ExecAsScript T - Xpath 2097152 " X: 0 - finally - unlet g:jump - try - Xpath 4194304 " X: 4194304 - call T("error") - Xpath 8388608 " X: 0 - finally - try - Xpath 16777216 " X: 16777216 - call T("interrupt") - Xpath 33554432 " X: 0 - finally - try - Xpath 67108864 " X: 67108864 - call T("throw") - Xpath 134217728 " X: 0 - finally - Xpath 268435456 " X: 268435456 - delfunction T - endtry - endtry - endtry - endtry - endtry - endtry - endtry - Xpath 536870912 " X: 0 - -endif - -Xcheck 357908480 - -" Tests 41 to 48 were moved to test_trycatch.vim -let Xtest = 49 - - -"------------------------------------------------------------------------------- -" Test 49: Throwing exceptions across functions {{{1 -" -" When an exception is thrown but not caught inside a function, the -" caller is checked for a matching :catch clause. -"------------------------------------------------------------------------------- - -XpathINIT - -function! C() - try - Xpath 1 " X: 1 - throw "arrgh" - Xpath 2 " X: 0 - catch /arrgh/ - Xpath 4 " X: 4 - endtry - Xpath 8 " X: 8 -endfunction - -XloopINIT! 16 16 - -function! T1() - XloopNEXT - try - Xloop 1 " X: 16 + 16*16 - throw "arrgh" - Xloop 2 " X: 0 - finally - Xloop 4 " X: 64 + 64*16 - endtry - Xloop 8 " X: 0 -endfunction - -function! T2() - try - Xpath 4096 " X: 4096 - call T1() - Xpath 8192 " X: 0 - finally - Xpath 16384 " X: 16384 - endtry - Xpath 32768 " X: 0 -endfunction - -try - Xpath 65536 " X: 65536 - call C() " throw and catch - Xpath 131072 " X: 131072 -catch /.*/ - Xpath 262144 " X: 0 - Xout v:exception "in" v:throwpoint -endtry - -try - Xpath 524288 " X: 524288 - call T1() " throw, one level - Xpath 1048576 " X: 0 -catch /arrgh/ - Xpath 2097152 " X: 2097152 -catch /.*/ - Xpath 4194304 " X: 0 - Xout v:exception "in" v:throwpoint -endtry - -try - Xpath 8388608 " X: 8388608 - call T2() " throw, two levels - Xpath 16777216 " X: 0 -catch /arrgh/ - Xpath 33554432 " X: 33554432 -catch /.*/ - Xpath 67108864 " X: 0 - Xout v:exception "in" v:throwpoint -endtry -Xpath 134217728 " X: 134217728 - -Xcheck 179000669 - -" Leave C, T1, and T2 for execution as scripts in the next test. - - -"------------------------------------------------------------------------------- -" Test 50: Throwing exceptions across script files {{{1 -" -" When an exception is thrown but not caught inside a script file, -" the sourcing script or function is checked for a matching :catch -" clause. -" -" This test executes the bodies of the functions C, T1, and T2 from -" the previous test as script files (:return replaced by :finish). -"------------------------------------------------------------------------------- - -XpathINIT - -let scriptC = MakeScript("C") " X: 1 + 4 + 8 -delfunction C - -XloopINIT! 16 16 - -let scriptT1 = MakeScript("T1") " X: 16 + 64 + 16*16 + 64*16 -delfunction T1 - -let scriptT2 = MakeScript("T2", scriptT1) " X: 4096 + 16384 -delfunction T2 - -function! F() - try - Xpath 65536 " X: 65536 - exec "source" g:scriptC - Xpath 131072 " X: 131072 - catch /.*/ - Xpath 262144 " X: 0 - Xout v:exception "in" v:throwpoint - endtry - - try - Xpath 524288 " X: 524288 - exec "source" g:scriptT1 - Xpath 1048576 " X: 0 - catch /arrgh/ - Xpath 2097152 " X: 2097152 - catch /.*/ - Xpath 4194304 " X: 0 - Xout v:exception "in" v:throwpoint - endtry -endfunction - -try - Xpath 8388608 " X: 8388608 - call F() - Xpath 16777216 " X: 16777216 - exec "source" scriptT2 - Xpath 33554432 " X: 0 -catch /arrgh/ - Xpath 67108864 " X: 67108864 -catch /.*/ - Xpath 134217728 " X: 0 - Xout v:exception "in" v:throwpoint -endtry -Xpath 268435456 " X: 268435456 - -call delete(scriptC) -call delete(scriptT1) -call delete(scriptT2) -unlet scriptC scriptT1 scriptT2 -delfunction F - -Xcheck 363550045 - -" Test 51 was moved to test_trycatch.vim -let Xtest = 52 - - -"------------------------------------------------------------------------------- -" Test 52: Uncaught exceptions {{{1 -" -" When an exception is thrown but not caught, an error message is -" displayed when the script is terminated. In case of an interrupt -" or error exception, the normal interrupt or error message(s) are -" displayed. -"------------------------------------------------------------------------------- - -XpathINIT - -let msgfile = tempname() - -function! MESSAGES(...) - try - exec "edit" g:msgfile - catch /^Vim(edit):/ - return 0 - endtry - - let english = v:lang == "C" || v:lang =~ '^[Ee]n' - let match = 1 - norm gg - - let num = a:0 / 2 - let cnt = 1 - while cnt <= num - let enr = a:{2*cnt - 1} - let emsg= a:{2*cnt} - let cnt = cnt + 1 - - if enr == "" - Xout "TODO: Add message number for:" emsg - elseif enr == "INT" - let enr = "" - endif - if enr == "" && !english - continue - endif - let pattern = (enr != "") ? enr . ':.*' : '' - if english - let pattern = pattern . emsg - endif - if !search(pattern, "W") - let match = 0 - Xout "No match for:" pattern - endif - norm $ - endwhile - - bwipeout! - return match -endfunction - -if ExtraVim(msgfile) - Xpath 1 " X: 1 - throw "arrgh" -endif - -Xpath 2 " X: 2 -if !MESSAGES('E605', "Exception not caught") - Xpath 4 " X: 0 -endif - -if ExtraVim(msgfile) - try - Xpath 8 " X: 8 - throw "oops" - catch /arrgh/ - Xpath 16 " X: 0 - endtry - Xpath 32 " X: 0 -endif - -Xpath 64 " X: 64 -if !MESSAGES('E605', "Exception not caught") - Xpath 128 " X: 0 -endif - -if ExtraVim(msgfile) - function! T() - throw "brrr" - endfunction - - try - Xpath 256 " X: 256 - throw "arrgh" - catch /.*/ - Xpath 512 " X: 512 - call T() - endtry - Xpath 1024 " X: 0 -endif - -Xpath 2048 " X: 2048 -if !MESSAGES('E605', "Exception not caught") - Xpath 4096 " X: 0 -endif - -if ExtraVim(msgfile) - try - Xpath 8192 " X: 8192 - throw "arrgh" - finally - Xpath 16384 " X: 16384 - throw "brrr" - endtry - Xpath 32768 " X: 0 -endif - -Xpath 65536 " X: 65536 -if !MESSAGES('E605', "Exception not caught") - Xpath 131072 " X: 0 -endif - -if ExtraVim(msgfile) - try - Xpath 262144 " X: 262144 - "INTERRUPT - endtry - Xpath 524288 " X: 0 -endif - -Xpath 1048576 " X: 1048576 -if !MESSAGES('INT', "Interrupted") - Xpath 2097152 " X: 0 -endif - -if ExtraVim(msgfile) - try - Xpath 4194304 " X: 4194304 - let x = novar " error E121/E15; exception: E121 - catch /E15:/ " should not catch - Xpath 8388608 " X: 0 - endtry - Xpath 16777216 " X: 0 -endif - -Xpath 33554432 " X: 33554432 -if !MESSAGES('E121', "Undefined variable", 'E15', "Invalid expression") - Xpath 67108864 " X: 0 -endif - -if ExtraVim(msgfile) - try - Xpath 134217728 " X: 134217728 -" unlet novar # " error E108/E488; exception: E488 - catch /E108:/ " should not catch - Xpath 268435456 " X: 0 - endtry - Xpath 536870912 " X: 0 -endif - -Xpath 1073741824 " X: 1073741824 -if !MESSAGES('E108', "No such variable", 'E488', "Trailing characters") - " The Xpath command does not accept 2^31 (negative); add explicitly: - let Xpath = Xpath + 2147483648 " X: 0 -endif - -call delete(msgfile) -unlet msgfile - -Xcheck 1247112011 - -" Leave MESSAGES() for the next tests. - - -"------------------------------------------------------------------------------- -" Test 53: Nesting errors: :endif/:else/:elseif {{{1 -" -" For nesting errors of :if conditionals the correct error messages -" should be given. -" -" This test reuses the function MESSAGES() from the previous test. -" This functions checks the messages in g:msgfile. -"------------------------------------------------------------------------------- - -XpathINIT - -let msgfile = tempname() - -if ExtraVim(msgfile) -" endif -endif -if MESSAGES('E580', ":endif without :if") - Xpath 1 " X: 1 -endif - -if ExtraVim(msgfile) -" while 1 -" endif -" endwhile -endif -if MESSAGES('E580', ":endif without :if") - Xpath 2 " X: 2 -endif - -if ExtraVim(msgfile) -" try -" finally -" endif -" endtry -endif -if MESSAGES('E580', ":endif without :if") - Xpath 4 " X: 4 -endif - -if ExtraVim(msgfile) -" try -" endif -" endtry -endif -if MESSAGES('E580', ":endif without :if") - Xpath 8 " X: 8 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" endif -" endtry -endif -if MESSAGES('E580', ":endif without :if") - Xpath 16 " X: 16 -endif - -if ExtraVim(msgfile) -" else -endif -if MESSAGES('E581', ":else without :if") - Xpath 32 " X: 32 -endif - -if ExtraVim(msgfile) -" while 1 -" else -" endwhile -endif -if MESSAGES('E581', ":else without :if") - Xpath 64 " X: 64 -endif - -if ExtraVim(msgfile) -" try -" finally -" else -" endtry -endif -if MESSAGES('E581', ":else without :if") - Xpath 128 " X: 128 -endif - -if ExtraVim(msgfile) -" try -" else -" endtry -endif -if MESSAGES('E581', ":else without :if") - Xpath 256 " X: 256 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" else -" endtry -endif -if MESSAGES('E581', ":else without :if") - Xpath 512 " X: 512 -endif - -if ExtraVim(msgfile) -" elseif -endif -if MESSAGES('E582', ":elseif without :if") - Xpath 1024 " X: 1024 -endif - -if ExtraVim(msgfile) -" while 1 -" elseif -" endwhile -endif -if MESSAGES('E582', ":elseif without :if") - Xpath 2048 " X: 2048 -endif - -if ExtraVim(msgfile) -" try -" finally -" elseif -" endtry -endif -if MESSAGES('E582', ":elseif without :if") - Xpath 4096 " X: 4096 -endif - -if ExtraVim(msgfile) -" try -" elseif -" endtry -endif -if MESSAGES('E582', ":elseif without :if") - Xpath 8192 " X: 8192 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" elseif -" endtry -endif -if MESSAGES('E582', ":elseif without :if") - Xpath 16384 " X: 16384 -endif - -if ExtraVim(msgfile) -" if 1 -" else -" else -" endif -endif -if MESSAGES('E583', "multiple :else") - Xpath 32768 " X: 32768 -endif - -if ExtraVim(msgfile) -" if 1 -" else -" elseif 1 -" endif -endif -if MESSAGES('E584', ":elseif after :else") - Xpath 65536 " X: 65536 -endif - -call delete(msgfile) -unlet msgfile - -Xcheck 131071 - -" Leave MESSAGES() for the next test. - - -"------------------------------------------------------------------------------- -" Test 54: Nesting errors: :while/:endwhile {{{1 -" -" For nesting errors of :while conditionals the correct error messages -" should be given. -" -" This test reuses the function MESSAGES() from the previous test. -" This functions checks the messages in g:msgfile. -"------------------------------------------------------------------------------- - -XpathINIT - -let msgfile = tempname() - -if ExtraVim(msgfile) -" endwhile -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 1 " X: 1 -endif - -if ExtraVim(msgfile) -" if 1 -" endwhile -" endif -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 2 " X: 2 -endif - -if ExtraVim(msgfile) -" while 1 -" if 1 -" endwhile -endif -if MESSAGES('E171', "Missing :endif") - Xpath 4 " X: 4 -endif - -if ExtraVim(msgfile) -" try -" finally -" endwhile -" endtry -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 8 " X: 8 -endif - -if ExtraVim(msgfile) -" while 1 -" try -" finally -" endwhile -endif -if MESSAGES('E600', "Missing :endtry") - Xpath 16 " X: 16 -endif - -if ExtraVim(msgfile) -" while 1 -" if 1 -" try -" finally -" endwhile -endif -if MESSAGES('E600', "Missing :endtry") - Xpath 32 " X: 32 -endif - -if ExtraVim(msgfile) -" while 1 -" try -" finally -" if 1 -" endwhile -endif -if MESSAGES('E171', "Missing :endif") - Xpath 64 " X: 64 -endif - -if ExtraVim(msgfile) -" try -" endwhile -" endtry -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 128 " X: 128 -endif - -if ExtraVim(msgfile) -" while 1 -" try -" endwhile -" endtry -" endwhile -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 256 " X: 256 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" endwhile -" endtry -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 512 " X: 512 -endif - -if ExtraVim(msgfile) -" while 1 -" try -" throw "a" -" catch /a/ -" endwhile -" endtry -" endwhile -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 1024 " X: 1024 -endif - - -call delete(msgfile) -unlet msgfile - -Xcheck 2047 - -" Leave MESSAGES() for the next test. - - -"------------------------------------------------------------------------------- -" Test 55: Nesting errors: :continue/:break {{{1 -" -" For nesting errors of :continue and :break commands the correct -" error messages should be given. -" -" This test reuses the function MESSAGES() from the previous test. -" This functions checks the messages in g:msgfile. -"------------------------------------------------------------------------------- - -XpathINIT - -let msgfile = tempname() - -if ExtraVim(msgfile) -" continue -endif -if MESSAGES('E586', ":continue without :while") - Xpath 1 " X: 1 -endif - -if ExtraVim(msgfile) -" if 1 -" continue -" endif -endif -if MESSAGES('E586', ":continue without :while") - Xpath 2 " X: 2 -endif - -if ExtraVim(msgfile) -" try -" finally -" continue -" endtry -endif -if MESSAGES('E586', ":continue without :while") - Xpath 4 " X: 4 -endif - -if ExtraVim(msgfile) -" try -" continue -" endtry -endif -if MESSAGES('E586', ":continue without :while") - Xpath 8 " X: 8 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" continue -" endtry -endif -if MESSAGES('E586', ":continue without :while") - Xpath 16 " X: 16 -endif - -if ExtraVim(msgfile) -" break -endif -if MESSAGES('E587', ":break without :while") - Xpath 32 " X: 32 -endif - -if ExtraVim(msgfile) -" if 1 -" break -" endif -endif -if MESSAGES('E587', ":break without :while") - Xpath 64 " X: 64 -endif - -if ExtraVim(msgfile) -" try -" finally -" break -" endtry -endif -if MESSAGES('E587', ":break without :while") - Xpath 128 " X: 128 -endif - -if ExtraVim(msgfile) -" try -" break -" endtry -endif -if MESSAGES('E587', ":break without :while") - Xpath 256 " X: 256 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" break -" endtry -endif -if MESSAGES('E587', ":break without :while") - Xpath 512 " X: 512 -endif - -call delete(msgfile) -unlet msgfile - -Xcheck 1023 - -" Leave MESSAGES() for the next test. - - -"------------------------------------------------------------------------------- -" Test 56: Nesting errors: :endtry {{{1 -" -" For nesting errors of :try conditionals the correct error messages -" should be given. -" -" This test reuses the function MESSAGES() from the previous test. -" This functions checks the messages in g:msgfile. -"------------------------------------------------------------------------------- - -XpathINIT - -let msgfile = tempname() - -if ExtraVim(msgfile) -" endtry -endif -if MESSAGES('E602', ":endtry without :try") - Xpath 1 " X: 1 -endif - -if ExtraVim(msgfile) -" if 1 -" endtry -" endif -endif -if MESSAGES('E602', ":endtry without :try") - Xpath 2 " X: 2 -endif - -if ExtraVim(msgfile) -" while 1 -" endtry -" endwhile -endif -if MESSAGES('E602', ":endtry without :try") - Xpath 4 " X: 4 -endif - -if ExtraVim(msgfile) -" try -" if 1 -" endtry -endif -if MESSAGES('E171', "Missing :endif") - Xpath 8 " X: 8 -endif - -if ExtraVim(msgfile) -" try -" while 1 -" endtry -endif -if MESSAGES('E170', "Missing :endwhile") - Xpath 16 " X: 16 -endif - -if ExtraVim(msgfile) -" try -" finally -" if 1 -" endtry -endif -if MESSAGES('E171', "Missing :endif") - Xpath 32 " X: 32 -endif - -if ExtraVim(msgfile) -" try -" finally -" while 1 -" endtry -endif -if MESSAGES('E170', "Missing :endwhile") - Xpath 64 " X: 64 -endif - -if ExtraVim(msgfile) - try - Xpath 4194304 " X: 4194304 - let x = novar " error E121; exception: E121 - catch /E15:/ " should not catch - Xpath 8388608 " X: 0 - endtry - Xpath 16777216 " X: 0 -endif - -Xpath 33554432 " X: 33554432 -if !MESSAGES('E121', "Undefined variable") - Xpath 67108864 " X: 0 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" while 1 -" endtry -endif -if MESSAGES('E170', "Missing :endwhile") - Xpath 256 " X: 256 -endif - -call delete(msgfile) -unlet msgfile - -delfunction MESSAGES - -Xcheck 511 - - -"------------------------------------------------------------------------------- -" Test 57: v:exception and v:throwpoint for user exceptions {{{1 -" -" v:exception evaluates to the value of the exception that was caught -" most recently and is not finished. (A caught exception is finished -" when the next ":catch", ":finally", or ":endtry" is reached.) -" v:throwpoint evaluates to the script/function name and line number -" where that exception has been thrown. -"------------------------------------------------------------------------------- - -XpathINIT - -function! FuncException() - let g:exception = v:exception -endfunction - -function! FuncThrowpoint() - let g:throwpoint = v:throwpoint -endfunction - -let scriptException = MakeScript("FuncException") -let scriptThrowPoint = MakeScript("FuncThrowpoint") - -command! CmdException let g:exception = v:exception -command! CmdThrowpoint let g:throwpoint = v:throwpoint - -XloopINIT! 1 2 - -function! CHECK(n, exception, throwname, throwline) - XloopNEXT - let error = 0 - if v:exception != a:exception - Xout a:n.": v:exception is" v:exception "instead of" a:exception - let error = 1 - endif - if v:throwpoint !~ a:throwname - let name = escape(a:throwname, '\') - Xout a:n.": v:throwpoint (".v:throwpoint.") does not match" name - let error = 1 - endif - if v:throwpoint !~ a:throwline - let line = escape(a:throwline, '\') - Xout a:n.": v:throwpoint (".v:throwpoint.") does not match" line - let error = 1 - endif - if error - Xloop 1 " X: 0 - endif -endfunction - -function! T(arg, line) - if a:line == 2 - throw a:arg " in line 2 - elseif a:line == 4 - throw a:arg " in line 4 - elseif a:line == 6 - throw a:arg " in line 6 - elseif a:line == 8 - throw a:arg " in line 8 - endif -endfunction - -function! G(arg, line) - call T(a:arg, a:line) -endfunction - -function! F(arg, line) - call G(a:arg, a:line) -endfunction - -let scriptT = MakeScript("T") -let scriptG = MakeScript("G", scriptT) -let scriptF = MakeScript("F", scriptG) - -try - Xpath 32768 " X: 32768 - call F("oops", 2) -catch /.*/ - Xpath 65536 " X: 65536 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(1, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') - exec "let exception = v:exception" - exec "let throwpoint = v:throwpoint" - call CHECK(2, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') - CmdException - CmdThrowpoint - call CHECK(3, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') - call FuncException() - call FuncThrowpoint() - call CHECK(4, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') - exec "source" scriptException - exec "source" scriptThrowPoint - call CHECK(5, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') - try - Xpath 131072 " X: 131072 - call G("arrgh", 4) - catch /.*/ - Xpath 262144 " X: 262144 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(6, "arrgh", '\<G\[1]\.\.T\>', '\<4\>') - try - Xpath 524288 " X: 524288 - let g:arg = "autsch" - let g:line = 6 - exec "source" scriptF - catch /.*/ - Xpath 1048576 " X: 1048576 - let exception = v:exception - let throwpoint = v:throwpoint - " Symbolic links in tempname()s are not resolved, whereas resolving - " is done for v:throwpoint. Resolve the temporary file name for - " scriptT, so that it can be matched against v:throwpoint. - call CHECK(7, "autsch", resolve(scriptT), '\<6\>') - finally - Xpath 2097152 " X: 2097152 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(8, "arrgh", '\<G\[1]\.\.T\>', '\<4\>') - try - Xpath 4194304 " X: 4194304 - let g:arg = "brrrr" - let g:line = 8 - exec "source" scriptG - catch /.*/ - Xpath 8388608 " X: 8388608 - let exception = v:exception - let throwpoint = v:throwpoint - " Resolve scriptT for matching it against v:throwpoint. - call CHECK(9, "brrrr", resolve(scriptT), '\<8\>') - finally - Xpath 16777216 " X: 16777216 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(10, "arrgh", '\<G\[1]\.\.T\>', '\<4\>') - endtry - Xpath 33554432 " X: 33554432 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(11, "arrgh", '\<G\[1]\.\.T\>', '\<4\>') - endtry - Xpath 67108864 " X: 67108864 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(12, "arrgh", '\<G\[1]\.\.T\>', '\<4\>') - finally - Xpath 134217728 " X: 134217728 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(13, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') - endtry - Xpath 268435456 " X: 268435456 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(14, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') -finally - Xpath 536870912 " X: 536870912 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(15, "", '^$', '^$') -endtry - -Xpath 1073741824 " X: 1073741824 - -unlet exception throwpoint -delfunction FuncException -delfunction FuncThrowpoint -call delete(scriptException) -call delete(scriptThrowPoint) -unlet scriptException scriptThrowPoint -delcommand CmdException -delcommand CmdThrowpoint -delfunction T -delfunction G -delfunction F -call delete(scriptT) -call delete(scriptG) -call delete(scriptF) -unlet scriptT scriptG scriptF - -Xcheck 2147450880 - - -"------------------------------------------------------------------------------- -" -" Test 58: v:exception and v:throwpoint for error/interrupt exceptions {{{1 -" -" v:exception and v:throwpoint work also for error and interrupt -" exceptions. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - function! T(line) - if a:line == 2 - delfunction T " error (function in use) in line 2 - elseif a:line == 4 - let dummy = 0 " INTERRUPT1 - interrupt in line 4 - endif - endfunction - - while 1 - try - Xpath 1 " X: 1 - let caught = 0 - call T(2) - catch /.*/ - let caught = 1 - if v:exception !~ 'Vim(delfunction):' - Xpath 2 " X: 0 - endif - if v:throwpoint !~ '\<T\>' - Xpath 4 " X: 0 - endif - if v:throwpoint !~ '\<2\>' - Xpath 8 " X: 0 - endif - finally - Xpath 16 " X: 16 - if caught || $VIMNOERRTHROW - Xpath 32 " X: 32 - endif - if v:exception != "" - Xpath 64 " X: 0 - endif - if v:throwpoint != "" - Xpath 128 " X: 0 - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - Xpath 256 " X: 256 - if v:exception != "" - Xpath 512 " X: 0 - endif - if v:throwpoint != "" - Xpath 1024 " X: 0 - endif - - while 1 - try - Xpath 2048 " X: 2048 - let caught = 0 - call T(4) - catch /.*/ - let caught = 1 - if v:exception != 'Vim:Interrupt' - Xpath 4096 " X: 0 - endif - if v:throwpoint !~ '\<T\>' - Xpath 8192 " X: 0 - endif - if v:throwpoint !~ '\<4\>' - Xpath 16384 " X: 0 - endif - finally - Xpath 32768 " X: 32768 - if caught || $VIMNOINTTHROW - Xpath 65536 " X: 65536 - endif - if v:exception != "" - Xpath 131072 " X: 0 - endif - if v:throwpoint != "" - Xpath 262144 " X: 0 - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - Xpath 524288 " X: 524288 - if v:exception != "" - Xpath 1048576 " X: 0 - endif - if v:throwpoint != "" - Xpath 2097152 " X: 0 - endif - -endif - -Xcheck 624945 - - -"------------------------------------------------------------------------------- -" -" Test 59: v:exception and v:throwpoint when discarding exceptions {{{1 -" -" When a :catch clause is left by a ":break" etc or an error or -" interrupt exception, v:exception and v:throwpoint are reset. They -" are not affected by an exception that is discarded before being -" caught. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 2 - - let sfile = expand("<sfile>") - - function! LineNumber() - return substitute(substitute(v:throwpoint, g:sfile, '', ""), - \ '\D*\(\d*\).*', '\1', "") - endfunction - - command! -nargs=1 SetLineNumber - \ try | throw "line" | catch /.*/ | let <args> = LineNumber() | endtry - - " Check v:exception/v:throwpoint against second/fourth parameter if - " specified, check for being empty else. - function! CHECK(n, ...) - XloopNEXT - let exception = a:0 != 0 ? a:1 : "" " second parameter (optional) - let emsg = a:0 != 0 ? a:2 : "" " third parameter (optional) - let line = a:0 != 0 ? a:3 : 0 " fourth parameter (optional) - let error = 0 - if emsg != "" - " exception is the error number, emsg the English error message text - if exception !~ '^E\d\+$' - Xout "TODO: Add message number for:" emsg - elseif v:lang == "C" || v:lang =~ '^[Ee]n' - if exception == "E492" && emsg == "Not an editor command" - let exception = '^Vim:' . exception . ': ' . emsg - else - let exception = '^Vim(\a\+):' . exception . ': ' . emsg - endif - else - if exception == "E492" - let exception = '^Vim:' . exception - else - let exception = '^Vim(\a\+):' . exception - endif - endif - endif - if exception == "" && v:exception != "" - Xout a:n.": v:exception is set:" v:exception - let error = 1 - elseif exception != "" && v:exception !~ exception - Xout a:n.": v:exception (".v:exception.") does not match" exception - let error = 1 - endif - if line == 0 && v:throwpoint != "" - Xout a:n.": v:throwpoint is set:" v:throwpoint - let error = 1 - elseif line != 0 && v:throwpoint !~ '\<' . line . '\>' - Xout a:n.": v:throwpoint (".v:throwpoint.") does not match" line - let error = 1 - endif - if !error - Xloop 1 " X: 2097151 - endif - endfunction - - while 1 - try - throw "x1" - catch /.*/ - break - endtry - endwhile - call CHECK(1) - - while 1 - try - throw "x2" - catch /.*/ - break - finally - call CHECK(2) - endtry - break - endwhile - call CHECK(3) - - while 1 - try - let errcaught = 0 - try - try - throw "x3" - catch /.*/ - SetLineNumber line_before_error - asdf - endtry - catch /.*/ - let errcaught = 1 - call CHECK(4, 'E492', "Not an editor command", - \ line_before_error + 1) - endtry - finally - if !errcaught && $VIMNOERRTHROW - call CHECK(4) - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - call CHECK(5) - - Xpath 2097152 " X: 2097152 - - while 1 - try - let intcaught = 0 - try - try - throw "x4" - catch /.*/ - SetLineNumber two_lines_before_interrupt - "INTERRUPT - let dummy = 0 - endtry - catch /.*/ - let intcaught = 1 - call CHECK(6, "Vim:Interrupt", '', - \ two_lines_before_interrupt + 2) - endtry - finally - if !intcaught && $VIMNOINTTHROW - call CHECK(6) - endif - break " discard interrupt for $VIMNOINTTHROW - endtry - endwhile - call CHECK(7) - - Xpath 4194304 " X: 4194304 - - while 1 - try - let errcaught = 0 - try - try -" if 1 - SetLineNumber line_before_throw - throw "x5" - " missing endif - catch /.*/ - Xpath 8388608 " X: 0 - endtry - catch /.*/ - let errcaught = 1 - call CHECK(8, 'E171', "Missing :endif", line_before_throw + 3) - endtry - finally - if !errcaught && $VIMNOERRTHROW - call CHECK(8) - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - call CHECK(9) - - Xpath 16777216 " X: 16777216 - - try - while 1 - try - throw "x6" - finally - break - endtry - break - endwhile - catch /.*/ - Xpath 33554432 " X: 0 - endtry - call CHECK(10) - - try - while 1 - try - throw "x7" - finally - break - endtry - break - endwhile - catch /.*/ - Xpath 67108864 " X: 0 - finally - call CHECK(11) - endtry - call CHECK(12) - - while 1 - try - let errcaught = 0 - try - try - throw "x8" - finally - SetLineNumber line_before_error - asdf - endtry - catch /.*/ - let errcaught = 1 - call CHECK(13, 'E492', "Not an editor command", - \ line_before_error + 1) - endtry - finally - if !errcaught && $VIMNOERRTHROW - call CHECK(13) - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - call CHECK(14) - - Xpath 134217728 " X: 134217728 - - while 1 - try - let intcaught = 0 - try - try - throw "x9" - finally - SetLineNumber two_lines_before_interrupt - "INTERRUPT - endtry - catch /.*/ - let intcaught = 1 - call CHECK(15, "Vim:Interrupt", '', - \ two_lines_before_interrupt + 2) - endtry - finally - if !intcaught && $VIMNOINTTHROW - call CHECK(15) - endif - break " discard interrupt for $VIMNOINTTHROW - endtry - endwhile - call CHECK(16) - - Xpath 268435456 " X: 268435456 - - while 1 - try - let errcaught = 0 - try - try -" if 1 - SetLineNumber line_before_throw - throw "x10" - " missing endif - finally - call CHECK(17) - endtry - catch /.*/ - let errcaught = 1 - call CHECK(18, 'E171', "Missing :endif", line_before_throw + 3) - endtry - finally - if !errcaught && $VIMNOERRTHROW - call CHECK(18) - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - call CHECK(19) - - Xpath 536870912 " X: 536870912 - - while 1 - try - let errcaught = 0 - try - try -" if 1 - SetLineNumber line_before_throw - throw "x11" - " missing endif - endtry - catch /.*/ - let errcaught = 1 - call CHECK(20, 'E171', "Missing :endif", line_before_throw + 3) - endtry - finally - if !errcaught && $VIMNOERRTHROW - call CHECK(20) - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - call CHECK(21) - - Xpath 1073741824 " X: 1073741824 - -endif - -Xcheck 2038431743 - - -"------------------------------------------------------------------------------- -" -" Test 60: (Re)throwing v:exception; :echoerr. {{{1 -" -" A user exception can be rethrown after catching by throwing -" v:exception. An error or interrupt exception cannot be rethrown -" because Vim exceptions cannot be faked. A Vim exception using the -" value of v:exception can, however, be triggered by the :echoerr -" command. -"------------------------------------------------------------------------------- - -XpathINIT - -try - try - Xpath 1 " X: 1 - throw "oops" - catch /oops/ - Xpath 2 " X: 2 - throw v:exception " rethrow user exception - catch /.*/ - Xpath 4 " X: 0 - endtry -catch /^oops$/ " catches rethrown user exception - Xpath 8 " X: 8 -catch /.*/ - Xpath 16 " X: 0 -endtry - -function! F() - try - let caught = 0 - try - Xpath 32 " X: 32 - write /n/o/n/w/r/i/t/a/b/l/e/_/f/i/l/e - Xpath 64 " X: 0 - Xout "did_emsg was reset before executing " . - \ "BufWritePost autocommands." - catch /^Vim(write):/ - let caught = 1 - throw v:exception " throw error: cannot fake Vim exception - catch /.*/ - Xpath 128 " X: 0 - finally - Xpath 256 " X: 256 - if !caught && !$VIMNOERRTHROW - Xpath 512 " X: 0 - endif - endtry - catch /^Vim(throw):/ " catches throw error - let caught = caught + 1 - catch /.*/ - Xpath 1024 " X: 0 - finally - Xpath 2048 " X: 2048 - if caught != 2 - if !caught && !$VIMNOERRTHROW - Xpath 4096 " X: 0 - elseif caught - Xpath 8192 " X: 0 - endif - return | " discard error for $VIMNOERRTHROW - endif - endtry -endfunction - -call F() -delfunction F - -function! G() - try - let caught = 0 - try - Xpath 16384 " X: 16384 - asdf - catch /^Vim/ " catch error exception - let caught = 1 - " Trigger Vim error exception with value specified after :echoerr - let value = substitute(v:exception, '^Vim\((.*)\)\=:', '', "") - echoerr value - catch /.*/ - Xpath 32768 " X: 0 - finally - Xpath 65536 " X: 65536 - if !caught - if !$VIMNOERRTHROW - Xpath 131072 " X: 0 - else - let value = "Error" - echoerr value - endif - endif - endtry - catch /^Vim(echoerr):/ - let caught = caught + 1 - if v:exception !~ value - Xpath 262144 " X: 0 - endif - catch /.*/ - Xpath 524288 " X: 0 - finally - Xpath 1048576 " X: 1048576 - if caught != 2 - if !caught && !$VIMNOERRTHROW - Xpath 2097152 " X: 0 - elseif caught - Xpath 4194304 " X: 0 - endif - return | " discard error for $VIMNOERRTHROW - endif - endtry -endfunction - -call G() -delfunction G - -unlet! value caught - -if ExtraVim() - try - let errcaught = 0 - try - Xpath 8388608 " X: 8388608 - let intcaught = 0 - "INTERRUPT - catch /^Vim:/ " catch interrupt exception - let intcaught = 1 - " Trigger Vim error exception with value specified after :echoerr - echoerr substitute(v:exception, '^Vim\((.*)\)\=:', '', "") - catch /.*/ - Xpath 16777216 " X: 0 - finally - Xpath 33554432 " X: 33554432 - if !intcaught - if !$VIMNOINTTHROW - Xpath 67108864 " X: 0 - else - echoerr "Interrupt" - endif - endif - endtry - catch /^Vim(echoerr):/ - let errcaught = 1 - if v:exception !~ "Interrupt" - Xpath 134217728 " X: 0 - endif - finally - Xpath 268435456 " X: 268435456 - if !errcaught && !$VIMNOERRTHROW - Xpath 536870912 " X: 0 - endif - endtry -endif - -Xcheck 311511339 - - -"------------------------------------------------------------------------------- -" Test 61: Catching interrupt exceptions {{{1 -" -" When an interrupt occurs inside a :try/:endtry region, an -" interrupt exception is thrown and can be caught. Its value is -" "Vim:Interrupt". If the interrupt occurs after an error or a :throw -" but before a matching :catch is reached, all following :catches of -" that try block are ignored, but the interrupt exception can be -" caught by the next surrounding try conditional. An interrupt is -" ignored when there is a previous interrupt that has not been caught -" or causes a :finally clause to be executed. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - while 1 - try - try - Xpath 1 " X: 1 - let caught = 0 - "INTERRUPT - Xpath 2 " X: 0 - catch /^Vim:Interrupt$/ - let caught = 1 - finally - Xpath 4 " X: 4 - if caught || $VIMNOINTTHROW - Xpath 8 " X: 8 - endif - endtry - catch /.*/ - Xpath 16 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard interrupt for $VIMNOINTTHROW - endtry - endwhile - - while 1 - try - try - let caught = 0 - try - Xpath 32 " X: 32 - asdf - Xpath 64 " X: 0 - catch /do_not_catch/ - Xpath 128 " X: 0 - catch /.*/ "INTERRUPT - throw interrupt if !$VIMNOERRTHROW - Xpath 256 " X: 0 - catch /.*/ - Xpath 512 " X: 0 - finally "INTERRUPT - throw interrupt if $VIMNOERRTHROW - Xpath 1024 " X: 1024 - endtry - catch /^Vim:Interrupt$/ - let caught = 1 - finally - Xpath 2048 " X: 2048 - if caught || $VIMNOINTTHROW - Xpath 4096 " X: 4096 - endif - endtry - catch /.*/ - Xpath 8192 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard interrupt for $VIMNOINTTHROW - endtry - endwhile - - while 1 - try - try - let caught = 0 - try - Xpath 16384 " X: 16384 - throw "x" - Xpath 32768 " X: 0 - catch /do_not_catch/ - Xpath 65536 " X: 0 - catch /x/ "INTERRUPT - Xpath 131072 " X: 0 - catch /.*/ - Xpath 262144 " X: 0 - endtry - catch /^Vim:Interrupt$/ - let caught = 1 - finally - Xpath 524288 " X: 524288 - if caught || $VIMNOINTTHROW - Xpath 1048576 " X: 1048576 - endif - endtry - catch /.*/ - Xpath 2097152 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard interrupt for $VIMNOINTTHROW - endtry - endwhile - - while 1 - try - let caught = 0 - try - Xpath 4194304 " X: 4194304 - "INTERRUPT - Xpath 8388608 " X: 0 - catch /do_not_catch/ "INTERRUPT - Xpath 16777216 " X: 0 - catch /^Vim:Interrupt$/ - let caught = 1 - finally - Xpath 33554432 " X: 33554432 - if caught || $VIMNOINTTHROW - Xpath 67108864 " X: 67108864 - endif - endtry - catch /.*/ - Xpath 134217728 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard interrupt for $VIMNOINTTHROW - endtry - endwhile - - Xpath 268435456 " X: 268435456 - -endif - -Xcheck 374889517 - - -"------------------------------------------------------------------------------- -" Test 62: Catching error exceptions {{{1 -" -" An error inside a :try/:endtry region is converted to an exception -" and can be caught. The error exception has a "Vim(cmdname):" prefix -" where cmdname is the name of the failing command, or a "Vim:" prefix -" if no command name is known. The "Vim" prefixes cannot be faked. -"------------------------------------------------------------------------------- - -XpathINIT - -function! MSG(enr, emsg) - let english = v:lang == "C" || v:lang =~ '^[Ee]n' - if a:enr == "" - Xout "TODO: Add message number for:" a:emsg - let v:errmsg = ":" . v:errmsg - endif - let match = 1 - if v:errmsg !~ '^'.a:enr.':' || (english && v:errmsg !~ a:emsg) - let match = 0 - if v:errmsg == "" - Xout "Message missing." - else - let v:errmsg = escape(v:errmsg, '"') - Xout "Unexpected message:" v:errmsg - endif - endif - return match -endfunction - -while 1 - try - try - let caught = 0 - unlet novar - catch /^Vim(unlet):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(unlet):', '', "") - finally - Xpath 1 " X: 1 - if !caught && !$VIMNOERRTHROW - Xpath 2 " X: 0 - endif - if !MSG('E108', "No such variable") - Xpath 4 " X: 0 - endif - endtry - catch /.*/ - Xpath 8 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - try - let caught = 0 - throw novar " error in :throw - catch /^Vim(throw):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(throw):', '', "") - finally - Xpath 16 " X: 16 - if !caught && !$VIMNOERRTHROW - Xpath 32 " X: 0 - endif - if caught ? !MSG('E121', "Undefined variable") - \ : !MSG('E15', "Invalid expression") - Xpath 64 " X: 0 - endif - endtry - catch /.*/ - Xpath 128 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - try - let caught = 0 - throw "Vim:faked" " error: cannot fake Vim exception - catch /^Vim(throw):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(throw):', '', "") - finally - Xpath 256 " X: 256 - if !caught && !$VIMNOERRTHROW - Xpath 512 " X: 0 - endif - if !MSG('E608', "Cannot :throw exceptions with 'Vim' prefix") - Xpath 1024 " X: 0 - endif - endtry - catch /.*/ - Xpath 2048 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -function! F() - while 1 - " Missing :endwhile -endfunction - -while 1 - try - try - let caught = 0 - call F() - catch /^Vim(endfunction):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(endfunction):', '', "") - finally - Xpath 4096 " X: 4096 - if !caught && !$VIMNOERRTHROW - Xpath 8192 " X: 0 - endif - if !MSG('E170', "Missing :endwhile") - Xpath 16384 " X: 0 - endif - endtry - catch /.*/ - Xpath 32768 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - try - let caught = 0 - ExecAsScript F - catch /^Vim:/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim:', '', "") - finally - Xpath 65536 " X: 65536 - if !caught && !$VIMNOERRTHROW - Xpath 131072 " X: 0 - endif - if !MSG('E170', "Missing :endwhile") - Xpath 262144 " X: 0 - endif - endtry - catch /.*/ - Xpath 524288 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -function! G() - call G() -endfunction - -while 1 - try - let mfd_save = &mfd - set mfd=3 - try - let caught = 0 - call G() - catch /^Vim(call):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(call):', '', "") - finally - Xpath 1048576 " X: 1048576 - if !caught && !$VIMNOERRTHROW - Xpath 2097152 " X: 0 - endif - if !MSG('E132', "Function call depth is higher than 'maxfuncdepth'") - Xpath 4194304 " X: 0 - endif - endtry - catch /.*/ - Xpath 8388608 " X: 0 - Xout v:exception "in" v:throwpoint - finally - let &mfd = mfd_save - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -function! H() - return H() -endfunction - -while 1 - try - let mfd_save = &mfd - set mfd=3 - try - let caught = 0 - call H() - catch /^Vim(return):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(return):', '', "") - finally - Xpath 16777216 " X: 16777216 - if !caught && !$VIMNOERRTHROW - Xpath 33554432 " X: 0 - endif - if !MSG('E132', "Function call depth is higher than 'maxfuncdepth'") - Xpath 67108864 " X: 0 - endif - endtry - catch /.*/ - Xpath 134217728 " X: 0 - Xout v:exception "in" v:throwpoint - finally - let &mfd = mfd_save - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -unlet! caught mfd_save -delfunction F -delfunction G -delfunction H -Xpath 268435456 " X: 268435456 - -Xcheck 286331153 - -" Leave MSG() for the next test. - - -"------------------------------------------------------------------------------- -" Test 63: Suppressing error exceptions by :silent!. {{{1 -" -" A :silent! command inside a :try/:endtry region suppresses the -" conversion of errors to an exception and the immediate abortion on -" error. When the commands executed by the :silent! themselves open -" a new :try/:endtry region, conversion of errors to exception and -" immediate abortion is switched on again - until the next :silent! -" etc. The :silent! has the effect of setting v:errmsg to the error -" message text (without displaying it) and continuing with the next -" script line. -" -" When a command triggering autocommands is executed by :silent! -" inside a :try/:endtry, the autocommand execution is not suppressed -" on error. -" -" This test reuses the function MSG() from the previous test. -"------------------------------------------------------------------------------- - -XpathINIT - -XloopINIT! 1 4 - -let taken = "" - -function! S(n) abort - XloopNEXT - let g:taken = g:taken . "E" . a:n - let v:errmsg = "" - exec "asdf" . a:n - - " Check that ":silent!" continues: - Xloop 1 - - " Check that ":silent!" sets "v:errmsg": - if MSG('E492', "Not an editor command") - Xloop 2 - endif -endfunction - -function! Foo() - while 1 - try - try - let caught = 0 - " This is not silent: - call S(3) " X: 0 * 16 - catch /^Vim:/ - let caught = 1 - let errmsg3 = substitute(v:exception, '^Vim:', '', "") - silent! call S(4) " X: 3 * 64 - finally - if !caught - let errmsg3 = v:errmsg - " Do call S(4) here if not executed in :catch. - silent! call S(4) - endif - Xpath 1048576 " X: 1048576 - if !caught && !$VIMNOERRTHROW - Xpath 2097152 " X: 0 - endif - let v:errmsg = errmsg3 - if !MSG('E492', "Not an editor command") - Xpath 4194304 " X: 0 - endif - silent! call S(5) " X: 3 * 256 - " Break out of try conditionals that cover ":silent!". This also - " discards the aborting error when $VIMNOERRTHROW is non-zero. - break - endtry - catch /.*/ - Xpath 8388608 " X: 0 - Xout v:exception "in" v:throwpoint - endtry - endwhile - " This is a double ":silent!" (see caller). - silent! call S(6) " X: 3 * 1024 -endfunction - -function! Bar() - try - silent! call S(2) " X: 3 * 4 - " X: 3 * 4096 - silent! execute "call Foo() | call S(7)" - silent! call S(8) " X: 3 * 16384 - endtry " normal end of try cond that covers ":silent!" - " This has a ":silent!" from the caller: - call S(9) " X: 3 * 65536 -endfunction - -silent! call S(1) " X: 3 * 1 -silent! call Bar() -silent! call S(10) " X: 3 * 262144 - -let expected = "E1E2E3E4E5E6E7E8E9E10" -if taken != expected - Xpath 16777216 " X: 0 - Xout "'taken' is" taken "instead of" expected -endif - -augroup TMP - autocmd BufWritePost * Xpath 33554432 " X: 33554432 -augroup END - -Xpath 67108864 " X: 67108864 -write /i/m/p/o/s/s/i/b/l/e -Xpath 134217728 " X: 134217728 - -autocmd! TMP -unlet! caught errmsg3 taken expected -delfunction S -delfunction Foo -delfunction Bar -delfunction MSG - -Xcheck 236978127 - - -"------------------------------------------------------------------------------- -" Test 64: Error exceptions after error, interrupt or :throw {{{1 -" -" When an error occurs after an interrupt or a :throw but before -" a matching :catch is reached, all following :catches of that try -" block are ignored, but the error exception can be caught by the next -" surrounding try conditional. Any previous error exception is -" discarded. An error is ignored when there is a previous error that -" has not been caught. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - while 1 - try - try - Xpath 1 " X: 1 - let caught = 0 - while 1 -" if 1 - " Missing :endif - endwhile " throw error exception - catch /^Vim(/ - let caught = 1 - finally - Xpath 2 " X: 2 - if caught || $VIMNOERRTHROW - Xpath 4 " X: 4 - endif - endtry - catch /.*/ - Xpath 8 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - try - Xpath 16 " X: 16 - let caught = 0 - try -" if 1 - " Missing :endif - catch /.*/ " throw error exception - Xpath 32 " X: 0 - catch /.*/ - Xpath 64 " X: 0 - endtry - catch /^Vim(/ - let caught = 1 - finally - Xpath 128 " X: 128 - if caught || $VIMNOERRTHROW - Xpath 256 " X: 256 - endif - endtry - catch /.*/ - Xpath 512 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - try - let caught = 0 - try - Xpath 1024 " X: 1024 - "INTERRUPT - catch /do_not_catch/ - Xpath 2048 " X: 0 -" if 1 - " Missing :endif - catch /.*/ " throw error exception - Xpath 4096 " X: 0 - catch /.*/ - Xpath 8192 " X: 0 - endtry - catch /^Vim(/ - let caught = 1 - finally - Xpath 16384 " X: 16384 - if caught || $VIMNOERRTHROW - Xpath 32768 " X: 32768 - endif - endtry - catch /.*/ - Xpath 65536 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - try - let caught = 0 - try - Xpath 131072 " X: 131072 - throw "x" - catch /do_not_catch/ - Xpath 262144 " X: 0 -" if 1 - " Missing :endif - catch /x/ " throw error exception - Xpath 524288 " X: 0 - catch /.*/ - Xpath 1048576 " X: 0 - endtry - catch /^Vim(/ - let caught = 1 - finally - Xpath 2097152 " X: 2097152 - if caught || $VIMNOERRTHROW - Xpath 4194304 " X: 4194304 - endif - endtry - catch /.*/ - Xpath 8388608 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - try - let caught = 0 - Xpath 16777216 " X: 16777216 -" endif " :endif without :if; throw error exception -" if 1 - " Missing :endif - catch /do_not_catch/ " ignore new error - Xpath 33554432 " X: 0 - catch /^Vim(endif):/ - let caught = 1 - catch /^Vim(/ - Xpath 67108864 " X: 0 - finally - Xpath 134217728 " X: 134217728 - if caught || $VIMNOERRTHROW - Xpath 268435456 " X: 268435456 - endif - endtry - catch /.*/ - Xpath 536870912 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - Xpath 1073741824 " X: 1073741824 - -endif - -Xcheck 1499645335 - - -"------------------------------------------------------------------------------- -" Test 65: Errors in the /pattern/ argument of a :catch {{{1 -" -" On an error in the /pattern/ argument of a :catch, the :catch does -" not match. Any following :catches of the same :try/:endtry don't -" match either. Finally clauses are executed. -"------------------------------------------------------------------------------- - -XpathINIT - -function! MSG(enr, emsg) - let english = v:lang == "C" || v:lang =~ '^[Ee]n' - if a:enr == "" - Xout "TODO: Add message number for:" a:emsg - let v:errmsg = ":" . v:errmsg - endif - let match = 1 - if v:errmsg !~ '^'.a:enr.':' || (english && v:errmsg !~ a:emsg) - let match = 0 - if v:errmsg == "" - Xout "Message missing." - else - let v:errmsg = escape(v:errmsg, '"') - Xout "Unexpected message:" v:errmsg - endif - endif - return match -endfunction - -try - try - Xpath 1 " X: 1 - throw "oops" - catch /^oops$/ - Xpath 2 " X: 2 - catch /\)/ " not checked; exception has already been caught - Xpath 4 " X: 0 - endtry - Xpath 8 " X: 8 -catch /.*/ - Xpath 16 " X: 0 - Xout v:exception "in" v:throwpoint -endtry - -function! F() - try - let caught = 0 - try - try - Xpath 32 " X: 32 - throw "ab" - catch /abc/ " does not catch - Xpath 64 " X: 0 - catch /\)/ " error; discards exception - Xpath 128 " X: 0 - catch /.*/ " not checked - Xpath 256 " X: 0 - finally - Xpath 512 " X: 512 - endtry - Xpath 1024 " X: 0 - catch /^ab$/ " checked, but original exception is discarded - Xpath 2048 " X: 0 - catch /^Vim(catch):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(catch):', '', "") - finally - Xpath 4096 " X: 4096 - if !caught && !$VIMNOERRTHROW - Xpath 8192 " X: 0 - endif - if !MSG('E475', "Invalid argument") - Xpath 16384 " X: 0 - endif - if !caught - return | " discard error - endif - endtry - catch /.*/ - Xpath 32768 " X: 0 - Xout v:exception "in" v:throwpoint - endtry -endfunction - -call F() -Xpath 65536 " X: 65536 - -delfunction MSG -delfunction F -unlet! caught - -Xcheck 70187 - - -"------------------------------------------------------------------------------- -" Test 66: Stop range :call on error, interrupt, or :throw {{{1 -" -" When a function which is multiply called for a range since it -" doesn't handle the range itself has an error in a command -" dynamically enclosed by :try/:endtry or gets an interrupt or -" executes a :throw, no more calls for the remaining lines in the -" range are made. On an error in a command not dynamically enclosed -" by :try/:endtry, the function is executed again for the remaining -" lines in the range. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - let file = tempname() - exec "edit" file - - insert -line 1 -line 2 -line 3 -. - - XloopINIT! 1 2 - - let taken = "" - let expected = "G1EF1E(1)F1E(2)F1E(3)G2EF2E(1)G3IF3I(1)G4TF4T(1)G5AF5A(1)" - - function! F(reason, n) abort - let g:taken = g:taken . "F" . a:n . - \ substitute(a:reason, '\(\l\).*', '\u\1', "") . - \ "(" . line(".") . ")" - - if a:reason == "error" - asdf - elseif a:reason == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:reason == "throw" - throw "xyz" - elseif a:reason == "aborting error" - XloopNEXT - if g:taken != g:expected - Xloop 1 " X: 0 - Xout "'taken' is" g:taken "instead of" g:expected - endif - try - bwipeout! - call delete(file) - asdf - endtry - endif - endfunction - - function! G(reason, n) - let g:taken = g:taken . "G" . a:n . - \ substitute(a:reason, '\(\l\).*', '\u\1', "") - 1,3call F(a:reason, a:n) - endfunction - - Xpath 8 " X: 8 - call G("error", 1) - try - Xpath 16 " X: 16 - try - call G("error", 2) - Xpath 32 " X: 0 - finally - Xpath 64 " X: 64 - try - call G("interrupt", 3) - Xpath 128 " X: 0 - finally - Xpath 256 " X: 256 - try - call G("throw", 4) - Xpath 512 " X: 0 - endtry - endtry - endtry - catch /xyz/ - Xpath 1024 " X: 1024 - catch /.*/ - Xpath 2048 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - endtry - Xpath 4096 " X: 4096 - call G("aborting error", 5) - Xpath 8192 " X: 0 - Xout "'taken' is" taken "instead of" expected - -endif - -Xcheck 5464 - - -"------------------------------------------------------------------------------- -" Test 67: :throw across :call command {{{1 -" -" On a call command, an exception might be thrown when evaluating the -" function name, during evaluation of the arguments, or when the -" function is being executed. The exception can be caught by the -" caller. -"------------------------------------------------------------------------------- - -XpathINIT - -function! THROW(x, n) - if a:n == 1 - Xpath 1 " X: 1 - elseif a:n == 2 - Xpath 2 " X: 2 - elseif a:n == 3 - Xpath 4 " X: 4 - endif - throw a:x -endfunction - -function! NAME(x, n) - if a:n == 1 - Xpath 8 " X: 0 - elseif a:n == 2 - Xpath 16 " X: 16 - elseif a:n == 3 - Xpath 32 " X: 32 - elseif a:n == 4 - Xpath 64 " X: 64 - endif - return a:x -endfunction - -function! ARG(x, n) - if a:n == 1 - Xpath 128 " X: 0 - elseif a:n == 2 - Xpath 256 " X: 0 - elseif a:n == 3 - Xpath 512 " X: 512 - elseif a:n == 4 - Xpath 1024 " X: 1024 - endif - return a:x -endfunction - -function! F(x, n) - if a:n == 2 - Xpath 2048 " X: 0 - elseif a:n == 4 - Xpath 4096 " X: 4096 - endif -endfunction - -while 1 - try - let error = 0 - let v:errmsg = "" - - while 1 - try - Xpath 8192 " X: 8192 - call {NAME(THROW("name", 1), 1)}(ARG(4711, 1), 1) - Xpath 16384 " X: 0 - catch /^name$/ - Xpath 32768 " X: 32768 - catch /.*/ - let error = 1 - Xout "1:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "1:" v:errmsg - endif - if error - Xpath 65536 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - Xpath 131072 " X: 131072 - call {NAME("F", 2)}(ARG(THROW("arg", 2), 2), 2) - Xpath 262144 " X: 0 - catch /^arg$/ - Xpath 524288 " X: 524288 - catch /.*/ - let error = 1 - Xout "2:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "2:" v:errmsg - endif - if error - Xpath 1048576 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - Xpath 2097152 " X: 2097152 - call {NAME("THROW", 3)}(ARG("call", 3), 3) - Xpath 4194304 " X: 0 - catch /^call$/ - Xpath 8388608 " X: 8388608 - catch /^0$/ " default return value - Xpath 16777216 " X: 0 - Xout "3:" v:throwpoint - catch /.*/ - let error = 1 - Xout "3:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "3:" v:errmsg - endif - if error - Xpath 33554432 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - Xpath 67108864 " X: 67108864 - call {NAME("F", 4)}(ARG(4711, 4), 4) - Xpath 134217728 " X: 134217728 - catch /.*/ - let error = 1 - Xout "4:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "4:" v:errmsg - endif - if error - Xpath 268435456 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - catch /^0$/ " default return value - Xpath 536870912 " X: 0 - Xout v:throwpoint - catch /.*/ - let error = 1 - Xout v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout v:errmsg - endif - if error - Xpath 1073741824 " X: 0 - endif - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -unlet error -delfunction F - -Xcheck 212514423 - -" Leave THROW(), NAME(), and ARG() for the next test. - - -"------------------------------------------------------------------------------- -" Test 68: :throw across function calls in expressions {{{1 -" -" On a function call within an expression, an exception might be -" thrown when evaluating the function name, during evaluation of the -" arguments, or when the function is being executed. The exception -" can be caught by the caller. -" -" This test reuses the functions THROW(), NAME(), and ARG() from the -" previous test. -"------------------------------------------------------------------------------- - -XpathINIT - -function! F(x, n) - if a:n == 2 - Xpath 2048 " X: 0 - elseif a:n == 4 - Xpath 4096 " X: 4096 - endif - return a:x -endfunction - -unlet! var1 var2 var3 var4 - -while 1 - try - let error = 0 - let v:errmsg = "" - - while 1 - try - Xpath 8192 " X: 8192 - let var1 = {NAME(THROW("name", 1), 1)}(ARG(4711, 1), 1) - Xpath 16384 " X: 0 - catch /^name$/ - Xpath 32768 " X: 32768 - catch /.*/ - let error = 1 - Xout "1:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "1:" v:errmsg - endif - if error - Xpath 65536 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - Xpath 131072 " X: 131072 - let var2 = {NAME("F", 2)}(ARG(THROW("arg", 2), 2), 2) - Xpath 262144 " X: 0 - catch /^arg$/ - Xpath 524288 " X: 524288 - catch /.*/ - let error = 1 - Xout "2:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "2:" v:errmsg - endif - if error - Xpath 1048576 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - Xpath 2097152 " X: 2097152 - let var3 = {NAME("THROW", 3)}(ARG("call", 3), 3) - Xpath 4194304 " X: 0 - catch /^call$/ - Xpath 8388608 " X: 8388608 - catch /^0$/ " default return value - Xpath 16777216 " X: 0 - Xout "3:" v:throwpoint - catch /.*/ - let error = 1 - Xout "3:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "3:" v:errmsg - endif - if error - Xpath 33554432 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - Xpath 67108864 " X: 67108864 - let var4 = {NAME("F", 4)}(ARG(4711, 4), 4) - Xpath 134217728 " X: 134217728 - catch /.*/ - let error = 1 - Xout "4:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "4:" v:errmsg - endif - if error - Xpath 268435456 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - catch /^0$/ " default return value - Xpath 536870912 " X: 0 - Xout v:throwpoint - catch /.*/ - let error = 1 - Xout v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout v:errmsg - endif - if error - Xpath 1073741824 " X: 0 - endif - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -if exists("var1") || exists("var2") || exists("var3") || - \ !exists("var4") || var4 != 4711 - " The Xpath command does not accept 2^31 (negative); add explicitly: - let Xpath = Xpath + 2147483648 " X: 0 - if exists("var1") - Xout "var1 =" var1 - endif - if exists("var2") - Xout "var2 =" var2 - endif - if exists("var3") - Xout "var3 =" var3 - endif - if !exists("var4") - Xout "var4 unset" - elseif var4 != 4711 - Xout "var4 =" var4 - endif -endif - -unlet! error var1 var2 var3 var4 -delfunction THROW -delfunction NAME -delfunction ARG -delfunction F - -Xcheck 212514423 - -" Tests 69 to 75 were moved to test_trycatch.vim -let Xtest = 76 - - -"------------------------------------------------------------------------------- -" Test 76: Errors, interrupts, :throw during expression evaluation {{{1 -" -" When a function call made during expression evaluation is aborted -" due to an error inside a :try/:endtry region or due to an interrupt -" or a :throw, the expression evaluation is aborted as well. No -" message is displayed for the cancelled expression evaluation. On an -" error not inside :try/:endtry, the expression evaluation continues. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - let taken = "" - - function! ERR(n) - let g:taken = g:taken . "E" . a:n - asdf - endfunction - - function! ERRabort(n) abort - let g:taken = g:taken . "A" . a:n - asdf - endfunction " returns -1; may cause follow-up msg for illegal var/func name - - function! WRAP(n, arg) - let g:taken = g:taken . "W" . a:n - let g:saved_errmsg = v:errmsg - return arg - endfunction - - function! INT(n) - let g:taken = g:taken . "I" . a:n - "INTERRUPT9 - let dummy = 0 - endfunction - - function! THR(n) - let g:taken = g:taken . "T" . a:n - throw "should not be caught" - endfunction - - function! CONT(n) - let g:taken = g:taken . "C" . a:n - endfunction - - function! MSG(n) - let g:taken = g:taken . "M" . a:n - let errmsg = (a:n >= 37 && a:n <= 44) ? g:saved_errmsg : v:errmsg - let msgptn = (a:n >= 10 && a:n <= 27) ? "^$" : "asdf" - if errmsg !~ msgptn - let g:taken = g:taken . "x" - Xout "Expr" a:n.": Unexpected message:" v:errmsg - endif - let v:errmsg = "" - let g:saved_errmsg = "" - endfunction - - let v:errmsg = "" - - try - let t = 1 - XloopINIT 1 2 - while t <= 9 - Xloop 1 " X: 511 - try - if t == 1 - let v{ERR(t) + CONT(t)} = 0 - elseif t == 2 - let v{ERR(t) + CONT(t)} - elseif t == 3 - let var = exists('v{ERR(t) + CONT(t)}') - elseif t == 4 - unlet v{ERR(t) + CONT(t)} - elseif t == 5 - function F{ERR(t) + CONT(t)}() - endfunction - elseif t == 6 - function F{ERR(t) + CONT(t)} - elseif t == 7 - let var = exists('*F{ERR(t) + CONT(t)}') - elseif t == 8 - delfunction F{ERR(t) + CONT(t)} - elseif t == 9 - let var = ERR(t) + CONT(t) - endif - catch /asdf/ - " v:errmsg is not set when the error message is converted to an - " exception. Set it to the original error message. - let v:errmsg = substitute(v:exception, '^Vim:', '', "") - catch /^Vim\((\a\+)\)\=:/ - " An error exception has been thrown after the original error. - let v:errmsg = "" - finally - call MSG(t) - let t = t + 1 - XloopNEXT - continue " discard an aborting error - endtry - endwhile - catch /.*/ - Xpath 512 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - endtry - - try - let t = 10 - XloopINIT 1024 2 - while t <= 18 - Xloop 1 " X: 1024 * 511 - try - if t == 10 - let v{INT(t) + CONT(t)} = 0 - elseif t == 11 - let v{INT(t) + CONT(t)} - elseif t == 12 - let var = exists('v{INT(t) + CONT(t)}') - elseif t == 13 - unlet v{INT(t) + CONT(t)} - elseif t == 14 - function F{INT(t) + CONT(t)}() - endfunction - elseif t == 15 - function F{INT(t) + CONT(t)} - elseif t == 16 - let var = exists('*F{INT(t) + CONT(t)}') - elseif t == 17 - delfunction F{INT(t) + CONT(t)} - elseif t == 18 - let var = INT(t) + CONT(t) - endif - catch /^Vim\((\a\+)\)\=:\(Interrupt\)\@!/ - " An error exception has been triggered after the interrupt. - let v:errmsg = substitute(v:exception, - \ '^Vim\((\a\+)\)\=:', '', "") - finally - call MSG(t) - let t = t + 1 - XloopNEXT - continue " discard interrupt - endtry - endwhile - catch /.*/ - Xpath 524288 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - endtry - - try - let t = 19 - XloopINIT 1048576 2 - while t <= 27 - Xloop 1 " X: 1048576 * 511 - try - if t == 19 - let v{THR(t) + CONT(t)} = 0 - elseif t == 20 - let v{THR(t) + CONT(t)} - elseif t == 21 - let var = exists('v{THR(t) + CONT(t)}') - elseif t == 22 - unlet v{THR(t) + CONT(t)} - elseif t == 23 - function F{THR(t) + CONT(t)}() - endfunction - elseif t == 24 - function F{THR(t) + CONT(t)} - elseif t == 25 - let var = exists('*F{THR(t) + CONT(t)}') - elseif t == 26 - delfunction F{THR(t) + CONT(t)} - elseif t == 27 - let var = THR(t) + CONT(t) - endif - catch /^Vim\((\a\+)\)\=:/ - " An error exception has been triggered after the :throw. - let v:errmsg = substitute(v:exception, - \ '^Vim\((\a\+)\)\=:', '', "") - finally - call MSG(t) - let t = t + 1 - XloopNEXT - continue " discard exception - endtry - endwhile - catch /.*/ - Xpath 536870912 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - endtry - - let v{ERR(28) + CONT(28)} = 0 - call MSG(28) - let v{ERR(29) + CONT(29)} - call MSG(29) - let var = exists('v{ERR(30) + CONT(30)}') - call MSG(30) - unlet v{ERR(31) + CONT(31)} - call MSG(31) - function F{ERR(32) + CONT(32)}() - endfunction - call MSG(32) - function F{ERR(33) + CONT(33)} - call MSG(33) - let var = exists('*F{ERR(34) + CONT(34)}') - call MSG(34) - delfunction F{ERR(35) + CONT(35)} - call MSG(35) - let var = ERR(36) + CONT(36) - call MSG(36) - - let saved_errmsg = "" - - let v{WRAP(37, ERRabort(37)) + CONT(37)} = 0 - call MSG(37) - let v{WRAP(38, ERRabort(38)) + CONT(38)} - call MSG(38) - let var = exists('v{WRAP(39, ERRabort(39)) + CONT(39)}') - call MSG(39) - unlet v{WRAP(40, ERRabort(40)) + CONT(40)} - call MSG(40) - function F{WRAP(41, ERRabort(41)) + CONT(41)}() - endfunction - call MSG(41) - function F{WRAP(42, ERRabort(42)) + CONT(42)} - call MSG(42) - let var = exists('*F{WRAP(43, ERRabort(43)) + CONT(43)}') - call MSG(43) - delfunction F{WRAP(44, ERRabort(44)) + CONT(44)} - call MSG(44) - let var = ERRabort(45) + CONT(45) - call MSG(45) - - Xpath 1073741824 " X: 1073741824 - - let expected = "" - \ . "E1M1E2M2E3M3E4M4E5M5E6M6E7M7E8M8E9M9" - \ . "I10M10I11M11I12M12I13M13I14M14I15M15I16M16I17M17I18M18" - \ . "T19M19T20M20T21M21T22M22T23M23T24M24T25M25T26M26T27M27" - \ . "E28C28M28E29C29M29E30C30M30E31C31M31E32C32M32E33C33M33" - \ . "E34C34M34E35C35M35E36C36M36" - \ . "A37W37C37M37A38W38C38M38A39W39C39M39A40W40C40M40A41W41C41M41" - \ . "A42W42C42M42A43W43C43M43A44W44C44M44A45C45M45" - - if taken != expected - " The Xpath command does not accept 2^31 (negative); display explicitly: - exec "!echo 2147483648 >>" . g:ExtraVimResult - " X: 0 - Xout "'taken' is" taken "instead of" expected - if substitute(taken, - \ '\(.*\)E3C3M3x\(.*\)E30C30M30x\(.*\)A39C39M39x\(.*\)', - \ '\1E3M3\2E30C30M30\3A39C39M39\4', - \ "") == expected - Xout "Is ++emsg_skip for var with expr_start non-NULL" - \ "in f_exists ok?" - endif - endif - - unlet! v var saved_errmsg taken expected - call delete(WA_t5) - call delete(WA_t14) - call delete(WA_t23) - unlet! WA_t5 WA_t14 WA_t23 - delfunction WA_t5 - delfunction WA_t14 - delfunction WA_t23 - -endif - -Xcheck 1610087935 - - -"------------------------------------------------------------------------------- -" Test 77: Errors, interrupts, :throw in name{brace-expression} {{{1 -" -" When a function call made during evaluation of an expression in -" braces as part of a function name after ":function" is aborted due -" to an error inside a :try/:endtry region or due to an interrupt or -" a :throw, the expression evaluation is aborted as well, and the -" function definition is ignored, skipping all commands to the -" ":endfunction". On an error not inside :try/:endtry, the expression -" evaluation continues and the function gets defined, and can be -" called and deleted. -"------------------------------------------------------------------------------- - -XpathINIT - -XloopINIT 1 4 - -function! ERR() abort - Xloop 1 " X: 1 + 4 + 16 + 64 - asdf -endfunction " returns -1 - -function! OK() - Xloop 2 " X: 2 * (1 + 4 + 16) - let v:errmsg = "" - return 0 -endfunction - -let v:errmsg = "" - -Xpath 4096 " X: 4096 -function! F{1 + ERR() + OK()}(arg) - " F0 should be defined. - if exists("a:arg") && a:arg == "calling" - Xpath 8192 " X: 8192 - else - Xpath 16384 " X: 0 - endif -endfunction -if v:errmsg != "" - Xpath 32768 " X: 0 -endif -XloopNEXT - -Xpath 65536 " X: 65536 -call F{1 + ERR() + OK()}("calling") -if v:errmsg != "" - Xpath 131072 " X: 0 -endif -XloopNEXT - -Xpath 262144 " X: 262144 -delfunction F{1 + ERR() + OK()} -if v:errmsg != "" - Xpath 524288 " X: 0 -endif -XloopNEXT - -try - while 1 - let caught = 0 - try - Xpath 1048576 " X: 1048576 - function! G{1 + ERR() + OK()}(arg) - " G0 should not be defined, and the function body should be - " skipped. - if exists("a:arg") && a:arg == "calling" - Xpath 2097152 " X: 0 - else - Xpath 4194304 " X: 0 - endif - " Use an unmatched ":finally" to check whether the body is - " skipped when an error occurs in ERR(). This works whether or - " not the exception is converted to an exception. - finally - Xpath 8388608 " X: 0 - Xout "Body of G{1 + ERR() + OK()}() not skipped" - " Discard the aborting error or exception, and break the - " while loop. - break - " End the try conditional and start a new one to avoid - " ":catch after :finally" errors. - endtry - try - Xpath 16777216 " X: 0 - endfunction - - " When the function was not defined, this won't be reached - whether - " the body was skipped or not. When the function was defined, it - " can be called and deleted here. - Xpath 33554432 " X: 0 - Xout "G0() has been defined" - XloopNEXT - try - call G{1 + ERR() + OK()}("calling") - catch /.*/ - Xpath 67108864 " X: 0 - endtry - Xpath 134217728 " X: 0 - XloopNEXT - try - delfunction G{1 + ERR() + OK()} - catch /.*/ - Xpath 268435456 " X: 0 - endtry - catch /asdf/ - " Jumped to when the function is not defined and the body is - " skipped. - let caught = 1 - catch /.*/ - Xpath 536870912 " X: 0 - finally - if !caught && !$VIMNOERRTHROW - Xpath 1073741824 " X: 0 - endif - break " discard error for $VIMNOERRTHROW - endtry " jumped to when the body is not skipped - endwhile -catch /.*/ - " The Xpath command does not accept 2^31 (negative); add explicitly: - let Xpath = Xpath + 2147483648 " X: 0 - Xout "Body of G{1 + ERR() + OK()}() not skipped, exception caught" - Xout v:exception "in" v:throwpoint -endtry - -Xcheck 1388671 - - -"------------------------------------------------------------------------------- -" Test 78: Messages on parsing errors in expression evaluation {{{1 -" -" When an expression evaluation detects a parsing error, an error -" message is given and converted to an exception, and the expression -" evaluation is aborted. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - let taken = "" - - function! F(n) - let g:taken = g:taken . "F" . a:n - endfunction - - function! MSG(n, enr, emsg) - let g:taken = g:taken . "M" . a:n - let english = v:lang == "C" || v:lang =~ '^[Ee]n' - if a:enr == "" - Xout "TODO: Add message number for:" a:emsg - let v:errmsg = ":" . v:errmsg - endif - if v:errmsg !~ '^'.a:enr.':' || (english && v:errmsg !~ a:emsg) - if v:errmsg == "" - Xout "Expr" a:n.": Message missing." - let g:taken = g:taken . "x" - else - let v:errmsg = escape(v:errmsg, '"') - Xout "Expr" a:n.": Unexpected message:" v:errmsg - Xout "Expected: " . a:enr . ': ' . a:emsg - let g:taken = g:taken . "X" - endif - endif - endfunction - - function! CONT(n) - let g:taken = g:taken . "C" . a:n - endfunction - - let v:errmsg = "" - XloopINIT 1 2 - - try - let t = 1 - while t <= 14 - let g:taken = g:taken . "T" . t - let v:errmsg = "" - try - let caught = 0 - if t == 1 - let v{novar + CONT(t)} = 0 - elseif t == 2 - let v{novar + CONT(t)} - elseif t == 3 - let var = exists('v{novar + CONT(t)}') - elseif t == 4 - unlet v{novar + CONT(t)} - elseif t == 5 - function F{novar + CONT(t)}() - endfunction - elseif t == 6 - function F{novar + CONT(t)} - elseif t == 7 - let var = exists('*F{novar + CONT(t)}') - elseif t == 8 - delfunction F{novar + CONT(t)} - elseif t == 9 - echo novar + CONT(t) - elseif t == 10 - echo v{novar + CONT(t)} - elseif t == 11 - echo F{novar + CONT(t)} - elseif t == 12 - let var = novar + CONT(t) - elseif t == 13 - let var = v{novar + CONT(t)} - elseif t == 14 - let var = F{novar + CONT(t)}() - endif - catch /^Vim\((\a\+)\)\=:/ - " v:errmsg is not set when the error message is converted to an - " exception. Set it to the original error message. - let v:errmsg = substitute(v:exception, - \ '^Vim\((\a\+)\)\=:', '', "") - let caught = 1 - finally - if t <= 8 && t != 3 && t != 7 - call MSG(t, 'E475', 'Invalid argument\>') - else - if !caught " no error exceptions ($VIMNOERRTHROW set) - call MSG(t, 'E15', "Invalid expression") - else - call MSG(t, 'E121', "Undefined variable") - endif - endif - let t = t + 1 - XloopNEXT - continue " discard an aborting error - endtry - endwhile - catch /.*/ - Xloop 1 " X: 0 - Xout t.":" v:exception "in" ExtraVimThrowpoint() - endtry - - function! T(n, expr, enr, emsg) - try - let g:taken = g:taken . "T" . a:n - let v:errmsg = "" - try - let caught = 0 - execute "let var = " . a:expr - catch /^Vim\((\a\+)\)\=:/ - " v:errmsg is not set when the error message is converted to an - " exception. Set it to the original error message. - let v:errmsg = substitute(v:exception, - \ '^Vim\((\a\+)\)\=:', '', "") - let caught = 1 - finally - if !caught " no error exceptions ($VIMNOERRTHROW set) - call MSG(a:n, 'E15', "Invalid expression") - else - call MSG(a:n, a:enr, a:emsg) - endif - XloopNEXT - " Discard an aborting error: - return - endtry - catch /.*/ - Xloop 1 " X: 0 - Xout a:n.":" v:exception "in" ExtraVimThrowpoint() - endtry - endfunction - - call T(15, 'Nofunc() + CONT(15)', 'E117', "Unknown function") - call T(16, 'F(1 2 + CONT(16))', 'E116', "Invalid arguments") - call T(17, 'F(1, 2) + CONT(17)', 'E118', "Too many arguments") - call T(18, 'F() + CONT(18)', 'E119', "Not enough arguments") - call T(19, '{(1} + CONT(19)', 'E110', "Missing ')'") - call T(20, '("abc"[1) + CONT(20)', 'E111', "Missing ']'") - call T(21, '(1 +) + CONT(21)', 'E15', "Invalid expression") - call T(22, '1 2 + CONT(22)', 'E15', "Invalid expression") - call T(23, '(1 ? 2) + CONT(23)', 'E109', "Missing ':' after '?'") - call T(24, '("abc) + CONT(24)', 'E114', "Missing quote") - call T(25, "('abc) + CONT(25)", 'E115', "Missing quote") - call T(26, '& + CONT(26)', 'E112', "Option name missing") - call T(27, '&asdf + CONT(27)', 'E113', "Unknown option") - - Xpath 134217728 " X: 134217728 - - let expected = "" - \ . "T1M1T2M2T3M3T4M4T5M5T6M6T7M7T8M8T9M9T10M10T11M11T12M12T13M13T14M14" - \ . "T15M15T16M16T17M17T18M18T19M19T20M20T21M21T22M22T23M23T24M24T25M25" - \ . "T26M26T27M27" - - if taken != expected - Xpath 268435456 " X: 0 - Xout "'taken' is" taken "instead of" expected - if substitute(taken, '\(.*\)T3M3x\(.*\)', '\1T3M3\2', "") == expected - Xout "Is ++emsg_skip for var with expr_start non-NULL" - \ "in f_exists ok?" - endif - endif - - unlet! var caught taken expected - call delete(WA_t5) - unlet! WA_t5 - delfunction WA_t5 - -endif - -Xcheck 134217728 - - -"------------------------------------------------------------------------------- -" Test 79: Throwing one of several errors for the same command {{{1 -" -" When several errors appear in a row (for instance during expression -" evaluation), the first as the most specific one is used when -" throwing an error exception. If, however, a syntax error is -" detected afterwards, this one is used for the error exception. -" On a syntax error, the next command is not executed, on a normal -" error, however, it is (relevant only in a function without the -" "abort" flag). v:errmsg is not set. -" -" If throwing error exceptions is configured off, v:errmsg is always -" set to the latest error message, that is, to the more general -" message or the syntax error, respectively. -"------------------------------------------------------------------------------- - -XpathINIT - -XloopINIT 1 2 - -function! NEXT(cmd) - exec a:cmd . " | Xloop 1" -endfunction - -call NEXT('echo novar') " X: 1 * 1 (checks nextcmd) -XloopNEXT -call NEXT('let novar #') " X: 0 * 2 (skips nextcmd) -XloopNEXT -call NEXT('unlet novar #') " X: 0 * 4 (skips nextcmd) -XloopNEXT -call NEXT('let {novar}') " X: 0 * 8 (skips nextcmd) -XloopNEXT -call NEXT('unlet{ novar}') " X: 0 * 16 (skips nextcmd) - -function! EXEC(cmd) - exec a:cmd -endfunction - -function! MATCH(expected, msg, enr, emsg) - let msg = a:msg - if a:enr == "" - Xout "TODO: Add message number for:" a:emsg - let msg = ":" . msg - endif - let english = v:lang == "C" || v:lang =~ '^[Ee]n' - if msg !~ '^'.a:enr.':' || (english && msg !~ a:emsg) - let match = 0 - if a:expected " no match although expected - if a:msg == "" - Xout "Message missing." - else - let msg = escape(msg, '"') - Xout "Unexpected message:" msg - Xout "Expected:" a:enr . ": " . a:emsg - endif - endif - else - let match = 1 - if !a:expected " match although not expected - let msg = escape(msg, '"') - Xout "Unexpected message:" msg - Xout "Expected none." - endif - endif - return match -endfunction - -try - - while 1 " dummy loop - try - let v:errmsg = "" - let caught = 0 - let thrmsg = "" - call EXEC('echo novar') " normal error - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let thrmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xpath 32 " X: 32 - if !caught - if !$VIMNOERRTHROW - Xpath 64 " X: 0 - endif - elseif !MATCH(1, thrmsg, 'E121', "Undefined variable") - \ || v:errmsg != "" - Xpath 128 " X: 0 - endif - if !caught && !MATCH(1, v:errmsg, 'E15', "Invalid expression") - Xpath 256 " X: 0 - endif - break " discard error if $VIMNOERRTHROW == 1 - endtry - endwhile - - Xpath 512 " X: 512 - let cmd = "let" - XloopINIT 1024 32 - while cmd != "" - try - let v:errmsg = "" - let caught = 0 - let thrmsg = "" - call EXEC(cmd . ' novar #') " normal plus syntax error - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let thrmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xloop 1 " X: 1024 * (1 + 32) - if !caught - if !$VIMNOERRTHROW - Xloop 2 " X: 0 - endif - else - if cmd == "let" - let match = MATCH(0, thrmsg, 'E121', "Undefined variable") - elseif cmd == "unlet" - let match = MATCH(0, thrmsg, 'E108', "No such variable") - endif - if match " normal error - Xloop 4 " X: 0 - endif - if !MATCH(1, thrmsg, 'E488', "Trailing characters") - \|| v:errmsg != "" - " syntax error - Xloop 8 " X: 0 - endif - endif - if !caught && !MATCH(1, v:errmsg, 'E488', "Trailing characters") - " last error - Xloop 16 " X: 0 - endif - if cmd == "let" - let cmd = "unlet" - else - let cmd = "" - endif - XloopNEXT - continue " discard error if $VIMNOERRTHROW == 1 - endtry - endwhile - - Xpath 1048576 " X: 1048576 - let cmd = "let" - XloopINIT 2097152 32 - while cmd != "" - try - let v:errmsg = "" - let caught = 0 - let thrmsg = "" - call EXEC(cmd . ' {novar}') " normal plus syntax error - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let thrmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xloop 1 " X: 2097152 * (1 + 32) - if !caught - if !$VIMNOERRTHROW - Xloop 2 " X: 0 - endif - else - if MATCH(0, thrmsg, 'E121', "Undefined variable") " normal error - Xloop 4 " X: 0 - endif - if !MATCH(1, thrmsg, 'E475', 'Invalid argument\>') - \ || v:errmsg != "" " syntax error - Xloop 8 " X: 0 - endif - endif - if !caught && !MATCH(1, v:errmsg, 'E475', 'Invalid argument\>') - " last error - Xloop 16 " X: 0 - endif - if cmd == "let" - let cmd = "unlet" - else - let cmd = "" - endif - XloopNEXT - continue " discard error if $VIMNOERRTHROW == 1 - endtry - endwhile - -catch /.*/ - " The Xpath command does not accept 2^31 (negative); add explicitly: - let Xpath = Xpath + 2147483648 " X: 0 - Xout v:exception "in" v:throwpoint -endtry - -unlet! next_command thrmsg match -delfunction NEXT -delfunction EXEC -delfunction MATCH - -Xcheck 70288929 - - -"------------------------------------------------------------------------------- -" Test 80: Syntax error in expression for illegal :elseif {{{1 -" -" If there is a syntax error in the expression after an illegal -" :elseif, an error message is given (or an error exception thrown) -" for the illegal :elseif rather than the expression error. -"------------------------------------------------------------------------------- - -XpathINIT - -function! MSG(enr, emsg) - let english = v:lang == "C" || v:lang =~ '^[Ee]n' - if a:enr == "" - Xout "TODO: Add message number for:" a:emsg - let v:errmsg = ":" . v:errmsg - endif - let match = 1 - if v:errmsg !~ '^'.a:enr.':' || (english && v:errmsg !~ a:emsg) - let match = 0 - if v:errmsg == "" - Xout "Message missing." - else - let v:errmsg = escape(v:errmsg, '"') - Xout "Unexpected message:" v:errmsg - endif - endif - return match -endfunction - -let v:errmsg = "" -if 0 -else -elseif 1 ||| 2 -endif -Xpath 1 " X: 1 -if !MSG('E584', ":elseif after :else") - Xpath 2 " X: 0 -endif - -let v:errmsg = "" -if 1 -else -elseif 1 ||| 2 -endif -Xpath 4 " X: 4 -if !MSG('E584', ":elseif after :else") - Xpath 8 " X: 0 -endif - -let v:errmsg = "" -elseif 1 ||| 2 -Xpath 16 " X: 16 -if !MSG('E582', ":elseif without :if") - Xpath 32 " X: 0 -endif - -let v:errmsg = "" -while 1 - elseif 1 ||| 2 -endwhile -Xpath 64 " X: 64 -if !MSG('E582', ":elseif without :if") - Xpath 128 " X: 0 -endif - -while 1 - try - try - let v:errmsg = "" - let caught = 0 - if 0 - else - elseif 1 ||| 2 - endif - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xpath 256 " X: 256 - if !caught && !$VIMNOERRTHROW - Xpath 512 " X: 0 - endif - if !MSG('E584', ":elseif after :else") - Xpath 1024 " X: 0 - endif - endtry - catch /.*/ - Xpath 2048 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - try - let v:errmsg = "" - let caught = 0 - if 1 - else - elseif 1 ||| 2 - endif - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xpath 4096 " X: 4096 - if !caught && !$VIMNOERRTHROW - Xpath 8192 " X: 0 - endif - if !MSG('E584', ":elseif after :else") - Xpath 16384 " X: 0 - endif - endtry - catch /.*/ - Xpath 32768 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - try - let v:errmsg = "" - let caught = 0 - elseif 1 ||| 2 - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xpath 65536 " X: 65536 - if !caught && !$VIMNOERRTHROW - Xpath 131072 " X: 0 - endif - if !MSG('E582', ":elseif without :if") - Xpath 262144 " X: 0 - endif - endtry - catch /.*/ - Xpath 524288 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - try - let v:errmsg = "" - let caught = 0 - while 1 - elseif 1 ||| 2 - endwhile - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xpath 1048576 " X: 1048576 - if !caught && !$VIMNOERRTHROW - Xpath 2097152 " X: 0 - endif - if !MSG('E582', ":elseif without :if") - Xpath 4194304 " X: 0 - endif - endtry - catch /.*/ - Xpath 8388608 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -Xpath 16777216 " X: 16777216 - -unlet! caught -delfunction MSG - -Xcheck 17895765 - - -"------------------------------------------------------------------------------- -" Test 81: Discarding exceptions after an error or interrupt {{{1 -" -" When an exception is thrown from inside a :try conditional without -" :catch and :finally clauses and an error or interrupt occurs before -" the :endtry is reached, the exception is discarded. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - try - Xpath 1 " X: 1 - try - Xpath 2 " X: 2 - throw "arrgh" - Xpath 4 " X: 0 -" if 1 - Xpath 8 " X: 0 - " error after :throw: missing :endif - endtry - Xpath 16 " X: 0 - catch /arrgh/ - Xpath 32 " X: 0 - endtry - Xpath 64 " X: 0 -endif - -if ExtraVim() - try - Xpath 128 " X: 128 - try - Xpath 256 " X: 256 - throw "arrgh" - Xpath 512 " X: 0 - endtry " INTERRUPT - Xpath 1024 " X: 0 - catch /arrgh/ - Xpath 2048 " X: 0 - endtry - Xpath 4096 " X: 0 -endif - -Xcheck 387 - - -"------------------------------------------------------------------------------- -" Test 82: Ignoring :catch clauses after an error or interrupt {{{1 -" -" When an exception is thrown and an error or interrupt occurs before -" the matching :catch clause is reached, the exception is discarded -" and the :catch clause is ignored (also for the error or interrupt -" exception being thrown then). -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - try - try - Xpath 1 " X: 1 - throw "arrgh" - Xpath 2 " X: 0 -" if 1 - Xpath 4 " X: 0 - " error after :throw: missing :endif - catch /.*/ - Xpath 8 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - catch /.*/ - Xpath 16 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - endtry - Xpath 32 " X: 0 - catch /arrgh/ - Xpath 64 " X: 0 - endtry - Xpath 128 " X: 0 -endif - -if ExtraVim() - function! E() - try - try - Xpath 256 " X: 256 - throw "arrgh" - Xpath 512 " X: 0 -" if 1 - Xpath 1024 " X: 0 - " error after :throw: missing :endif - catch /.*/ - Xpath 2048 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - catch /.*/ - Xpath 4096 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - endtry - Xpath 8192 " X: 0 - catch /arrgh/ - Xpath 16384 " X: 0 - endtry - endfunction - - call E() - Xpath 32768 " X: 0 -endif - -if ExtraVim() - try - try - Xpath 65536 " X: 65536 - throw "arrgh" - Xpath 131072 " X: 0 - catch /.*/ "INTERRUPT - Xpath 262144 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - catch /.*/ - Xpath 524288 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - endtry - Xpath 1048576 " X: 0 - catch /arrgh/ - Xpath 2097152 " X: 0 - endtry - Xpath 4194304 " X: 0 -endif - -if ExtraVim() - function I() - try - try - Xpath 8388608 " X: 8388608 - throw "arrgh" - Xpath 16777216 " X: 0 - catch /.*/ "INTERRUPT - Xpath 33554432 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - catch /.*/ - Xpath 67108864 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - endtry - Xpath 134217728 " X: 0 - catch /arrgh/ - Xpath 268435456 " X: 0 - endtry - endfunction - - call I() - Xpath 536870912 " X: 0 -endif - -Xcheck 8454401 - - -"------------------------------------------------------------------------------- -" Test 83: Executing :finally clauses after an error or interrupt {{{1 -" -" When an exception is thrown and an error or interrupt occurs before -" the :finally of the innermost :try is reached, the exception is -" discarded and the :finally clause is executed. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - try - Xpath 1 " X: 1 - try - Xpath 2 " X: 2 - throw "arrgh" - Xpath 4 " X: 0 -" if 1 - Xpath 8 " X: 0 - " error after :throw: missing :endif - finally - Xpath 16 " X: 16 - endtry - Xpath 32 " X: 0 - catch /arrgh/ - Xpath 64 " X: 0 - endtry - Xpath 128 " X: 0 -endif - -if ExtraVim() - try - Xpath 256 " X: 256 - try - Xpath 512 " X: 512 - throw "arrgh" - Xpath 1024 " X: 0 - finally "INTERRUPT - Xpath 2048 " X: 2048 - endtry - Xpath 4096 " X: 0 - catch /arrgh/ - Xpath 8192 " X: 0 - endtry - Xpath 16384 " X: 0 -endif - -Xcheck 2835 - - -"------------------------------------------------------------------------------- -" Test 84: Exceptions in autocommand sequences. {{{1 -" -" When an exception occurs in a sequence of autocommands for -" a specific event, the rest of the sequence is not executed. The -" command that triggered the autocommand execution aborts, and the -" exception is propagated to the caller. -" -" For the FuncUndefined event under a function call expression or -" :call command, the function is not executed, even when it has -" been defined by the autocommands before the exception occurred. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - function! INT() - "INTERRUPT - let dummy = 0 - endfunction - - aug TMP - autocmd! - - autocmd User x1 Xpath 1 " X: 1 - autocmd User x1 throw "x1" - autocmd User x1 Xpath 2 " X: 0 - - autocmd User x2 Xpath 4 " X: 4 - autocmd User x2 asdf - autocmd User x2 Xpath 8 " X: 0 - - autocmd User x3 Xpath 16 " X: 16 - autocmd User x3 call INT() - autocmd User x3 Xpath 32 " X: 0 - - autocmd FuncUndefined U1 function! U1() - autocmd FuncUndefined U1 Xpath 64 " X: 0 - autocmd FuncUndefined U1 endfunction - autocmd FuncUndefined U1 Xpath 128 " X: 128 - autocmd FuncUndefined U1 throw "U1" - autocmd FuncUndefined U1 Xpath 256 " X: 0 - - autocmd FuncUndefined U2 function! U2() - autocmd FuncUndefined U2 Xpath 512 " X: 0 - autocmd FuncUndefined U2 endfunction - autocmd FuncUndefined U2 Xpath 1024 " X: 1024 - autocmd FuncUndefined U2 ASDF - autocmd FuncUndefined U2 Xpath 2048 " X: 0 - - autocmd FuncUndefined U3 function! U3() - autocmd FuncUndefined U3 Xpath 4096 " X: 0 - autocmd FuncUndefined U3 endfunction - autocmd FuncUndefined U3 Xpath 8192 " X: 8192 - autocmd FuncUndefined U3 call INT() - autocmd FuncUndefined U3 Xpath 16384 " X: 0 - aug END - - try - try - Xpath 32768 " X: 32768 - doautocmd User x1 - catch /x1/ - Xpath 65536 " X: 65536 - endtry - - while 1 - try - Xpath 131072 " X: 131072 - let caught = 0 - doautocmd User x2 - catch /asdf/ - let caught = 1 - finally - Xpath 262144 " X: 262144 - if !caught && !$VIMNOERRTHROW - Xpath 524288 " X: 0 - " Propagate uncaught error exception, - else - " ... but break loop for caught error exception, - " or discard error and break loop if $VIMNOERRTHROW - break - endif - endtry - endwhile - - while 1 - try - Xpath 1048576 " X: 1048576 - let caught = 0 - doautocmd User x3 - catch /Vim:Interrupt/ - let caught = 1 - finally - Xpath 2097152 " X: 2097152 - if !caught && !$VIMNOINTTHROW - Xpath 4194304 " X: 0 - " Propagate uncaught interrupt exception, - else - " ... but break loop for caught interrupt exception, - " or discard interrupt and break loop if $VIMNOINTTHROW - break - endif - endtry - endwhile - - if exists("*U1") | delfunction U1 | endif - if exists("*U2") | delfunction U2 | endif - if exists("*U3") | delfunction U3 | endif - - try - Xpath 8388608 " X: 8388608 - call U1() - catch /U1/ - Xpath 16777216 " X: 16777216 - endtry - - while 1 - try - Xpath 33554432 " X: 33554432 - let caught = 0 - call U2() - catch /ASDF/ - let caught = 1 - finally - Xpath 67108864 " X: 67108864 - if !caught && !$VIMNOERRTHROW - Xpath 134217728 " X: 0 - " Propagate uncaught error exception, - else - " ... but break loop for caught error exception, - " or discard error and break loop if $VIMNOERRTHROW - break - endif - endtry - endwhile - - while 1 - try - Xpath 268435456 " X: 268435456 - let caught = 0 - call U3() - catch /Vim:Interrupt/ - let caught = 1 - finally - Xpath 536870912 " X: 536870912 - if !caught && !$VIMNOINTTHROW - Xpath 1073741824 " X: 0 - " Propagate uncaught interrupt exception, - else - " ... but break loop for caught interrupt exception, - " or discard interrupt and break loop if $VIMNOINTTHROW - break - endif - endtry - endwhile - catch /.*/ - " The Xpath command does not accept 2^31 (negative); display explicitly: - exec "!echo 2147483648 >>" . g:ExtraVimResult - Xout "Caught" v:exception "in" v:throwpoint - endtry - - unlet caught - delfunction INT - delfunction U1 - delfunction U2 - delfunction U3 - au! TMP - aug! TMP -endif - -Xcheck 934782101 - - -"------------------------------------------------------------------------------- -" Test 85: Error exceptions in autocommands for I/O command events {{{1 -" -" When an I/O command is inside :try/:endtry, autocommands to be -" executed after it should be skipped on an error (exception) in the -" command itself or in autocommands to be executed before the command. -" In the latter case, the I/O command should not be executed either. -" Example 1: BufWritePre, :write, BufWritePost -" Example 2: FileReadPre, :read, FileReadPost. -"------------------------------------------------------------------------------- - -XpathINIT - -function! MSG(enr, emsg) - let english = v:lang == "C" || v:lang =~ '^[Ee]n' - if a:enr == "" - Xout "TODO: Add message number for:" a:emsg - let v:errmsg = ":" . v:errmsg - endif - let match = 1 - if v:errmsg !~ '^'.a:enr.':' || (english && v:errmsg !~ a:emsg) - let match = 0 - if v:errmsg == "" - Xout "Message missing." - else - let v:errmsg = escape(v:errmsg, '"') - Xout "Unexpected message:" v:errmsg - endif - endif - return match -endfunction - -" Remove the autocommands for the events specified as arguments in all used -" autogroups. -function Delete_autocommands(...) - let augfile = tempname() - while 1 - try - exec "redir >" . augfile - aug - redir END - exec "edit" augfile - g/^$/d - norm G$ - let wrap = "w" - while search('\%( \|^\)\@<=.\{-}\%( \)\@=', wrap) > 0 - let wrap = "W" - exec "norm y/ \n" - let argno = 1 - while argno <= a:0 - exec "au!" escape(@", " ") a:{argno} - let argno = argno + 1 - endwhile - endwhile - catch /.*/ - finally - bwipeout! - call delete(augfile) - break " discard errors for $VIMNOERRTHROW - endtry - endwhile -endfunction - -call Delete_autocommands("BufWritePre", "BufWritePost") - -while 1 - try - try - let post = 0 - aug TMP - au! BufWritePost * let post = 1 - aug END - let caught = 0 - write /n/o/n/e/x/i/s/t/e/n/t - catch /^Vim(write):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(write):', '', "") - finally - Xpath 1 " X: 1 - if !caught && !$VIMNOERRTHROW - Xpath 2 " X: 0 - endif - let v:errmsg = substitute(v:errmsg, '^"/n/o/n/e/x/i/s/t/e/n/t" ', - \ '', "") - if !MSG('E212', "Can't open file for writing") - Xpath 4 " X: 0 - endif - if post - Xpath 8 " X: 0 - Xout "BufWritePost commands executed after write error" - endif - au! TMP - aug! TMP - endtry - catch /.*/ - Xpath 16 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - try - let post = 0 - aug TMP - au! BufWritePre * asdf - au! BufWritePost * let post = 1 - aug END - let tmpfile = tempname() - let caught = 0 - exec "write" tmpfile - catch /^Vim\((write)\)\=:/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim\((write)\)\=:', '', "") - finally - Xpath 32 " X: 32 - if !caught && !$VIMNOERRTHROW - Xpath 64 " X: 0 - endif - let v:errmsg = substitute(v:errmsg, '^"'.tmpfile.'" ', '', "") - if !MSG('E492', "Not an editor command") - Xpath 128 " X: 0 - endif - if filereadable(tmpfile) - Xpath 256 " X: 0 - Xout ":write command not suppressed after BufWritePre error" - endif - if post - Xpath 512 " X: 0 - Xout "BufWritePost commands executed after BufWritePre error" - endif - au! TMP - aug! TMP - endtry - catch /.*/ - Xpath 1024 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -call delete(tmpfile) - -call Delete_autocommands("BufWritePre", "BufWritePost", - \ "BufReadPre", "BufReadPost", "FileReadPre", "FileReadPost") - -while 1 - try - try - let post = 0 - aug TMP - au! FileReadPost * let post = 1 - aug END - let caught = 0 - read /n/o/n/e/x/i/s/t/e/n/t - catch /^Vim(read):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(read):', '', "") - finally - Xpath 2048 " X: 2048 - if !caught && !$VIMNOERRTHROW - Xpath 4096 " X: 0 - endif - let v:errmsg = substitute(v:errmsg, ' /n/o/n/e/x/i/s/t/e/n/t$', - \ '', "") - if !MSG('E484', "Can't open file") - Xpath 8192 " X: 0 - endif - if post - Xpath 16384 " X: 0 - Xout "FileReadPost commands executed after write error" - endif - au! TMP - aug! TMP - endtry - catch /.*/ - Xpath 32768 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - let infile = tempname() - let tmpfile = tempname() - exec "!echo XYZ >" . infile - exec "edit" tmpfile - try - Xpath 65536 " X: 65536 - try - let post = 0 - aug TMP - au! FileReadPre * asdf - au! FileReadPost * let post = 1 - aug END - let caught = 0 - exec "0read" infile - catch /^Vim\((read)\)\=:/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim\((read)\)\=:', '', - \ "") - finally - Xpath 131072 " X: 131072 - if !caught && !$VIMNOERRTHROW - Xpath 262144 " X: 0 - endif - let v:errmsg = substitute(v:errmsg, ' '.infile.'$', '', "") - if !MSG('E492', "Not an editor command") - Xpath 524288 " X: 0 - endif - if getline("1") == "XYZ" - Xpath 1048576 " X: 0 - Xout ":read command not suppressed after FileReadPre error" - endif - if post - Xpath 2097152 " X: 0 - Xout "FileReadPost commands executed after " . - \ "FileReadPre error" - endif - au! TMP - aug! TMP - endtry - finally - bwipeout! - endtry - catch /.*/ - Xpath 4194304 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -call delete(infile) -call delete(tmpfile) -unlet! caught post infile tmpfile -delfunction MSG -delfunction Delete_autocommands - -Xcheck 198689 - -"------------------------------------------------------------------------------- -" Test 86: setloclist crash {{{1 -" -" Executing a setloclist() on BufUnload shouldn't crash Vim -"------------------------------------------------------------------------------- - -func F - au BufUnload * :call setloclist(0, [{'bufnr':1, 'lnum':1, 'col':1, 'text': 'tango down'}]) - - :lvimgrep /.*/ *.mak -endfunc - -XpathINIT - -ExecAsScript F - -delfunction F -Xout "No Crash for vimgrep on BufUnload" -Xcheck 0 - -" Test 87 was moved to test_vimscript.vim -let Xtest = 88 - - -"------------------------------------------------------------------------------- -" Test 88: $VIMNOERRTHROW and $VIMNOINTTHROW support {{{1 -" -" It is possible to configure Vim for throwing exceptions on error -" or interrupt, controlled by variables $VIMNOERRTHROW and -" $VIMNOINTTHROW. This is just for increasing the number of tests. -" All tests here should run for all four combinations of setting -" these variables to 0 or 1. The variables are intended for the -" development phase only. In the final release, Vim should be -" configured to always use error and interrupt exceptions. -" -" The test result is "OK", -" -" - if the $VIMNOERRTHROW and the $VIMNOINTTHROW control are not -" configured and exceptions are thrown on error and on -" interrupt. -" -" - if the $VIMNOERRTHROW or the $VIMNOINTTHROW control is -" configured and works as intended. -" -" What actually happens, is shown in the test output. -" -" Otherwise, the test result is "FAIL", and the test output describes -" the problem. -" -" IMPORTANT: This must be the last test because it sets $VIMNOERRTHROW and -" $VIMNOINTTHROW. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - function! ThrowOnError() - XloopNEXT - let caught = 0 - try - Xloop 1 " X: 1 + 8 + 64 - asdf - catch /.*/ - let caught = 1 " error exception caught - finally - Xloop 2 " X: 2 + 16 + 128 - return caught " discard aborting error - endtry - Xloop 4 " X: 0 - endfunction - - let quits_skipped = 0 - - function! ThrowOnInterrupt() - XloopNEXT - let caught = 0 - try - Xloop 1 " X: (1 + 8 + 64) * 512 - "INTERRUPT3 - let dummy = 0 - let g:quits_skipped = g:quits_skipped + 1 - catch /.*/ - let caught = 1 " interrupt exception caught - finally - Xloop 2 " X: (2 + 16 + 128) * 512 - return caught " discard interrupt - endtry - Xloop 4 " X: 0 - endfunction - - function! CheckThrow(Type) - execute 'return ThrowOn' . a:Type . '()' - endfunction - - function! CheckConfiguration(type) " type is "error" or "interrupt" - - let type = a:type - let Type = substitute(type, '.*', '\u&', "") - let VAR = '$VIMNO' . substitute(type, '\(...\).*', '\U\1', "") . 'THROW' - - if type == "error" - XloopINIT! 1 8 - elseif type == "interrupt" - XloopINIT! 512 8 - endif - - exec 'let requested_for_tests = exists(VAR) && ' . VAR . ' == 0' - exec 'let suppressed_for_tests = ' . VAR . ' != 0' - let used_in_tests = CheckThrow(Type) - - exec 'let ' . VAR . ' = 0' - let request_works = CheckThrow(Type) - - exec 'let ' . VAR . ' = 1' - let suppress_works = !CheckThrow(Type) - - if type == "error" - XloopINIT! 262144 8 - elseif type == "interrupt" - XloopINIT! 2097152 8 - - if g:quits_skipped != 0 - Xloop 1 " X: 0*2097152 - Xout "Test environment error. Interrupt breakpoints skipped: " - \ . g:quits_skipped . ".\n" - \ . "Cannot check whether interrupt exceptions are thrown." - return - endif - endif - - let failure = - \ !suppressed_for_tests && !used_in_tests - \ || !request_works - - let contradiction = - \ used_in_tests - \ ? suppressed_for_tests && !request_works - \ : !suppressed_for_tests - - if failure - " Failure in configuration. - Xloop 2 " X: 0 * 2* (262144 + 2097152) - elseif contradiction - " Failure in test logic. Should not happen. - Xloop 4 " X: 0 * 4 * (262144 + 2097152) - endif - - let var_control_configured = - \ request_works != used_in_tests - \ || suppress_works == used_in_tests - - let var_control_not_configured = - \ requested_for_tests || suppressed_for_tests - \ ? request_works && !suppress_works - \ : request_works == used_in_tests - \ && suppress_works != used_in_tests - - let with = used_in_tests ? "with" : "without" - - let set = suppressed_for_tests ? "non-zero" : - \ requested_for_tests ? "0" : "unset" - - let although = contradiction && !var_control_not_configured - \ ? ",\nalthough " - \ : ".\n" - - let output = "All tests were run " . with . " throwing exceptions on " - \ . type . although - - if !var_control_not_configured - let output = output . VAR . " was " . set . "." - - if !request_works && !requested_for_tests - let output = output . - \ "\n" . Type . " exceptions are not thrown when " . VAR . - \ " is\nset to 0." - endif - - if !suppress_works && (!used_in_tests || - \ !request_works && - \ !requested_for_tests && !suppressed_for_tests) - let output = output . - \ "\n" . Type . " exceptions are thrown when " . VAR . - \ " is set to 1." - endif - - if !failure && var_control_configured - let output = output . - \ "\nRun tests also with " . substitute(VAR, '^\$', '', "") - \ . "=" . used_in_tests . "." - \ . "\nThis is for testing in the development phase only." - \ . " Remove the \n" - \ . VAR . " control in the final release." - endif - else - let output = output . - \ "The " . VAR . " control is not configured." - endif - - Xout output - endfunction - - call CheckConfiguration("error") - Xpath 16777216 " X: 16777216 - call CheckConfiguration("interrupt") - Xpath 33554432 " X: 33554432 -endif - -Xcheck 50443995 - -" IMPORTANT: No test should be added after this test because it changes -" $VIMNOERRTHROW and $VIMNOINTTHROW. - - -"------------------------------------------------------------------------------- -" Modelines {{{1 -" vim: ts=8 sw=4 tw=80 fdm=marker -"------------------------------------------------------------------------------- diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index a83ef50abc..a3d240f27e 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -15,7 +15,6 @@ source test_fnamemodify.vim source test_ga.vim source test_glob2regpat.vim source test_global.vim -source test_lispwords.vim source test_move.vim source test_put.vim source test_reltime.vim diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim index 0fd65e8f5a..fb8b17cd16 100644 --- a/src/nvim/testdir/test_arglist.vim +++ b/src/nvim/testdir/test_arglist.vim @@ -1,5 +1,6 @@ " Test argument list commands +source check.vim source shared.vim source term_util.vim @@ -417,15 +418,19 @@ func Test_argdedupe() call Reset_arglist() argdedupe call assert_equal([], argv()) + args a a a aa b b a b aa argdedupe call assert_equal(['a', 'aa', 'b'], argv()) + args a b c argdedupe call assert_equal(['a', 'b', 'c'], argv()) + args a argdedupe call assert_equal(['a'], argv()) + args a A b B argdedupe if has('fname_case') @@ -433,11 +438,17 @@ func Test_argdedupe() else call assert_equal(['a', 'b'], argv()) endif + args a b a c a b last argdedupe next call assert_equal('c', expand('%:t')) + + args a ./a + argdedupe + call assert_equal(['a'], argv()) + %argd endfunc @@ -550,11 +561,9 @@ func Test_argdo() bwipe Xa.c Xb.c Xc.c endfunc -" Test for quiting Vim with unedited files in the argument list +" Test for quitting Vim with unedited files in the argument list func Test_quit_with_arglist() - if !CanRunVimInTerminal() - throw 'Skipped: cannot run vim in terminal' - endif + CheckRunVimInTerminal let buf = RunVimInTerminal('', {'rows': 6}) call term_sendkeys(buf, ":set nomore\n") call term_sendkeys(buf, ":args a b c\n") diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim index 8723a0a38d..431908e95c 100644 --- a/src/nvim/testdir/test_assert.vim +++ b/src/nvim/testdir/test_assert.vim @@ -48,6 +48,11 @@ func Test_assert_equal() call assert_equal('XxxxxxxxxxxxxxxxxxxxxxX', 'XyyyyyyyyyyyyyyyyyyyyyyyyyX') call assert_match("Expected 'X\\\\\\[x occurs 21 times]X' but got 'X\\\\\\[y occurs 25 times]X'", v:errors[0]) call remove(v:errors, 0) + + " special characters are escaped + call assert_equal("\b\e\f\n\t\r\\\x01\x7f", 'x') + call assert_match('Expected ''\\b\\e\\f\\n\\t\\r\\\\\\x01\\x7f'' but got ''x''', v:errors[0]) + call remove(v:errors, 0) endfunc func Test_assert_equal_dict() @@ -146,6 +151,14 @@ func Test_assert_exception() try nocommand catch + call assert_equal(1, assert_exception('E12345:')) + endtry + call assert_match("Expected 'E12345:' but got 'Vim:E492: ", v:errors[0]) + call remove(v:errors, 0) + + try + nocommand + catch try " illegal argument, get NULL for error call assert_equal(1, assert_exception([])) @@ -153,6 +166,10 @@ func Test_assert_exception() call assert_equal(0, assert_exception('E730:')) endtry endtry + + call assert_equal(1, assert_exception('E492:')) + call assert_match('v:exception is not set', v:errors[0]) + call remove(v:errors, 0) endfunc func Test_wrong_error_type() @@ -202,6 +219,14 @@ func Test_assert_fail_fails() call assert_match("stupid: Expected 'E9876' but got 'E492:", v:errors[0]) call remove(v:errors, 0) + call assert_equal(1, assert_fails('xxx', ['E9876'])) + call assert_match("Expected \\['E9876'\\] but got 'E492:", v:errors[0]) + call remove(v:errors, 0) + + call assert_equal(1, assert_fails('xxx', ['E492:', 'E9876'])) + call assert_match("Expected \\['E492:', 'E9876'\\] but got 'E492:", v:errors[0]) + call remove(v:errors, 0) + call assert_equal(1, assert_fails('echo', '', 'echo command')) call assert_match("command did not fail: echo command", v:errors[0]) call remove(v:errors, 0) @@ -209,6 +234,41 @@ func Test_assert_fail_fails() call assert_equal(1, 'echo'->assert_fails('', 'echo command')) call assert_match("command did not fail: echo command", v:errors[0]) call remove(v:errors, 0) + + try + call assert_equal(1, assert_fails('xxx', [])) + catch + let exp = v:exception + endtry + call assert_match("E856: assert_fails() second argument", exp) + + try + call assert_equal(1, assert_fails('xxx', ['1', '2', '3'])) + catch + let exp = v:exception + endtry + call assert_match("E856: assert_fails() second argument", exp) + + try + call assert_equal(1, assert_fails('xxx', #{one: 1})) + catch + let exp = v:exception + endtry + call assert_match("E856: assert_fails() second argument", exp) + + try + call assert_equal(1, assert_fails('xxx', 'E492', '', 'burp')) + catch + let exp = v:exception + endtry + call assert_match("E1115: assert_fails() fourth argument must be a number", exp) + + try + call assert_equal(1, assert_fails('xxx', 'E492', '', 54, 123)) + catch + let exp = v:exception + endtry + call assert_match("E1116: assert_fails() fifth argument must be a string", exp) endfunc func Test_assert_fails_in_try_block() diff --git a/src/nvim/testdir/test_autochdir.vim b/src/nvim/testdir/test_autochdir.vim index 4229095f9f..a8810047a0 100644 --- a/src/nvim/testdir/test_autochdir.vim +++ b/src/nvim/testdir/test_autochdir.vim @@ -122,9 +122,7 @@ endfunc func Test_multibyte() " using an invalid character should not cause a crash set wic - " Except on Windows, E472 is also thrown last, but v8.1.1183 isn't ported yet - " call assert_fails('tc *', has('win32') ? 'E480:' : 'E344:') - call assert_fails('tc *', has('win32') ? 'E480:' : 'E472:') + call assert_fails('tc *', has('win32') ? 'E480:' : 'E344:') set nowic endfunc diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 025bda4515..83af0f6be0 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -4,6 +4,7 @@ source shared.vim source check.vim source term_util.vim source screendump.vim +source load.vim func s:cleanup_buffers() abort for bnr in range(1, bufnr('$')) @@ -20,8 +21,35 @@ func Test_vim_did_enter() " becomes one. endfunc +" Test for the CursorHold autocmd +func Test_CursorHold_autocmd() + CheckRunVimInTerminal + call writefile(['one', 'two', 'three'], 'Xfile') + let before =<< trim END + set updatetime=10 + au CursorHold * call writefile([line('.')], 'Xoutput', 'a') + END + call writefile(before, 'Xinit') + let buf = RunVimInTerminal('-S Xinit Xfile', {}) + call term_sendkeys(buf, "G") + call term_wait(buf, 20) + call term_sendkeys(buf, "gg") + call term_wait(buf) + call WaitForAssert({-> assert_equal(['1'], readfile('Xoutput')[-1:-1])}) + call term_sendkeys(buf, "j") + call term_wait(buf) + call WaitForAssert({-> assert_equal(['1', '2'], readfile('Xoutput')[-2:-1])}) + call term_sendkeys(buf, "j") + call term_wait(buf) + call WaitForAssert({-> assert_equal(['1', '2', '3'], readfile('Xoutput')[-3:-1])}) + call StopVimInTerminal(buf) + + call delete('Xinit') + call delete('Xoutput') + call delete('Xfile') +endfunc + if has('timers') - source load.vim func ExitInsertMode(id) call feedkeys("\<Esc>") @@ -175,9 +203,7 @@ func Test_autocmd_bufunload_avoiding_SEGV_01() exe 'autocmd BufUnload <buffer> ' . (lastbuf + 1) . 'bwipeout!' augroup END - " Todo: check for E937 generated first - " call assert_fails('edit bb.txt', 'E937:') - call assert_fails('edit bb.txt', 'E517:') + call assert_fails('edit bb.txt', 'E937:') autocmd! test_autocmd_bufunload augroup! test_autocmd_bufunload @@ -269,6 +295,61 @@ func Test_win_tab_autocmd() unlet g:record endfunc +func Test_WinResized() + CheckRunVimInTerminal + + let lines =<< trim END + set scrolloff=0 + call setline(1, ['111', '222']) + vnew + call setline(1, ['aaa', 'bbb']) + new + call setline(1, ['foo', 'bar']) + + let g:resized = 0 + au WinResized * let g:resized += 1 + + func WriteResizedEvent() + call writefile([json_encode(v:event)], 'XresizeEvent') + endfunc + au WinResized * call WriteResizedEvent() + END + call writefile(lines, 'Xtest_winresized', 'D') + let buf = RunVimInTerminal('-S Xtest_winresized', {'rows': 10}) + + " redraw now to avoid a redraw after the :echo command + call term_sendkeys(buf, ":redraw!\<CR>") + call TermWait(buf) + + call term_sendkeys(buf, ":echo g:resized\<CR>") + call WaitForAssert({-> assert_match('^0$', term_getline(buf, 10))}, 1000) + + " increase window height, two windows will be reported + call term_sendkeys(buf, "\<C-W>+") + call TermWait(buf) + call term_sendkeys(buf, ":echo g:resized\<CR>") + call WaitForAssert({-> assert_match('^1$', term_getline(buf, 10))}, 1000) + + let event = readfile('XresizeEvent')[0]->json_decode() + call assert_equal({ + \ 'windows': [1002, 1001], + \ }, event) + + " increase window width, three windows will be reported + call term_sendkeys(buf, "\<C-W>>") + call TermWait(buf) + call term_sendkeys(buf, ":echo g:resized\<CR>") + call WaitForAssert({-> assert_match('^2$', term_getline(buf, 10))}, 1000) + + let event = readfile('XresizeEvent')[0]->json_decode() + call assert_equal({ + \ 'windows': [1002, 1001, 1000], + \ }, event) + + call delete('XresizeEvent') + call StopVimInTerminal(buf) +endfunc + func Test_WinScrolled() CheckRunVimInTerminal @@ -279,13 +360,17 @@ func Test_WinScrolled() endfor let win_id = win_getid() let g:matched = v:false + func WriteScrollEvent() + call writefile([json_encode(v:event)], 'XscrollEvent') + endfunc 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>')) + au WinScrolled * call WriteScrollEvent() END - call writefile(lines, 'Xtest_winscrolled') + call writefile(lines, 'Xtest_winscrolled', 'D') let buf = RunVimInTerminal('-S Xtest_winscrolled', {'rows': 6}) call term_sendkeys(buf, ":echo g:scrolled\<CR>") @@ -295,15 +380,33 @@ func Test_WinScrolled() call term_sendkeys(buf, "zlzh:echo g:scrolled\<CR>") call WaitForAssert({-> assert_match('^2 ', term_getline(buf, 6))}, 1000) + let event = readfile('XscrollEvent')[0]->json_decode() + call assert_equal({ + \ 'all': {'leftcol': 1, 'topline': 0, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1000': {'leftcol': -1, 'topline': 0, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0} + \ }, event) + " 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) + let event = readfile('XscrollEvent')[0]->json_decode() + call assert_equal({ + \ 'all': {'leftcol': 0, 'topline': 1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1000': {'leftcol': 0, 'topline': -1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0} + \ }, event) + " 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) + let event = readfile('XscrollEvent')[0]->json_decode() + call assert_equal({ + \ 'all': {'leftcol': 0, 'topline': 1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1000': {'leftcol': 0, 'topline': -1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0} + \ }, event) + " 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. @@ -311,6 +414,12 @@ func Test_WinScrolled() call term_sendkeys(buf, ":echo g:scrolled\<CR>") call WaitForAssert({-> assert_match('^8 ', term_getline(buf, 6))}, 1000) + let event = readfile('XscrollEvent')[0]->json_decode() + call assert_equal({ + \ 'all': {'leftcol': 5, 'topline': 0, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1000': {'leftcol': -5, 'topline': 0, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0} + \ }, event) + " 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) @@ -319,8 +428,38 @@ func Test_WinScrolled() 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 delete('XscrollEvent') + call StopVimInTerminal(buf) +endfunc + +func Test_WinScrolled_mouse() + CheckRunVimInTerminal + + let lines =<< trim END + set nowrap scrolloff=0 + set mouse=a term=xterm ttymouse=sgr mousetime=200 clipboard= + call setline(1, ['foo']->repeat(32)) + split + let g:scrolled = 0 + au WinScrolled * let g:scrolled += 1 + END + call writefile(lines, 'Xtest_winscrolled_mouse', 'D') + let buf = RunVimInTerminal('-S Xtest_winscrolled_mouse', {'rows': 10}) + + " With the upper split focused, send a scroll-down event to the unfocused one. + call test_setmouse(7, 1) + call term_sendkeys(buf, "\<ScrollWheelDown>") + call TermWait(buf) + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^1', term_getline(buf, 10))}, 1000) + + " Again, but this time while we're in insert mode. + call term_sendkeys(buf, "i\<ScrollWheelDown>\<Esc>") + call TermWait(buf) + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^2', term_getline(buf, 10))}, 1000) + call StopVimInTerminal(buf) - call delete('Xtest_winscrolled') endfunc func Test_WinScrolled_close_curwin() @@ -333,7 +472,7 @@ func Test_WinScrolled_close_curwin() au WinScrolled * close au VimLeave * call writefile(['123456'], 'Xtestout') END - call writefile(lines, 'Xtest_winscrolled_close_curwin') + call writefile(lines, 'Xtest_winscrolled_close_curwin', 'D') let buf = RunVimInTerminal('-S Xtest_winscrolled_close_curwin', {'rows': 6}) " This was using freed memory @@ -341,12 +480,64 @@ func Test_WinScrolled_close_curwin() call TermWait(buf) call StopVimInTerminal(buf) + " check the startup script finished to the end call assert_equal(['123456'], readfile('Xtestout')) - - call delete('Xtest_winscrolled_close_curwin') call delete('Xtestout') endfunc +func Test_WinScrolled_once_only() + CheckRunVimInTerminal + + let lines =<< trim END + set cmdheight=2 + call setline(1, ['aaa', 'bbb']) + let trigger_count = 0 + func ShowInfo(id) + echo g:trigger_count g:winid winlayout() + endfunc + + vsplit + split + " use a timer to show the info after a redraw + au WinScrolled * let trigger_count += 1 | let winid = expand('<amatch>') | call timer_start(100, 'ShowInfo') + wincmd j + wincmd l + END + call writefile(lines, 'Xtest_winscrolled_once', 'D') + let buf = RunVimInTerminal('-S Xtest_winscrolled_once', #{rows: 10, cols: 60, statusoff: 2}) + + call term_sendkeys(buf, "\<C-E>") + call VerifyScreenDump(buf, 'Test_winscrolled_once_only_1', {}) + + call StopVimInTerminal(buf) +endfunc + +" Check that WinScrolled is not triggered immediately when defined and there +" are split windows. +func Test_WinScrolled_not_when_defined() + CheckRunVimInTerminal + + let lines =<< trim END + call setline(1, ['aaa', 'bbb']) + echo 'nothing happened' + func ShowTriggered(id) + echo 'triggered' + endfunc + END + call writefile(lines, 'Xtest_winscrolled_not', 'D') + let buf = RunVimInTerminal('-S Xtest_winscrolled_not', #{rows: 10, cols: 60, statusoff: 2}) + call term_sendkeys(buf, ":split\<CR>") + call TermWait(buf) + " use a timer to show the message after redrawing + call term_sendkeys(buf, ":au WinScrolled * call timer_start(100, 'ShowTriggered')\<CR>") + call VerifyScreenDump(buf, 'Test_winscrolled_not_when_defined_1', {}) + + call term_sendkeys(buf, "\<C-E>") + call VerifyScreenDump(buf, 'Test_winscrolled_not_when_defined_2', {}) + + call StopVimInTerminal(buf) +endfunc + func Test_WinScrolled_long_wrapped() CheckRunVimInTerminal @@ -359,7 +550,7 @@ func Test_WinScrolled_long_wrapped() call setline(1, repeat('foo', height * width)) call cursor(1, height * width) END - call writefile(lines, 'Xtest_winscrolled_long_wrapped') + call writefile(lines, 'Xtest_winscrolled_long_wrapped', 'D') let buf = RunVimInTerminal('-S Xtest_winscrolled_long_wrapped', {'rows': 6}) call term_sendkeys(buf, ":echo g:scrolled\<CR>") @@ -377,7 +568,68 @@ func Test_WinScrolled_long_wrapped() call term_sendkeys(buf, ":echo g:scrolled\<CR>") call WaitForAssert({-> assert_match('^3 ', term_getline(buf, 6))}, 1000) - call delete('Xtest_winscrolled_long_wrapped') + call StopVimInTerminal(buf) +endfunc + +func Test_WinScrolled_diff() + CheckRunVimInTerminal + + let lines =<< trim END + set diffopt+=foldcolumn:0 + call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']) + vnew + call setline(1, ['d', 'e', 'f', 'g', 'h', 'i']) + windo diffthis + func WriteScrollEvent() + call writefile([json_encode(v:event)], 'XscrollEvent') + endfunc + au WinScrolled * call WriteScrollEvent() + END + call writefile(lines, 'Xtest_winscrolled_diff', 'D') + let buf = RunVimInTerminal('-S Xtest_winscrolled_diff', {'rows': 8}) + + call term_sendkeys(buf, "\<C-E>") + call WaitForAssert({-> assert_match('^d', term_getline(buf, 3))}, 1000) + + let event = readfile('XscrollEvent')[0]->json_decode() + call assert_equal({ + \ 'all': {'leftcol': 0, 'topline': 1, 'topfill': 1, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1000': {'leftcol': 0, 'topline': 1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1001': {'leftcol': 0, 'topline': 0, 'topfill': -1, 'width': 0, 'height': 0, 'skipcol': 0} + \ }, event) + + call term_sendkeys(buf, "2\<C-E>") + call WaitForAssert({-> assert_match('^f', term_getline(buf, 3))}, 1000) + + let event = readfile('XscrollEvent')[0]->json_decode() + call assert_equal({ + \ 'all': {'leftcol': 0, 'topline': 2, 'topfill': 2, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1000': {'leftcol': 0, 'topline': 2, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1001': {'leftcol': 0, 'topline': 0, 'topfill': -2, 'width': 0, 'height': 0, 'skipcol': 0} + \ }, event) + + call term_sendkeys(buf, "\<C-E>") + call WaitForAssert({-> assert_match('^g', term_getline(buf, 3))}, 1000) + + let event = readfile('XscrollEvent')[0]->json_decode() + call assert_equal({ + \ 'all': {'leftcol': 0, 'topline': 2, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1000': {'leftcol': 0, 'topline': 1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1001': {'leftcol': 0, 'topline': 1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0} + \ }, event) + + call term_sendkeys(buf, "2\<C-Y>") + call WaitForAssert({-> assert_match('^e', term_getline(buf, 3))}, 1000) + + let event = readfile('XscrollEvent')[0]->json_decode() + call assert_equal({ + \ 'all': {'leftcol': 0, 'topline': 3, 'topfill': 1, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1000': {'leftcol': 0, 'topline': -2, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1001': {'leftcol': 0, 'topline': -1, 'topfill': 1, 'width': 0, 'height': 0, 'skipcol': 0} + \ }, event) + + call StopVimInTerminal(buf) + call delete('XscrollEvent') endfunc func Test_WinClosed() @@ -483,17 +735,20 @@ endfunc func Test_early_bar() " test that a bar is recognized before the {event} call s:AddAnAutocmd() - augroup vimBarTest | au! | augroup END + augroup vimBarTest | au! | let done = 77 | augroup END call assert_equal(1, len(split(execute('au vimBarTest'), "\n"))) + call assert_equal(77, done) call s:AddAnAutocmd() - augroup vimBarTest| au!| augroup END + augroup vimBarTest| au!| let done = 88 | augroup END call assert_equal(1, len(split(execute('au vimBarTest'), "\n"))) + call assert_equal(88, done) " test that a bar is recognized after the {event} call s:AddAnAutocmd() - augroup vimBarTest| au!BufReadCmd| augroup END + augroup vimBarTest| au!BufReadCmd| let done = 99 | augroup END call assert_equal(1, len(split(execute('au vimBarTest'), "\n"))) + call assert_equal(99, done) " test that a bar is recognized after the {group} call s:AddAnAutocmd() @@ -533,6 +788,8 @@ func Test_augroup_warning() redir END call assert_notmatch("W19:", res) au! VimEnter + + call assert_fails('augroup!', 'E471:') endfunc func Test_BufReadCmdHelp() @@ -1975,9 +2232,7 @@ func Test_change_mark_in_autocmds() endfunc func Test_Filter_noshelltemp() - if !executable('cat') - return - endif + CheckExecutable cat enew! call setline(1, ['a', 'b', 'c', 'd']) @@ -2142,9 +2397,8 @@ function Test_dirchanged_auto() endfunc " Test TextChangedI and TextChangedP -" See test/functional/viml/completion_spec.lua' func Test_ChangedP() - CheckFunction test_override + throw 'Skipped: use test/functional/editor/completion_spec.lua' new call setline(1, ['foo', 'bar', 'foobar']) call test_override("char_avail", 1) @@ -2665,7 +2919,7 @@ func Test_autocmd_CmdWinEnter() call term_sendkeys(buf, "q:") call term_wait(buf) call term_sendkeys(buf, ":echo b:dummy_var\<cr>") - call WaitForAssert({-> assert_match('^This is a dummy', term_getline(buf, 6))}, 1000) + call WaitForAssert({-> assert_match('^This is a dummy', term_getline(buf, 6))}, 2000) call term_sendkeys(buf, ":echo &buftype\<cr>") call WaitForAssert({-> assert_notmatch('^nofile', term_getline(buf, 6))}, 1000) call term_sendkeys(buf, ":echo winnr\<cr>") @@ -2752,6 +3006,17 @@ func Test_FileType_spell() setglobal spellfile= endfunc +" this was wiping out the current buffer and using freed memory +func Test_SpellFileMissing_bwipe() + next 0 + au SpellFileMissing 0 bwipe + call assert_fails('set spell spelllang=0', 'E937:') + + au! SpellFileMissing + set nospell spelllang=en + bwipe +endfunc + " Test closing a window or editing another buffer from a FileChangedRO handler " in a readonly buffer func Test_FileChangedRO_winclose() @@ -2855,6 +3120,7 @@ func Test_autocmd_invalid_args() call assert_fails('doautocmd * BufEnter', 'E217:') call assert_fails('augroup! x1a2b3', 'E367:') call assert_fails('autocmd BufNew <buffer=999> pwd', 'E680:') + call assert_fails('autocmd BufNew \) set ff=unix', 'E55:') endfunc " Test for deep nesting of autocmds @@ -2921,7 +3187,7 @@ func Test_BufDelete_changebuf() augroup END let save_cpo = &cpo set cpo+=f - call assert_fails('r Xfile', 'E484:') + call assert_fails('r Xfile', ['E812:', 'E484:']) call assert_equal('somefile', @%) let &cpo = save_cpo augroup TestAuCmd @@ -3188,19 +3454,29 @@ func Test_mode_changes() call assert_equal(5, g:nori_to_any) endif - if has('cmdwin') - let g:n_to_c = 0 - au ModeChanged n:c let g:n_to_c += 1 - let g:c_to_n = 0 - au ModeChanged c:n let g:c_to_n += 1 - let g:mode_seq += ['c', 'n', 'c', 'n'] - call feedkeys("q:\<C-C>\<Esc>", 'tnix') - call assert_equal(len(g:mode_seq) - 1, g:index) - call assert_equal(2, g:n_to_c) - call assert_equal(2, g:c_to_n) - unlet g:n_to_c - unlet g:c_to_n - endif + let g:n_to_c = 0 + au ModeChanged n:c let g:n_to_c += 1 + let g:c_to_n = 0 + au ModeChanged c:n let g:c_to_n += 1 + let g:mode_seq += ['c', 'n', 'c', 'n'] + call feedkeys("q:\<C-C>\<Esc>", 'tnix') + call assert_equal(len(g:mode_seq) - 1, g:index) + call assert_equal(2, g:n_to_c) + call assert_equal(2, g:c_to_n) + unlet g:n_to_c + unlet g:c_to_n + + let g:n_to_v = 0 + au ModeChanged n:v let g:n_to_v += 1 + let g:v_to_n = 0 + au ModeChanged v:n let g:v_to_n += 1 + let g:mode_seq += ['v', 'n'] + call feedkeys("v\<C-C>", 'tnix') + call assert_equal(len(g:mode_seq) - 1, g:index) + call assert_equal(1, g:n_to_v) + call assert_equal(1, g:v_to_n) + unlet g:n_to_v + unlet g:v_to_n au! ModeChanged delfunc TestMode @@ -3249,4 +3525,48 @@ func Test_noname_autocmd() augroup! test_noname_autocmd_group endfunc +func Test_autocmd_split_dummy() + " Autocommand trying to split a window containing a dummy buffer. + auto BufReadPre * exe "sbuf " .. expand("<abuf>") + " Avoid the "W11" prompt + au FileChangedShell * let v:fcs_choice = 'reload' + func Xautocmd_changelist() + cal writefile(['Xtestfile2:4:4'], 'Xerr') + edit Xerr + lex 'Xtestfile2:4:4' + endfunc + call Xautocmd_changelist() + " Should get E86, but it doesn't always happen (timing?) + silent! call Xautocmd_changelist() + + au! BufReadPre + au! FileChangedShell + delfunc Xautocmd_changelist + bwipe! Xerr + call delete('Xerr') +endfunc + +" This was crashing because there was only one window to execute autocommands +" in. +func Test_autocmd_nested_setbufvar() + CheckFeature python3 + + set hidden + edit Xaaa + edit Xbbb + call setline(1, 'bar') + enew + au BufWriteCmd Xbbb ++nested call setbufvar('Xaaa', '&ft', 'foo') | bw! Xaaa + au FileType foo call py3eval('vim.current.buffer.options["cindent"]') + wall + + au! BufWriteCmd + au! FileType foo + set nohidden + call delete('Xaaa') + call delete('Xbbb') + %bwipe! +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_autoload.vim b/src/nvim/testdir/test_autoload.vim index b8c4fa251f..e89fe3943b 100644 --- a/src/nvim/testdir/test_autoload.vim +++ b/src/nvim/testdir/test_autoload.vim @@ -10,6 +10,9 @@ func Test_autoload_dict_func() call assert_equal(1, g:called_foo_bar_echo) eval 'bar'->g:foo#addFoo()->assert_equal('barfoo') + + " empty name works in legacy script + call assert_equal('empty', foo#()) endfunc func Test_source_autoload() @@ -17,3 +20,6 @@ func Test_source_autoload() source sautest/autoload/sourced.vim call assert_equal(1, g:loaded_sourced_vim) endfunc + + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_backup.vim b/src/nvim/testdir/test_backup.vim index ce2bfe72bc..d304773da4 100644 --- a/src/nvim/testdir/test_backup.vim +++ b/src/nvim/testdir/test_backup.vim @@ -1,5 +1,7 @@ " Tests for the backup function +source check.vim + func Test_backup() set backup backupdir=. backupskip= new @@ -17,6 +19,22 @@ func Test_backup() call delete('Xbackup.txt~') endfunc +func Test_backup_backupskip() + set backup backupdir=. backupskip=*.txt + new + call setline(1, ['line1', 'line2']) + :f Xbackup.txt + :w! Xbackup.txt + " backup file is only created after + " writing a second time (before overwriting) + :w! Xbackup.txt + call assert_false(filereadable('Xbackup.txt~')) + bw! + set backup&vim backupdir&vim backupskip&vim + call delete('Xbackup.txt') + call delete('Xbackup.txt~') +endfunc + func Test_backup2() set backup backupdir=.// backupskip= new @@ -28,7 +46,7 @@ func Test_backup2() :w! Xbackup.txt sp *Xbackup.txt~ call assert_equal(['line1', 'line2', 'line3'], getline(1,'$')) - let f=expand('%') + let f = expand('%') call assert_match('%testdir%Xbackup.txt\~', f) bw! bw! @@ -48,7 +66,7 @@ func Test_backup2_backupcopy() :w! Xbackup.txt sp *Xbackup.txt~ call assert_equal(['line1', 'line2', 'line3'], getline(1,'$')) - let f=expand('%') + let f = expand('%') call assert_match('%testdir%Xbackup.txt\~', f) bw! bw! @@ -56,3 +74,16 @@ func Test_backup2_backupcopy() call delete(f) set backup&vim backupdir&vim backupcopy&vim backupskip&vim endfunc + +" Test for using a non-existing directory as a backup directory +func Test_non_existing_backupdir() + throw 'Skipped: Nvim auto-creates backup directory' + set backupdir=./non_existing_dir backupskip= + call writefile(['line1'], 'Xfile') + new Xfile + call assert_fails('write', 'E510:') + set backupdir&vim backupskip&vim + call delete('Xfile') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_blob.vim b/src/nvim/testdir/test_blob.vim index 70529c14d5..046acb81e1 100644 --- a/src/nvim/testdir/test_blob.vim +++ b/src/nvim/testdir/test_blob.vim @@ -88,6 +88,7 @@ func Test_blob_get_range() call assert_equal(0z0011223344, b[:]) call assert_equal(0z0011223344, b[:-1]) call assert_equal(0z, b[5:6]) + call assert_equal(0z0011, b[-10:1]) endfunc func Test_blob_get() @@ -208,6 +209,7 @@ func Test_blob_add() call assert_equal(0z001122, b) call add(b, '51') call assert_equal(0z00112233, b) + call assert_equal(1, add(v:_null_blob, 0x22)) call assert_fails('call add(b, [9])', 'E745:') call assert_fails('call add("", 0x01)', 'E897:') @@ -250,6 +252,7 @@ func Test_blob_func_remove() call assert_fails("call remove(b, 3, 2)", 'E979:') call assert_fails("call remove(1, 0)", 'E896:') call assert_fails("call remove(b, b)", 'E974:') + call assert_fails("call remove(b, 1, [])", 'E745:') call assert_fails("call remove(v:_null_blob, 1, 2)", 'E979:') " Translated from v8.2.3284 @@ -272,6 +275,7 @@ endfunc " filter() item in blob func Test_blob_filter() + call assert_equal(v:_null_blob, filter(v:_null_blob, '0')) call assert_equal(0z, filter(0zDEADBEEF, '0')) call assert_equal(0zADBEEF, filter(0zDEADBEEF, 'v:val != 0xDE')) call assert_equal(0zDEADEF, filter(0zDEADBEEF, 'v:val != 0xBE')) @@ -297,6 +301,9 @@ func Test_blob_index() call assert_equal(3, 0z11110111->index(0x11, 2)) call assert_equal(2, index(0z11111111, 0x11, -2)) call assert_equal(3, index(0z11110111, 0x11, -2)) + call assert_equal(0, index(0z11110111, 0x11, -10)) + call assert_fails("echo index(0z11110111, 0x11, [])", 'E745:') + call assert_equal(-1, index(v:_null_blob, 1)) call assert_fails('call index("asdf", 0)', 'E897:') endfunc @@ -313,6 +320,9 @@ func Test_blob_insert() call assert_fails('call insert(b, -1)', 'E475:') call assert_fails('call insert(b, 257)', 'E475:') call assert_fails('call insert(b, 0, [9])', 'E745:') + call assert_fails('call insert(b, 0, -20)', 'E475:') + call assert_fails('call insert(b, 0, 20)', 'E475:') + call assert_fails('call insert(b, [])', 'E745:') call assert_equal(0, insert(v:_null_blob, 0x33)) " Translated from v8.2.3284 diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim index a37751e748..2e377aa434 100644 --- a/src/nvim/testdir/test_breakindent.vim +++ b/src/nvim/testdir/test_breakindent.vim @@ -4,11 +4,11 @@ " while the test is run, the breakindent caching gets in its way. " It helps to change the tabstop setting and force a redraw (e.g. see " Test_breakindent08()) -if !exists('+breakindent') - throw 'Skipped: breakindent option not supported' -endif +source check.vim +CheckOption breakindent source view_util.vim +source screendump.vim let s:input ="\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP" @@ -422,6 +422,7 @@ func Test_breakindent11() let width = strlen(text[1:]) + indent(2) + strlen(&sbr) * 3 " text wraps 3 times call assert_equal(width, strdisplaywidth(text)) call s:close_windows('set sbr=') + call assert_equal(4, strdisplaywidth("\t", 4)) endfunc func Test_breakindent11_vartabs() @@ -855,7 +856,7 @@ func Test_breakindent20_list() " check formatlistpat indent with different list level " showbreak and sbr - setl briopt=min:5,sbr,list:-1,shift:2 + setl briopt=min:5,sbr,list:-1 setl showbreak=> redraw! let expect = [ @@ -868,6 +869,44 @@ func Test_breakindent20_list() \ ] let lines = s:screen_lines2(1, 6, 20) call s:compare_lines(expect, lines) + + " check formatlistpat indent with different list level + " showbreak sbr and shift + setl briopt=min:5,sbr,list:-1,shift:2 + setl showbreak=> + redraw! + let expect = [ + \ "* Congress shall ", + \ "> make no law ", + \ "*** Congress shall ", + \ "> make no law ", + \ "**** Congress shall ", + \ "> make no law ", + \ ] + let lines = s:screen_lines2(1, 6, 20) + call s:compare_lines(expect, lines) + + " check breakindent works if breakindentopt=list:-1 + " for a non list content + %delete _ + call setline(1, [' Congress shall make no law', + \ ' Congress shall make no law', + \ ' Congress shall make no law']) + norm! 1gg + setl briopt=min:5,list:-1 + setl showbreak= + redraw! + let expect = [ + \ " Congress shall ", + \ " make no law ", + \ " Congress shall ", + \ " make no law ", + \ " Congress shall ", + \ " make no law ", + \ ] + let lines = s:screen_lines2(1, 6, 20) + call s:compare_lines(expect, lines) + call s:close_windows('set breakindent& briopt& linebreak& list& listchars& showbreak&') endfunc @@ -876,17 +915,164 @@ endfunc func Test_window_resize_with_linebreak() new 53vnew - set linebreak - set showbreak=>> - set breakindent - set breakindentopt=shift:4 + setl linebreak + setl showbreak=>> + setl breakindent + setl breakindentopt=shift:4 call setline(1, "\naaaaaaaaa\n\na\naaaaa\n¯aaaaaaaaaa\naaaaaaaaaaaa\naaa\n\"a:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - aaaaaaaa\"\naaaaaaaa\n\"a") redraw! call assert_equal([" >>aa^@\"a: "], ScreenLines(2, 14)) vertical resize 52 redraw! call assert_equal([" >>aaa^@\"a:"], ScreenLines(2, 14)) + set linebreak& showbreak& breakindent& breakindentopt& %bw! endfunc +func Test_cursor_position_with_showbreak() + CheckScreendump + + let lines =<< trim END + vim9script + &signcolumn = 'yes' + &showbreak = '+ ' + var leftcol: number = win_getid()->getwininfo()->get(0, {})->get('textoff') + repeat('x', &columns - leftcol - 1)->setline(1) + 'second line'->setline(2) + END + call writefile(lines, 'XscriptShowbreak') + let buf = RunVimInTerminal('-S XscriptShowbreak', #{rows: 6}) + + call term_sendkeys(buf, "AX") + call VerifyScreenDump(buf, 'Test_cursor_position_with_showbreak', {}) + + call StopVimInTerminal(buf) + call delete('XscriptShowbreak') +endfunc + +func Test_no_spurious_match() + let s:input = printf('- y %s y %s', repeat('x', 50), repeat('x', 50)) + call s:test_windows('setl breakindent breakindentopt=list:-1 formatlistpat=^- hls') + let @/ = '\%>3v[y]' + redraw! + call searchcount().total->assert_equal(1) + " cleanup + set hls&vim + let s:input = "\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP" + bwipeout! +endfunc + +func Test_no_extra_indent() + call s:test_windows('setl breakindent breakindentopt=list:-1,min:10') + %d + let &l:formatlistpat='^\s*\d\+\.\s\+' + let text = 'word ' + let len = text->strcharlen() + let line1 = text->repeat((winwidth(0) / len) * 2) + let line2 = repeat(' ', 2) .. '1. ' .. line1 + call setline(1, [line2]) + redraw! + " 1) matches formatlist pattern, so indent + let expect = [ + \ " 1. word word word ", + \ " word word word ", + \ " word word ", + \ "~ ", + \ ] + let lines = s:screen_lines2(1, 4, 20) + call s:compare_lines(expect, lines) + " 2) change formatlist pattern + " -> indent adjusted + let &l:formatlistpat='^\s*\d\+\.' + let expect = [ + \ " 1. word word word ", + \ " word word word ", + \ " word word ", + \ "~ ", + \ ] + let lines = s:screen_lines2(1, 4, 20) + " 3) no local formatlist pattern, + " so use global one -> indent + let g_flp = &g:flp + let &g:formatlistpat='^\s*\d\+\.\s\+' + let &l:formatlistpat='' + let expect = [ + \ " 1. word word word ", + \ " word word word ", + \ " word word ", + \ "~ ", + \ ] + let lines = s:screen_lines2(1, 4, 20) + call s:compare_lines(expect, lines) + let &g:flp = g_flp + let &l:formatlistpat='^\s*\d\+\.' + " 4) add something in front, no additional indent + norm! gg0 + exe ":norm! 5iword \<esc>" + redraw! + let expect = [ + \ "word word word word ", + \ "word 1. word word ", + \ "word word word word ", + \ "word word ", + \ "~ ", + \ ] + let lines = s:screen_lines2(1, 5, 20) + call s:compare_lines(expect, lines) + bwipeout! +endfunc + +func Test_breakindent_column() + " restore original + let s:input ="\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP" + call s:test_windows('setl breakindent breakindentopt=column:10') + redraw! + " 1) default: does not indent, too wide :( + let expect = [ + \ " ", + \ " abcdefghijklmnop", + \ "qrstuvwxyzABCDEFGHIJ", + \ "KLMNOP " + \ ] + let lines = s:screen_lines2(1, 4, 20) + call s:compare_lines(expect, lines) + " 2) lower min value, so that breakindent works + setl breakindentopt+=min:5 + redraw! + let expect = [ + \ " ", + \ " abcdefghijklmnop", + \ " qrstuvwxyz", + \ " ABCDEFGHIJ", + \ " KLMNOP " + \ ] + let lines = s:screen_lines2(1, 5, 20) + " 3) set shift option -> no influence + setl breakindentopt+=shift:5 + redraw! + let expect = [ + \ " ", + \ " abcdefghijklmnop", + \ " qrstuvwxyz", + \ " ABCDEFGHIJ", + \ " KLMNOP " + \ ] + let lines = s:screen_lines2(1, 5, 20) + call s:compare_lines(expect, lines) + " 4) add showbreak value + setl showbreak=++ + redraw! + let expect = [ + \ " ", + \ " abcdefghijklmnop", + \ " ++qrstuvwx", + \ " ++yzABCDEF", + \ " ++GHIJKLMN", + \ " ++OP " + \ ] + let lines = s:screen_lines2(1, 6, 20) + call s:compare_lines(expect, lines) + bwipeout! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_buffer.vim b/src/nvim/testdir/test_buffer.vim index 4def3b5df9..9220cc17b9 100644 --- a/src/nvim/testdir/test_buffer.vim +++ b/src/nvim/testdir/test_buffer.vim @@ -67,16 +67,7 @@ func Test_bunload_with_offset() call assert_fails('1,4bunload', 'E16:') call assert_fails(',100bunload', 'E16:') - " Use a try-catch for this test. When assert_fails() is used for this - " test, the command fails with E515: instead of E90: - let caught_E90 = 0 - try - $bunload - catch /E90:/ - let caught_E90 = 1 - endtry - call assert_equal(1, caught_E90) - call assert_fails('$bunload', 'E515:') + call assert_fails('$bunload', 'E90:') endfunc " Test for :buffer, :bnext, :bprevious, :brewind, :blast and :bmodified @@ -147,6 +138,7 @@ func Test_bdelete_cmd() %bwipe! call assert_fails('bdelete 5', 'E516:') call assert_fails('1,1bdelete 1 2', 'E488:') + call assert_fails('bdelete \)', 'E55:') " Deleting a unlisted and unloaded buffer edit Xfile1 @@ -282,7 +274,7 @@ func Test_goto_buf_with_confirm() call assert_equal(1, &modified) call assert_equal('', @%) call feedkeys('y', 'L') - call assert_fails('confirm b Xfile', 'E37:') + call assert_fails('confirm b Xfile', ['', 'E37:']) call assert_equal(1, &modified) call assert_equal('', @%) call feedkeys('n', 'L') @@ -401,12 +393,12 @@ func Test_buffer_scheme() 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}, + \ #{id: 'ssb0', name: 'test://xyz/foo/ssb0' , match: 1}, + \ #{id: 'ssb1', name: 'test+abc://xyz/foo/ssb1', match: 0}, + \ #{id: 'ssb2', name: 'test_abc://xyz/foo/ssb2', match: 0}, + \ #{id: 'ssb3', name: 'test-abc://xyz/foo/ssb3', match: 1}, + \ #{id: 'ssb4', name: '-test://xyz/foo/ssb4' , match: 0}, + \ #{id: 'ssb5', name: 'test-://xyz/foo/ssb5' , match: 0}, \] for buf in bufnames new `=buf.name` @@ -431,6 +423,32 @@ func Test_buf_pattern_invalid() vsplit 00000000000000000000000000 silent! buf [0--]\&\zs*\zs*e bwipe! + + " similar case with different code path + split 0 + edit ÿ + silent! buf [0--]\&\zs*\zs*0 + bwipe! +endfunc + +" Test for the 'maxmem' and 'maxmemtot' options +func Test_buffer_maxmem() + " use 1KB per buffer and 2KB for all the buffers + " set maxmem=1 maxmemtot=2 + new + let v:errmsg = '' + " try opening some files + edit test_arglist.vim + call assert_equal('test_arglist.vim', bufname()) + edit test_eval_stuff.vim + call assert_equal('test_eval_stuff.vim', bufname()) + b test_arglist.vim + call assert_equal('test_arglist.vim', bufname()) + b test_eval_stuff.vim + call assert_equal('test_eval_stuff.vim', bufname()) + close + call assert_equal('', v:errmsg) + " set maxmem& maxmemtot& endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_bufline.vim b/src/nvim/testdir/test_bufline.vim index 3b5bcbce89..0468025f55 100644 --- a/src/nvim/testdir/test_bufline.vim +++ b/src/nvim/testdir/test_bufline.vim @@ -2,14 +2,18 @@ source shared.vim source screendump.vim +source check.vim func Test_setbufline_getbufline() + " similar to Test_set_get_bufline() new let b = bufnr('%') hide call assert_equal(0, setbufline(b, 1, ['foo', 'bar'])) call assert_equal(['foo'], getbufline(b, 1)) + call assert_equal('foo', getbufoneline(b, 1)) call assert_equal(['bar'], getbufline(b, '$')) + call assert_equal('bar', getbufoneline(b, '$')) call assert_equal(['foo', 'bar'], getbufline(b, 1, 2)) exe "bd!" b call assert_equal([], getbufline(b, 1, 2)) @@ -18,13 +22,36 @@ func Test_setbufline_getbufline() call setline(1, ['a', 'b', 'c']) let b = bufnr('%') wincmd w + + call assert_equal(1, setbufline(b, 5, 'x')) call assert_equal(1, setbufline(b, 5, ['x'])) + call assert_equal(1, setbufline(b, 5, [])) + call assert_equal(1, setbufline(b, 5, v:_null_list)) + + call assert_equal(1, 'x'->setbufline(bufnr('$') + 1, 1)) call assert_equal(1, ['x']->setbufline(bufnr('$') + 1, 1)) + call assert_equal(1, []->setbufline(bufnr('$') + 1, 1)) + call assert_equal(1, v:_null_list->setbufline(bufnr('$') + 1, 1)) + + call assert_equal(['a', 'b', 'c'], getbufline(b, 1, '$')) + call assert_equal(0, setbufline(b, 4, ['d', 'e'])) call assert_equal(['c'], b->getbufline(3)) + call assert_equal('c', b->getbufoneline(3)) call assert_equal(['d'], getbufline(b, 4)) + call assert_equal('d', getbufoneline(b, 4)) call assert_equal(['e'], getbufline(b, 5)) + call assert_equal('e', getbufoneline(b, 5)) call assert_equal([], getbufline(b, 6)) + call assert_equal([], getbufline(b, 2, 1)) + + if has('job') + call setbufline(b, 2, [function('eval'), #{key: 123}, test_null_job()]) + call assert_equal(["function('eval')", + \ "{'key': 123}", + \ "no process"], + \ getbufline(b, 2, 4)) + endif exe "bwipe! " . b endfunc @@ -71,20 +98,53 @@ func Test_appendbufline() new let b = bufnr('%') hide + + new + call setline(1, ['line1', 'line2', 'line3']) + normal! 2gggg + call assert_equal(2, line("''")) + call assert_equal(0, appendbufline(b, 0, ['foo', 'bar'])) call assert_equal(['foo'], getbufline(b, 1)) call assert_equal(['bar'], getbufline(b, 2)) call assert_equal(['foo', 'bar'], getbufline(b, 1, 2)) + call assert_equal(0, appendbufline(b, 0, 'baz')) + call assert_equal(['baz', 'foo', 'bar'], getbufline(b, 1, 3)) + + " appendbufline() in a hidden buffer shouldn't move marks in current window. + call assert_equal(2, line("''")) + bwipe! + exe "bd!" b - call assert_equal([], getbufline(b, 1, 2)) + call assert_equal([], getbufline(b, 1, 3)) split Xtest call setline(1, ['a', 'b', 'c']) let b = bufnr('%') wincmd w + + call assert_equal(1, appendbufline(b, -1, 'x')) call assert_equal(1, appendbufline(b, -1, ['x'])) + call assert_equal(1, appendbufline(b, -1, [])) + call assert_equal(1, appendbufline(b, -1, v:_null_list)) + + call assert_equal(1, appendbufline(b, 4, 'x')) call assert_equal(1, appendbufline(b, 4, ['x'])) + call assert_equal(1, appendbufline(b, 4, [])) + call assert_equal(1, appendbufline(b, 4, v:_null_list)) + + call assert_equal(1, appendbufline(1234, 1, 'x')) call assert_equal(1, appendbufline(1234, 1, ['x'])) + call assert_equal(1, appendbufline(1234, 1, [])) + call assert_equal(1, appendbufline(1234, 1, v:_null_list)) + + call assert_equal(0, appendbufline(b, 1, [])) + call assert_equal(0, appendbufline(b, 1, v:_null_list)) + call assert_equal(1, appendbufline(b, 3, [])) + call assert_equal(1, appendbufline(b, 3, v:_null_list)) + + call assert_equal(['a', 'b', 'c'], getbufline(b, 1, '$')) + call assert_equal(0, appendbufline(b, 3, ['d', 'e'])) call assert_equal(['c'], getbufline(b, 3)) call assert_equal(['d'], getbufline(b, 4)) @@ -98,10 +158,21 @@ func Test_deletebufline() let b = bufnr('%') call setline(1, ['aaa', 'bbb', 'ccc']) hide + + new + call setline(1, ['line1', 'line2', 'line3']) + normal! 2gggg + call assert_equal(2, line("''")) + call assert_equal(0, deletebufline(b, 2)) call assert_equal(['aaa', 'ccc'], getbufline(b, 1, 2)) call assert_equal(0, deletebufline(b, 2, 8)) call assert_equal(['aaa'], getbufline(b, 1, 2)) + + " deletebufline() in a hidden buffer shouldn't move marks in current window. + call assert_equal(2, line("''")) + bwipe! + exe "bd!" b call assert_equal(1, b->deletebufline(1)) @@ -130,9 +201,8 @@ func Test_deletebufline() endfunc func Test_appendbufline_redraw() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckScreendump + let lines =<< trim END new foo let winnr = 'foo'->bufwinnr() @@ -207,4 +277,20 @@ func Test_setbufline_startup_nofile() call delete('Xresult') endfunc +" Test that setbufline(), appendbufline() and deletebufline() should fail and +" return 1 when "textlock" is active. +func Test_change_bufline_with_textlock() + new + inoremap <buffer> <expr> <F2> setbufline('', 1, '') + call assert_fails("normal a\<F2>", 'E565:') + call assert_equal('1', getline(1)) + inoremap <buffer> <expr> <F2> appendbufline('', 1, '') + call assert_fails("normal a\<F2>", 'E565:') + call assert_equal('11', getline(1)) + inoremap <buffer> <expr> <F2> deletebufline('', 1) + call assert_fails("normal a\<F2>", 'E565:') + call assert_equal('111', getline(1)) + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim index d6d44d1901..2b37f2c7c0 100644 --- a/src/nvim/testdir/test_cd.vim +++ b/src/nvim/testdir/test_cd.vim @@ -5,7 +5,7 @@ source check.vim func Test_cd_large_path() " This used to crash with a heap write overflow. - call assert_fails('cd ' . repeat('x', 5000), 'E472:') + call assert_fails('cd ' . repeat('x', 5000), 'E344:') endfunc func Test_cd_up_and_down() @@ -45,9 +45,7 @@ func Test_cd_minus() call assert_equal(path, getcwd()) " Test for :cd - after a failed :cd - " v8.2.1183 is not ported yet - " call assert_fails('cd /nonexistent', 'E344:') - call assert_fails('cd /nonexistent', 'E472:') + call assert_fails('cd /nonexistent', 'E344:') call assert_equal(path, getcwd()) cd - call assert_equal(path_dotdot, getcwd()) @@ -68,30 +66,6 @@ func Test_cd_minus() call delete('Xresult') endfunc -func Test_cd_with_cpo_chdir() - e Xfoo - call setline(1, 'foo') - let path = getcwd() - " set cpo+=. - - " :cd should fail when buffer is modified and 'cpo' contains dot. - " call assert_fails('cd ..', 'E747:') - call assert_equal(path, getcwd()) - - " :cd with exclamation mark should succeed. - cd! .. - call assert_notequal(path, getcwd()) - - " :cd should succeed when buffer has been written. - w! - exe 'cd ' .. fnameescape(path) - call assert_equal(path, getcwd()) - - call delete('Xfoo') - set cpo& - bw! -endfunc - " Test for chdir() func Test_chdir_func() let topdir = getcwd() @@ -127,7 +101,7 @@ func Test_chdir_func() call assert_equal('testdir', fnamemodify(getcwd(1, 1), ':t')) " Error case - call assert_fails("call chdir('dir-abcd')", 'E472:') + call assert_fails("call chdir('dir-abcd')", 'E344:') silent! let d = chdir("dir_abcd") call assert_equal("", d) " Should not crash @@ -259,6 +233,8 @@ endfunc func Test_getcwd_actual_dir() CheckFunction test_autochdir + CheckOption autochdir + let startdir = getcwd() call mkdir('Xactual') call test_autochdir() diff --git a/src/nvim/testdir/test_charsearch.vim b/src/nvim/testdir/test_charsearch.vim index d386d74f8d..54e0a62ce5 100644 --- a/src/nvim/testdir/test_charsearch.vim +++ b/src/nvim/testdir/test_charsearch.vim @@ -43,36 +43,6 @@ func Test_charsearch() enew! endfunc -" Test for t,f,F,T movement commands and 'cpo-;' setting -func Test_search_cmds() - enew! - call append(0, ["aaa two three four", " zzz", "yyy ", - \ "bbb yee yoo four", "ccc two three four", - \ "ddd yee yoo four"]) - set cpo-=; - 1 - normal! 0tt;D - 2 - normal! 0fz;D - 3 - normal! $Fy;D - 4 - normal! $Ty;D - set cpo+=; - 5 - normal! 0tt;;D - 6 - normal! $Ty;;D - - call assert_equal('aaa two', getline(1)) - call assert_equal(' z', getline(2)) - call assert_equal('y', getline(3)) - call assert_equal('bbb y', getline(4)) - call assert_equal('ccc', getline(5)) - call assert_equal('ddd yee y', getline(6)) - enew! -endfunc - " Test for character search in virtual edit mode with <Tab> func Test_csearch_virtualedit() new @@ -81,7 +51,7 @@ func Test_csearch_virtualedit() normal! tb call assert_equal([0, 1, 2, 6], getpos('.')) set virtualedit& - close! + bw! endfunc " Test for character search failure in latin1 encoding @@ -95,7 +65,34 @@ func Test_charsearch_latin1() call assert_beeps('normal $Fz') call assert_beeps('normal $Tx') let &encoding = save_enc - close! + bw! +endfunc + +" Test for using character search to find a multibyte character with composing +" characters. +func Test_charsearch_composing_char() + new + call setline(1, "one two thq\u0328\u0301r\u0328\u0301ree") + call feedkeys("fr\u0328\u0301", 'xt') + call assert_equal([0, 1, 16, 0, 12], getcurpos()) + + " use character search with a multi-byte character followed by a + " non-composing character + call setline(1, "abc deȉf ghi") + call feedkeys("ggcf\u0209\u0210", 'xt') + call assert_equal("\u0210f ghi", getline(1)) + bw! +endfunc + +" Test for character search with 'hkmap' +func Test_charsearch_hkmap() + new + set hkmap + call setline(1, "ùðáâ÷ëòéïçìêöî") + call feedkeys("fë", 'xt') + call assert_equal([0, 1, 11, 0, 6], getcurpos()) + set hkmap& + bw! endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_checkpath.vim b/src/nvim/testdir/test_checkpath.vim index eff30cf205..f6a3bbce08 100644 --- a/src/nvim/testdir/test_checkpath.vim +++ b/src/nvim/testdir/test_checkpath.vim @@ -102,3 +102,20 @@ func Test_checkpath3() set include& set includeexpr& endfunc + +" Test for invalid regex in 'include' and 'define' options +func Test_checkpath_errors() + let save_include = &include + set include=\\%( + call assert_fails('checkpath', 'E53:') + let &include = save_include + + let save_define = &define + set define=\\%( + call assert_fails('dsearch abc', 'E53:') + let &define = save_define + + call assert_fails('psearch \%(', 'E53:') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_clientserver.vim b/src/nvim/testdir/test_clientserver.vim index a4ebce5af9..d8b29f6c42 100644 --- a/src/nvim/testdir/test_clientserver.vim +++ b/src/nvim/testdir/test_clientserver.vim @@ -13,9 +13,7 @@ source shared.vim func Check_X11_Connection() if has('x11') - if empty($DISPLAY) - throw 'Skipped: $DISPLAY is not set' - endif + CheckEnv DISPLAY try call remote_send('xxx', '') catch @@ -89,6 +87,7 @@ func Test_client_server() call writefile(['one', 'two'], 'Xclientfile') call system(cmd) call WaitForAssert({-> assert_equal('two', remote_expr(name, "getline(2)", "", 2))}) + call delete('Xclientfile') " Expression evaluated locally. if v:servername == '' @@ -102,7 +101,7 @@ func Test_client_server() call remote_send(v:servername, ":let g:testvar2 = 75\<CR>") call feedkeys('', 'x') call assert_equal(75, g:testvar2) - call assert_fails('let v = remote_expr(v:servername, "/2")', 'E449:') + call assert_fails('let v = remote_expr(v:servername, "/2")', ['E15:.*/2']) call remote_send(name, ":call server2client(expand('<client>'), 'got it')\<CR>", 'g:myserverid') call assert_equal('got it', g:myserverid->remote_read(2)) @@ -142,19 +141,19 @@ func Test_client_server() " Edit multiple files using --remote call system(cmd .. ' --remote Xfile1 Xfile2 Xfile3') - call assert_equal("Xfile1\nXfile2\nXfile3\n", remote_expr(name, 'argv()')) + call assert_match(".*Xfile1\n.*Xfile2\n.*Xfile3\n", remote_expr(name, 'argv()')) eval name->remote_send(":%bw!\<CR>") " Edit files in separate tab pages call system(cmd .. ' --remote-tab Xfile1 Xfile2 Xfile3') - call assert_equal('3', remote_expr(name, 'tabpagenr("$")')) - call assert_equal('Xfile2', remote_expr(name, 'bufname(tabpagebuflist(2)[0])')) + call WaitForAssert({-> assert_equal('3', remote_expr(name, 'tabpagenr("$")'))}) + call assert_match('.*\<Xfile2', remote_expr(name, 'bufname(tabpagebuflist(2)[0])')) eval name->remote_send(":%bw!\<CR>") " Edit a file using --remote-wait eval name->remote_send(":source $VIMRUNTIME/plugin/rrhelper.vim\<CR>") call system(cmd .. ' --remote-wait +enew Xfile1') - call assert_equal("Xfile1", remote_expr(name, 'bufname("#")')) + call assert_match('.*\<Xfile1', remote_expr(name, 'bufname("#")')) eval name->remote_send(":%bw!\<CR>") " Edit files using --remote-tab-wait @@ -182,9 +181,12 @@ func Test_client_server() endif endtry + call assert_fails('call remote_startserver([])', 'E730:') call assert_fails("let x = remote_peek([])", 'E730:') - call assert_fails("let x = remote_read('vim10')", 'E277:') - call assert_fails("call server2client('abc', 'xyz')", 'E258:') + call assert_fails("let x = remote_read('vim10')", + \ has('unix') ? ['E573:.*vim10'] : 'E277:') + call assert_fails("call server2client('abc', 'xyz')", + \ has('unix') ? ['E573:.*abc'] : 'E258:') endfunc " Uncomment this line to get a debugging log diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 3f8e141afa..cf1d56ae38 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -5,9 +5,22 @@ source screendump.vim source view_util.vim source shared.vim +func SetUp() + func SaveLastScreenLine() + let g:Sline = Screenline(&lines - 1) + return '' + endfunc + cnoremap <expr> <F4> SaveLastScreenLine() +endfunc + +func TearDown() + delfunc SaveLastScreenLine + cunmap <F4> +endfunc + func Test_complete_tab() call writefile(['testfile'], 'Xtestfile') - call feedkeys(":e Xtestf\t\r", "tx") + call feedkeys(":e Xtest\t\r", "tx") call assert_equal('testfile', getline(1)) " Pressing <Tab> after '%' completes the current file, also on MS-Windows @@ -25,6 +38,47 @@ func Test_complete_list() " used. call feedkeys(":chistory \<C-D>\<C-B>\"\<CR>", 'xt') call assert_equal("\"chistory \<C-D>", @:) + + " Test for displaying the tail of the completion matches + set wildmode=longest,full + call mkdir('Xtest') + call writefile([], 'Xtest/a.c') + call writefile([], 'Xtest/a.h') + let g:Sline = '' + call feedkeys(":e Xtest/\<C-D>\<F4>\<C-B>\"\<CR>", 'xt') + call assert_equal('a.c a.h', g:Sline) + call assert_equal('"e Xtest/', @:) + if has('win32') + " Test for 'completeslash' + set completeslash=backslash + call feedkeys(":e Xtest\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xtest\', @:) + call feedkeys(":e Xtest/\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xtest\a.', @:) + set completeslash=slash + call feedkeys(":e Xtest\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xtest/', @:) + call feedkeys(":e Xtest\\\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xtest/a.', @:) + set completeslash& + endif + + " Test for displaying the tail with wildcards + let g:Sline = '' + call feedkeys(":e Xtes?/\<C-D>\<F4>\<C-B>\"\<CR>", 'xt') + call assert_equal('Xtest/a.c Xtest/a.h', g:Sline) + call assert_equal('"e Xtes?/', @:) + let g:Sline = '' + call feedkeys(":e Xtes*/\<C-D>\<F4>\<C-B>\"\<CR>", 'xt') + call assert_equal('Xtest/a.c Xtest/a.h', g:Sline) + call assert_equal('"e Xtes*/', @:) + let g:Sline = '' + call feedkeys(":e Xtes[/\<C-D>\<F4>\<C-B>\"\<CR>", 'xt') + call assert_equal(':e Xtes[/', g:Sline) + call assert_equal('"e Xtes[/', @:) + + call delete('Xtest', 'rf') + set wildmode& endfunc func Test_complete_wildmenu() @@ -89,14 +143,25 @@ func Test_complete_wildmenu() call assert_equal('"e Xtestfile3 Xtestfile4', @:) cd - + " test for wildmenumode() + cnoremap <expr> <F2> wildmenumode() + call feedkeys(":cd Xdir\<Tab>\<F2>\<C-B>\"\<CR>", 'tx') + call assert_equal('"cd Xdir1/0', @:) + call feedkeys(":e Xdir1/\<Tab>\<F2>\<C-B>\"\<CR>", 'tx') + call assert_equal('"e Xdir1/Xdir2/1', @:) + cunmap <F2> + + " Test for canceling the wild menu by pressing <PageDown> or <PageUp>. + " After this pressing <Left> or <Right> should not change the selection. + call feedkeys(":sign \<Tab>\<PageDown>\<Left>\<Right>\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign define', @:) + call histadd('cmd', 'TestWildMenu') + call feedkeys(":sign \<Tab>\<PageUp>\<Left>\<Right>\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"TestWildMenu', @:) + " cleanup %bwipe - call delete('Xdir1/Xdir2/Xtestfile4') - call delete('Xdir1/Xdir2/Xtestfile3') - call delete('Xdir1/Xtestfile2') - call delete('Xdir1/Xtestfile1') - call delete('Xdir1/Xdir2', 'd') - call delete('Xdir1', 'd') + call delete('Xdir1', 'rf') set nowildmenu endfunc @@ -236,9 +301,6 @@ func Test_cmdheight_tabline() endfunc func Test_map_completion() - if !has('cmdline_compl') - return - endif call feedkeys(":map <unique> <si\<Tab>\<Home>\"\<CR>", 'xt') call assert_equal('"map <unique> <silent>', getreg(':')) call feedkeys(":map <script> <un\<Tab>\<Home>\"\<CR>", 'xt') @@ -309,26 +371,29 @@ func Test_map_completion() unmap <Left> " set cpo-=k + call assert_fails('call feedkeys(":map \\\\%(\<Tab>\<Home>\"\<CR>", "xt")', 'E53:') + unmap <Middle>x set cpo&vim endfunc func Test_match_completion() - if !has('cmdline_compl') - return - endif hi Aardig ctermfg=green + " call feedkeys(":match \<Tab>\<Home>\"\<CR>", 'xt') call feedkeys(":match A\<Tab>\<Home>\"\<CR>", 'xt') - call assert_equal('"match Aardig', getreg(':')) + call assert_equal('"match Aardig', @:) call feedkeys(":match \<S-Tab>\<Home>\"\<CR>", 'xt') - call assert_equal('"match none', getreg(':')) + call assert_equal('"match none', @:) + call feedkeys(":match | chist\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"match | chistory', @:) endfunc func Test_highlight_completion() - if !has('cmdline_compl') - return - endif hi Aardig ctermfg=green + " call feedkeys(":hi \<Tab>\<Home>\"\<CR>", 'xt') + call feedkeys(":hi A\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"hi Aardig', getreg(':')) + " call feedkeys(":hi default \<Tab>\<Home>\"\<CR>", 'xt') call feedkeys(":hi default A\<Tab>\<Home>\"\<CR>", 'xt') call assert_equal('"hi default Aardig', getreg(':')) call feedkeys(":hi clear Aa\<Tab>\<Home>\"\<CR>", 'xt') @@ -349,39 +414,7 @@ func Test_highlight_completion() call assert_equal([], getcompletion('A', 'highlight')) endfunc -func Test_expr_completion() - if !has('cmdline_compl') - return - endif - for cmd in [ - \ 'let a = ', - \ 'const a = ', - \ 'if', - \ 'elseif', - \ 'while', - \ 'for', - \ 'echo', - \ 'echon', - \ 'execute', - \ 'echomsg', - \ 'echoerr', - \ 'call', - \ 'return', - \ 'cexpr', - \ 'caddexpr', - \ 'cgetexpr', - \ 'lexpr', - \ 'laddexpr', - \ 'lgetexpr'] - call feedkeys(":" . cmd . " getl\<Tab>\<Home>\"\<CR>", 'xt') - call assert_equal('"' . cmd . ' getline(', getreg(':')) - endfor -endfunc - func Test_getcompletion() - if !has('cmdline_compl') - return - endif let groupcount = len(getcompletion('', 'event')) call assert_true(groupcount > 0) let matchcount = len('File'->getcompletion('event')) @@ -396,6 +429,7 @@ func Test_getcompletion() call assert_true(matchcount > 0) let matchcount = len(getcompletion('File.', 'menu')) call assert_true(matchcount > 0) + source $VIMRUNTIME/delmenu.vim endif let l = getcompletion('v:n', 'var') @@ -406,6 +440,8 @@ func Test_getcompletion() args a.c b.c let l = getcompletion('', 'arglist') call assert_equal(['a.c', 'b.c'], l) + let l = getcompletion('a.', 'buffer') + call assert_equal(['a.c'], l) %argdelete let l = getcompletion('', 'augroup') @@ -461,6 +497,11 @@ func Test_getcompletion() let l = getcompletion('run', 'file', 1) call assert_true(index(l, 'runtest.vim') < 0) set wildignore& + " Directory name with space character + call mkdir('Xdir with space') + call assert_equal(['Xdir with space/'], getcompletion('Xdir\ w', 'shellcmd')) + call assert_equal(['./Xdir with space/'], getcompletion('./Xdir', 'shellcmd')) + call delete('Xdir with space', 'd') let l = getcompletion('ha', 'filetype') call assert_true(index(l, 'hamster') >= 0) @@ -535,11 +576,17 @@ func Test_getcompletion() call assert_equal(cmds, l) let l = getcompletion('list ', 'sign') call assert_equal(['Testing'], l) + let l = getcompletion('de*', 'sign') + call assert_equal(['define'], l) + let l = getcompletion('p?', 'sign') + call assert_equal(['place'], l) + let l = getcompletion('j.', 'sign') + call assert_equal(['jump'], l) endif " Command line completion tests let l = getcompletion('cd ', 'cmdline') - call assert_true(index(l, 'sautest/') >= 0) + call assert_true(index(l, 'samples/') >= 0) let l = getcompletion('cd NoMatch', 'cmdline') call assert_equal([], l) let l = getcompletion('let v:n', 'cmdline') @@ -587,11 +634,39 @@ func Test_getcompletion() call delete('Xtags') set tags& + edit a~b + enew + call assert_equal(['a~b'], getcompletion('a~', 'buffer')) + bw a~b + + if has('unix') + edit Xtest\ + enew + call assert_equal(['Xtest\'], getcompletion('Xtest\', 'buffer')) + bw Xtest\ + endif + call assert_fails("call getcompletion('\\\\@!\\\\@=', 'buffer')", 'E871:') call assert_fails('call getcompletion("", "burp")', 'E475:') call assert_fails('call getcompletion("abc", [])', 'E475:') endfunc +" Test for getcompletion() with "fuzzy" in 'wildoptions' +func Test_getcompletion_wildoptions() + let save_wildoptions = &wildoptions + set wildoptions& + let l = getcompletion('space', 'option') + call assert_equal([], l) + let l = getcompletion('ier', 'command') + call assert_equal([], l) + set wildoptions=fuzzy + let l = getcompletion('space', 'option') + call assert_true(index(l, 'backspace') >= 0) + let l = getcompletion('ier', 'command') + call assert_true(index(l, 'compiler') >= 0) + let &wildoptions = save_wildoptions +endfunc + func Test_fullcommand() let tests = { \ '': '', @@ -664,7 +739,7 @@ func Test_expand_star_star() call mkdir('a/b', 'p') call writefile(['asdfasdf'], 'a/b/fileXname') call feedkeys(":find **/fileXname\<Tab>\<CR>", 'xt') - call assert_equal('find a/b/fileXname', getreg(':')) + call assert_equal('find a/b/fileXname', @:) bwipe! call delete('a', 'rf') endfunc @@ -812,7 +887,31 @@ func Test_cmdline_complete_user_cmd() call assert_equal('"Foo blue', @:) call feedkeys(":Foo b\<Tab>\<Home>\"\<cr>", 'tx') call assert_equal('"Foo blue', @:) + call feedkeys(":Foo a b\<Tab>\<Home>\"\<cr>", 'tx') + call assert_equal('"Foo a blue', @:) + call feedkeys(":Foo b\\\<Tab>\<Home>\"\<cr>", 'tx') + call assert_equal('"Foo b\', @:) + call feedkeys(":Foo b\\x\<Tab>\<Home>\"\<cr>", 'tx') + call assert_equal('"Foo b\x', @:) delcommand Foo + + redraw + call assert_equal('~', Screenline(&lines - 1)) + command! FooOne : + command! FooTwo : + + set nowildmenu + call feedkeys(":Foo\<Tab>\<Home>\"\<cr>", 'tx') + call assert_equal('"FooOne', @:) + call assert_equal('~', Screenline(&lines - 1)) + + call feedkeys(":Foo\<S-Tab>\<Home>\"\<cr>", 'tx') + call assert_equal('"FooTwo', @:) + call assert_equal('~', Screenline(&lines - 1)) + + delcommand FooOne + delcommand FooTwo + set wildmenu& endfunc func Test_complete_user_cmd() @@ -883,7 +982,7 @@ func Test_cmdline_complete_bang() endif endfunc -funct Test_cmdline_complete_languages() +func Test_cmdline_complete_languages() let lang = substitute(execute('language time'), '.*"\(.*\)"$', '\1', '') call assert_equal(lang, v:lc_time) @@ -920,10 +1019,8 @@ endfunc func Test_cmdline_complete_env_variable() let $X_VIM_TEST_COMPLETE_ENV = 'foo' - call feedkeys(":edit $X_VIM_TEST_COMPLETE_E\<C-A>\<C-B>\"\<CR>", 'tx') call assert_match('"edit $X_VIM_TEST_COMPLETE_ENV', @:) - unlet $X_VIM_TEST_COMPLETE_ENV endfunc @@ -1012,14 +1109,6 @@ func Test_cmdline_complete_various() call feedkeys(":match Search /pat/\<C-A>\<C-B>\"\<CR>", 'xt') call assert_equal("\"match Search /pat/\<C-A>", @:) - " completion for the :s command - call feedkeys(":s/from/to/g\<C-A>\<C-B>\"\<CR>", 'xt') - call assert_equal("\"s/from/to/g\<C-A>", @:) - - " completion for the :dlist command - call feedkeys(":dlist 10 /pat/ a\<C-A>\<C-B>\"\<CR>", 'xt') - call assert_equal("\"dlist 10 /pat/ a\<C-A>", @:) - " completion for the :doautocmd command call feedkeys(":doautocmd User MyCmd a.c\<C-A>\<C-B>\"\<CR>", 'xt') call assert_equal("\"doautocmd User MyCmd a.c\<C-A>", @:) @@ -1079,7 +1168,7 @@ func Test_cmdline_complete_various() map <F3> :ls<CR> com -nargs=* -complete=mapping MyCmd call feedkeys(":MyCmd <F\<C-A>\<C-B>\"\<CR>", 'xt') - call assert_equal('"MyCmd <F3>', @:) + call assert_equal('"MyCmd <F3> <F4>', @:) mapclear delcom MyCmd @@ -1103,9 +1192,77 @@ func Test_cmdline_complete_various() call feedkeys(":e `a1b2c\t\<C-B>\"\<CR>", 'xt') call assert_equal('"e `a1b2c', @:) - " completion for the expression register - call feedkeys(":\"\<C-R>=float2\t\"\<C-B>\"\<CR>", 'xt') - call assert_equal('"float2nr("', @=) + " completion for :language command with an invalid argument + call feedkeys(":language dummy \t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"language dummy \t", @:) + + " completion for commands after a :global command + call feedkeys(":g/a\\xb/clearj\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"g/a\xb/clearjumps', @:) + + " completion with ambiguous user defined commands + com TCmd1 echo 'TCmd1' + com TCmd2 echo 'TCmd2' + call feedkeys(":TCmd \t\<C-B>\"\<CR>", 'xt') + call assert_equal('"TCmd ', @:) + delcom TCmd1 + delcom TCmd2 + + " completion after a range followed by a pipe (|) character + call feedkeys(":1,10 | chist\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"1,10 | chistory', @:) + + " completion after a :global command + call feedkeys(":g/a/chist\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"g/a/chistory', @:) + call feedkeys(":g/a\\/chist\t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"g/a\\/chist\t", @:) + + " use <Esc> as the 'wildchar' for completion + set wildchar=<Esc> + call feedkeys(":g/a\\xb/clearj\<Esc>\<C-B>\"\<CR>", 'xt') + call assert_equal('"g/a\xb/clearjumps', @:) + " pressing <esc> twice should cancel the command + call feedkeys(":chist\<Esc>\<Esc>", 'xt') + call assert_equal('"g/a\xb/clearjumps', @:) + set wildchar& + + if has('unix') + " should be able to complete a file name that starts with a '~'. + call writefile([], '~Xtest') + call feedkeys(":e \\~X\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e \~Xtest', @:) + call delete('~Xtest') + + " should be able to complete a file name that has a '*' + call writefile([], 'Xx*Yy') + call feedkeys(":e Xx\*\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xx\*Yy', @:) + call delete('Xx*Yy') + + " use a literal star + call feedkeys(":e \\*\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e \*', @:) + endif + + call feedkeys(":py3f\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"py3file', @:) +endfunc + +" Test for 'wildignorecase' +func Test_cmdline_wildignorecase() + CheckUnix + call writefile([], 'XTEST') + set wildignorecase + call feedkeys(":e xt\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e XTEST', @:) + call assert_equal(['XTEST'], getcompletion('xt', 'file')) + let g:Sline = '' + call feedkeys(":e xt\<C-d>\<F4>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e xt', @:) + call assert_equal('XTEST', g:Sline) + set wildignorecase& + call delete('XTEST') endfunc func Test_cmdline_write_alternatefile() @@ -1162,7 +1319,7 @@ func Test_cmdline_search_range() call assert_equal('B', getline(2)) let @/ = 'apple' - call assert_fails('\/print', 'E486:') + call assert_fails('\/print', ['E486:.*apple']) bwipe! endfunc @@ -1172,7 +1329,7 @@ func Test_tick_mark_in_range() " If only the tick is passed as a range and no command is specified, there " should not be an error call feedkeys(":'\<CR>", 'xt') - call assert_equal("'", getreg(':')) + call assert_equal("'", @:) call assert_fails("',print", 'E78:') endfunc @@ -1278,6 +1435,11 @@ func Test_verbosefile() let log = readfile('Xlog') call assert_match("foo\nbar", join(log, "\n")) call delete('Xlog') + call mkdir('Xdir') + if !has('win32') " FIXME: no error on Windows, libuv bug? + call assert_fails('set verbosefile=Xdir', ['E484:.*Xdir', 'E474:']) + endif + call delete('Xdir', 'd') endfunc func Test_verbose_option() @@ -1514,6 +1676,7 @@ func Test_cmdline_expand_special() call assert_fails('e <afile>', 'E495:') call assert_fails('e <abuf>', 'E496:') call assert_fails('e <amatch>', 'E497:') + call writefile([], 'Xfile.cpp') call writefile([], 'Xfile.java') new Xfile.cpp @@ -1528,7 +1691,7 @@ func Test_cmdwin_jump_to_win() call assert_fails('call feedkeys("q:\<C-W>\<C-W>\<CR>", "xt")', 'E11:') new set modified - call assert_fails('call feedkeys("q/:qall\<CR>", "xt")', 'E162:') + call assert_fails('call feedkeys("q/:qall\<CR>", "xt")', ['E37:', 'E162:']) close! call feedkeys("q/:close\<CR>", "xt") call assert_equal(1, winnr('$')) @@ -1542,13 +1705,7 @@ endfunc func Test_cmdwin_tabpage() tabedit - " v8.2.1919 isn't ported yet, so E492 is thrown after E11 here. - " v8.2.1183 also isn't ported yet, so we also can't assert E11 directly. - " For now, assert E11 and E492 separately. When v8.2.1183 is ported, the - " assert for E492 will fail and this workaround should be removed. - " call assert_fails("silent norm q/g :I\<Esc>", 'E11:') - call assert_fails("silent norm q/g ", 'E11:') - call assert_fails("silent norm q/g :I\<Esc>", 'E492:') + call assert_fails("silent norm q/g :I\<Esc>", 'E11:') tabclose! endfunc @@ -1634,6 +1791,53 @@ func Test_cmd_bang_E135() %bwipe! endfunc +func Test_cmd_bang_args() + new + :.! + call assert_equal(0, v:shell_error) + + " Note that below there is one space char after the '!'. This caused a + " shell error in the past, see https://github.com/vim/vim/issues/11495. + :.! + call assert_equal(0, v:shell_error) + bwipe! + + CheckUnix + :.!pwd + call assert_equal(0, v:shell_error) + :.! pwd + call assert_equal(0, v:shell_error) + + " Note there is one space after 'pwd'. + :.! pwd + call assert_equal(0, v:shell_error) + + " Note there are two spaces after 'pwd'. + :.! pwd + call assert_equal(0, v:shell_error) + :.!ls ~ + call assert_equal(0, v:shell_error) + + " Note there is one space char after '~'. + :.!ls ~ + call assert_equal(0, v:shell_error) + + " Note there are two spaces after '~'. + :.!ls ~ + call assert_equal(0, v:shell_error) + + :.!echo "foo" + call assert_equal(getline('.'), "foo") + :.!echo "foo " + call assert_equal(getline('.'), "foo ") + :.!echo " foo " + call assert_equal(getline('.'), " foo ") + :.!echo " foo " + call assert_equal(getline('.'), " foo ") + + %bwipe! +endfunc + " Test for using ~ for home directory in cmdline completion matches func Test_cmdline_expand_home() call mkdir('Xdir') @@ -1669,22 +1873,16 @@ func Test_cmdline_ctrl_g() endfunc " Test for 'wildmode' -func Test_wildmode() +func Wildmode_tests() func T(a, c, p) return "oneA\noneB\noneC" endfunc command -nargs=1 -complete=custom,T MyCmd - func SaveScreenLine() - let g:Sline = Screenline(&lines - 1) - return '' - endfunc - cnoremap <expr> <F2> SaveScreenLine() - set nowildmenu set wildmode=full,list let g:Sline = '' - call feedkeys(":MyCmd \t\t\<F2>\<C-B>\"\<CR>", 'xt') + call feedkeys(":MyCmd \t\t\<F4>\<C-B>\"\<CR>", 'xt') call assert_equal('oneA oneB oneC', g:Sline) call assert_equal('"MyCmd oneA', @:) @@ -1700,7 +1898,7 @@ func Test_wildmode() set wildmode=list:longest let g:Sline = '' - call feedkeys(":MyCmd \t\<F2>\<C-B>\"\<CR>", 'xt') + call feedkeys(":MyCmd \t\<F4>\<C-B>\"\<CR>", 'xt') call assert_equal('oneA oneB oneC', g:Sline) call assert_equal('"MyCmd one', @:) @@ -1711,27 +1909,67 @@ func Test_wildmode() " Test for wildmode=longest with 'fileignorecase' set set wildmode=longest set fileignorecase - argadd AA AAA AAAA - call feedkeys(":buffer \t\<C-B>\"\<CR>", 'xt') - call assert_equal('"buffer AA', @:) + argadd AAA AAAA AAAAA + call feedkeys(":buffer a\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"buffer AAA', @:) set fileignorecase& " Test for listing files with wildmode=list set wildmode=list let g:Sline = '' - call feedkeys(":b A\t\t\<F2>\<C-B>\"\<CR>", 'xt') - call assert_equal('AA AAA AAAA', g:Sline) + call feedkeys(":b A\t\t\<F4>\<C-B>\"\<CR>", 'xt') + call assert_equal('AAA AAAA AAAAA', g:Sline) call assert_equal('"b A', @:) + " when using longest completion match, matches shorter than the argument + " should be ignored (happens with :help) + set wildmode=longest,full + set wildmenu + call feedkeys(":help a*\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"help a', @:) + " non existing file + call feedkeys(":e a1b2y3z4\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"e a1b2y3z4', @:) + set wildmenu& + + " Test for longest file name completion with 'fileignorecase' + " On MS-Windows, file names are case insensitive. + if has('unix') + call writefile([], 'XTESTfoo') + call writefile([], 'Xtestbar') + set nofileignorecase + call feedkeys(":e XT\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e XTESTfoo', @:) + call feedkeys(":e Xt\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xtestbar', @:) + set fileignorecase + call feedkeys(":e XT\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xtest', @:) + call feedkeys(":e Xt\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xtest', @:) + set fileignorecase& + call delete('XTESTfoo') + call delete('Xtestbar') + endif + %argdelete delcommand MyCmd delfunc T - delfunc SaveScreenLine - cunmap <F2> set wildmode& %bwipe! endfunc +func Test_wildmode() + " Test with utf-8 encoding + call Wildmode_tests() + + " Test with latin1 encoding + let save_encoding = &encoding + " set encoding=latin1 + " call Wildmode_tests() + let &encoding = save_encoding +endfunc + " Test for interrupting the command-line completion func Test_interrupt_compl() func F(lead, cmdl, p) @@ -1753,6 +1991,14 @@ func Test_interrupt_compl() endtry call assert_equal(1, interrupted) + let interrupted = 0 + try + call feedkeys(":Tcmd tw\<C-d>\<C-B>\"\<CR>", 'xt') + catch /^Vim:Interrupt$/ + let interrupted = 1 + endtry + call assert_equal(1, interrupted) + delcommand Tcmd delfunc F set wildmode& @@ -1809,6 +2055,11 @@ func Test_cmdline_expr() call assert_equal("\"e \<C-\>\<C-Y>", @:) endfunc +" This was making the insert position negative +func Test_cmdline_expr_register() + exe "sil! norm! ?\<C-\>e0\<C-R>0\<Esc>?\<C-\>e0\<CR>" +endfunc + " Test for 'imcmdline' and 'imsearch' " This test doesn't actually test the input method functionality. func Test_cmdline_inputmethod() @@ -1939,34 +2190,41 @@ endfunc func Test_wildmenu_dirstack() CheckUnix %bw! - call mkdir('Xdir1/dir2/dir3', 'p') + call mkdir('Xdir1/dir2/dir3/dir4', 'p') call writefile([], 'Xdir1/file1_1.txt') call writefile([], 'Xdir1/file1_2.txt') call writefile([], 'Xdir1/dir2/file2_1.txt') call writefile([], 'Xdir1/dir2/file2_2.txt') call writefile([], 'Xdir1/dir2/dir3/file3_1.txt') call writefile([], 'Xdir1/dir2/dir3/file3_2.txt') - cd Xdir1/dir2/dir3 + call writefile([], 'Xdir1/dir2/dir3/dir4/file4_1.txt') + call writefile([], 'Xdir1/dir2/dir3/dir4/file4_2.txt') set wildmenu + cd Xdir1/dir2/dir3/dir4 call feedkeys(":e \<Tab>\<C-B>\"\<CR>", 'xt') - call assert_equal('"e file3_1.txt', @:) + call assert_equal('"e file4_1.txt', @:) call feedkeys(":e \<Tab>\<Up>\<C-B>\"\<CR>", 'xt') - call assert_equal('"e ../dir3/', @:) + call assert_equal('"e ../dir4/', @:) call feedkeys(":e \<Tab>\<Up>\<Up>\<C-B>\"\<CR>", 'xt') - call assert_equal('"e ../../dir2/', @:) + call assert_equal('"e ../../dir3/', @:) + call feedkeys(":e \<Tab>\<Up>\<Up>\<Up>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e ../../../dir2/', @:) call feedkeys(":e \<Tab>\<Up>\<Up>\<Down>\<C-B>\"\<CR>", 'xt') - call assert_equal('"e ../../dir2/dir3/', @:) + call assert_equal('"e ../../dir3/dir4/', @:) call feedkeys(":e \<Tab>\<Up>\<Up>\<Down>\<Down>\<C-B>\"\<CR>", 'xt') - call assert_equal('"e ../../dir2/dir3/file3_1.txt', @:) - + call assert_equal('"e ../../dir3/dir4/file4_1.txt', @:) cd - + call feedkeys(":e Xdir1/\<Tab>\<Down>\<Down>\<Down>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xdir1/dir2/dir3/dir4/file4_1.txt', @:) + call delete('Xdir1', 'rf') set wildmenu& endfunc " Test for recalling newer or older cmdline from history with <Up>, <Down>, -" <S-Up>, <S-Down>, <PageUp>, <PageDown>, <C-p>, or <C-n>. +" <S-Up>, <S-Down>, <PageUp>, <PageDown>, <kPageUp>, <kPageDown>, <C-p>, or +" <C-n>. func Test_recalling_cmdline() CheckFeature cmdline_hist @@ -1974,17 +2232,18 @@ func Test_recalling_cmdline() cnoremap <Plug>(save-cmdline) <Cmd>let g:cmdlines += [getcmdline()]<CR> let histories = [ - \ {'name': 'cmd', 'enter': ':', 'exit': "\<Esc>"}, - \ {'name': 'search', 'enter': '/', 'exit': "\<Esc>"}, - \ {'name': 'expr', 'enter': ":\<C-r>=", 'exit': "\<Esc>\<Esc>"}, - \ {'name': 'input', 'enter': ":call input('')\<CR>", 'exit': "\<CR>"}, + \ #{name: 'cmd', enter: ':', exit: "\<Esc>"}, + \ #{name: 'search', enter: '/', exit: "\<Esc>"}, + \ #{name: 'expr', enter: ":\<C-r>=", exit: "\<Esc>\<Esc>"}, + \ #{name: 'input', enter: ":call input('')\<CR>", exit: "\<CR>"}, "\ TODO: {'name': 'debug', ...} \] let keypairs = [ - \ {'older': "\<Up>", 'newer': "\<Down>", 'prefixmatch': v:true}, - \ {'older': "\<S-Up>", 'newer': "\<S-Down>", 'prefixmatch': v:false}, - \ {'older': "\<PageUp>", 'newer': "\<PageDown>", 'prefixmatch': v:false}, - \ {'older': "\<C-p>", 'newer': "\<C-n>", 'prefixmatch': v:false}, + \ #{older: "\<Up>", newer: "\<Down>", prefixmatch: v:true}, + \ #{older: "\<S-Up>", newer: "\<S-Down>", prefixmatch: v:false}, + \ #{older: "\<PageUp>", newer: "\<PageDown>", prefixmatch: v:false}, + \ #{older: "\<kPageUp>", newer: "\<kPageDown>", prefixmatch: v:false}, + \ #{older: "\<C-p>", newer: "\<C-n>", prefixmatch: v:false}, \] let prefix = 'vi' for h in histories @@ -2013,6 +2272,59 @@ func Test_recalling_cmdline() cunmap <Plug>(save-cmdline) endfunc +func Test_cmd_map_cmdlineChanged() + let g:log = [] + cnoremap <F1> l<Cmd><CR>s + augroup test + autocmd! + autocmd CmdlineChanged : let g:log += [getcmdline()] + augroup END + + call feedkeys(":\<F1>\<CR>", 'xt') + call assert_equal(['l', 'ls'], g:log) + + let @b = 'b' + cnoremap <F1> a<C-R>b + let g:log = [] + call feedkeys(":\<F1>\<CR>", 'xt') + call assert_equal(['a', 'ab'], g:log) + + unlet g:log + cunmap <F1> + augroup test + autocmd! + augroup END +endfunc + +" Test for the 'suffixes' option +func Test_suffixes_opt() + call writefile([], 'Xfile') + call writefile([], 'Xfile.c') + call writefile([], 'Xfile.o') + set suffixes= + call feedkeys(":e Xfi*\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xfile Xfile.c Xfile.o', @:) + call feedkeys(":e Xfi*\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xfile.c', @:) + set suffixes=.c + call feedkeys(":e Xfi*\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xfile Xfile.o Xfile.c', @:) + call feedkeys(":e Xfi*\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xfile.o', @:) + set suffixes=,, + call feedkeys(":e Xfi*\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xfile.c Xfile.o Xfile', @:) + call feedkeys(":e Xfi*\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xfile.o', @:) + set suffixes& + " Test for getcompletion() with different patterns + call assert_equal(['Xfile', 'Xfile.c', 'Xfile.o'], getcompletion('Xfile', 'file')) + call assert_equal(['Xfile'], getcompletion('Xfile$', 'file')) + call delete('Xfile') + call delete('Xfile.c') + call delete('Xfile.o') +endfunc + " Test for using a popup menu for the command line completion matches " (wildoptions=pum) func Test_wildmenu_pum() @@ -2024,39 +2336,63 @@ func Test_wildmenu_pum() set shm+=I set noruler set noshowcmd + + func CmdCompl(a, b, c) + return repeat(['aaaa'], 120) + endfunc + command -nargs=* -complete=customlist,CmdCompl Tcmd + + func MyStatusLine() abort + return 'status' + endfunc + func SetupStatusline() + set statusline=%!MyStatusLine() + set laststatus=2 + endfunc + + func MyTabLine() + return 'my tab line' + endfunc + func SetupTabline() + set statusline= + set tabline=%!MyTabLine() + set showtabline=2 + endfunc + + func DoFeedKeys() + let &wildcharm = char2nr("\t") + call feedkeys(":edit $VIMRUNTIME/\<Tab>\<Left>\<C-U>ab\<Tab>") + endfunc [CODE] call writefile(commands, 'Xtest') let buf = RunVimInTerminal('-S Xtest', #{rows: 10}) call term_sendkeys(buf, ":sign \<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_01', {}) + " going down the popup menu using <Down> call term_sendkeys(buf, "\<Down>\<Down>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_02', {}) + " going down the popup menu using <C-N> call term_sendkeys(buf, "\<C-N>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_03', {}) + " going up the popup menu using <C-P> call term_sendkeys(buf, "\<C-P>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_04', {}) + " going up the popup menu using <Up> call term_sendkeys(buf, "\<Up>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_05', {}) " pressing <C-E> should end completion and go back to the original match call term_sendkeys(buf, "\<C-E>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_06', {}) " pressing <C-Y> should select the current match and end completion call term_sendkeys(buf, "\<Tab>\<C-P>\<C-P>\<C-Y>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_07', {}) " With 'wildmode' set to 'longest,full', completing a match should display @@ -2064,31 +2400,25 @@ func Test_wildmenu_pum() call term_sendkeys(buf, ":\<C-U>set wildmode=longest,full\<CR>") call TermWait(buf) call term_sendkeys(buf, ":sign u\<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_08', {}) " pressing <Tab> should display the wildmenu call term_sendkeys(buf, "\<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_09', {}) " pressing <Tab> second time should select the next entry in the menu call term_sendkeys(buf, "\<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_10', {}) call term_sendkeys(buf, ":\<C-U>set wildmode=full\<CR>") - " " showing popup menu in different columns in the cmdline + " showing popup menu in different columns in the cmdline call term_sendkeys(buf, ":sign define \<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_11', {}) call term_sendkeys(buf, " \<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_12', {}) call term_sendkeys(buf, " \<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_13', {}) " Directory name completion @@ -2098,95 +2428,77 @@ func Test_wildmenu_pum() call writefile([], 'Xdir/XdirA/XdirB/XfileC') call term_sendkeys(buf, "\<C-U>e Xdi\<Tab>\<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_14', {}) " Pressing <Right> on a directory name should go into that directory call term_sendkeys(buf, "\<Right>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_15', {}) " Pressing <Left> on a directory name should go to the parent directory call term_sendkeys(buf, "\<Left>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_16', {}) " Pressing <C-A> when the popup menu is displayed should list all the - " matches and remove the popup menu + " matches but the popup menu should still remain call term_sendkeys(buf, "\<C-U>sign \<Tab>\<C-A>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_17', {}) " Pressing <C-D> when the popup menu is displayed should remove the popup " menu call term_sendkeys(buf, "\<C-U>sign \<Tab>\<C-D>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_18', {}) " Pressing <S-Tab> should open the popup menu with the last entry selected call term_sendkeys(buf, "\<C-U>\<CR>:sign \<S-Tab>\<C-P>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_19', {}) " Pressing <Esc> should close the popup menu and cancel the cmd line call term_sendkeys(buf, "\<C-U>\<CR>:sign \<Tab>\<Esc>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_20', {}) " Typing a character when the popup is open, should close the popup call term_sendkeys(buf, ":sign \<Tab>x") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_21', {}) " When the popup is open, entering the cmdline window should close the popup call term_sendkeys(buf, "\<C-U>sign \<Tab>\<C-F>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_22', {}) call term_sendkeys(buf, ":q\<CR>") " After the last popup menu item, <C-N> should show the original string call term_sendkeys(buf, ":sign u\<Tab>\<C-N>\<C-N>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_23', {}) " Use the popup menu for the command name call term_sendkeys(buf, "\<C-U>bu\<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_24', {}) " Pressing the left arrow should remove the popup menu call term_sendkeys(buf, "\<Left>\<Left>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_25', {}) " Pressing <BS> should remove the popup menu and erase the last character call term_sendkeys(buf, "\<C-E>\<C-U>sign \<Tab>\<BS>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_26', {}) " Pressing <C-W> should remove the popup menu and erase the previous word call term_sendkeys(buf, "\<C-E>\<C-U>sign \<Tab>\<C-W>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_27', {}) " Pressing <C-U> should remove the popup menu and erase the entire line call term_sendkeys(buf, "\<C-E>\<C-U>sign \<Tab>\<C-U>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_28', {}) " Using <C-E> to cancel the popup menu and then pressing <Up> should recall " the cmdline from history call term_sendkeys(buf, "sign xyz\<Esc>:sign \<Tab>\<C-E>\<Up>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_29', {}) " Check "list" still works call term_sendkeys(buf, "\<C-U>set wildmode=longest,list\<CR>") call term_sendkeys(buf, ":cn\<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_30', {}) call term_sendkeys(buf, "s") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_31', {}) " Tests a directory name contained full-width characters. @@ -2197,15 +2509,933 @@ func Test_wildmenu_pum() call term_sendkeys(buf, "\<C-U>set wildmode&\<CR>") call term_sendkeys(buf, ":\<C-U>e Xdir/あいう/\<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_32', {}) + " Pressing <C-A> when the popup menu is displayed should list all the + " matches and pressing a key after that should remove the popup menu + call term_sendkeys(buf, "\<C-U>set wildmode=full\<CR>") + call term_sendkeys(buf, ":sign \<Tab>\<C-A>x") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_33', {}) + + " Pressing <C-A> when the popup menu is displayed should list all the + " matches and pressing <Left> after that should move the cursor + call term_sendkeys(buf, "\<C-U>abc\<Esc>") + call term_sendkeys(buf, ":sign \<Tab>\<C-A>\<Left>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_34', {}) + + " When <C-A> displays a lot of matches (screen scrolls), all the matches + " should be displayed correctly on the screen. + call term_sendkeys(buf, "\<End>\<C-U>Tcmd \<Tab>\<C-A>\<Left>\<Left>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_35', {}) + + " After using <C-A> to expand all the filename matches, pressing <Up> + " should not open the popup menu again. + call term_sendkeys(buf, "\<C-E>\<C-U>:cd Xdir/XdirA\<CR>") + call term_sendkeys(buf, ":e \<Tab>\<C-A>\<Up>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_36', {}) + call term_sendkeys(buf, "\<C-E>\<C-U>:cd -\<CR>") + + " After using <C-A> to expand all the matches, pressing <S-Tab> used to + " crash Vim + call term_sendkeys(buf, ":sign \<Tab>\<C-A>\<S-Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_37', {}) + + " After removing the pum the command line is redrawn + call term_sendkeys(buf, ":edit foo\<CR>") + call term_sendkeys(buf, ":edit bar\<CR>") + call term_sendkeys(buf, ":ls\<CR>") + call term_sendkeys(buf, ":com\<Tab> ") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_38', {}) + call term_sendkeys(buf, "\<C-U>\<CR>") + + " Esc still works to abort the command when 'statusline' is set + call term_sendkeys(buf, ":call SetupStatusline()\<CR>") + call term_sendkeys(buf, ":si\<Tab>") + call term_sendkeys(buf, "\<Esc>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_39', {}) + + " Esc still works to abort the command when 'tabline' is set + call term_sendkeys(buf, ":call SetupTabline()\<CR>") + call term_sendkeys(buf, ":si\<Tab>") + call term_sendkeys(buf, "\<Esc>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_40', {}) + + " popup is cleared also when 'lazyredraw' is set + call term_sendkeys(buf, ":set showtabline=1 laststatus=1 lazyredraw\<CR>") + call term_sendkeys(buf, ":call DoFeedKeys()\<CR>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_41', {}) + call term_sendkeys(buf, "\<Esc>") + + " Pressing <PageDown> should scroll the menu downward + call term_sendkeys(buf, ":sign \<Tab>\<PageDown>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_42', {}) + call term_sendkeys(buf, "\<PageDown>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_43', {}) + call term_sendkeys(buf, "\<PageDown>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_44', {}) + call term_sendkeys(buf, "\<PageDown>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_45', {}) + call term_sendkeys(buf, "\<C-U>sign \<Tab>\<Down>\<Down>\<PageDown>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_46', {}) + + " Pressing <PageUp> should scroll the menu upward + call term_sendkeys(buf, "\<C-U>sign \<Tab>\<PageUp>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_47', {}) + call term_sendkeys(buf, "\<PageUp>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_48', {}) + call term_sendkeys(buf, "\<PageUp>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_49', {}) + call term_sendkeys(buf, "\<PageUp>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_50', {}) + call term_sendkeys(buf, "\<C-U>\<CR>") call StopVimInTerminal(buf) call delete('Xtest') call delete('Xdir', 'rf') endfunc +" Test for wildmenumode() with the cmdline popup menu +func Test_wildmenumode_with_pum() + set wildmenu + set wildoptions=pum + cnoremap <expr> <F2> wildmenumode() + call feedkeys(":sign \<Tab>\<F2>\<F2>\<C-B>\"\<CR>", 'xt') + call assert_equal('"sign define10', @:) + call feedkeys(":sign \<Tab>\<C-A>\<F2>\<C-B>\"\<CR>", 'xt') + call assert_equal('"sign define jump list place undefine unplace0', @:) + call feedkeys(":sign \<Tab>\<C-E>\<F2>\<C-B>\"\<CR>", 'xt') + call assert_equal('"sign 0', @:) + call feedkeys(":sign \<Tab>\<C-Y>\<F2>\<C-B>\"\<CR>", 'xt') + call assert_equal('"sign define0', @:) + set nowildmenu wildoptions& + cunmap <F2> +endfunc + +" Test for opening the cmdline completion popup menu from the terminal window. +" The popup menu should be positioned correctly over the status line of the +" bottom-most window. +func Test_wildmenu_pum_from_terminal() + CheckRunVimInTerminal + let python = PythonProg() + call CheckPython(python) + + %bw! + let cmds = ['set wildmenu wildoptions=pum'] + let pcmd = python .. ' -c "import sys; sys.stdout.write(sys.stdin.read())"' + call add(cmds, "call term_start('" .. pcmd .. "')") + call writefile(cmds, 'Xtest') + let buf = RunVimInTerminal('-S Xtest', #{rows: 10}) + call term_sendkeys(buf, "\r\r\r") + call term_wait(buf) + call term_sendkeys(buf, "\<C-W>:sign \<Tab>") + call term_wait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_term_01', {}) + call term_wait(buf) + call StopVimInTerminal(buf) + call delete('Xtest') +endfunc + +func Test_wildmenu_pum_clear_entries() + CheckRunVimInTerminal + + " This was using freed memory. Run in a terminal to get the pum to update. + let lines =<< trim END + set wildoptions=pum + set wildchar=<C-E> + END + call writefile(lines, 'XwildmenuTest', 'D') + let buf = RunVimInTerminal('-S XwildmenuTest', #{rows: 10}) + + call term_sendkeys(buf, ":\<C-E>\<C-E>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_clear_entries_1', {}) + + set wildoptions& wildchar& +endfunc + +" Test for completion after a :substitute command followed by a pipe (|) +" character +func Test_cmdline_complete_substitute() + call feedkeys(":s | \t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"s | \t", @:) + call feedkeys(":s/ | \t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"s/ | \t", @:) + call feedkeys(":s/one | \t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"s/one | \t", @:) + call feedkeys(":s/one/ | \t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"s/one/ | \t", @:) + call feedkeys(":s/one/two | \t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"s/one/two | \t", @:) + call feedkeys(":s/one/two/ | chist\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"s/one/two/ | chistory', @:) + call feedkeys(":s/one/two/g \t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"s/one/two/g \t", @:) + call feedkeys(":s/one/two/g | chist\t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"s/one/two/g | chistory", @:) + call feedkeys(":s/one/t\\/ | \t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"s/one/t\\/ | \t", @:) + call feedkeys(":s/one/t\"o/ | chist\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"s/one/t"o/ | chistory', @:) + call feedkeys(":s/one/t|o/ | chist\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"s/one/t|o/ | chistory', @:) + call feedkeys(":&\t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"&\t", @:) +endfunc + +" Test for the :dlist command completion +func Test_cmdline_complete_dlist() + call feedkeys(":dlist 10 /pat/ a\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"dlist 10 /pat/ a\<C-A>", @:) + call feedkeys(":dlist 10 /pat/ \t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"dlist 10 /pat/ \t", @:) + call feedkeys(":dlist 10 /pa\\t/\t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"dlist 10 /pa\\t/\t", @:) + call feedkeys(":dlist 10 /pat\\\t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"dlist 10 /pat\\\t", @:) + call feedkeys(":dlist 10 /pat/ | chist\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"dlist 10 /pat/ | chistory", @:) +endfunc + +" argument list (only for :argdel) fuzzy completion +func Test_fuzzy_completion_arglist() + argadd change.py count.py charge.py + set wildoptions& + call feedkeys(":argdel cge\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"argdel cge', @:) + set wildoptions=fuzzy + call feedkeys(":argdel cge\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"argdel change.py charge.py', @:) + %argdelete + set wildoptions& +endfunc + +" autocmd group name fuzzy completion +func Test_fuzzy_completion_autocmd() + set wildoptions& + augroup MyFuzzyGroup + augroup END + call feedkeys(":augroup mfg\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"augroup mfg', @:) + call feedkeys(":augroup My*p\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"augroup MyFuzzyGroup', @:) + set wildoptions=fuzzy + call feedkeys(":augroup mfg\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"augroup MyFuzzyGroup', @:) + call feedkeys(":augroup My*p\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"augroup My*p', @:) + augroup! MyFuzzyGroup + set wildoptions& +endfunc + +" buffer name fuzzy completion +func Test_fuzzy_completion_bufname() + set wildoptions& + " Use a long name to reduce the risk of matching a random directory name + edit SomeRandomFileWithLetters.txt + enew + call feedkeys(":b SRFWL\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"b SRFWL', @:) + call feedkeys(":b S*FileWithLetters.txt\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"b SomeRandomFileWithLetters.txt', @:) + set wildoptions=fuzzy + call feedkeys(":b SRFWL\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"b SomeRandomFileWithLetters.txt', @:) + call feedkeys(":b S*FileWithLetters.txt\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"b S*FileWithLetters.txt', @:) + %bw! + set wildoptions& +endfunc + +" buffer name (full path) fuzzy completion +func Test_fuzzy_completion_bufname_fullpath() + CheckUnix + set wildoptions& + call mkdir('Xcmd/Xstate/Xfile.js', 'p') + edit Xcmd/Xstate/Xfile.js + cd Xcmd/Xstate + enew + call feedkeys(":b CmdStateFile\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"b CmdStateFile', @:) + set wildoptions=fuzzy + call feedkeys(":b CmdStateFile\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_match('Xcmd/Xstate/Xfile.js$', @:) + cd - + call delete('Xcmd', 'rf') + set wildoptions& +endfunc + +" :behave suboptions fuzzy completion +func Test_fuzzy_completion_behave() + set wildoptions& + call feedkeys(":behave xm\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"behave xm', @:) + call feedkeys(":behave xt*m\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"behave xterm', @:) + set wildoptions=fuzzy + call feedkeys(":behave xm\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"behave xterm', @:) + call feedkeys(":behave xt*m\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"behave xt*m', @:) + let g:Sline = '' + call feedkeys(":behave win\<C-D>\<F4>\<C-B>\"\<CR>", 'tx') + call assert_equal('mswin', g:Sline) + call assert_equal('"behave win', @:) + set wildoptions& +endfunc + +" " colorscheme name fuzzy completion - NOT supported +" func Test_fuzzy_completion_colorscheme() +" endfunc + +" built-in command name fuzzy completion +func Test_fuzzy_completion_cmdname() + set wildoptions& + call feedkeys(":sbwin\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sbwin', @:) + call feedkeys(":sbr*d\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sbrewind', @:) + set wildoptions=fuzzy + call feedkeys(":sbwin\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sbrewind', @:) + call feedkeys(":sbr*d\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sbr*d', @:) + set wildoptions& +endfunc + +" " compiler name fuzzy completion - NOT supported +" func Test_fuzzy_completion_compiler() +" endfunc + +" :cscope suboptions fuzzy completion +func Test_fuzzy_completion_cscope() + CheckFeature cscope + set wildoptions& + call feedkeys(":cscope ret\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"cscope ret', @:) + call feedkeys(":cscope re*t\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"cscope reset', @:) + set wildoptions=fuzzy + call feedkeys(":cscope ret\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"cscope reset', @:) + call feedkeys(":cscope re*t\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"cscope re*t', @:) + set wildoptions& +endfunc + +" :diffget/:diffput buffer name fuzzy completion +func Test_fuzzy_completion_diff() + new SomeBuffer + diffthis + new OtherBuffer + diffthis + set wildoptions& + call feedkeys(":diffget sbuf\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffget sbuf', @:) + call feedkeys(":diffput sbuf\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffput sbuf', @:) + set wildoptions=fuzzy + call feedkeys(":diffget sbuf\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffget SomeBuffer', @:) + call feedkeys(":diffput sbuf\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffput SomeBuffer', @:) + %bw! + set wildoptions& +endfunc + +" " directory name fuzzy completion - NOT supported +" func Test_fuzzy_completion_dirname() +" endfunc + +" environment variable name fuzzy completion +func Test_fuzzy_completion_env() + set wildoptions& + call feedkeys(":echo $VUT\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"echo $VUT', @:) + set wildoptions=fuzzy + call feedkeys(":echo $VUT\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"echo $VIMRUNTIME', @:) + set wildoptions& +endfunc + +" autocmd event fuzzy completion +func Test_fuzzy_completion_autocmd_event() + set wildoptions& + call feedkeys(":autocmd BWout\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"autocmd BWout', @:) + set wildoptions=fuzzy + call feedkeys(":autocmd BWout\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"autocmd BufWipeout', @:) + set wildoptions& +endfunc + +" vim expression fuzzy completion +func Test_fuzzy_completion_expr() + let g:PerPlaceCount = 10 + set wildoptions& + call feedkeys(":let c = ppc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"let c = ppc', @:) + set wildoptions=fuzzy + call feedkeys(":let c = ppc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"let c = PerPlaceCount', @:) + set wildoptions& +endfunc + +" " file name fuzzy completion - NOT supported +" func Test_fuzzy_completion_filename() +" endfunc + +" " files in path fuzzy completion - NOT supported +" func Test_fuzzy_completion_filesinpath() +" endfunc + +" " filetype name fuzzy completion - NOT supported +" func Test_fuzzy_completion_filetype() +" endfunc + +" user defined function name completion +func Test_fuzzy_completion_userdefined_func() + set wildoptions& + call feedkeys(":call Test_f_u_f\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"call Test_f_u_f', @:) + set wildoptions=fuzzy + call feedkeys(":call Test_f_u_f\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"call Test_fuzzy_completion_userdefined_func()', @:) + set wildoptions& +endfunc + +" <SNR> functions should be sorted to the end +func Test_fuzzy_completion_userdefined_snr_func() + func s:Sendmail() + endfunc + func SendSomemail() + endfunc + func S1e2n3dmail() + endfunc + set wildoptions=fuzzy + call feedkeys(":call sendmail\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"call SendSomemail() S1e2n3dmail() ' + \ .. expand("<SID>") .. 'Sendmail()', @:) + set wildoptions& + delfunc s:Sendmail + delfunc SendSomemail + delfunc S1e2n3dmail +endfunc + +" user defined command name completion +func Test_fuzzy_completion_userdefined_cmd() + set wildoptions& + call feedkeys(":MsFeat\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"MsFeat', @:) + set wildoptions=fuzzy + call feedkeys(":MsFeat\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"MissingFeature', @:) + set wildoptions& +endfunc + +" " :help tag fuzzy completion - NOT supported +" func Test_fuzzy_completion_helptag() +" endfunc + +" highlight group name fuzzy completion +func Test_fuzzy_completion_hlgroup() + set wildoptions& + call feedkeys(":highlight SKey\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"highlight SKey', @:) + call feedkeys(":highlight Sp*Key\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"highlight SpecialKey', @:) + set wildoptions=fuzzy + call feedkeys(":highlight SKey\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"highlight SpecialKey', @:) + call feedkeys(":highlight Sp*Key\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"highlight Sp*Key', @:) + set wildoptions& +endfunc + +" :history suboptions fuzzy completion +func Test_fuzzy_completion_history() + set wildoptions& + call feedkeys(":history dg\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"history dg', @:) + call feedkeys(":history se*h\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"history search', @:) + set wildoptions=fuzzy + call feedkeys(":history dg\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"history debug', @:) + call feedkeys(":history se*h\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"history se*h', @:) + set wildoptions& +endfunc + +" :language locale name fuzzy completion +func Test_fuzzy_completion_lang() + CheckUnix + set wildoptions& + call feedkeys(":lang psx\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"lang psx', @:) + set wildoptions=fuzzy + call feedkeys(":lang psx\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"lang POSIX', @:) + set wildoptions& +endfunc + +" :mapclear buffer argument fuzzy completion +func Test_fuzzy_completion_mapclear() + set wildoptions& + call feedkeys(":mapclear buf\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"mapclear buf', @:) + set wildoptions=fuzzy + call feedkeys(":mapclear buf\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"mapclear <buffer>', @:) + set wildoptions& +endfunc + +" map name fuzzy completion +func Test_fuzzy_completion_mapname() + " test regex completion works + set wildoptions=fuzzy + call feedkeys(":cnoremap <ex\<Tab> <esc> \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"cnoremap <expr> <esc> \<Tab>", @:) + nmap <plug>MyLongMap :p<CR> + call feedkeys(":nmap MLM\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"nmap <Plug>MyLongMap", @:) + call feedkeys(":nmap MLM \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"nmap MLM \t", @:) + call feedkeys(":nmap <F2> one two \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"nmap <F2> one two \t", @:) + " duplicate entries should be removed + vmap <plug>MyLongMap :<C-U>#<CR> + call feedkeys(":nmap MLM\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"nmap <Plug>MyLongMap", @:) + nunmap <plug>MyLongMap + vunmap <plug>MyLongMap + call feedkeys(":nmap ABC\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"nmap ABC\t", @:) + " results should be sorted by best match + nmap <Plug>format : + nmap <Plug>goformat : + nmap <Plug>TestFOrmat : + nmap <Plug>fendoff : + nmap <Plug>state : + nmap <Plug>FendingOff : + call feedkeys(":nmap <Plug>fo\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"nmap <Plug>format <Plug>TestFOrmat <Plug>FendingOff <Plug>goformat <Plug>fendoff", @:) + nunmap <Plug>format + nunmap <Plug>goformat + nunmap <Plug>TestFOrmat + nunmap <Plug>fendoff + nunmap <Plug>state + nunmap <Plug>FendingOff + set wildoptions& +endfunc + +" abbreviation fuzzy completion +func Test_fuzzy_completion_abbr() + set wildoptions=fuzzy + call feedkeys(":iabbr wait\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"iabbr <nowait>", @:) + iabbr WaitForCompletion WFC + call feedkeys(":iabbr fcl\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"iabbr WaitForCompletion", @:) + call feedkeys(":iabbr a1z\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"iabbr a1z\t", @:) + + iunabbrev WaitForCompletion + set wildoptions& +endfunc + +" menu name fuzzy completion +func Test_fuzzy_completion_menu() + CheckFeature menu + + source $VIMRUNTIME/menu.vim + set wildoptions& + call feedkeys(":menu pup\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"menu pup', @:) + set wildoptions=fuzzy + call feedkeys(":menu pup\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"menu PopUp.', @:) + + set wildoptions& + source $VIMRUNTIME/delmenu.vim +endfunc + +" :messages suboptions fuzzy completion +func Test_fuzzy_completion_messages() + set wildoptions& + call feedkeys(":messages clr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"messages clr', @:) + set wildoptions=fuzzy + call feedkeys(":messages clr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"messages clear', @:) + set wildoptions& +endfunc + +" :set option name fuzzy completion +func Test_fuzzy_completion_option() + set wildoptions& + call feedkeys(":set brkopt\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set brkopt', @:) + set wildoptions=fuzzy + call feedkeys(":set brkopt\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set breakindentopt', @:) + set wildoptions& + call feedkeys(":set fixeol\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set fixendofline', @:) + set wildoptions=fuzzy + call feedkeys(":set fixeol\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set fixendofline', @:) + set wildoptions& +endfunc + +" :set <term_option> +func Test_fuzzy_completion_term_option() + throw 'Skipped: Nvim does not support term options' + set wildoptions& + call feedkeys(":set t_E\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set t_EC', @:) + call feedkeys(":set <t_E\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set <t_EC>', @:) + set wildoptions=fuzzy + call feedkeys(":set t_E\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set t_EC', @:) + call feedkeys(":set <t_E\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set <t_EC>', @:) + set wildoptions& +endfunc + +" " :packadd directory name fuzzy completion - NOT supported +" func Test_fuzzy_completion_packadd() +" endfunc + +" " shell command name fuzzy completion - NOT supported +" func Test_fuzzy_completion_shellcmd() +" endfunc + +" :sign suboptions fuzzy completion +func Test_fuzzy_completion_sign() + set wildoptions& + call feedkeys(":sign ufe\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign ufe', @:) + set wildoptions=fuzzy + call feedkeys(":sign ufe\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign undefine', @:) + set wildoptions& +endfunc + +" :syntax suboptions fuzzy completion +func Test_fuzzy_completion_syntax_cmd() + set wildoptions& + call feedkeys(":syntax kwd\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"syntax kwd', @:) + set wildoptions=fuzzy + call feedkeys(":syntax kwd\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"syntax keyword', @:) + set wildoptions& +endfunc + +" syntax group name fuzzy completion +func Test_fuzzy_completion_syntax_group() + set wildoptions& + call feedkeys(":syntax list mpar\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"syntax list mpar', @:) + set wildoptions=fuzzy + call feedkeys(":syntax list mpar\<Tab>\<C-B>\"\<CR>", 'tx') + " Fuzzy match prefers NvimParenthesis over MatchParen + " call assert_equal('"syntax list MatchParen', @:) + call assert_equal('"syntax list NvimParenthesis', @:) + set wildoptions& +endfunc + +" :syntime suboptions fuzzy completion +func Test_fuzzy_completion_syntime() + CheckFeature profile + set wildoptions& + call feedkeys(":syntime clr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"syntime clr', @:) + set wildoptions=fuzzy + call feedkeys(":syntime clr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"syntime clear', @:) + set wildoptions& +endfunc + +" " tag name fuzzy completion - NOT supported +" func Test_fuzzy_completion_tagname() +" endfunc + +" " tag name and file fuzzy completion - NOT supported +" func Test_fuzzy_completion_tagfile() +" endfunc + +" " user names fuzzy completion - how to test this functionality? +" func Test_fuzzy_completion_username() +" endfunc + +" user defined variable name fuzzy completion +func Test_fuzzy_completion_userdefined_var() + let g:SomeVariable=10 + set wildoptions& + call feedkeys(":let SVar\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"let SVar', @:) + set wildoptions=fuzzy + call feedkeys(":let SVar\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"let SomeVariable', @:) + set wildoptions& +endfunc + +" Test for sorting the results by the best match +func Test_fuzzy_completion_cmd_sort_results() + %bw! + command T123format : + command T123goformat : + command T123TestFOrmat : + command T123fendoff : + command T123state : + command T123FendingOff : + set wildoptions=fuzzy + call feedkeys(":T123fo\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"T123format T123TestFOrmat T123FendingOff T123goformat T123fendoff', @:) + delcommand T123format + delcommand T123goformat + delcommand T123TestFOrmat + delcommand T123fendoff + delcommand T123state + delcommand T123FendingOff + %bw + set wildoptions& +endfunc + +" Test for fuzzy completion of a command with lower case letters and a number +func Test_fuzzy_completion_cmd_alnum() + command Foo2Bar : + set wildoptions=fuzzy + call feedkeys(":foo2\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"Foo2Bar', @:) + call feedkeys(":foo\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"Foo2Bar', @:) + call feedkeys(":bar\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"Foo2Bar', @:) + delcommand Foo2Bar + set wildoptions& +endfunc + +" Test for command completion for a command starting with 'k' +func Test_fuzzy_completion_cmd_k() + command KillKillKill : + set wildoptions& + call feedkeys(":killkill\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"killkill\<Tab>", @:) + set wildoptions=fuzzy + call feedkeys(":killkill\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"KillKillKill', @:) + delcom KillKillKill + set wildoptions& +endfunc + +" Test for fuzzy completion for user defined custom completion function +func Test_fuzzy_completion_custom_func() + func Tcompl(a, c, p) + return "format\ngoformat\nTestFOrmat\nfendoff\nstate" + endfunc + command -nargs=* -complete=custom,Tcompl Fuzzy : + set wildoptions& + call feedkeys(":Fuzzy fo\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"Fuzzy format", @:) + call feedkeys(":Fuzzy xy\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"Fuzzy xy", @:) + call feedkeys(":Fuzzy ttt\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"Fuzzy ttt", @:) + set wildoptions=fuzzy + call feedkeys(":Fuzzy \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"Fuzzy format goformat TestFOrmat fendoff state", @:) + call feedkeys(":Fuzzy fo\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"Fuzzy format TestFOrmat goformat fendoff", @:) + call feedkeys(":Fuzzy xy\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"Fuzzy xy", @:) + call feedkeys(":Fuzzy ttt\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"Fuzzy TestFOrmat", @:) + delcom Fuzzy + set wildoptions& +endfunc + +" Test for :breakadd argument completion +func Test_cmdline_complete_breakadd() + call feedkeys(":breakadd \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd expr file func here", @:) + call feedkeys(":breakadd \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd expr", @:) + call feedkeys(":breakadd \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd expr", @:) + call feedkeys(":breakadd he\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd here", @:) + call feedkeys(":breakadd he\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd here", @:) + call feedkeys(":breakadd abc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd abc", @:) + call assert_equal(['expr', 'file', 'func', 'here'], getcompletion('', 'breakpoint')) + let l = getcompletion('not', 'breakpoint') + call assert_equal([], l) + + " Test for :breakadd file [lnum] <file> + call writefile([], 'Xscript') + call feedkeys(":breakadd file Xsc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd file Xscript", @:) + call feedkeys(":breakadd file Xsc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd file Xscript", @:) + call feedkeys(":breakadd file 20 Xsc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd file 20 Xscript", @:) + call feedkeys(":breakadd file 20 Xsc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd file 20 Xscript", @:) + call feedkeys(":breakadd file 20x Xsc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd file 20x Xsc\t", @:) + call feedkeys(":breakadd file 20\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd file 20\t", @:) + call feedkeys(":breakadd file 20x\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd file 20x\t", @:) + call feedkeys(":breakadd file Xscript \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd file Xscript ", @:) + call feedkeys(":breakadd file X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd file X1B2C3", @:) + call delete('Xscript') + + " Test for :breakadd func [lnum] <function> + func Xbreak_func() + endfunc + call feedkeys(":breakadd func Xbr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd func Xbreak_func", @:) + call feedkeys(":breakadd func Xbr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd func Xbreak_func", @:) + call feedkeys(":breakadd func 20 Xbr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd func 20 Xbreak_func", @:) + call feedkeys(":breakadd func 20 Xbr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd func 20 Xbreak_func", @:) + call feedkeys(":breakadd func 20x Xbr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd func 20x Xbr\t", @:) + call feedkeys(":breakadd func 20\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd func 20\t", @:) + call feedkeys(":breakadd func 20x\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd func 20x\t", @:) + call feedkeys(":breakadd func Xbreak_func \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd func Xbreak_func ", @:) + call feedkeys(":breakadd func X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd func X1B2C3", @:) + delfunc Xbreak_func + + " Test for :breakadd expr <expression> + let g:Xtest_var = 10 + call feedkeys(":breakadd expr Xtest\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd expr Xtest_var", @:) + call feedkeys(":breakadd expr Xtest\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd expr Xtest_var", @:) + call feedkeys(":breakadd expr Xtest_var \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd expr Xtest_var ", @:) + call feedkeys(":breakadd expr X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd expr X1B2C3", @:) + unlet g:Xtest_var + + " Test for :breakadd here + call feedkeys(":breakadd here Xtest\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd here Xtest", @:) + call feedkeys(":breakadd here Xtest\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd here Xtest", @:) + call feedkeys(":breakadd here \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd here ", @:) +endfunc + +" Test for :breakdel argument completion +func Test_cmdline_complete_breakdel() + call feedkeys(":breakdel \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file func here", @:) + call feedkeys(":breakdel \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file", @:) + call feedkeys(":breakdel \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file", @:) + call feedkeys(":breakdel he\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel here", @:) + call feedkeys(":breakdel he\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel here", @:) + call feedkeys(":breakdel abc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel abc", @:) + + " Test for :breakdel file [lnum] <file> + call writefile([], 'Xscript') + call feedkeys(":breakdel file Xsc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file Xscript", @:) + call feedkeys(":breakdel file Xsc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file Xscript", @:) + call feedkeys(":breakdel file 20 Xsc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file 20 Xscript", @:) + call feedkeys(":breakdel file 20 Xsc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file 20 Xscript", @:) + call feedkeys(":breakdel file 20x Xsc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file 20x Xsc\t", @:) + call feedkeys(":breakdel file 20\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file 20\t", @:) + call feedkeys(":breakdel file 20x\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file 20x\t", @:) + call feedkeys(":breakdel file Xscript \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file Xscript ", @:) + call feedkeys(":breakdel file X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file X1B2C3", @:) + call delete('Xscript') + + " Test for :breakdel func [lnum] <function> + func Xbreak_func() + endfunc + call feedkeys(":breakdel func Xbr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel func Xbreak_func", @:) + call feedkeys(":breakdel func Xbr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel func Xbreak_func", @:) + call feedkeys(":breakdel func 20 Xbr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel func 20 Xbreak_func", @:) + call feedkeys(":breakdel func 20 Xbr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel func 20 Xbreak_func", @:) + call feedkeys(":breakdel func 20x Xbr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel func 20x Xbr\t", @:) + call feedkeys(":breakdel func 20\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel func 20\t", @:) + call feedkeys(":breakdel func 20x\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel func 20x\t", @:) + call feedkeys(":breakdel func Xbreak_func \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel func Xbreak_func ", @:) + call feedkeys(":breakdel func X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel func X1B2C3", @:) + delfunc Xbreak_func + + " Test for :breakdel here + call feedkeys(":breakdel here Xtest\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel here Xtest", @:) + call feedkeys(":breakdel here Xtest\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel here Xtest", @:) + call feedkeys(":breakdel here \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel here ", @:) +endfunc + +" Test for :scriptnames argument completion +func Test_cmdline_complete_scriptnames() + set wildmenu + call writefile(['let a = 1'], 'Xa1b2c3.vim') + source Xa1b2c3.vim + call feedkeys(":script \<Tab>\<Left>\<Left>\<C-B>\"\<CR>", 'tx') + call assert_match("\"script .*Xa1b2c3.vim$", @:) + call feedkeys(":script \<Tab>\<Left>\<Left>\<C-B>\"\<CR>", 'tx') + call assert_match("\"script .*Xa1b2c3.vim$", @:) + call feedkeys(":script b2c3\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"script b2c3", @:) + call feedkeys(":script 2\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_match("\"script 2\<Tab>$", @:) + call feedkeys(":script \<Tab>\<Left>\<Left> \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_match("\"script .*Xa1b2c3.vim $", @:) + call feedkeys(":script \<Tab>\<Left>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"script ", @:) + call assert_match('Xa1b2c3.vim$', getcompletion('.*Xa1b2.*', 'scriptnames')[0]) + call assert_equal([], getcompletion('Xa1b2', 'scriptnames')) + new + call feedkeys(":script \<Tab>\<Left>\<Left>\<CR>", 'tx') + call assert_equal('Xa1b2c3.vim', fnamemodify(@%, ':t')) + bw! + call delete('Xa1b2c3.vim') + set wildmenu& +endfunc + " this was going over the end of IObuff func Test_report_error_with_composing() let caught = 'no' @@ -2230,6 +3460,16 @@ func Test_cmdline_complete_substitute_short() endfor endfunc +" Test for :! shell command argument completion +func Test_cmdline_complete_bang_cmd_argument() + set wildoptions=fuzzy + call feedkeys(":!vim test_cmdline.\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"!vim test_cmdline.vim', @:) + set wildoptions& + call feedkeys(":!vim test_cmdline.\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"!vim test_cmdline.vim', @:) +endfunc + func Check_completion() call assert_equal('let a', getcmdline()) call assert_equal(6, getcmdpos()) diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim index 3dc8710d63..ec7d143030 100644 --- a/src/nvim/testdir/test_compiler.vim +++ b/src/nvim/testdir/test_compiler.vim @@ -1,9 +1,11 @@ " Test the :compiler command +source check.vim +source shared.vim + func Test_compiler() - if !executable('perl') - return - endif + CheckExecutable perl + CheckFeature quickfix " $LANG changes the output of Perl. if $LANG != '' diff --git a/src/nvim/testdir/test_const.vim b/src/nvim/testdir/test_const.vim index 0d064617a5..4a6c8779dd 100644 --- a/src/nvim/testdir/test_const.vim +++ b/src/nvim/testdir/test_const.vim @@ -194,6 +194,14 @@ func Test_lockvar() if 0 | lockvar x | endif let x = 'again' + + let val = [1, 2, 3] + lockvar 0 val + let val[0] = 9 + call assert_equal([9, 2, 3], val) + call add(val, 4) + call assert_equal([9, 2, 3, 4], val) + call assert_fails('let val = [4, 5, 6]', 'E1122:') endfunc @@ -231,6 +239,14 @@ func Test_const_with_special_variables() call assert_fails('const &filetype = "vim"', 'E996:') call assert_fails('const &l:filetype = "vim"', 'E996:') call assert_fails('const &g:encoding = "utf-8"', 'E996:') + + call assert_fails('const [a, $CONST_FOO] = [369, "abc"]', 'E996:') + call assert_equal(369, a) + call assert_equal(v:null, getenv("CONST_FOO")) + + call assert_fails('const [b; $CONST_FOO] = [246, 2, "abc"]', 'E996:') + call assert_equal(246, b) + call assert_equal(v:null, getenv("CONST_FOO")) endfunc func Test_const_with_eval_name() @@ -274,3 +290,5 @@ func Test_lock_depth_is_2() const d2 = #{a: 0, b: lvar, c: 4} let d2.b[1] = 'd' endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cpoptions.vim b/src/nvim/testdir/test_cpoptions.vim new file mode 100644 index 0000000000..b9307ab30b --- /dev/null +++ b/src/nvim/testdir/test_cpoptions.vim @@ -0,0 +1,927 @@ +" Test for the various 'cpoptions' (cpo) flags + +source check.vim +source shared.vim +source view_util.vim + +" Test for the 'a' flag in 'cpo'. Reading a file should set the alternate +" file name. +func Test_cpo_a() + let save_cpo = &cpo + call writefile(['one'], 'XfileCpoA') + " Wipe out all the buffers, so that the alternate file is empty + edit Xfoo | %bw + set cpo-=a + new + read XfileCpoA + call assert_equal('', @#) + %d + set cpo+=a + read XfileCpoA + call assert_equal('XfileCpoA', @#) + close! + call delete('XfileCpoA') + let &cpo = save_cpo +endfunc + +" Test for the 'A' flag in 'cpo'. Writing a file should set the alternate +" file name. +func Test_cpo_A() + let save_cpo = &cpo + " Wipe out all the buffers, so that the alternate file is empty + edit Xfoo | %bw + set cpo-=A + new Xfile1 + write Xfile2 + call assert_equal('', @#) + %bw + call delete('Xfile2') + new Xfile1 + set cpo+=A + write Xfile2 + call assert_equal('Xfile2', @#) + close! + call delete('Xfile2') + let &cpo = save_cpo +endfunc + +" Test for the 'b' flag in 'cpo'. "\|" at the end of a map command is +" recognized as the end of the map. +func Test_cpo_b() + let save_cpo = &cpo + set cpo+=b + nnoremap <F5> :pwd\<CR>\|let i = 1 + call assert_equal(':pwd\<CR>\', maparg('<F5>')) + nunmap <F5> + exe "nnoremap <F5> :pwd\<C-V>|let i = 1" + call assert_equal(':pwd|let i = 1', maparg('<F5>')) + nunmap <F5> + set cpo-=b + nnoremap <F5> :pwd\<CR>\|let i = 1 + call assert_equal(':pwd\<CR>|let i = 1', maparg('<F5>')) + let &cpo = save_cpo + nunmap <F5> +endfunc + +" Test for the 'B' flag in 'cpo'. A backslash in mappings, abbreviations, user +" commands and menu commands has no special meaning. +func Test_cpo_B() + let save_cpo = &cpo + new + set cpo-=B + iabbr <buffer> abc ab\<BS>d + exe "normal iabc " + call assert_equal('ab<BS>d ', getline(1)) + %d + set cpo+=B + iabbr <buffer> abc ab\<BS>d + exe "normal iabc " + call assert_equal('abd ', getline(1)) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'c' flag in 'cpo'. +func Test_cpo_c() + let save_cpo = &cpo + set cpo+=c + new + call setline(1, ' abababababab') + exe "normal gg/abab\<CR>" + call assert_equal(3, searchcount().total) + set cpo-=c + exe "normal gg/abab\<CR>" + call assert_equal(5, searchcount().total) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'C' flag in 'cpo' (line continuation) +func Test_cpo_C() + let save_cpo = &cpo + call writefile(['let l = [', '\ 1,', '\ 2]'], 'XfileCpoC') + set cpo-=C + source XfileCpoC + call assert_equal([1, 2], g:l) + set cpo+=C + call assert_fails('source XfileCpoC', ['E697:', 'E10:']) + call delete('XfileCpoC') + let &cpo = save_cpo +endfunc + +" Test for the 'd' flag in 'cpo' (tags relative to the current file) +func Test_cpo_d() + let save_cpo = &cpo + call mkdir('XdirCpoD') + call writefile(["one\tXfile1\t/^one$/"], 'tags') + call writefile(["two\tXfile2\t/^two$/"], 'XdirCpoD/tags') + set tags=./tags + set cpo-=d + edit XdirCpoD/Xfile + call assert_equal('two', taglist('.*')[0].name) + set cpo+=d + call assert_equal('one', taglist('.*')[0].name) + %bw! + call delete('tags') + call delete('XdirCpoD', 'rf') + set tags& + let &cpo = save_cpo +endfunc + +" Test for the 'D' flag in 'cpo' (digraph after a r, f or t) +func Test_cpo_D() + CheckFeature digraphs + let save_cpo = &cpo + new + set cpo-=D + call setline(1, 'abcdefgh|') + exe "norm! 1gg0f\<c-k>!!" + call assert_equal(9, col('.')) + set cpo+=D + exe "norm! 1gg0f\<c-k>!!" + call assert_equal(1, col('.')) + set cpo-=D + close! + let &cpo = save_cpo +endfunc + +" Test for the 'e' flag in 'cpo' +func Test_cpo_e() + let save_cpo = &cpo + let @a='let i = 45' + set cpo+=e + call feedkeys(":@a\<CR>", 'xt') + call assert_equal(45, i) + set cpo-=e + call feedkeys(":@a\<CR>6\<CR>", 'xt') + call assert_equal(456, i) + let &cpo = save_cpo +endfunc + +" Test for the 'E' flag in 'cpo' with yank, change, delete, etc. operators +func Test_cpo_E() + new + call setline(1, '') + set cpo+=E + " yank an empty line + call assert_beeps('normal "ayl') + " change an empty line + call assert_beeps('normal lcTa') + call assert_beeps('normal 0c0') + " delete an empty line + call assert_beeps('normal D') + call assert_beeps('normal dl') + call assert_equal('', getline(1)) + " change case of an empty line + call assert_beeps('normal gul') + call assert_beeps('normal gUl') + " replace a character + call assert_beeps('normal vrx') + " increment and decrement + call assert_beeps('exe "normal v\<C-A>"') + call assert_beeps('exe "normal v\<C-X>"') + set cpo-=E + close! +endfunc + +" Test for the 'f' flag in 'cpo' (read in an empty buffer sets the file name) +func Test_cpo_f() + let save_cpo = &cpo + new + set cpo-=f + read test_cpoptions.vim + call assert_equal('', @%) + %d + set cpo+=f + read test_cpoptions.vim + call assert_equal('test_cpoptions.vim', @%) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'F' flag in 'cpo' (write in an empty buffer sets the file name) +func Test_cpo_F() + let save_cpo = &cpo + new + set cpo-=F + write XfileCpoF + call assert_equal('', @%) + call delete('XfileCpoF') + set cpo+=F + write XfileCpoF + call assert_equal('XfileCpoF', @%) + close! + call delete('XfileCpoF') + let &cpo = save_cpo +endfunc + +" Test for the 'g' flag in 'cpo' (jump to line 1 when re-editing a file) +func Test_cpo_g() + throw 'Skipped: Nvim does not support cpoptions flag "g"' + let save_cpo = &cpo + new test_cpoptions.vim + set cpo-=g + normal 20G + edit + call assert_equal(20, line('.')) + set cpo+=g + edit + call assert_equal(1, line('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for inserting text in a line with only spaces ('H' flag in 'cpoptions') +func Test_cpo_H() + throw 'Skipped: Nvim does not support cpoptions flag "H"' + let save_cpo = &cpo + new + set cpo-=H + call setline(1, ' ') + normal! Ia + call assert_equal(' a', getline(1)) + set cpo+=H + call setline(1, ' ') + normal! Ia + call assert_equal(' a ', getline(1)) + close! + let &cpo = save_cpo +endfunc + +" TODO: Add a test for the 'i' flag in 'cpo' +" Interrupting the reading of a file will leave it modified. + +" Test for the 'I' flag in 'cpo' (deleting autoindent when using arrow keys) +func Test_cpo_I() + let save_cpo = &cpo + new + setlocal autoindent + set cpo+=I + exe "normal i one\<CR>\<Up>" + call assert_equal(' ', getline(2)) + set cpo-=I + %d + exe "normal i one\<CR>\<Up>" + call assert_equal('', getline(2)) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'j' flag in 'cpo' is in the test_join.vim file. + +" Test for the 'J' flag in 'cpo' (two spaces after a sentence) +func Test_cpo_J() + let save_cpo = &cpo + new + set cpo-=J + call setline(1, 'one. two! three? four."'' five.)]') + normal 0 + for colnr in [6, 12, 19, 28, 34] + normal ) + call assert_equal(colnr, col('.')) + endfor + for colnr in [28, 19, 12, 6, 1] + normal ( + call assert_equal(colnr, col('.')) + endfor + set cpo+=J + normal 0 + for colnr in [12, 28, 34] + normal ) + call assert_equal(colnr, col('.')) + endfor + for colnr in [28, 12, 1] + normal ( + call assert_equal(colnr, col('.')) + endfor + close! + let &cpo = save_cpo +endfunc + +" TODO: Add a test for the 'k' flag in 'cpo'. +" Disable the recognition of raw key codes in mappings, abbreviations, and the +" "to" part of menu commands. + +" TODO: Add a test for the 'K' flag in 'cpo'. +" Don't wait for a key code to complete when it is halfway a mapping. + +" Test for the 'l' flag in 'cpo' (backslash in a [] range) +func Test_cpo_l() + let save_cpo = &cpo + new + call setline(1, ['', "a\tc" .. '\t']) + set cpo-=l + exe 'normal gg/[\t]' .. "\<CR>" + call assert_equal([2, 8], [col('.'), virtcol('.')]) + set cpo+=l + exe 'normal gg/[\t]' .. "\<CR>" + call assert_equal([4, 10], [col('.'), virtcol('.')]) + close! + let &cpo = save_cpo +endfunc + +" Test for inserting tab in virtual replace mode ('L' flag in 'cpoptions') +func Test_cpo_L() + let save_cpo = &cpo + new + set cpo-=L + call setline(1, 'abcdefghijklmnopqr') + exe "normal 0gR\<Tab>" + call assert_equal("\<Tab>ijklmnopqr", getline(1)) + set cpo+=L + set list + call setline(1, 'abcdefghijklmnopqr') + exe "normal 0gR\<Tab>" + call assert_equal("\<Tab>cdefghijklmnopqr", getline(1)) + set nolist + call setline(1, 'abcdefghijklmnopqr') + exe "normal 0gR\<Tab>" + call assert_equal("\<Tab>ijklmnopqr", getline(1)) + close! + let &cpo = save_cpo +endfunc + +" TODO: Add a test for the 'm' flag in 'cpo'. +" When included, a showmatch will always wait half a second. When not +" included, a showmatch will wait half a second or until a character is typed. + +" Test for the 'M' flag in 'cpo' (% with escape parenthesis) +func Test_cpo_M() + let save_cpo = &cpo + new + call setline(1, ['( \( )', '\( ( \)']) + + set cpo-=M + call cursor(1, 1) + normal % + call assert_equal(6, col('.')) + call cursor(1, 4) + call assert_beeps('normal %') + call cursor(2, 2) + normal % + call assert_equal(7, col('.')) + call cursor(2, 4) + call assert_beeps('normal %') + + set cpo+=M + call cursor(1, 4) + normal % + call assert_equal(6, col('.')) + call cursor(1, 1) + call assert_beeps('normal %') + call cursor(2, 4) + normal % + call assert_equal(7, col('.')) + call cursor(2, 1) + call assert_beeps('normal %') + + close! + let &cpo = save_cpo +endfunc + +" Test for the 'n' flag in 'cpo' (using number column for wrapped lines) +func Test_cpo_n() + let save_cpo = &cpo + new + call setline(1, repeat('a', &columns)) + setlocal number + set cpo-=n + redraw! + call assert_equal(' aaaa', Screenline(2)) + set cpo+=n + redraw! + call assert_equal('aaaa', Screenline(2)) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'o' flag in 'cpo' (line offset to search command) +func Test_cpo_o() + let save_cpo = &cpo + new + call setline(1, ['', 'one', 'two', 'three', 'one', 'two', 'three']) + set cpo-=o + exe "normal /one/+2\<CR>" + normal n + call assert_equal(7, line('.')) + set cpo+=o + exe "normal /one/+2\<CR>" + normal n + call assert_equal(5, line('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'O' flag in 'cpo' (overwriting an existing file) +func Test_cpo_O() + let save_cpo = &cpo + new XfileCpoO + call setline(1, 'one') + call writefile(['two'], 'XfileCpoO') + set cpo-=O + call assert_fails('write', 'E13:') + set cpo+=O + write + call assert_equal(['one'], readfile('XfileCpoO')) + close! + call delete('XfileCpoO') + let &cpo = save_cpo +endfunc + +" Test for the 'p' flag in 'cpo' is in the test_lispwords.vim file. + +" Test for the 'P' flag in 'cpo' (appending to a file sets the current file +" name) +func Test_cpo_P() + let save_cpo = &cpo + call writefile([], 'XfileCpoP') + new + call setline(1, 'one') + set cpo+=F + set cpo-=P + write >> XfileCpoP + call assert_equal('', @%) + set cpo+=P + write >> XfileCpoP + call assert_equal('XfileCpoP', @%) + close! + call delete('XfileCpoP') + let &cpo = save_cpo +endfunc + +" Test for the 'q' flag in 'cpo' (joining multiple lines) +func Test_cpo_q() + let save_cpo = &cpo + new + call setline(1, ['one', 'two', 'three', 'four', 'five']) + set cpo-=q + normal gg4J + call assert_equal(14, col('.')) + %d + call setline(1, ['one', 'two', 'three', 'four', 'five']) + set cpo+=q + normal gg4J + call assert_equal(4, col('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'r' flag in 'cpo' (redo command with a search motion) +func Test_cpo_r() + let save_cpo = &cpo + new + call setline(1, repeat(['one two three four'], 2)) + set cpo-=r + exe "normal ggc/two\<CR>abc " + let @/ = 'three' + normal 2G. + call assert_equal('abc two three four', getline(2)) + %d + call setline(1, repeat(['one two three four'], 2)) + set cpo+=r + exe "normal ggc/two\<CR>abc " + let @/ = 'three' + normal 2G. + call assert_equal('abc three four', getline(2)) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'R' flag in 'cpo' (clear marks after a filter command) +func Test_cpo_R() + CheckUnix + let save_cpo = &cpo + new + call setline(1, ['three', 'one', 'two']) + set cpo-=R + 3mark r + %!sort + call assert_equal(3, line("'r")) + %d + call setline(1, ['three', 'one', 'two']) + set cpo+=R + 3mark r + %!sort + call assert_equal(0, line("'r")) + close! + let &cpo = save_cpo +endfunc + +" TODO: Add a test for the 's' flag in 'cpo'. +" Set buffer options when entering the buffer for the first time. If not +" present the options are set when the buffer is created. + +" Test for the 'S' flag in 'cpo' (copying buffer options) +func Test_cpo_S() + let save_cpo = &cpo + new Xfile1 + set noautoindent + new Xfile2 + set cpo-=S + set autoindent + wincmd p + call assert_equal(0, &autoindent) + wincmd p + call assert_equal(1, &autoindent) + set cpo+=S + wincmd p + call assert_equal(1, &autoindent) + set noautoindent + wincmd p + call assert_equal(0, &autoindent) + wincmd t + close! + close! + let &cpo = save_cpo +endfunc + +" Test for the 't' flag in 'cpo' is in the test_tagjump.vim file. + +" Test for the 'u' flag in 'cpo' (Vi-compatible undo) +func Test_cpo_u() + let save_cpo = &cpo + new + set cpo-=u + exe "normal iabc\<C-G>udef\<C-G>ughi" + normal uu + call assert_equal('abc', getline(1)) + %d + set cpo+=u + exe "normal iabc\<C-G>udef\<C-G>ughi" + normal uu + call assert_equal('abcdefghi', getline(1)) + close! + let &cpo = save_cpo +endfunc + +" TODO: Add a test for the 'v' flag in 'cpo'. +" Backspaced characters remain visible on the screen in Insert mode. + +" Test for the 'w' flag in 'cpo' ('cw' on a blank character changes only one +" character) +func Test_cpo_w() + throw 'Skipped: Nvim does not support cpoptions flag "w"' + let save_cpo = &cpo + new + set cpo+=w + call setline(1, 'here are some words') + norm! 1gg0elcwZZZ + call assert_equal('hereZZZ are some words', getline('.')) + norm! 1gg2elcWYYY + call assert_equal('hereZZZ areYYY some words', getline('.')) + set cpo-=w + call setline(1, 'here are some words') + norm! 1gg0elcwZZZ + call assert_equal('hereZZZare some words', getline('.')) + norm! 1gg2elcWYYY + call assert_equal('hereZZZare someYYYwords', getline('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'W' flag in 'cpo' is in the test_writefile.vim file + +" Test for the 'x' flag in 'cpo' (Esc on command-line executes command) +func Test_cpo_x() + let save_cpo = &cpo + set cpo-=x + let i = 1 + call feedkeys(":let i=10\<Esc>", 'xt') + call assert_equal(1, i) + set cpo+=x + call feedkeys(":let i=10\<Esc>", 'xt') + call assert_equal(10, i) + let &cpo = save_cpo +endfunc + +" Test for the 'X' flag in 'cpo' ('R' with a count) +func Test_cpo_X() + let save_cpo = &cpo + new + call setline(1, 'aaaaaa') + set cpo-=X + normal gg4Rx + call assert_equal('xxxxaa', getline(1)) + normal ggRy + normal 4. + call assert_equal('yyyyaa', getline(1)) + call setline(1, 'aaaaaa') + set cpo+=X + normal gg4Rx + call assert_equal('xxxxaaaaa', getline(1)) + normal ggRy + normal 4. + call assert_equal('yyyyxxxaaaaa', getline(1)) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'y' flag in 'cpo' (repeating a yank command) +func Test_cpo_y() + let save_cpo = &cpo + new + call setline(1, ['one', 'two']) + set cpo-=y + normal ggyy + normal 2G. + call assert_equal("one\n", @") + %d + call setline(1, ['one', 'two']) + set cpo+=y + normal ggyy + normal 2G. + call assert_equal("two\n", @") + close! + let &cpo = save_cpo +endfunc + +" Test for the 'Z' flag in 'cpo' (write! resets 'readonly') +func Test_cpo_Z() + let save_cpo = &cpo + call writefile([], 'XfileCpoZ') + new XfileCpoZ + setlocal readonly + set cpo-=Z + write! + call assert_equal(0, &readonly) + set cpo+=Z + setlocal readonly + write! + call assert_equal(1, &readonly) + close! + call delete('XfileCpoZ') + let &cpo = save_cpo +endfunc + +" Test for the '!' flag in 'cpo' is in the test_normal.vim file + +" Test for displaying dollar when changing text ('$' flag in 'cpoptions') +func Test_cpo_dollar() + throw 'Skipped: use test/functional/legacy/cpoptions_spec.lua' + new + let g:Line = '' + func SaveFirstLine() + let g:Line = Screenline(1) + return '' + endfunc + inoremap <expr> <buffer> <F2> SaveFirstLine() + call test_override('redraw_flag', 1) + set cpo+=$ + call setline(1, 'one two three') + redraw! + exe "normal c2w\<F2>vim" + call assert_equal('one tw$ three', g:Line) + call assert_equal('vim three', getline(1)) + set cpo-=$ + call test_override('ALL', 0) + delfunc SaveFirstLine + %bw! +endfunc + +" Test for the '%' flag in 'cpo' (parenthesis matching inside strings) +func Test_cpo_percent() + let save_cpo = &cpo + new + call setline(1, ' if (strcmp("ab)cd(", s))') + set cpo-=% + normal 8|% + call assert_equal(28, col('.')) + normal 15|% + call assert_equal(27, col('.')) + normal 27|% + call assert_equal(15, col('.')) + call assert_beeps("normal 19|%") + call assert_beeps("normal 22|%") + set cpo+=% + normal 8|% + call assert_equal(28, col('.')) + normal 15|% + call assert_equal(19, col('.')) + normal 27|% + call assert_equal(22, col('.')) + normal 19|% + call assert_equal(15, col('.')) + normal 22|% + call assert_equal(27, col('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for cursor movement with '-' in 'cpoptions' +func Test_cpo_minus() + throw 'Skipped: Nvim does not support cpoptions flag "-"' + new + call setline(1, ['foo', 'bar', 'baz']) + let save_cpo = &cpo + set cpo+=- + call assert_beeps('normal 10j') + call assert_equal(1, line('.')) + normal G + call assert_beeps('normal 10k') + call assert_equal(3, line('.')) + call assert_fails(10, 'E16:') + close! + let &cpo = save_cpo +endfunc + +" Test for the '+' flag in 'cpo' ('write file' command resets the 'modified' +" flag) +func Test_cpo_plus() + let save_cpo = &cpo + call writefile([], 'XfileCpoPlus') + new XfileCpoPlus + call setline(1, 'foo') + write X1 + call assert_equal(1, &modified) + set cpo+=+ + write X2 + call assert_equal(0, &modified) + close! + call delete('XfileCpoPlus') + call delete('X1') + call delete('X2') + let &cpo = save_cpo +endfunc + +" Test for the '*' flag in 'cpo' (':*' is same as ':@') +func Test_cpo_star() + throw 'Skipped: Nvim does not support cpoptions flag "*"' + let save_cpo = &cpo + let x = 0 + new + set cpo-=* + let @a = 'let x += 1' + call assert_fails('*a', 'E20:') + set cpo+=* + *a + call assert_equal(1, x) + close! + let &cpo = save_cpo +endfunc + +" Test for the '<' flag in 'cpo' is in the test_mapping.vim file + +" Test for the '>' flag in 'cpo' (use a new line when appending to a register) +func Test_cpo_gt() + let save_cpo = &cpo + new + call setline(1, 'one two') + set cpo-=> + let @r = '' + normal gg"Rye + normal "Rye + call assert_equal("oneone", @r) + set cpo+=> + let @r = '' + normal gg"Rye + normal "Rye + call assert_equal("\none\none", @r) + close! + let &cpo = save_cpo +endfunc + +" Test for the ';' flag in 'cpo' +" Test for t,f,F,T movement commands and 'cpo-;' setting +func Test_cpo_semicolon() + let save_cpo = &cpo + new + call append(0, ["aaa two three four", " zzz", "yyy ", + \ "bbb yee yoo four", "ccc two three four", + \ "ddd yee yoo four"]) + set cpo-=; + 1 + normal! 0tt;D + 2 + normal! 0fz;D + 3 + normal! $Fy;D + 4 + normal! $Ty;D + set cpo+=; + 5 + normal! 0tt;;D + 6 + normal! $Ty;;D + + call assert_equal('aaa two', getline(1)) + call assert_equal(' z', getline(2)) + call assert_equal('y', getline(3)) + call assert_equal('bbb y', getline(4)) + call assert_equal('ccc', getline(5)) + call assert_equal('ddd yee y', getline(6)) + close! + let &cpo = save_cpo +endfunc + +" Test for the '#' flag in 'cpo' (count before 'D', 'o' and 'O' operators) +func Test_cpo_hash() + throw 'Skipped: Nvim does not support cpoptions flag "#"' + let save_cpo = &cpo + new + set cpo-=# + call setline(1, ['one', 'two', 'three']) + normal gg2D + call assert_equal(['three'], getline(1, '$')) + normal gg2ofour + call assert_equal(['three', 'four', 'four'], getline(1, '$')) + normal gg2Otwo + call assert_equal(['two', 'two', 'three', 'four', 'four'], getline(1, '$')) + %d + set cpo+=# + call setline(1, ['one', 'two', 'three']) + normal gg2D + call assert_equal(['', 'two', 'three'], getline(1, '$')) + normal gg2oone + call assert_equal(['', 'one', 'two', 'three'], getline(1, '$')) + normal gg2Ozero + call assert_equal(['zero', '', 'one', 'two', 'three'], getline(1, '$')) + close! + let &cpo = save_cpo +endfunc + +" Test for the '&' flag in 'cpo'. The swap file is kept when a buffer is still +" loaded and ':preserve' is used. +func Test_cpo_ampersand() + throw 'Skipped: Nvim does not support cpoptions flag "&"' + call writefile(['one'], 'XfileCpoAmp') + let after =<< trim [CODE] + set cpo+=& + preserve + qall + [CODE] + if RunVim([], after, 'XfileCpoAmp') + call assert_equal(1, filereadable('.XfileCpoAmp.swp')) + call delete('.XfileCpoAmp.swp') + endif + call delete('XfileCpoAmp') +endfunc + +" Test for the '\' flag in 'cpo' (backslash in a [] range in a search pattern) +func Test_cpo_backslash() + throw 'Skipped: Nvim does not support cpoptions flag "\"' + let save_cpo = &cpo + new + call setline(1, ['', " \\-string"]) + set cpo-=\ + exe 'normal gg/[ \-]' .. "\<CR>n" + call assert_equal(3, col('.')) + set cpo+=\ + exe 'normal gg/[ \-]' .. "\<CR>n" + call assert_equal(2, col('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for the '/' flag in 'cpo' is in the test_substitute.vim file + +" Test for the '{' flag in 'cpo' (the "{" and "}" commands stop at a { +" character at the start of a line) +func Test_cpo_brace() + throw 'Skipped: Nvim does not support cpoptions flag "{"' + let save_cpo = &cpo + new + call setline(1, ['', '{', ' int i;', '}', '']) + set cpo-={ + normal gg} + call assert_equal(5, line('.')) + normal G{ + call assert_equal(1, line('.')) + set cpo+={ + normal gg} + call assert_equal(2, line('.')) + normal G{ + call assert_equal(2, line('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for the '.' flag in 'cpo' (:cd command fails if the current buffer is +" modified) +func Test_cpo_dot() + throw 'Skipped: Nvim does not support cpoptions flag "."' + let save_cpo = &cpo + new Xfoo + call setline(1, 'foo') + let save_dir = getcwd() + set cpo+=. + + " :cd should fail when buffer is modified and 'cpo' contains dot. + call assert_fails('cd ..', 'E747:') + call assert_equal(save_dir, getcwd()) + + " :cd with exclamation mark should succeed. + cd! .. + call assert_notequal(save_dir, getcwd()) + + " :cd should succeed when buffer has been written. + w! + exe 'cd ' .. fnameescape(save_dir) + call assert_equal(save_dir, getcwd()) + + call delete('Xfoo') + set cpo& + close! + let &cpo = save_cpo +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cscope.vim b/src/nvim/testdir/test_cscope.vim deleted file mode 100644 index 76ea35fa10..0000000000 --- a/src/nvim/testdir/test_cscope.vim +++ /dev/null @@ -1,344 +0,0 @@ -" Test for cscope commands. - -source check.vim -CheckFeature cscope -CheckFeature quickfix - -if !executable('cscope') - throw 'Skipped: cscope program missing' -endif - -func CscopeSetupOrClean(setup) - if a:setup - noa sp samples/memfile_test.c - saveas! Xmemfile_test.c - call system('cscope -bk -fXcscope.out Xmemfile_test.c') - call system('cscope -bk -fXcscope2.out Xmemfile_test.c') - cscope add Xcscope.out - set cscopequickfix=s-,g-,d-,c-,t-,e-,f-,i-,a- - else - cscope kill -1 - for file in ['Xcscope.out', 'Xcscope2.out', 'Xmemfile_test.c'] - call delete(file) - endfo - endif -endfunc - -func Test_cscopeWithCscopeConnections() - call CscopeSetupOrClean(1) - " Test: E568: duplicate cscope database not added - try - set nocscopeverbose - cscope add Xcscope.out - set cscopeverbose - catch - call assert_report('exception thrown') - endtry - call assert_fails('cscope add', 'E560') - call assert_fails('cscope add Xcscope.out', 'E568') - call assert_fails('cscope add doesnotexist.out', 'E563') - if has('unix') - call assert_fails('cscope add /dev/null', 'E564:') - endif - - " Test: Find this C-Symbol - for cmd in ['cs find s main', 'cs find 0 main'] - let a = execute(cmd) - " Test where it moves the cursor - call assert_equal('main(void)', getline('.')) - " Test the output of the :cs command - call assert_match('\n(1 of 1): <<main>> main(void )', a) - endfor - - " Test: Find this definition - for cmd in ['cs find g test_mf_hash', - \ 'cs find 1 test_mf_hash', - \ 'cs find 1 test_mf_hash'] " leading space ignored. - exe cmd - call assert_equal(['', '/*', ' * Test mf_hash_*() functions.', ' */', ' static void', 'test_mf_hash(void)', '{'], getline(line('.')-5, line('.')+1)) - endfor - - " Test: Find functions called by this function - for cmd in ['cs find d test_mf_hash', 'cs find 2 test_mf_hash'] - let a = execute(cmd) - call assert_match('\n(1 of 42): <<mf_hash_init>> mf_hash_init(&ht);', a) - call assert_equal(' mf_hash_init(&ht);', getline('.')) - endfor - - " Test: Find functions calling this function - for cmd in ['cs find c test_mf_hash', 'cs find 3 test_mf_hash'] - let a = execute(cmd) - call assert_match('\n(1 of 1): <<main>> test_mf_hash();', a) - call assert_equal(' test_mf_hash();', getline('.')) - endfor - - " Test: Find this text string - for cmd in ['cs find t Bram', 'cs find 4 Bram'] - let a = execute(cmd) - call assert_match('(1 of 1): <<<unknown>>> \* VIM - Vi IMproved^Iby Bram Moolenaar', a) - call assert_equal(' * VIM - Vi IMproved by Bram Moolenaar', getline('.')) - endfor - - " Test: Find this egrep pattern - " test all matches returned by cscope - for cmd in ['cs find e ^\#includ.', 'cs find 6 ^\#includ.'] - let a = execute(cmd) - call assert_match('\n(1 of 3): <<<unknown>>> #include <assert.h>', a) - call assert_equal('#include <assert.h>', getline('.')) - cnext - call assert_equal('#include "main.c"', getline('.')) - cnext - call assert_equal('#include "memfile.c"', getline('.')) - call assert_fails('cnext', 'E553:') - endfor - - " Test: Find the same egrep pattern using lcscope this time. - let a = execute('lcs find e ^\#includ.') - call assert_match('\n(1 of 3): <<<unknown>>> #include <assert.h>', a) - call assert_equal('#include <assert.h>', getline('.')) - lnext - call assert_equal('#include "main.c"', getline('.')) - lnext - call assert_equal('#include "memfile.c"', getline('.')) - call assert_fails('lnext', 'E553:') - - " Test: Find this file - 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\+B') - call assert_equal('Xmemfile_test.c', @%) - endfor - - " Test: Find files #including this file - for cmd in ['cs find i assert.h', 'cs find 8 assert.h'] - enew - 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\+B') - call assert_equal('(1 of 1): <<global>> #include <assert.h>', alines[2]) - call assert_equal('#include <assert.h>', getline('.')) - endfor - - " Test: Invalid find command - call assert_fails('cs find', 'E560:') - call assert_fails('cs find x', 'E560:') - - if has('float') - " Test: Find places where this symbol is assigned a value - " this needs a cscope >= 15.8 - " unfortunately, Travis has cscope version 15.7 - let cscope_version = systemlist('cscope --version')[0] - let cs_version = str2float(matchstr(cscope_version, '\d\+\(\.\d\+\)\?')) - if cs_version >= 15.8 - for cmd in ['cs find a item', 'cs find 9 item'] - let a = execute(cmd) - call assert_equal(['', '(1 of 4): <<test_mf_hash>> item = (mf_hashitem_T *)lalloc_clear(sizeof(*item), FALSE);'], split(a, '\n', 1)) - call assert_equal(' item = (mf_hashitem_T *)lalloc_clear(sizeof(*item), FALSE);', getline('.')) - cnext - call assert_equal(' item = mf_hash_find(&ht, key);', getline('.')) - cnext - call assert_equal(' item = mf_hash_find(&ht, key);', getline('.')) - cnext - call assert_equal(' item = mf_hash_find(&ht, key);', getline('.')) - endfor - endif - endif - - " Test: leading whitespace is not removed for cscope find text - let a = execute('cscope find t test_mf_hash') - call assert_equal(['', '(1 of 1): <<<unknown>>> test_mf_hash();'], split(a, '\n', 1)) - call assert_equal(' test_mf_hash();', getline('.')) - - " Test: test with scscope - let a = execute('scs find t Bram') - call assert_match('(1 of 1): <<<unknown>>> \* VIM - Vi IMproved^Iby Bram Moolenaar', a) - call assert_equal(' * VIM - Vi IMproved by Bram Moolenaar', getline('.')) - - " Test: cscope help - for cmd in ['cs', 'cs help', 'cs xxx'] - let a = execute(cmd) - call assert_match('^cscope commands:\n', a) - call assert_match('\nadd :', a) - call assert_match('\nfind :', a) - call assert_match('\nhelp : Show this message', a) - call assert_match('\nkill : Kill a connection', a) - call assert_match('\nreset: Reinit all connections', a) - call assert_match('\nshow : Show connections', a) - endfor - let a = execute('scscope help') - call assert_match('This cscope command does not support splitting the window\.', a) - - " Test: reset connections - let a = execute('cscope reset') - call assert_match('\nAdded cscope database.*Xcscope.out (#0)', a) - call assert_match('\nAll cscope databases reset', a) - - " Test: cscope show - let a = execute('cscope show') - call assert_match('\n 0 \d\+.*Xcscope.out\s*<none>', a) - - " Test: cstag and 'csto' option - set csto=0 - let a = execute('cstag TEST_COUNT') - call assert_match('(1 of 1): <<TEST_COUNT>> #define TEST_COUNT 50000', a) - call assert_equal('#define TEST_COUNT 50000', getline('.')) - call assert_fails('cstag DOES_NOT_EXIST', 'E257:') - set csto=1 - let a = execute('cstag index_to_key') - call assert_match('(1 of 1): <<index_to_key>> #define index_to_key(i) ((i) ^ 15167)', a) - call assert_equal('#define index_to_key(i) ((i) ^ 15167)', getline('.')) - call assert_fails('cstag DOES_NOT_EXIST', 'E257:') - call assert_fails('cstag', 'E562:') - let save_tags = &tags - set tags= - call assert_fails('cstag DOES_NOT_EXIST', 'E257:') - let a = execute('cstag index_to_key') - call assert_match('(1 of 1): <<index_to_key>> #define index_to_key(i) ((i) ^ 15167)', a) - let &tags = save_tags - - " Test: 'cst' option - set nocst - call assert_fails('tag TEST_COUNT', 'E426:') - set cst - let a = execute('tag TEST_COUNT') - call assert_match('(1 of 1): <<TEST_COUNT>> #define TEST_COUNT 50000', a) - call assert_equal('#define TEST_COUNT 50000', getline('.')) - let a = execute('tags') - call assert_match('1 1 TEST_COUNT\s\+\d\+\s\+#define index_to_key', a) - - " Test: 'cscoperelative' - call mkdir('Xcscoperelative') - cd Xcscoperelative - let a = execute('cs find g test_mf_hash') - call assert_notequal('test_mf_hash(void)', getline('.')) - set cscoperelative - let a = execute('cs find g test_mf_hash') - call assert_equal('test_mf_hash(void)', getline('.')) - set nocscoperelative - cd .. - call delete('Xcscoperelative', 'd') - - " Test: E259: no match found - call assert_fails('cscope find g DOES_NOT_EXIST', 'E259:') - - " Test: this should trigger call to cs_print_tags() - " Unclear how to check result though, we just exercise the code. - set cst cscopequickfix=s0 - call feedkeys(":cs find s main\<CR>", 't') - - " Test: cscope kill - call assert_fails('cscope kill', 'E560:') - call assert_fails('cscope kill 2', 'E261:') - call assert_fails('cscope kill xxx', 'E261:') - - let a = execute('cscope kill 0') - call assert_match('cscope connection 0 closed', a) - - cscope add Xcscope.out - let a = execute('cscope kill Xcscope.out') - call assert_match('cscope connection Xcscope.out closed', a) - - cscope add Xcscope.out . - let a = execute('cscope kill -1') - call assert_match('cscope connection .*Xcscope.out closed', a) - let a = execute('cscope kill -1') - call assert_equal('', a) - - " Test: 'csprg' option - call assert_equal('cscope', &csprg) - set csprg=doesnotexist - call assert_fails('cscope add Xcscope2.out', 'E609:') - set csprg=cscope - - " Test: multiple cscope connections - cscope add Xcscope.out - cscope add Xcscope2.out . -C - let a = execute('cscope show') - call assert_match('\n 0 \d\+.*Xcscope.out\s*<none>', a) - call assert_match('\n 1 \d\+.*Xcscope2.out\s*\.', a) - - " Test: test Ex command line completion - call feedkeys(":cs \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"cs add find help kill reset show', @:) - - call feedkeys(":scs \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"scs find', @:) - - call feedkeys(":cs find \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"cs find a c d e f g i s t', @:) - - call feedkeys(":cs kill \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"cs kill -1 0 1', @:) - - call feedkeys(":cs add Xcscope\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"cs add Xcscope.out Xcscope2.out', @:) - - " Test: cscope_connection() - call assert_equal(cscope_connection(), 1) - call assert_equal(cscope_connection(0, 'out'), 1) - call assert_equal(cscope_connection(0, 'xxx'), 1) - - call assert_equal(cscope_connection(1, 'out'), 1) - call assert_equal(cscope_connection(1, 'xxx'), 0) - - call assert_equal(cscope_connection(2, 'out'), 0) - call assert_equal(cscope_connection(2, getcwd() .. '/Xcscope.out', 1), 1) - - call assert_equal(cscope_connection(3, 'xxx', '..'), 0) - call assert_equal(cscope_connection(3, 'out', 'xxx'), 0) - call assert_equal(cscope_connection(3, 'out', '.'), 1) - - call assert_equal(cscope_connection(4, 'out', '.'), 0) - - call assert_equal(cscope_connection(5, 'out'), 0) - call assert_equal(cscope_connection(-1, 'out'), 0) - - call CscopeSetupOrClean(0) -endfunc - -" Test ":cs add {dir}" (add the {dir}/cscope.out database) -func Test_cscope_add_dir() - call mkdir('Xcscopedir', 'p') - - " Cscope doesn't handle symlinks, so this needs to be resolved in case a - " shadow directory is being used. - let memfile = resolve('./samples/memfile_test.c') - call system('cscope -bk -fXcscopedir/cscope.out ' . memfile) - - cs add Xcscopedir - let a = execute('cscope show') - let lines = split(a, "\n", 1) - call assert_equal(3, len(lines)) - call assert_equal(' # pid database name prepend path', lines[0]) - call assert_equal('', lines[1]) - call assert_match('^ 0 \d\+.*Xcscopedir/cscope.out\s\+<none>$', lines[2]) - - cs kill -1 - call delete('Xcscopedir/cscope.out') - call assert_fails('cs add Xcscopedir', 'E563:') - - call delete('Xcscopedir', 'd') -endfunc - -func Test_cscopequickfix() - set cscopequickfix=s-,g-,d+,c-,t+,e-,f0,i-,a- - call assert_equal('s-,g-,d+,c-,t+,e-,f0,i-,a-', &cscopequickfix) - - call assert_fails('set cscopequickfix=x-', 'E474:') - call assert_fails('set cscopequickfix=s', 'E474:') - call assert_fails('set cscopequickfix=s7', 'E474:') - call assert_fails('set cscopequickfix=s-a', 'E474:') -endfunc - -func Test_withoutCscopeConnection() - call assert_equal(cscope_connection(), 0) - - call assert_fails('cscope find s main', 'E567:') - let a = execute('cscope show') - call assert_match('no cscope connections', a) -endfunc - - -" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim index 9801a45915..bb8e7cd5c5 100644 --- a/src/nvim/testdir/test_cursor_func.vim +++ b/src/nvim/testdir/test_cursor_func.vim @@ -1,7 +1,10 @@ " Tests for cursor() and other functions that get/set the cursor position +source check.vim + func Test_wrong_arguments() call assert_fails('call cursor(1. 3)', 'E474:') + call assert_fails('call cursor(v:_null_list)', 'E474:') endfunc func Test_move_cursor() @@ -82,34 +85,85 @@ func Test_screenpos() let winid = win_getid() let [winrow, wincol] = win_screenpos(winid) call assert_equal({'row': winrow, - \ 'col': wincol + 0, - \ 'curscol': wincol + 7, - \ 'endcol': wincol + 7}, winid->screenpos(1, 1)) + \ 'col': wincol + 0, + \ 'curscol': wincol + 7, + \ 'endcol': wincol + 7}, winid->screenpos(1, 1)) call assert_equal({'row': winrow, - \ 'col': wincol + 13, - \ 'curscol': wincol + 13, - \ 'endcol': wincol + 13}, winid->screenpos(1, 7)) + \ 'col': wincol + 13, + \ 'curscol': wincol + 13, + \ 'endcol': wincol + 13}, winid->screenpos(1, 7)) call assert_equal({'row': winrow + 2, - \ 'col': wincol + 1, - \ 'curscol': wincol + 1, - \ 'endcol': wincol + 1}, screenpos(winid, 2, 22)) + \ 'col': wincol + 1, + \ 'curscol': wincol + 1, + \ 'endcol': wincol + 1}, screenpos(winid, 2, 22)) setlocal number call assert_equal({'row': winrow + 3, - \ 'col': wincol + 9, - \ 'curscol': wincol + 9, - \ 'endcol': wincol + 9}, screenpos(winid, 2, 22)) + \ 'col': wincol + 9, + \ 'curscol': wincol + 9, + \ 'endcol': wincol + 9}, screenpos(winid, 2, 22)) + + let wininfo = getwininfo(winid)[0] + call setline(3, ['x']->repeat(wininfo.height)) + call setline(line('$') + 1, 'x'->repeat(wininfo.width * 3)) + setlocal nonumber display=lastline so=0 + exe "normal G\<C-Y>\<C-Y>" + redraw + call assert_equal({'row': winrow + wininfo.height - 1, + \ 'col': wincol + 7, + \ 'curscol': wincol + 7, + \ 'endcol': wincol + 7}, winid->screenpos(line('$'), 8)) + call assert_equal({'row': 0, 'col': 0, 'curscol': 0, 'endcol': 0}, + \ winid->screenpos(line('$'), 22)) + close call assert_equal({}, screenpos(999, 1, 1)) + bwipe! + set display& - call assert_equal({'col': 1, 'row': 1, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1)) + call assert_equal(#{col: 1, row: 1, endcol: 1, curscol: 1}, screenpos(win_getid(), 1, 1)) " nmenu WinBar.TEST : setlocal winbar=TEST - call assert_equal({'col': 1, 'row': 2, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1)) + call assert_equal(#{col: 1, row: 2, endcol: 1, curscol: 1}, screenpos(win_getid(), 1, 1)) " nunmenu WinBar.TEST setlocal winbar& endfunc +func Test_screenpos_fold() + CheckFeature folding + + enew! + call setline(1, range(10)) + 3,5fold + redraw + call assert_equal(2, screenpos(1, 2, 1).row) + call assert_equal(#{col: 1, row: 3, endcol: 1, curscol: 1}, screenpos(1, 3, 1)) + call assert_equal(#{col: 1, row: 3, endcol: 1, curscol: 1}, screenpos(1, 4, 1)) + call assert_equal(#{col: 1, row: 3, endcol: 1, curscol: 1}, screenpos(1, 5, 1)) + setlocal number + call assert_equal(#{col: 5, row: 3, endcol: 5, curscol: 5}, screenpos(1, 3, 1)) + call assert_equal(#{col: 5, row: 3, endcol: 5, curscol: 5}, screenpos(1, 4, 1)) + call assert_equal(#{col: 5, row: 3, endcol: 5, curscol: 5}, screenpos(1, 5, 1)) + call assert_equal(4, screenpos(1, 6, 1).row) + bwipe! +endfunc + +func Test_screenpos_diff() + CheckFeature diff + + enew! + call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']) + vnew + call setline(1, ['a', 'b', 'c', 'g', 'h', 'i']) + windo diffthis + wincmd w + call assert_equal(#{col: 3, row: 7, endcol: 3, curscol: 3}, screenpos(0, 4, 1)) + + windo diffoff + bwipe! + bwipe! +endfunc + func Test_screenpos_number() rightbelow new rightbelow 73vsplit @@ -121,6 +175,9 @@ func Test_screenpos_number() let pos = screenpos(winid, 1, 66) call assert_equal(winrow, pos.row) call assert_equal(wincol + 66 + 3, pos.col) + + call assert_fails('echo screenpos(0, 2, 1)', 'E966:') + close bwipe! endfunc @@ -241,8 +298,9 @@ endfunc " Test for the charcol() function func Test_charcol() - call assert_fails('call charcol({})', 'E731:') - call assert_equal(0, charcol(0)) + call assert_fails('call charcol({})', 'E1222:') + call assert_fails('call charcol(".", [])', 'E1210:') + call assert_fails('call charcol(0)', 'E1222:') new call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) @@ -298,6 +356,25 @@ func Test_charcol() call assert_equal([1, 10, 2, 10, 7], g:InsertCurrentCol) iunmap <F3> + " Test for getting the column number in another window. + let winid = win_getid() + new + call win_execute(winid, 'normal 1G') + call assert_equal(1, charcol('.', winid)) + call assert_equal(1, charcol('$', winid)) + call win_execute(winid, 'normal 2G6l') + call assert_equal(7, charcol('.', winid)) + call assert_equal(10, charcol('$', winid)) + + " calling from another tab page also works + tabnew + call assert_equal(7, charcol('.', winid)) + call assert_equal(10, charcol('$', winid)) + tabclose + + " unknown window ID + call assert_equal(0, charcol('.', 10001)) + %bw! endfunc diff --git a/src/nvim/testdir/test_cursorline.vim b/src/nvim/testdir/test_cursorline.vim index 47646125db..70f39a8601 100644 --- a/src/nvim/testdir/test_cursorline.vim +++ b/src/nvim/testdir/test_cursorline.vim @@ -319,7 +319,7 @@ func Test_cursorline_cursorbind_horizontal_scroll() let lines =<< trim END call setline(1, 'aa bb cc dd ee ff gg hh ii jj kk ll mm' .. - \ ' nn oo pp qq rr ss tt uu vv ww xx yy zz') + \ ' nn oo pp qq rr ss tt uu vv ww xx yy zz') set nowrap " The following makes the cursor apparent on the screen dump set sidescroll=1 cursorcolumn diff --git a/src/nvim/testdir/test_debugger.vim b/src/nvim/testdir/test_debugger.vim index 2be94409ca..f5177c8fb2 100644 --- a/src/nvim/testdir/test_debugger.vim +++ b/src/nvim/testdir/test_debugger.vim @@ -36,7 +36,7 @@ endfunc " If the expected output argument is supplied, then check for it. func RunDbgCmd(buf, cmd, ...) call term_sendkeys(a:buf, a:cmd . "\r") - call term_wait(a:buf) + call term_wait(a:buf, 20) if a:0 != 0 let options = #{match: 'equal'} @@ -1145,7 +1145,6 @@ func Test_breakpt_endif_intr() let caught_intr = 0 debuggreedy call feedkeys(":call F()\<CR>quit\<CR>", "xt") - call F() catch /^Vim:Interrupt$/ call assert_match('\.F, line 4', v:throwpoint) let caught_intr = 1 @@ -1176,7 +1175,6 @@ func Test_breakpt_else_intr() let caught_intr = 0 debuggreedy call feedkeys(":call F()\<CR>quit\<CR>", "xt") - call F() catch /^Vim:Interrupt$/ call assert_match('\.F, line 4', v:throwpoint) let caught_intr = 1 @@ -1205,7 +1203,6 @@ func Test_breakpt_endwhile_intr() let caught_intr = 0 debuggreedy call feedkeys(":call F()\<CR>quit\<CR>", "xt") - call F() catch /^Vim:Interrupt$/ call assert_match('\.F, line 4', v:throwpoint) let caught_intr = 1 @@ -1217,38 +1214,24 @@ func Test_breakpt_endwhile_intr() delfunc F endfunc -" Test for setting a breakpoint on an :endtry where an exception is pending to -" be processed and then quit the script. This should generate an interrupt and -" the thrown exception should be ignored. -func Test_breakpt_endtry_intr() - func F() - try - let g:Xpath ..= 'a' - throw "abc" - endtry - invalid_command +" Test for setting a breakpoint on a script local function +func Test_breakpt_scriptlocal_func() + let g:Xpath = '' + func s:G() + let g:Xpath ..= 'a' endfunc - let g:Xpath = '' - breakadd func 4 F - try - let caught_intr = 0 - let caught_abc = 0 - debuggreedy - call feedkeys(":call F()\<CR>quit\<CR>", "xt") - call F() - catch /abc/ - let caught_abc = 1 - catch /^Vim:Interrupt$/ - call assert_match('\.F, line 4', v:throwpoint) - let caught_intr = 1 - endtry + let funcname = expand("<SID>") .. "G" + exe "breakadd func 1 " .. funcname + debuggreedy + redir => output + call feedkeys(":call " .. funcname .. "()\<CR>c\<CR>", "xt") + redir END 0debuggreedy - call assert_equal(1, caught_intr) - call assert_equal(0, caught_abc) + call assert_match('Breakpoint in "' .. funcname .. '" line 1', output) call assert_equal('a', g:Xpath) breakdel * - delfunc F + exe "delfunc " .. funcname endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_delete.vim b/src/nvim/testdir/test_delete.vim index b23a3bd025..6b49f153c6 100644 --- a/src/nvim/testdir/test_delete.vim +++ b/src/nvim/testdir/test_delete.vim @@ -1,5 +1,7 @@ " Test for delete(). +source check.vim + func Test_file_delete() split Xfile call setline(1, ['a', 'b']) @@ -41,9 +43,7 @@ func Test_recursive_delete() endfunc func Test_symlink_delete() - if !has('unix') - return - endif + CheckUnix split Xfile call setline(1, ['a', 'b']) wq @@ -56,9 +56,7 @@ func Test_symlink_delete() endfunc func Test_symlink_dir_delete() - if !has('unix') - return - endif + CheckUnix call mkdir('Xdir1') silent !ln -s Xdir1 Xlink call assert_true(isdirectory('Xdir1')) @@ -70,9 +68,7 @@ func Test_symlink_dir_delete() endfunc func Test_symlink_recursive_delete() - if !has('unix') - return - endif + CheckUnix call mkdir('Xdir3') call mkdir('Xdir3/subdir') call mkdir('Xdir4') diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 1cb71664bd..0049398776 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -1,4 +1,5 @@ " Tests for diff mode + source shared.vim source screendump.vim source check.vim @@ -243,6 +244,36 @@ func Test_diffput_two() bwipe! b endfunc +" Test for :diffget/:diffput with a range that is inside a diff chunk +func Test_diffget_diffput_range() + call setline(1, range(1, 10)) + new + call setline(1, range(11, 20)) + windo diffthis + 3,5diffget + call assert_equal(['13', '14', '15'], getline(3, 5)) + call setline(1, range(1, 10)) + 4,8diffput + wincmd p + call assert_equal(['13', '4', '5', '6', '7', '8', '19'], getline(3, 9)) + %bw! +endfunc + +" Test for :diffget/:diffput with an empty buffer and a non-empty buffer +func Test_diffget_diffput_empty_buffer() + %d _ + new + call setline(1, 'one') + windo diffthis + diffget + call assert_equal(['one'], getline(1, '$')) + %d _ + diffput + wincmd p + call assert_equal([''], getline(1, '$')) + %bw! +endfunc + " :diffput and :diffget completes names of buffers which " are in diff mode and which are different then current buffer. " No completion when the current window is not in diff mode. @@ -621,9 +652,7 @@ func Test_diff_move_to() endfunc func Test_diffexpr() - if !executable('diff') - return - endif + CheckExecutable diff func DiffExpr() " Prepend some text to check diff type detection @@ -647,17 +676,31 @@ func Test_diffexpr() call assert_equal(normattr, screenattr(1, 1)) call assert_equal(normattr, screenattr(2, 1)) call assert_notequal(normattr, screenattr(3, 1)) + diffoff! + " Try using an non-existing function for 'diffexpr'. + set diffexpr=NewDiffFunc() + call assert_fails('windo diffthis', ['E117:', 'E97:']) diffoff! + + " Using a script-local function + func s:NewDiffExpr() + endfunc + set diffexpr=s:NewDiffExpr() + call assert_equal(expand('<SID>') .. 'NewDiffExpr()', &diffexpr) + set diffexpr=<SID>NewDiffExpr() + call assert_equal(expand('<SID>') .. 'NewDiffExpr()', &diffexpr) + %bwipe! set diffexpr& diffopt& + delfunc DiffExpr + delfunc s:NewDiffExpr endfunc func Test_diffpatch() " The patch program on MS-Windows may fail or hang. - if !executable('patch') || !has('unix') - return - endif + CheckExecutable patch + CheckUnix new insert *************** @@ -1198,6 +1241,42 @@ func Test_diff_maintains_change_mark() delfunc DiffMaintainsChangeMark endfunc +" Test for 'patchexpr' +func Test_patchexpr() + let g:patch_args = [] + func TPatch() + call add(g:patch_args, readfile(v:fname_in)) + call add(g:patch_args, readfile(v:fname_diff)) + call writefile(['output file'], v:fname_out) + endfunc + set patchexpr=TPatch() + + call writefile(['input file'], 'Xinput') + call writefile(['diff file'], 'Xdiff') + %bwipe! + edit Xinput + diffpatch Xdiff + call assert_equal('output file', getline(1)) + call assert_equal('Xinput.new', bufname()) + call assert_equal(2, winnr('$')) + call assert_true(&diff) + + " Using a script-local function + func s:NewPatchExpr() + endfunc + set patchexpr=s:NewPatchExpr() + call assert_equal(expand('<SID>') .. 'NewPatchExpr()', &patchexpr) + set patchexpr=<SID>NewPatchExpr() + call assert_equal(expand('<SID>') .. 'NewPatchExpr()', &patchexpr) + + call delete('Xinput') + call delete('Xdiff') + set patchexpr& + delfunc TPatch + delfunc s:NewPatchExpr + %bwipe! +endfunc + func Test_diff_rnu() CheckScreendump @@ -1291,6 +1370,89 @@ func Test_diff_filler_cursorcolumn() call delete('Xtest_diff_cuc') endfunc +" Test for adding/removing lines inside diff chunks, between diff chunks +" and before diff chunks +func Test_diff_modify_chunks() + enew! + let w2_id = win_getid() + call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']) + new + let w1_id = win_getid() + call setline(1, ['a', '2', '3', 'd', 'e', 'f', '7', '8', 'i']) + windo diffthis + + " remove a line between two diff chunks and create a new diff chunk + call win_gotoid(w2_id) + 5d + call win_gotoid(w1_id) + call diff_hlID(5, 1)->synIDattr('name')->assert_equal('DiffAdd') + + " add a line between two diff chunks + call win_gotoid(w2_id) + normal! 4Goe + call win_gotoid(w1_id) + call diff_hlID(4, 1)->synIDattr('name')->assert_equal('') + call diff_hlID(5, 1)->synIDattr('name')->assert_equal('') + + " remove all the lines in a diff chunk. + call win_gotoid(w2_id) + 7,8d + call win_gotoid(w1_id) + let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')}) + call assert_equal(['', 'DiffText', 'DiffText', '', '', '', 'DiffAdd', + \ 'DiffAdd', ''], hl) + + " remove lines from one diff chunk to just before the next diff chunk + call win_gotoid(w2_id) + call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']) + 2,6d + call win_gotoid(w1_id) + let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')}) + call assert_equal(['', 'DiffText', 'DiffText', 'DiffAdd', 'DiffAdd', + \ 'DiffAdd', 'DiffAdd', 'DiffAdd', ''], hl) + + " remove lines just before the top of a diff chunk + call win_gotoid(w2_id) + call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']) + 5,6d + call win_gotoid(w1_id) + let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')}) + call assert_equal(['', 'DiffText', 'DiffText', '', 'DiffText', 'DiffText', + \ 'DiffAdd', 'DiffAdd', ''], hl) + + " remove line after the end of a diff chunk + call win_gotoid(w2_id) + call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']) + 4d + call win_gotoid(w1_id) + let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')}) + call assert_equal(['', 'DiffText', 'DiffText', 'DiffAdd', '', '', 'DiffText', + \ 'DiffText', ''], hl) + + " remove lines starting from the end of one diff chunk and ending inside + " another diff chunk + call win_gotoid(w2_id) + call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']) + 4,7d + call win_gotoid(w1_id) + let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')}) + call assert_equal(['', 'DiffText', 'DiffText', 'DiffText', 'DiffAdd', + \ 'DiffAdd', 'DiffAdd', 'DiffAdd', ''], hl) + + " removing the only remaining diff chunk should make the files equal + call win_gotoid(w2_id) + call setline(1, ['a', '2', '3', 'x', 'd', 'e', 'f', 'x', '7', '8', 'i']) + 8d + let hl = range(1, 10)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')}) + call assert_equal(['', '', '', 'DiffAdd', '', '', '', '', '', ''], hl) + call win_gotoid(w2_id) + 4d + call win_gotoid(w1_id) + let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')}) + call assert_equal(['', '', '', '', '', '', '', '', ''], hl) + + %bw! +endfunc func Test_diff_binary() CheckScreendump diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim index 13796449ab..b642f39c9f 100644 --- a/src/nvim/testdir/test_display.vim +++ b/src/nvim/testdir/test_display.vim @@ -195,8 +195,6 @@ func Test_edit_long_file_name() call VerifyScreenDump(buf, 'Test_long_file_name_1', {}) - call term_sendkeys(buf, ":q\<cr>") - " clean up call StopVimInTerminal(buf) call delete(longName) @@ -228,7 +226,6 @@ endfunc " Test for scrolling that modifies buffer during visual block func Test_visual_block_scroll() - " See test/functional/legacy/visual_mode_spec.lua CheckScreendump let lines =<< trim END @@ -239,7 +236,7 @@ func Test_visual_block_scroll() END let filename = 'Xvisualblockmodifiedscroll' - call writefile(lines, filename) + call writefile(lines, filename, 'D') let buf = RunVimInTerminal('-S '.filename, #{rows: 7}) call term_sendkeys(buf, "V\<C-D>\<C-D>") @@ -247,11 +244,40 @@ func Test_visual_block_scroll() call VerifyScreenDump(buf, 'Test_display_visual_block_scroll', {}) call StopVimInTerminal(buf) - call delete(filename) +endfunc + +" Test for clearing paren highlight when switching buffers +func Test_matchparen_clear_highlight() + CheckScreendump + + let lines =<< trim END + source $VIMRUNTIME/plugin/matchparen.vim + set hidden + call setline(1, ['()']) + normal 0 + + func OtherBuffer() + enew + exe "normal iaa\<Esc>0" + endfunc + END + call writefile(lines, 'XMatchparenClear', 'D') + let buf = RunVimInTerminal('-S XMatchparenClear', #{rows: 5}) + call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_1', {}) + + call term_sendkeys(buf, ":call OtherBuffer()\<CR>:\<Esc>") + call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_2', {}) + + call term_sendkeys(buf, "\<C-^>:\<Esc>") + call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_1', {}) + + call term_sendkeys(buf, "\<C-^>:\<Esc>") + call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_2', {}) + + call StopVimInTerminal(buf) endfunc func Test_display_scroll_at_topline() - " See test/functional/legacy/display_spec.lua CheckScreendump let buf = RunVimInTerminal('', #{cols: 20}) diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index 9783ed19a7..fd54f77ccb 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -330,6 +330,16 @@ func Test_edit_11_indentexpr() set cinkeys&vim indentkeys&vim set nocindent indentexpr= delfu Do_Indent + + " Using a script-local function + func s:NewIndentExpr() + endfunc + set indentexpr=s:NewIndentExpr() + call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &indentexpr) + set indentexpr=<SID>NewIndentExpr() + call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &indentexpr) + set indentexpr& + bw! endfunc @@ -1433,9 +1443,7 @@ endfunc func Test_edit_rightleft() " Cursor in rightleft mode moves differently - if !exists("+rightleft") - return - endif + CheckFeature rightleft call NewWindow(10, 20) call setline(1, ['abc', 'def', 'ghi']) call cursor(1, 2) @@ -1480,6 +1488,13 @@ func Test_edit_rightleft() \" ihg", \" ~"] call assert_equal(join(expect, "\n"), join(lines, "\n")) + %d _ + " call test_override('redraw_flag', 1) + " call test_override('char_avail', 1) + call feedkeys("a\<C-V>x41", "xt") + redraw! + call assert_equal(repeat(' ', 19) .. 'A', Screenline(1)) + " call test_override('ALL', 0) set norightleft bw! endfunc @@ -1724,40 +1739,6 @@ func Test_edit_illegal_filename() close! endfunc -" Test for inserting text in a line with only spaces ('H' flag in 'cpoptions') -func Test_edit_cpo_H() - throw 'Skipped: Nvim does not support cpoptions flag "H"' - new - call setline(1, ' ') - normal! Ia - call assert_equal(' a', getline(1)) - set cpo+=H - call setline(1, ' ') - normal! Ia - call assert_equal(' a ', getline(1)) - set cpo-=H - close! -endfunc - -" Test for inserting tab in virtual replace mode ('L' flag in 'cpoptions') -func Test_edit_cpo_L() - new - call setline(1, 'abcdefghijklmnopqr') - exe "normal 0gR\<Tab>" - call assert_equal("\<Tab>ijklmnopqr", getline(1)) - set cpo+=L - set list - call setline(1, 'abcdefghijklmnopqr') - exe "normal 0gR\<Tab>" - call assert_equal("\<Tab>cdefghijklmnopqr", getline(1)) - set nolist - call setline(1, 'abcdefghijklmnopqr') - exe "normal 0gR\<Tab>" - call assert_equal("\<Tab>ijklmnopqr", getline(1)) - set cpo-=L - %bw! -endfunc - " Test for editing a directory func Test_edit_is_a_directory() CheckEnglish @@ -1865,7 +1846,8 @@ endfunc " Test for editing a file without read permission func Test_edit_file_no_read_perm() CheckUnix - CheckNotBSD + CheckNotRoot + call writefile(['one', 'two'], 'Xfile') call setfperm('Xfile', '-w-------') new @@ -1891,17 +1873,117 @@ func Test_edit_insertmode_ex_edit() 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 WaitForAssert({-> 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)) + call WaitForAssert({-> assert_notmatch('^-- INSERT --\s*$', term_getline(buf, 6))}) " clean up call StopVimInTerminal(buf) call delete('Xtest_edit_insertmode_ex_edit') endfunc +" Pressing escape in 'insertmode' should beep +" FIXME: Execute this later, when using valgrind it makes the next test +" Test_edit_insertmode_ex_edit() fail. +func Test_z_edit_insertmode_esc_beeps() + new + " set insertmode + " call assert_beeps("call feedkeys(\"one\<Esc>\", 'xt')") + set insertmode& + " unsupported "CTRL-G l" command should beep in insert mode. + call assert_beeps("normal i\<C-G>l") + bwipe! +endfunc + +" Test for 'hkmap' and 'hkmapp' +func Test_edit_hkmap() + CheckFeature rightleft + if has('win32') && !has('gui') + " Test fails on the MS-Windows terminal version + return + endif + new + + set revins hkmap + let str = 'abcdefghijklmnopqrstuvwxyz' + let str ..= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + let str ..= '`/'',.;' + call feedkeys('i' .. str, 'xt') + let expected = "óõú,.;" + let expected ..= "ZYXWVUTSRQPONMLKJIHGFEDCBA" + let expected ..= "æèñ'äåàãø/ôíîöêìçïéòë÷âáðù" + call assert_equal(expected, getline(1)) + + %d + set revins hkmap hkmapp + let str = 'abcdefghijklmnopqrstuvwxyz' + let str ..= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + call feedkeys('i' .. str, 'xt') + let expected = "õYXWVUTSRQóOïíLKJIHGFEDêBA" + let expected ..= "öòXùåèúæø'ôñðîì÷çéäâóǟãëáà" + call assert_equal(expected, getline(1)) + + set revins& hkmap& hkmapp& + close! +endfunc + +" Test for 'allowrevins' and using CTRL-_ in insert mode +func Test_edit_allowrevins() + CheckFeature rightleft + new + set allowrevins + call feedkeys("iABC\<C-_>DEF\<C-_>GHI", 'xt') + call assert_equal('ABCFEDGHI', getline(1)) + set allowrevins& + close! +endfunc + +" Test for inserting a register in insert mode using CTRL-R +func Test_edit_insert_reg() + throw 'Skipped: use test/functional/legacy/edit_spec.lua' + new + let g:Line = '' + func SaveFirstLine() + let g:Line = Screenline(1) + return 'r' + endfunc + inoremap <expr> <buffer> <F2> SaveFirstLine() + call test_override('redraw_flag', 1) + call test_override('char_avail', 1) + let @r = 'sample' + call feedkeys("a\<C-R>=SaveFirstLine()\<CR>", "xt") + call assert_equal('"', g:Line) + call test_override('ALL', 0) + close! +endfunc + +" When a character is inserted at the last position of the last line in a +" window, the window contents should be scrolled one line up. If the top line +" is part of a fold, then the entire fold should be scrolled up. +func Test_edit_lastline_scroll() + new + let h = winheight(0) + let lines = ['one', 'two', 'three'] + let lines += repeat(['vim'], h - 4) + call setline(1, lines) + call setline(h, repeat('x', winwidth(0) - 1)) + call feedkeys("GAx", 'xt') + redraw! + call assert_equal(h - 1, winline()) + call assert_equal(2, line('w0')) + + " scroll with a fold + 1,2fold + normal gg + call setline(h + 1, repeat('x', winwidth(0) - 1)) + call feedkeys("GAx", 'xt') + redraw! + call assert_equal(h - 1, winline()) + call assert_equal(3, line('w0')) + + close! +endfunc + func Test_edit_browse() " in the GUI this opens a file picker, we only test the terminal behavior CheckNotGui diff --git a/src/nvim/testdir/test_escaped_glob.vim b/src/nvim/testdir/test_escaped_glob.vim index 1a4fd8bdab..9f53c76a2c 100644 --- a/src/nvim/testdir/test_escaped_glob.vim +++ b/src/nvim/testdir/test_escaped_glob.vim @@ -1,7 +1,7 @@ " Test whether glob()/globpath() return correct results with certain escaped " characters. -function SetUp() +func SetUp() " consistent sorting of file names set nofileignorecase endfunction diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim index dc110af356..46482c34a1 100644 --- a/src/nvim/testdir/test_eval_stuff.vim +++ b/src/nvim/testdir/test_eval_stuff.vim @@ -20,13 +20,8 @@ func Test_nocatch_restore_silent_emsg() throw 1 catch endtry - echoerr 'wrong' - let c1 = nr2char(screenchar(&lines, 1)) - let c2 = nr2char(screenchar(&lines, 2)) - let c3 = nr2char(screenchar(&lines, 3)) - let c4 = nr2char(screenchar(&lines, 4)) - let c5 = nr2char(screenchar(&lines, 5)) - call assert_equal('wrong', c1 . c2 . c3 . c4 . c5) + echoerr 'wrong again' + call assert_equal('wrong again', ScreenLine(&lines)) endfunc func Test_mkdir_p() @@ -120,6 +115,13 @@ func Test_readfile_binary() call delete('XReadfile_bin') endfunc +func Test_readfile_binary_empty() + call writefile([], 'Xempty-file') + " This used to compare uninitialized memory in Vim <= 8.2.4065 + call assert_equal([''], readfile('Xempty-file', 'b')) + call delete('Xempty-file') +endfunc + func Test_readfile_bom() call writefile(["\ufeffFOO", "FOO\ufeffBAR"], 'XReadfile_bom') call assert_equal(['FOO', 'FOOBAR'], readfile('XReadfile_bom')) @@ -360,6 +362,11 @@ func Test_curly_assignment() unlet g:gvar endfunc +func Test_deep_recursion() + " this was running out of stack + call assert_fails("exe 'if ' .. repeat('(', 1002)", 'E1169: Expression too recursive: ((') +endfunc + " K_SPECIAL in the modified character used be escaped, which causes " double-escaping with feedkeys() or as the return value of an <expr> mapping, " and doesn't match what getchar() returns, diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim index 2f734cba26..93100732ed 100644 --- a/src/nvim/testdir/test_ex_mode.vim +++ b/src/nvim/testdir/test_ex_mode.vim @@ -97,7 +97,6 @@ func Test_Ex_substitute() call term_sendkeys(buf, ":vi\<CR>") call WaitForAssert({-> assert_match('foo bar', term_getline(buf, 1))}, 1000) - call term_sendkeys(buf, ":q!\n") call StopVimInTerminal(buf) endfunc diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim index 7692d4fc55..44bed890f5 100644 --- a/src/nvim/testdir/test_excmd.vim +++ b/src/nvim/testdir/test_excmd.vim @@ -78,6 +78,14 @@ func Test_file_cmd() call assert_fails('3file', 'E474:') call assert_fails('0,0file', 'E474:') call assert_fails('0file abc', 'E474:') + if !has('win32') + " Change the name of the buffer to the same name + new Xfile1 + file Xfile1 + call assert_equal('Xfile1', @%) + call assert_equal('Xfile1', @#) + bw! + endif endfunc " Test for the :drop command @@ -476,20 +484,23 @@ func Test_winsize_cmd() endfunc " Test for the :redir command +" NOTE: if you run tests as root this will fail. Don't run tests as root! func Test_redir_cmd() call assert_fails('redir @@', 'E475:') call assert_fails('redir abc', 'E475:') + call assert_fails('redir => 1abc', 'E474:') + call assert_fails('redir => a b', 'E488:') + call assert_fails('redir => abc[1]', 'E121:') + let b = 0zFF + call assert_fails('redir =>> b', 'E734:') + unlet b + if has('unix') + " Redirecting to a directory name call mkdir('Xdir') call assert_fails('redir > Xdir', 'E17:') call delete('Xdir', 'd') endif - if !has('bsd') - call writefile([], 'Xfile') - call setfperm('Xfile', 'r--r--r--') - call assert_fails('redir! > Xfile', 'E190:') - call delete('Xfile') - endif " Test for redirecting to a register redir @q> | echon 'clean ' | redir END @@ -502,6 +513,16 @@ func Test_redir_cmd() call assert_equal('blue sky', color) endfunc +func Test_redir_cmd_readonly() + CheckNotRoot + + " Redirecting to a read-only file + call writefile([], 'Xfile') + call setfperm('Xfile', 'r--r--r--') + call assert_fails('redir! > Xfile', 'E190:') + call delete('Xfile') +endfunc + " Test for the :filetype command func Test_filetype_cmd() call assert_fails('filetype abc', 'E475:') @@ -674,6 +695,12 @@ func Test_sandbox() sandbox call Sandbox_tests() endfunc +func Test_command_not_implemented_E319() + if !has('mzscheme') + call assert_fails('mzscheme', 'E319:') + endif +endfunc + func Test_not_break_expression_register() call setreg('=', '1+1') if 0 diff --git a/src/nvim/testdir/test_exists.vim b/src/nvim/testdir/test_exists.vim index 471c77853d..62c66192ef 100644 --- a/src/nvim/testdir/test_exists.vim +++ b/src/nvim/testdir/test_exists.vim @@ -68,6 +68,10 @@ func Test_exists() " Existing environment variable let $EDITOR_NAME = 'Vim Editor' call assert_equal(1, exists('$EDITOR_NAME')) + if has('unix') + " ${name} environment variables are supported only on Unix-like systems + call assert_equal(1, exists('${VIM}')) + endif " Non-existing environment variable call assert_equal(0, exists('$NON_ENV_VAR')) @@ -323,3 +327,5 @@ endfunc func Test_exists_funcarg() call FuncArg_Tests("arg1", "arg2") endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_exit.vim b/src/nvim/testdir/test_exit.vim index 37be293950..6dbfb7047c 100644 --- a/src/nvim/testdir/test_exit.vim +++ b/src/nvim/testdir/test_exit.vim @@ -117,6 +117,7 @@ func Test_exit_error_reading_input() CheckNotMSWindows " The early exit causes memory not to be freed somehow CheckNotAsan + CheckNotValgrind call writefile([":au VimLeave * call writefile(['l = ' .. v:exiting], 'Xtestout')", ":tabnew", "q:"], 'Xscript', 'b') diff --git a/src/nvim/testdir/test_expand.vim b/src/nvim/testdir/test_expand.vim index aa131a49ff..4f5bb67d21 100644 --- a/src/nvim/testdir/test_expand.vim +++ b/src/nvim/testdir/test_expand.vim @@ -90,14 +90,26 @@ func Test_expandcmd() " Test for expression expansion `= let $FOO= "blue" call assert_equal("blue sky", expandcmd("`=$FOO .. ' sky'`")) + let x = expandcmd("`=axbycz`") + call assert_equal('`=axbycz`', x) + call assert_fails('let x = expandcmd("`=axbycz`", #{errmsg: 1})', 'E121:') + let x = expandcmd("`=axbycz`", #{abc: []}) + call assert_equal('`=axbycz`', x) " Test for env variable with spaces let $FOO= "foo bar baz" call assert_equal("e foo bar baz", expandcmd("e $FOO")) - if has('unix') - " test for using the shell to expand a command argument - call assert_equal('{1..4}', expandcmd('{1..4}')) + if has('unix') && executable('bash') + " test for using the shell to expand a command argument. + " only bash supports the {..} syntax + set shell=bash + let x = expandcmd('{1..4}') + call assert_equal('{1..4}', x) + call assert_fails("let x = expandcmd('{1..4}', #{errmsg: v:true})", 'E77:') + let x = expandcmd('{1..4}', #{error: v:true}) + call assert_equal('{1..4}', x) + set shell& endif unlet $FOO diff --git a/src/nvim/testdir/test_expand_func.vim b/src/nvim/testdir/test_expand_func.vim index 80bfdb8553..454d76f0aa 100644 --- a/src/nvim/testdir/test_expand_func.vim +++ b/src/nvim/testdir/test_expand_func.vim @@ -139,6 +139,7 @@ func Test_expand_wildignore() call assert_equal('test_expand_func.vim', expand('test_expand_func.vim', 1)) call assert_equal(['test_expand_func.vim'], \ expand('test_expand_func.vim', 1, 1)) + call assert_fails("call expand('*', [])", 'E745:') set wildignore& endfunc diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index 15622cd6fe..47f7f5eb0e 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -1,5 +1,7 @@ " Tests for expressions. +source check.vim + func Test_equal() let base = {} func base.method() @@ -44,6 +46,7 @@ func Test_dict() call assert_equal('zero', d[0]) call assert_true(has_key(d, '')) call assert_true(has_key(d, 'a')) + call assert_fails("let i = has_key([], 'a')", 'E715:') let d[''] = 'none' let d['a'] = 'aaa' @@ -62,6 +65,8 @@ func Test_strgetchar() call assert_equal(-1, strgetchar('axb', -1)) call assert_equal(-1, strgetchar('axb', 3)) call assert_equal(-1, strgetchar('', 0)) + call assert_fails("let c=strgetchar([], 1)", 'E730:') + call assert_fails("let c=strgetchar('axb', [])", 'E745:') endfunc func Test_strcharpart() @@ -76,22 +81,27 @@ func Test_strcharpart() call assert_equal('', strcharpart('axb', -2, 2)) call assert_equal('a', strcharpart('axb', -1, 2)) + + call assert_equal('edit', "editor"[-10:3]) +endfunc + +func Test_getreg_empty_list() + call assert_equal('', getreg('x')) + call assert_equal([], getreg('x', 1, 1)) + let x = getreg('x', 1, 1) + let y = x + call add(x, 'foo') + call assert_equal(['foo'], y) + call assert_fails('call getreg([])', 'E730:') endfunc func Test_loop_over_null_list() - let null_list = submatch(1, 1) + let null_list = v:_null_list for i in null_list call assert_report('should not get here') endfor endfunc -func Test_compare_null_dict() - call assert_fails('let x = v:_null_dict[10]') - call assert_equal({}, {}) - call assert_equal(v:_null_dict, v:_null_dict) - call assert_notequal({}, v:_null_dict) -endfunc - func Test_set_reg_null_list() call setreg('x', v:_null_list) endfunc @@ -478,49 +488,6 @@ function Test_max_min_errors() call assert_fails('call min(v:true)', 'min()') endfunc -func Test_substitute_expr() - let g:val = 'XXX' - call assert_equal('XXX', substitute('yyy', 'y*', '\=g:val', '')) - call assert_equal('XXX', substitute('yyy', 'y*', {-> g:val}, '')) - call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)', - \ '\=nr2char("0x" . submatch(1))', 'g')) - call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)', - \ {-> nr2char("0x" . submatch(1))}, 'g')) - - call assert_equal('231', substitute('123', '\(.\)\(.\)\(.\)', - \ {-> submatch(2) . submatch(3) . submatch(1)}, '')) - - func Recurse() - return substitute('yyy', 'y\(.\)y', {-> submatch(1)}, '') - endfunc - " recursive call works - call assert_equal('-y-x-', substitute('xxx', 'x\(.\)x', {-> '-' . Recurse() . '-' . submatch(1) . '-'}, '')) -endfunc - -func Test_invalid_submatch() - " This was causing invalid memory access in Vim-7.4.2232 and older - call assert_fails("call substitute('x', '.', {-> submatch(10)}, '')", 'E935:') -endfunc - -func Test_substitute_expr_arg() - call assert_equal('123456789-123456789=', substitute('123456789', - \ '\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)', - \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) - - call assert_equal('123456-123456=789', substitute('123456789', - \ '\(.\)\(.\)\(.\)\(a*\)\(n*\)\(.\)\(.\)\(.\)\(x*\)', - \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) - - call assert_equal('123456789-123456789x=', substitute('123456789', - \ '\(.\)\(.\)\(.*\)', - \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . 'x' . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) - - call assert_fails("call substitute('xxx', '.', {m -> string(add(m, 'x'))}, '')", 'E742:') - call assert_fails("call substitute('xxx', '.', {m -> string(insert(m, 'x'))}, '')", 'E742:') - call assert_fails("call substitute('xxx', '.', {m -> string(extend(m, ['x']))}, '')", 'E742:') - call assert_fails("call substitute('xxx', '.', {m -> string(remove(m, 1))}, '')", 'E742:') -endfunc - func Test_function_with_funcref() let s:f = function('type') let s:fref = function(s:f) @@ -529,6 +496,14 @@ func Test_function_with_funcref() call assert_fails("call function('foo()')", 'E475:') call assert_fails("call function('foo()')", 'foo()') + call assert_fails("function('')", 'E129:') + + let Len = {s -> strlen(s)} + call assert_equal(6, Len('foobar')) + let name = string(Len) + " can evaluate "function('<lambda>99')" + call execute('let Ref = ' .. name) + call assert_equal(4, Ref('text')) endfunc func Test_funcref() @@ -550,6 +525,21 @@ func Test_funcref() call assert_fails('echo function("min") =~ function("min")', 'E694:') endfunc +" Test for calling function() and funcref() outside of a Vim script context. +func Test_function_outside_script() + let cleanup =<< trim END + call writefile([execute('messages')], 'Xtest.out') + qall + END + call writefile(cleanup, 'Xverify.vim') + call RunVim([], [], "-c \"echo function('s:abc')\" -S Xverify.vim") + call assert_match('E81: Using <SID> not in a', readfile('Xtest.out')[0]) + call RunVim([], [], "-c \"echo funcref('s:abc')\" -S Xverify.vim") + call assert_match('E81: Using <SID> not in a', readfile('Xtest.out')[0]) + call delete('Xtest.out') + call delete('Xverify.vim') +endfunc + func Test_setmatches() hi def link 1 Comment hi def link 2 PreProc @@ -561,6 +551,7 @@ func Test_setmatches() endif eval set->setmatches() call assert_equal(exp, getmatches()) + call assert_fails('let m = setmatches([], [])', 'E745:') endfunc func Test_empty_concatenate() @@ -587,3 +578,99 @@ func Test_eval_after_if() if 0 | eval SetVal('a') | endif | call SetVal('b') call assert_equal('b', s:val) endfunc + +func Test_divide_by_zero() + " only tests that this doesn't crash, the result is not important + echo 0 / 0 + echo 0 / 0 / -1 +endfunc + +" Test for command-line completion of expressions +func Test_expr_completion() + CheckFeature cmdline_compl + for cmd in [ + \ 'let a = ', + \ 'const a = ', + \ 'if', + \ 'elseif', + \ 'while', + \ 'for', + \ 'echo', + \ 'echon', + \ 'execute', + \ 'echomsg', + \ 'echoerr', + \ 'call', + \ 'return', + \ 'cexpr', + \ 'caddexpr', + \ 'cgetexpr', + \ 'lexpr', + \ 'laddexpr', + \ 'lgetexpr'] + call feedkeys(":" . cmd . " getl\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"' . cmd . ' getline(', getreg(':')) + endfor + + " completion for the expression register + call feedkeys(":\"\<C-R>=float2\t\"\<C-B>\"\<CR>", 'xt') + call assert_equal('"float2nr("', @=) + + " completion for window local variables + let w:wvar1 = 10 + let w:wvar2 = 10 + call feedkeys(":echo w:wvar\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"echo w:wvar1 w:wvar2', @:) + unlet w:wvar1 w:wvar2 + + " completion for tab local variables + let t:tvar1 = 10 + let t:tvar2 = 10 + call feedkeys(":echo t:tvar\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"echo t:tvar1 t:tvar2', @:) + unlet t:tvar1 t:tvar2 + + " completion for variables + let g:tvar1 = 1 + let g:tvar2 = 2 + call feedkeys(":let g:tv\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"let g:tvar1 g:tvar2', @:) + " completion for variables after a || + call feedkeys(":echo 1 || g:tv\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"echo 1 || g:tvar1 g:tvar2', @:) + + " completion for options + call feedkeys(":echo &compat\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"echo &compatible', @:) + call feedkeys(":echo 1 && &compat\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"echo 1 && &compatible', @:) + call feedkeys(":echo &g:equala\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"echo &g:equalalways', @:) + + " completion for string + call feedkeys(":echo \"Hello\\ World\"\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"echo \"Hello\\ World\"\<C-A>", @:) + call feedkeys(":echo 'Hello World'\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"echo 'Hello World'\<C-A>", @:) + + " completion for command after a | + call feedkeys(":echo 'Hello' | cwin\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"echo 'Hello' | cwindow", @:) + + " completion for environment variable + let $X_VIM_TEST_COMPLETE_ENV = 'foo' + call feedkeys(":let $X_VIM_TEST_COMPLETE_E\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_match('"let $X_VIM_TEST_COMPLETE_ENV', @:) + unlet $X_VIM_TEST_COMPLETE_ENV +endfunc + +" Test for errors in expression evaluation +func Test_expr_eval_error() + call assert_fails("let i = 'abc' . []", 'E730:') + call assert_fails("let l = [] + 10", 'E745:') + call assert_fails("let v = 10 + []", 'E745:') + call assert_fails("let v = 10 / []", 'E745:') + call assert_fails("let v = -{}", 'E728:') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_fileformat.vim b/src/nvim/testdir/test_fileformat.vim index 81127ea59a..8d727a68c4 100644 --- a/src/nvim/testdir/test_fileformat.vim +++ b/src/nvim/testdir/test_fileformat.vim @@ -31,6 +31,15 @@ func Test_fileformat_autocommand() bw! endfunc +func Test_fileformat_nomodifiable() + new + setlocal nomodifiable + + call assert_fails('set fileformat=latin1', 'E21:') + + bw +endfunc + " Convert the contents of a file into a literal string func s:file2str(fname) let b = readfile(a:fname, 'B') diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index d123d469a6..cddb1349f5 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -91,13 +91,14 @@ let s:filename_checks = { \ 'blueprint': ['file.blp'], \ 'bsdl': ['file.bsd', 'file.bsdl'], \ 'bst': ['file.bst'], - \ 'bzl': ['file.bazel', 'file.bzl', 'WORKSPACE'], + \ 'bzl': ['file.bazel', 'file.bzl', 'WORKSPACE', 'WORKSPACE.bzlmod'], \ 'bzr': ['bzr_log.any', 'bzr_log.file'], \ 'c': ['enlightenment/file.cfg', 'file.qc', 'file.c', 'some-enlightenment/file.cfg'], \ 'cabal': ['file.cabal'], \ 'cabalconfig': ['cabal.config'], \ 'cabalproject': ['cabal.project', 'cabal.project.local'], \ 'calendar': ['calendar', '/.calendar/file', '/share/calendar/any/calendar.file', '/share/calendar/calendar.file', 'any/share/calendar/any/calendar.file', 'any/share/calendar/calendar.file'], + \ 'capnp': ['file.capnp'], \ 'catalog': ['catalog', 'sgml.catalogfile', 'sgml.catalog', 'sgml.catalog-file'], \ 'cdl': ['file.cdl'], \ 'cdrdaoconf': ['/etc/cdrdao.conf', '/etc/defaults/cdrdao', '/etc/default/cdrdao', '.cdrdao', 'any/etc/cdrdao.conf', 'any/etc/default/cdrdao', 'any/etc/defaults/cdrdao'], @@ -122,10 +123,11 @@ let s:filename_checks = { \ 'conaryrecipe': ['file.recipe'], \ 'conf': ['auto.master'], \ 'config': ['configure.in', 'configure.ac', '/etc/hostname.file', 'any/etc/hostname.file'], - \ 'confini': ['/etc/pacman.conf', 'any/etc/pacman.conf', 'mpv.conf'], + \ 'confini': ['/etc/pacman.conf', 'any/etc/pacman.conf', 'mpv.conf', 'any/.aws/config', 'any/.aws/credentials'], \ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi', 'file.mkxl', 'file.mklx'], \ 'cook': ['file.cook'], \ 'cpp': ['file.cxx', 'file.c++', 'file.hh', 'file.hxx', 'file.hpp', 'file.ipp', 'file.moc', 'file.tcc', 'file.inl', 'file.tlh'], + \ 'cqlang': ['file.cql'], \ 'crm': ['file.crm'], \ 'crontab': ['crontab', 'crontab.file', '/etc/cron.d/file', 'any/etc/cron.d/file'], \ 'cs': ['file.cs', 'file.csx'], @@ -161,7 +163,7 @@ let s:filename_checks = { \ 'dnsmasq': ['/etc/dnsmasq.conf', '/etc/dnsmasq.d/file', 'any/etc/dnsmasq.conf', 'any/etc/dnsmasq.d/file'], \ 'dockerfile': ['Containerfile', 'Dockerfile', 'dockerfile', 'file.Dockerfile', 'file.dockerfile', 'Dockerfile.debian', 'Containerfile.something'], \ 'dosbatch': ['file.bat'], - \ 'dosini': ['.editorconfig', '/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5', 'php.ini-file', '/etc/yum.repos.d/file', 'any/etc/yum.conf', 'any/etc/yum.repos.d/file', 'file.wrap'], + \ 'dosini': ['/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5', 'php.ini-file', '/etc/yum.repos.d/file', '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'], @@ -173,6 +175,7 @@ let s:filename_checks = { \ 'dylanlid': ['file.lid'], \ 'ecd': ['file.ecd'], \ 'edif': ['file.edf', 'file.edif', 'file.edo'], + \ 'editorconfig': ['.editorconfig'], \ 'eelixir': ['file.eex', 'file.leex'], \ 'elinks': ['elinks.conf'], \ 'elixir': ['file.ex', 'file.exs', 'mix.lock'], @@ -203,6 +206,7 @@ let s:filename_checks = { \ 'fpcmake': ['file.fpc'], \ 'framescript': ['file.fsl'], \ 'freebasic': ['file.fb'], + \ 'fsh': ['file.fsh'], \ 'fsharp': ['file.fs', 'file.fsi', 'file.fsx'], \ 'fstab': ['fstab', 'mtab'], \ 'fusion': ['file.fusion'], @@ -229,6 +233,7 @@ let s:filename_checks = { \ 'gnuplot': ['file.gpi', '.gnuplot'], \ 'go': ['file.go'], \ 'gomod': ['go.mod'], + \ 'gosum': ['go.sum'], \ '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'], @@ -286,12 +291,13 @@ let s:filename_checks = { \ 'javascriptreact': ['file.jsx'], \ 'jess': ['file.clp'], \ 'jgraph': ['file.jgr'], + \ 'jq': ['file.jq'], \ 'jovial': ['file.jov', 'file.j73', 'file.jovial'], - \ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file'], - \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', '.babelrc', '.eslintrc', '.prettierrc', '.firebaserc', 'file.slnf'], + \ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file', 'org.eclipse.xyz.prefs'], + \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', '.prettierrc', '.firebaserc', 'file.slnf'], \ 'json5': ['file.json5'], - \ 'jsonc': ['file.jsonc'], - \ 'jsonnet': ['file.jsonnet', 'file.libjsonnet'], + \ 'jsonc': ['file.jsonc', '.babelrc', '.eslintrc', '.jsfmtrc', '.jshintrc', '.hintrc', '.swrc', 'jsconfig.json', 'tsconfig.json', 'tsconfig.test.json', 'tsconfig-test.json'], + \ 'jsonnet': ['file.jsonnet', 'file.libsonnet'], \ 'jsp': ['file.jsp'], \ 'julia': ['file.jl'], \ 'kconfig': ['Kconfig', 'Kconfig.debug', 'Kconfig.file'], @@ -349,6 +355,7 @@ let s:filename_checks = { \ 'maxima': ['file.demo', 'file.dmt', 'file.dm1', 'file.dm2', 'file.dm3', \ 'file.wxm', 'maxima-init.mac'], \ 'mel': ['file.mel'], + \ 'mermaid': ['file.mmd', 'file.mmdc', 'file.mermaid'], \ 'meson': ['meson.build', 'meson_options.txt'], \ 'messages': ['/log/auth', '/log/cron', '/log/daemon', '/log/debug', '/log/kern', '/log/lpr', '/log/mail', '/log/messages', '/log/news/news', '/log/syslog', '/log/user', \ '/log/auth.log', '/log/cron.log', '/log/daemon.log', '/log/debug.log', '/log/kern.log', '/log/lpr.log', '/log/mail.log', '/log/messages.log', '/log/news/news.log', '/log/syslog.log', '/log/user.log', @@ -366,7 +373,7 @@ let s:filename_checks = { \ 'mmp': ['file.mmp'], \ 'modconf': ['/etc/modules.conf', '/etc/modules', '/etc/conf.modules', '/etc/modprobe.file', 'any/etc/conf.modules', 'any/etc/modprobe.file', 'any/etc/modules', 'any/etc/modules.conf'], \ 'modula2': ['file.m2', 'file.mi'], - \ 'modula3': ['file.m3', 'file.mg', 'file.i3', 'file.ig'], + \ 'modula3': ['file.m3', 'file.mg', 'file.i3', 'file.ig', 'file.lm3'], \ 'monk': ['file.isc', 'file.monk', 'file.ssc', 'file.tsc'], \ 'moo': ['file.moo'], \ 'moonscript': ['file.moon'], @@ -394,6 +401,7 @@ let s:filename_checks = { \ 'nroff': ['file.tr', 'file.nr', 'file.roff', 'file.tmac', 'file.mom', 'tmac.file'], \ 'nsis': ['file.nsi', 'file.nsh'], \ 'obj': ['file.obj'], + \ 'obse': ['file.obl', 'file.obse', 'file.oblivion', 'file.obscript'], \ 'ocaml': ['file.ml', 'file.mli', 'file.mll', 'file.mly', '.ocamlinit', 'file.mlt', 'file.mlp', 'file.mlip', 'file.mli.cppo', 'file.ml.cppo'], \ 'occam': ['file.occ'], \ 'octave': ['octaverc', '.octaverc', 'octave.conf'], @@ -401,6 +409,7 @@ let s:filename_checks = { \ 'opam': ['opam', 'file.opam', 'file.opam.template'], \ 'openroad': ['file.or'], \ 'openscad': ['file.scad'], + \ 'openvpn': ['file.ovpn', '/etc/openvpn/client/client.conf', '/usr/share/openvpn/examples/server.conf'], \ 'opl': ['file.OPL', 'file.OPl', 'file.OpL', 'file.Opl', 'file.oPL', 'file.oPl', 'file.opL', 'file.opl'], \ 'ora': ['file.ora'], \ 'org': ['file.org', 'file.org_archive'], @@ -415,7 +424,7 @@ let s:filename_checks = { \ 'pdf': ['file.pdf'], \ 'perl': ['file.plx', 'file.al', 'file.psgi', 'gitolite.rc', '.gitolite.rc', 'example.gitolite.rc', '.latexmkrc', 'latexmkrc'], \ 'pf': ['pf.conf'], - \ 'pfmain': ['main.cf'], + \ 'pfmain': ['main.cf', 'main.cf.proto'], \ 'php': ['file.php', 'file.php9', 'file.phtml', 'file.ctp', 'file.phpt', 'file.theme'], \ 'pike': ['file.pike', 'file.pmod'], \ 'pilrc': ['file.rcp'], @@ -454,7 +463,7 @@ let s:filename_checks = { \ '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'], \ 'quarto': ['file.qmd'], - \ 'r': ['file.r'], + \ 'r': ['file.r', '.Rprofile', 'Rprofile', 'Rprofile.site'], \ 'radiance': ['file.rad', 'file.mat'], \ 'raku': ['file.pm6', 'file.p6', 'file.t6', 'file.pod6', 'file.raku', 'file.rakumod', 'file.rakudoc', 'file.rakutest'], \ 'raml': ['file.raml'], @@ -518,9 +527,11 @@ let s:filename_checks = { \ 'slrnrc': ['.slrnrc'], \ 'slrnsc': ['file.score'], \ 'sm': ['sendmail.cf'], + \ 'smali': ['file.smali'], \ 'smarty': ['file.tpl'], \ 'smcl': ['file.hlp', 'file.ihlp', 'file.smcl'], \ 'smith': ['file.smt', 'file.smith'], + \ 'smithy': ['file.smithy'], \ 'sml': ['file.sml'], \ 'snobol4': ['file.sno', 'file.spt'], \ 'solidity': ['file.sol'], @@ -570,6 +581,7 @@ let s:filename_checks = { \ 'texmf': ['texmf.cnf'], \ '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'], + \ 'thrift': ['file.thrift'], \ 'tidy': ['.tidyrc', 'tidyrc', 'tidy.conf'], \ 'tilde': ['file.t.html'], \ 'tla': ['file.tla'], @@ -611,6 +623,7 @@ let s:filename_checks = { \ 'verilogams': ['file.va', 'file.vams'], \ 'vgrindefs': ['vgrindefs'], \ 'vhdl': ['file.hdl', 'file.vhd', 'file.vhdl', 'file.vbe', 'file.vst', 'file.vhdl_123', 'file.vho', 'some.vhdl_1', 'some.vhdl_1-file'], + \ 'vhs': ['file.tape'], \ 'vim': ['file.vim', 'file.vba', '.exrc', '_exrc', 'some-vimrc', 'some-vimrc-file', 'vimrc', 'vimrc-file'], \ 'viminfo': ['.viminfo', '_viminfo'], \ 'vmasm': ['file.mar'], @@ -619,6 +632,7 @@ let s:filename_checks = { \ 'vroom': ['file.vroom'], \ 'vue': ['file.vue'], \ 'wast': ['file.wast', 'file.wat'], + \ 'wdl': ['file.wdl'], \ 'webmacro': ['file.wm'], \ 'wget': ['.wgetrc', 'wgetrc'], \ 'wget2': ['.wget2rc', 'wget2rc'], @@ -641,12 +655,13 @@ let s:filename_checks = { \ 'xsd': ['file.xsd'], \ 'xslt': ['file.xsl', 'file.xslt'], \ 'yacc': ['file.yy', 'file.yxx', 'file.y++'], - \ 'yaml': ['file.yaml', 'file.yml'], + \ 'yaml': ['file.yaml', 'file.yml', '.clang-format', '.clang-tidy'], \ 'yang': ['file.yang'], \ 'z8a': ['file.z8a'], \ 'zig': ['file.zig'], \ 'zimbu': ['file.zu'], \ 'zimbutempl': ['file.zut'], + \ 'zir': ['file.zir'], \ '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'], \ \ 'help': [$VIMRUNTIME . '/doc/help.txt'], @@ -700,6 +715,13 @@ let s:script_checks = { \ ['__libc_start_main and something']], \ 'clojure': [['#!/path/clojure']], \ 'scala': [['#!/path/scala']], + \ 'sh': [['#!/path/sh'], + \ ['#!/path/bash'], + \ ['#!/path/bash2'], + \ ['#!/path/dash'], + \ ['#!/path/ksh'], + \ ['#!/path/ksh93']], + \ 'csh': [['#!/path/csh']], \ 'tcsh': [['#!/path/tcsh']], \ 'zsh': [['#!/path/zsh']], \ 'tcl': [['#!/path/tclsh'], @@ -1541,13 +1563,6 @@ endfunc func Test_sc_file() filetype on - " SC file methods 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 @@ -1667,17 +1682,45 @@ endfunc func Test_tex_file() filetype on - " only tests one case, should do more + call writefile(['%& pdflatex'], 'Xfile.tex') + split Xfile.tex + call assert_equal('tex', &filetype) + bwipe + + call writefile(['\newcommand{\test}{some text}'], 'Xfile.tex') + split Xfile.tex + call assert_equal('tex', &filetype) + bwipe + + " tex_flavor is unset + call writefile(['%& plain'], 'Xfile.tex') + split Xfile.tex + call assert_equal('plaintex', &filetype) + bwipe + + let g:tex_flavor = 'plain' + call writefile(['just some text'], 'Xfile.tex') + split Xfile.tex + call assert_equal('plaintex', &filetype) + bwipe + let lines =<< trim END - % This is a sentence. + % This is a comment. - This is a sentence. + \usemodule[translate] END - call writefile(lines, "Xfile.tex") + call writefile(lines, 'Xfile.tex') split Xfile.tex - call assert_equal('plaintex', &filetype) + call assert_equal('context', &filetype) bwipe + let g:tex_flavor = 'context' + call writefile(['just some text'], 'Xfile.tex') + split Xfile.tex + call assert_equal('context', &filetype) + bwipe + unlet g:tex_flavor + call delete('Xfile.tex') filetype off endfunc @@ -1768,6 +1811,11 @@ func Test_cls_file() call assert_equal('tex', &filetype) bwipe! + call writefile(['\NeedsTeXFormat{LaTeX2e}'], 'Xfile.cls') + split Xfile.cls + call assert_equal('tex', &filetype) + bwipe! + " Rexx call writefile(['# rexx'], 'Xfile.cls') @@ -1957,4 +2005,36 @@ func Test_inc_file() filetype off endfunc +func Test_lsl_file() + filetype on + + call writefile(['looks like Linden Scripting Language'], 'Xfile.lsl') + split Xfile.lsl + call assert_equal('lsl', &filetype) + bwipe! + + " Test dist#ft#FTlsl() + + let g:filetype_lsl = 'larch' + split Xfile.lsl + call assert_equal('larch', &filetype) + bwipe! + unlet g:filetype_lsl + + " Larch Shared Language + + call writefile(['% larch comment'], 'Xfile.lsl') + split Xfile.lsl + call assert_equal('larch', &filetype) + bwipe! + + call writefile(['foo: trait'], 'Xfile.lsl') + split Xfile.lsl + call assert_equal('larch', &filetype) + bwipe! + + call delete('Xfile.lsl') + filetype off +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 1cd3a2287b..c75177ea39 100644 --- a/src/nvim/testdir/test_filter_map.vim +++ b/src/nvim/testdir/test_filter_map.vim @@ -86,6 +86,13 @@ func Test_map_filter_fails() call assert_fails('call filter([1], "42 +")', 'E15:') call assert_fails("let l = map('abc', '\"> \" . v:val')", 'E896:') call assert_fails("let l = filter('abc', '\"> \" . v:val')", 'E896:') + call assert_fails("let l = filter([1, 2, 3], '{}')", 'E728:') + call assert_fails("let l = filter({'k' : 10}, '{}')", 'E728:') + call assert_fails("let l = filter([1, 2], {})", 'E731:') + call assert_equal(v:_null_list, filter(v:_null_list, 0)) + call assert_equal(v:_null_dict, filter(v:_null_dict, 0)) + call assert_equal(v:_null_list, map(v:_null_list, '"> " .. v:val')) + call assert_equal(v:_null_dict, map(v:_null_dict, '"> " .. v:val')) endfunc func Test_map_and_modify() diff --git a/src/nvim/testdir/test_fixeol.vim b/src/nvim/testdir/test_fixeol.vim index 32cb059e26..41d47d6a06 100644 --- a/src/nvim/testdir/test_fixeol.vim +++ b/src/nvim/testdir/test_fixeol.vim @@ -1,16 +1,17 @@ -" Tests for 'fixeol' and 'eol' +" Tests for 'fixeol', 'eof' and 'eol' + func Test_fixeol() " first write two test files – with and without trailing EOL " use Unix fileformat for consistency set ff=unix enew! - call setline('.', 'with eol') + call setline('.', 'with eol or eof') w! XXEol enew! - set noeol nofixeol - call setline('.', 'without eol') + set noeof noeol nofixeol + call setline('.', 'without eol or eof') w! XXNoEol - set eol fixeol + set eol eof fixeol bwipe XXEol XXNoEol " try editing files with 'fixeol' disabled @@ -33,16 +34,85 @@ func Test_fixeol() w >>XXTestEol w >>XXTestNoEol - call assert_equal(['with eol', 'END'], readfile('XXEol')) - call assert_equal(['without eolEND'], readfile('XXNoEol')) - call assert_equal(['with eol', 'stays eol', 'END'], readfile('XXTestEol')) - call assert_equal(['without eol', 'stays withoutEND'], + call assert_equal(['with eol or eof', 'END'], readfile('XXEol')) + call assert_equal(['without eol or eofEND'], readfile('XXNoEol')) + call assert_equal(['with eol or eof', 'stays eol', 'END'], readfile('XXTestEol')) + call assert_equal(['without eol or eof', 'stays withoutEND'], \ readfile('XXTestNoEol')) call delete('XXEol') call delete('XXNoEol') call delete('XXTestEol') call delete('XXTestNoEol') - set ff& fixeol& eol& + set ff& fixeol& eof& eol& + enew! +endfunc + +func Test_eof() + let data = 0z68656c6c6f.0d0a.776f726c64 " "hello\r\nworld" + + " 1. Eol, Eof + " read + call writefile(data + 0z0d0a.1a, 'XXEolEof') + e! XXEolEof + call assert_equal(['hello', 'world'], getline(1, 2)) + call assert_equal([1, 1], [&eol, &eof]) + " write + set fixeol + w! + call assert_equal(data + 0z0d0a, readblob('XXEolEof')) + set nofixeol + w! + call assert_equal(data + 0z0d0a.1a, readblob('XXEolEof')) + + " 2. NoEol, Eof + " read + call writefile(data + 0z1a, 'XXNoEolEof') + e! XXNoEolEof + call assert_equal(['hello', 'world'], getline(1, 2)) + call assert_equal([0, 1], [&eol, &eof]) + " write + set fixeol + w! + call assert_equal(data + 0z0d0a, readblob('XXNoEolEof')) + set nofixeol + w! + call assert_equal(data + 0z1a, readblob('XXNoEolEof')) + + " 3. Eol, NoEof + " read + call writefile(data + 0z0d0a, 'XXEolNoEof') + e! XXEolNoEof + call assert_equal(['hello', 'world'], getline(1, 2)) + call assert_equal([1, 0], [&eol, &eof]) + " write + set fixeol + w! + call assert_equal(data + 0z0d0a, readblob('XXEolNoEof')) + set nofixeol + w! + call assert_equal(data + 0z0d0a, readblob('XXEolNoEof')) + + " 4. NoEol, NoEof + " read + call writefile(data, 'XXNoEolNoEof') + e! XXNoEolNoEof + call assert_equal(['hello', 'world'], getline(1, 2)) + call assert_equal([0, 0], [&eol, &eof]) + " write + set fixeol + w! + call assert_equal(data + 0z0d0a, readblob('XXNoEolNoEof')) + set nofixeol + w! + call assert_equal(data, readblob('XXNoEolNoEof')) + + call delete('XXEolEof') + call delete('XXNoEolEof') + call delete('XXEolNoEof') + call delete('XXNoEolNoEof') + set ff& fixeol& eof& eol& enew! endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim index 327f0f73f2..19415286ad 100644 --- a/src/nvim/testdir/test_fold.vim +++ b/src/nvim/testdir/test_fold.vim @@ -1,5 +1,6 @@ " Test for folding +source check.vim source view_util.vim source screendump.vim @@ -71,6 +72,54 @@ func Test_address_fold() quit! endfunc +func Test_address_offsets() + " check the help for :range-closed-fold + enew + call setline(1, [ + \ '1 one', + \ '2 two', + \ '3 three', + \ '4 four FOLDED', + \ '5 five FOLDED', + \ '6 six', + \ '7 seven', + \ '8 eight', + \]) + set foldmethod=manual + normal 4Gvjzf + 3,4+2yank + call assert_equal([ + \ '3 three', + \ '4 four FOLDED', + \ '5 five FOLDED', + \ '6 six', + \ '7 seven', + \ ], getreg(0,1,1)) + + enew! + call setline(1, [ + \ '1 one', + \ '2 two', + \ '3 three FOLDED', + \ '4 four FOLDED', + \ '5 five FOLDED', + \ '6 six FOLDED', + \ '7 seven', + \ '8 eight', + \]) + normal 3Gv3jzf + 2,4-1yank + call assert_equal([ + \ '2 two', + \ '3 three FOLDED', + \ '4 four FOLDED', + \ '5 five FOLDED', + \ '6 six FOLDED', + \ ], getreg(0,1,1)) + + bwipe! +endfunc + func Test_indent_fold() new call setline(1, ['', 'a', ' b', ' c']) @@ -93,10 +142,44 @@ func Test_indent_fold2() bw! endfunc +" Test for fold indent with indents greater than 'foldnestmax' +func Test_indent_fold_max() + new + setlocal foldmethod=indent + setlocal shiftwidth=2 + " 'foldnestmax' default value is 20 + call setline(1, "\t\t\t\t\t\ta") + call assert_equal(20, foldlevel(1)) + setlocal foldnestmax=10 + call assert_equal(10, foldlevel(1)) + setlocal foldnestmax=-1 + call assert_equal(0, foldlevel(1)) + bw! +endfunc + +func Test_indent_fold_tabstop() + call setline(1, ['0', ' 1', ' 1', "\t2", "\t2"]) + setlocal shiftwidth=4 + setlocal foldcolumn=1 + setlocal foldlevel=2 + setlocal foldmethod=indent + redraw + call assert_equal('2 2', ScreenLines(5, 10)[0]) + vsplit + windo diffthis + botright new + " This 'tabstop' value should not be used for folding in other buffers. + setlocal tabstop=4 + diffoff! + redraw + call assert_equal('2 2', ScreenLines(5, 10)[0]) + + bwipe! + bwipe! +endfunc + func Test_manual_fold_with_filter() - if !executable('cat') - return - endif + CheckExecutable cat for type in ['manual', 'marker'] exe 'set foldmethod=' . type new @@ -418,27 +501,6 @@ func Test_move_folds_around_indent() bw! endfunc -" test for patch 7.3.637 -" Cannot catch the error caused by a foldopen when there is no fold. -func Test_foldopen_exception() - enew! - let a = 'No error caught' - try - foldopen - catch - let a = matchstr(v:exception,'^[^ ]*') - endtry - call assert_equal('Vim(foldopen):E490:', a) - - let a = 'No error caught' - try - foobar - catch - let a = matchstr(v:exception,'^[^ ]*') - endtry - call assert_match('E492:', a) -endfunc - func Test_folddoopen_folddoclosed() new call setline(1, range(1, 9)) @@ -492,11 +554,24 @@ func Test_fold_error() bw! endfunc +func Test_foldtext_recursive() + new + call setline(1, ['{{{', 'some text', '}}}']) + setlocal foldenable foldmethod=marker foldtext=foldtextresult(v\:foldstart) + " This was crashing because of endless recursion. + 2foldclose + redraw + call assert_equal(1, foldlevel(2)) + call assert_equal(1, foldclosed(2)) + call assert_equal(3, foldclosedend(2)) + bwipe! +endfunc + " Various fold related tests " Basic test if a fold can be created, opened, moving to the end and closed func Test_fold_manual() - enew! + new set fdm=manual let content = ['1 aa', '2 bb', '3 cc'] @@ -511,13 +586,67 @@ func Test_fold_manual() normal zc call assert_equal('1 aa', getline(foldclosed('.'))) + " Create a fold inside a closed fold after setting 'foldlevel' + %d _ + call setline(1, range(1, 5)) + 1,5fold + normal zR + 2,4fold + set foldlevel=1 + 3fold + call assert_equal([1, 3, 3, 3, 1], map(range(1, 5), {->foldlevel(v:val)})) + set foldlevel& + + " Create overlapping folds (at the start and at the end) + normal zE + 2,3fold + normal zR + 3,4fold + call assert_equal([0, 2, 2, 1, 0], map(range(1, 5), {->foldlevel(v:val)})) + normal zE + 3,4fold + normal zR + 2,3fold + call assert_equal([0, 1, 2, 2, 0], map(range(1, 5), {->foldlevel(v:val)})) + + " Create a nested fold across two non-adjoining folds + %d _ + call setline(1, range(1, 7)) + 1,2fold + normal zR + 4,5fold + normal zR + 6,7fold + normal zR + 1,5fold + call assert_equal([2, 2, 1, 2, 2, 1, 1], + \ map(range(1, 7), {->foldlevel(v:val)})) + + " A newly created nested fold should be closed + %d _ + call setline(1, range(1, 6)) + 1,6fold + normal zR + 3,4fold + normal zR + 2,5fold + call assert_equal([1, 2, 3, 3, 2, 1], map(range(1, 6), {->foldlevel(v:val)})) + call assert_equal(2, foldclosed(4)) + call assert_equal(5, foldclosedend(4)) + + " Test zO, zC and zA on a line with no folds. + normal zE + call assert_fails('normal zO', 'E490:') + call assert_fails('normal zC', 'E490:') + call assert_fails('normal zA', 'E490:') + set fdm& - enew! + bw! endfunc " test folding with markers. func Test_fold_marker() - enew! + new set fdm=marker fdl=1 fdc=3 let content = ['4 dd {{{', '5 ee {{{ }}}', '6 ff }}}'] @@ -531,13 +660,22 @@ func Test_fold_marker() normal kYpj call assert_equal(0, foldlevel('.')) + " Use only closing fold marker (without and with a count) + set fdl& + %d _ + call setline(1, ['one }}}', 'two']) + call assert_equal([0, 0], [foldlevel(1), foldlevel(2)]) + %d _ + call setline(1, ['one }}}4', 'two']) + call assert_equal([4, 3], [foldlevel(1), foldlevel(2)]) + set fdm& fdl& fdc& - enew! + bw! endfunc " test create fold markers with C filetype func Test_fold_create_marker_in_C() - enew! + bw! set fdm=marker fdl=9 set filetype=c @@ -562,12 +700,12 @@ func Test_fold_create_marker_in_C() endfor set fdm& fdl& - enew! + bw! endfunc " test folding with indent func Test_fold_indent() - enew! + new set fdm=indent sw=2 let content = ['1 aa', '2 bb', '3 cc'] @@ -579,16 +717,14 @@ func Test_fold_indent() call assert_equal(1, foldlevel('.')) set fdm& sw& - enew! + bw! endfunc " test syntax folding func Test_fold_syntax() - if !has('syntax') - return - endif + CheckFeature syntax - enew! + new set fdm=syntax fdl=0 syn region Hup start="dd" end="ii" fold contains=Fd1,Fd2,Fd3 @@ -612,7 +748,7 @@ func Test_fold_syntax() syn clear Fd1 Fd2 Fd3 Hup set fdm& fdl& - enew! + bw! endfunc func Flvl() @@ -631,7 +767,7 @@ endfun " test expression folding func Test_fold_expr() - enew! + new set fdm=expr fde=Flvl() let content = ['1 aa', @@ -659,14 +795,14 @@ func Test_fold_expr() call assert_equal(0, foldlevel('.')) set fdm& fde& - enew! + bw! endfunc " Bug with fdm=indent and moving folds " Moving a fold a few times, messes up the folds below the moved fold. " Fixed by 7.4.700 func Test_fold_move() - enew! + new set fdm=indent sw=2 fdl=0 let content = ['', '', 'Line1', ' Line2', ' Line3', @@ -686,24 +822,33 @@ func Test_fold_move() call assert_equal('+-- 2 lines: Line8', 10->foldtextresult()) set fdm& sw& fdl& - enew! + bw! endfunc -func Test_foldtext_recursive() +" test for patch 7.3.637 +" Cannot catch the error caused by a foldopen when there is no fold. +func Test_foldopen_exception() new - call setline(1, ['{{{', 'some text', '}}}']) - setlocal foldenable foldmethod=marker foldtext=foldtextresult(v\:foldstart) - " This was crashing because of endless recursion. - 2foldclose - redraw - call assert_equal(1, foldlevel(2)) - call assert_equal(1, foldclosed(2)) - call assert_equal(3, foldclosedend(2)) - bwipe! + let a = 'No error caught' + try + foldopen + catch + let a = matchstr(v:exception,'^[^ ]*') + endtry + call assert_equal('Vim(foldopen):E490:', a) + + let a = 'No error caught' + try + foobar + catch + let a = matchstr(v:exception,'^[^ ]*') + endtry + call assert_match('E492:', a) + bw! endfunc func Test_fold_last_line_with_pagedown() - enew! + new set fdm=manual let expect = '+-- 11 lines: 9---' @@ -723,13 +868,11 @@ func Test_fold_last_line_with_pagedown() call assert_equal(expect, ScreenLines(1, len(expect))[0]) set fdm& - enew! + bw! endfunc func Test_folds_with_rnu() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckScreendump call writefile([ \ 'set fdm=marker rnu foldcolumn=2', @@ -816,6 +959,55 @@ func Test_fold_delete_first_line() set foldmethod& endfunc +" Add a test for deleting the outer fold of a nested fold and promoting the +" inner folds to one level up with already a fold at that level following the +" nested fold. +func Test_fold_delete_recursive_fold() + new + call setline(1, range(1, 7)) + 2,3fold + normal zR + 4,5fold + normal zR + 1,5fold + normal zR + 6,7fold + normal zR + normal 1Gzd + normal 1Gzj + call assert_equal(2, line('.')) + normal zj + call assert_equal(4, line('.')) + normal zj + call assert_equal(6, line('.')) + bw! +endfunc + +" Test for errors in 'foldexpr' +func Test_fold_expr_error() + new + call setline(1, ['one', 'two', 'three']) + " In a window with no folds, foldlevel() should return 0 + call assert_equal(0, foldlevel(1)) + + " Return a list from the expression + set foldexpr=[] + set foldmethod=expr + for i in range(3) + call assert_equal(0, foldlevel(i)) + endfor + + " expression error + set foldexpr=[{] + set foldmethod=expr + for i in range(3) + call assert_equal(0, foldlevel(i)) + endfor + + set foldmethod& foldexpr& + close! +endfunc + func Test_undo_fold_deletion() new set fdm=marker @@ -899,6 +1091,264 @@ func Test_fold_relative_move() call assert_equal(2, winline()) set fdm& sw& wrap& tw& + bw! +endfunc + +" Test for calling foldlevel() from a fold expression +let g:FoldLevels = [] +func FoldExpr1(lnum) + let f = [a:lnum] + for i in range(1, line('$')) + call add(f, foldlevel(i)) + endfor + call add(g:FoldLevels, f) + return getline(a:lnum)[0] == "\t" +endfunc + +func Test_foldexpr_foldlevel() + new + call setline(1, ['one', "\ttwo", "\tthree"]) + setlocal foldmethod=expr + setlocal foldexpr=FoldExpr1(v:lnum) + setlocal foldenable + setlocal foldcolumn=3 + redraw! + call assert_equal([[1, -1, -1, -1], [2, -1, -1, -1], [3, 0, 1, -1]], + \ g:FoldLevels) + set foldmethod& foldexpr& foldenable& foldcolumn& + bw! +endfunc + +" Test for returning different values from a fold expression +func FoldExpr2(lnum) + if a:lnum == 1 || a:lnum == 4 + return -2 + elseif a:lnum == 2 + return 'a1' + elseif a:lnum == 3 + return 's4' + endif + return '=' +endfunc + +func Test_foldexpr_2() + new + call setline(1, ['one', 'two', 'three', 'four']) + setlocal foldexpr=FoldExpr2(v:lnum) + setlocal foldmethod=expr + call assert_equal([0, 1, 1, 0], [foldlevel(1), foldlevel(2), foldlevel(3), + \ foldlevel(4)]) + bw! +endfunc + +" Test for the 'foldclose' option +func Test_foldclose_opt() + CheckScreendump + + let lines =<< trim END + set foldmethod=manual foldclose=all foldopen=all + call setline(1, ['one', 'two', 'three', 'four']) + 2,3fold + func XsaveFoldLevels() + redraw! + call writefile([json_encode([foldclosed(1), foldclosed(2), foldclosed(3), + \ foldclosed(4)])], 'Xoutput', 'a') + endfunc + END + call writefile(lines, 'Xscript') + let rows = 10 + let buf = RunVimInTerminal('-S Xscript', {'rows': rows}) + call term_wait(buf) + call term_sendkeys(buf, ":set noruler\n") + call term_wait(buf) + call term_sendkeys(buf, ":call XsaveFoldLevels()\n") + call term_sendkeys(buf, "2G") + call WaitForAssert({-> assert_equal('two', term_getline(buf, 2))}) + call term_sendkeys(buf, ":call XsaveFoldLevels()\n") + call term_sendkeys(buf, "4G") + call WaitForAssert({-> assert_equal('four', term_getline(buf, 3))}) + call term_sendkeys(buf, ":call XsaveFoldLevels()\n") + call term_sendkeys(buf, "3G") + call WaitForAssert({-> assert_equal('three', term_getline(buf, 3))}) + call term_sendkeys(buf, ":call XsaveFoldLevels()\n") + call term_sendkeys(buf, "1G") + call WaitForAssert({-> assert_equal('four', term_getline(buf, 3))}) + call term_sendkeys(buf, ":call XsaveFoldLevels()\n") + call term_sendkeys(buf, "2G") + call WaitForAssert({-> assert_equal('two', term_getline(buf, 2))}) + call term_sendkeys(buf, "k") + call WaitForAssert({-> assert_equal('four', term_getline(buf, 3))}) + + " clean up + call StopVimInTerminal(buf) + + call assert_equal(['[-1,2,2,-1]', '[-1,-1,-1,-1]', '[-1,2,2,-1]', + \ '[-1,-1,-1,-1]', '[-1,2,2,-1]'], readfile('Xoutput')) + call delete('Xscript') + call delete('Xoutput') +endfunc + +" Test for foldtextresult() +func Test_foldtextresult() + new + call assert_equal('', foldtextresult(-1)) + call assert_equal('', foldtextresult(0)) + call assert_equal('', foldtextresult(1)) + call setline(1, ['one', 'two', 'three', 'four']) + 2,3fold + call assert_equal('', foldtextresult(1)) + call assert_equal('+-- 2 lines: two', foldtextresult(2)) + setlocal foldtext= + call assert_equal('+-- 2 lines folded ', foldtextresult(2)) + + " Fold text for a C comment fold + %d _ + setlocal foldtext& + call setline(1, ['', '/*', ' * Comment', ' */', '']) + 2,4fold + call assert_equal('+-- 3 lines: Comment', foldtextresult(2)) + + bw! +endfunc + +" Test for merging two recursive folds when an intermediate line with no fold +" is removed +func Test_fold_merge_recursive() + new + call setline(1, [' one', ' two', 'xxxx', ' three', + \ ' four', "\tfive"]) + setlocal foldmethod=indent shiftwidth=2 + 3d_ + %foldclose + call assert_equal([1, 5], [foldclosed(5), foldclosedend(1)]) + bw! +endfunc + +" Test for moving a line which is the start of a fold from a recursive fold to +" outside. The fold length should reduce. +func Test_fold_move_foldlevel() + new + call setline(1, ['a{{{', 'b{{{', 'c{{{', 'd}}}', 'e}}}', 'f}}}', 'g']) + setlocal foldmethod=marker + normal zR + call assert_equal([3, 2, 1], [foldlevel(4), foldlevel(5), foldlevel(6)]) + 3move 7 + call assert_equal([2, 1, 0], [foldlevel(3), foldlevel(4), foldlevel(5)]) + call assert_equal(1, foldlevel(7)) + + " Move a line from outside a fold to inside the fold. + %d _ + call setline(1, ['a', 'b{{{', 'c}}}']) + normal zR + 1move 2 + call assert_equal([1, 1, 1], [foldlevel(1), foldlevel(2), foldlevel(3)]) + + " Move the start of one fold to inside another fold + %d _ + call setline(1, ['a', 'b{{{', 'c}}}', 'd{{{', 'e}}}']) + normal zR + call assert_equal([0, 1, 1, 1, 1], [foldlevel(1), foldlevel(2), + \ foldlevel(3), foldlevel(4), foldlevel(5)]) + 1,2move 4 + call assert_equal([0, 1, 1, 2, 2], [foldlevel(1), foldlevel(2), + \ foldlevel(3), foldlevel(4), foldlevel(5)]) + + bw! +endfunc + +" Test for using zj and zk to move downwards and upwards to the start and end +" of the next fold. +" Test for using [z and ]z in a closed fold to jump to the beginning and end +" of the fold. +func Test_fold_jump() + new + call setline(1, ["\t1", "\t2", "\t\t3", "\t\t4", "\t\t\t5", "\t\t\t6", "\t\t7", "\t\t8", "\t9", "\t10"]) + setlocal foldmethod=indent + normal zR + normal zj + call assert_equal(3, line('.')) + normal zj + call assert_equal(5, line('.')) + call assert_beeps('normal zj') + call assert_equal(5, line('.')) + call assert_beeps('normal 9Gzj') + call assert_equal(9, line('.')) + normal Gzk + call assert_equal(8, line('.')) + normal zk + call assert_equal(6, line('.')) + call assert_beeps('normal zk') + call assert_equal(6, line('.')) + call assert_beeps('normal 2Gzk') + call assert_equal(2, line('.')) + + " Using [z or ]z in a closed fold should not move the cursor + %d _ + call setline(1, ["1", "\t2", "\t3", "\t4", "\t5", "\t6", "7"]) + normal zR4Gzc + call assert_equal(4, line('.')) + call assert_beeps('normal [z') + call assert_equal(4, line('.')) + call assert_beeps('normal ]z') + call assert_equal(4, line('.')) + bw! +endfunc + +" Test for using a script-local function for 'foldexpr' +func Test_foldexpr_scriptlocal_func() + func! s:FoldFunc() + let g:FoldLnum = v:lnum + endfunc + new | only + call setline(1, 'abc') + let g:FoldLnum = 0 + set foldmethod=expr foldexpr=s:FoldFunc() + redraw! + call assert_equal(expand('<SID>') .. 'FoldFunc()', &foldexpr) + call assert_equal(1, g:FoldLnum) + set foldmethod& foldexpr= + bw! + new | only + call setline(1, 'abc') + let g:FoldLnum = 0 + set foldmethod=expr foldexpr=<SID>FoldFunc() + redraw! + call assert_equal(expand('<SID>') .. 'FoldFunc()', &foldexpr) + call assert_equal(1, g:FoldLnum) + set foldmethod& foldexpr= + delfunc s:FoldFunc + bw! +endfunc + +" Test for using a script-local function for 'foldtext' +func Test_foldtext_scriptlocal_func() + func! s:FoldText() + let g:FoldTextArgs = [v:foldstart, v:foldend] + return foldtext() + endfunc + new | only + call setline(1, range(50)) + let g:FoldTextArgs = [] + set foldmethod=manual + set foldtext=s:FoldText() + norm! 4Gzf4j + redraw! + call assert_equal(expand('<SID>') .. 'FoldText()', &foldtext) + call assert_equal([4, 8], g:FoldTextArgs) + set foldtext& + bw! + new | only + call setline(1, range(50)) + let g:FoldTextArgs = [] + set foldmethod=manual + set foldtext=<SID>FoldText() + norm! 8Gzf4j + redraw! + call assert_equal(expand('<SID>') .. 'FoldText()', &foldtext) + call assert_equal([8, 12], g:FoldTextArgs) + set foldtext& + bw! + delfunc s:FoldText endfunc " Make sure a fold containing a nested fold is split correctly when using @@ -989,4 +1439,61 @@ func Test_indent_append_blank_small_fold_close() bw! endfunc +func Test_sort_closed_fold() + CheckExecutable sort + + call setline(1, [ + \ 'Section 1', + \ ' how', + \ ' now', + \ ' brown', + \ ' cow', + \ 'Section 2', + \ ' how', + \ ' now', + \ ' brown', + \ ' cow', + \]) + setlocal foldmethod=indent sw=3 + normal 2G + + " The "!!" expands to ".,.+3" and must only sort four lines + call feedkeys("!!sort\<CR>", 'xt') + call assert_equal([ + \ 'Section 1', + \ ' brown', + \ ' cow', + \ ' how', + \ ' now', + \ 'Section 2', + \ ' how', + \ ' now', + \ ' brown', + \ ' cow', + \ ], getline(1, 10)) + + bwipe! +endfunc + +func Test_indent_with_L_command() + " The "L" command moved the cursor to line zero, causing the text saved for + " undo to use line number -1, which caused trouble for undo later. + new + sil! norm 8R
V{zf8=Lu + bwipe! +endfunc + +" Make sure that when there is a fold at the bottom of the buffer and a newline +" character is appended to the line, the fold gets expanded (instead of the new +" line not being part of the fold). +func Test_expand_fold_at_bottom_of_buffer() + new + " create a fold on the only line + fold + execute "normal A\<CR>" + call assert_equal([1, 1], range(1, 2)->map('foldlevel(v:val)')) + + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 7ad0cb5884..500c30c76b 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -2,6 +2,9 @@ source shared.vim source check.vim +source term_util.vim +source screendump.vim +source vim9.vim " Must be done first, since the alternate buffer must be unset. func Test_00_bufexists() @@ -19,6 +22,27 @@ func Test_00_bufexists() call assert_equal(0, bufexists('Xfoo')) endfunc +func Test_has() + throw 'Skipped: Nvim has removed some features' + call assert_equal(1, has('eval')) + call assert_equal(1, has('eval', 1)) + + if has('unix') + call assert_equal(1, or(has('ttyin'), 1)) + call assert_equal(0, and(has('ttyout'), 0)) + call assert_equal(1, has('multi_byte_encoding')) + endif + call assert_equal(1, has('vcon', 1)) + call assert_equal(1, has('mouse_gpm_enabled', 1)) + + call assert_equal(0, has('nonexistent')) + call assert_equal(0, has('nonexistent', 1)) + + " Will we ever have patch 9999? + let ver = 'patch-' .. v:version / 100 .. '.' .. v:version % 100 .. '.9999' + call assert_equal(0, has(ver)) +endfunc + func Test_empty() call assert_equal(1, empty('')) call assert_equal(0, empty('a')) @@ -67,9 +91,11 @@ func Test_len() call assert_equal(2, len('ab')) call assert_equal(0, len([])) + call assert_equal(0, len(v:_null_list)) call assert_equal(2, len([2, 1])) call assert_equal(0, len({})) + call assert_equal(0, len(v:_null_dict)) call assert_equal(2, len({'a': 1, 'b': 2})) " call assert_fails('call len(v:none)', 'E701:') @@ -87,6 +113,10 @@ func Test_max() call assert_fails('call max(1)', 'E712:') " call assert_fails('call max(v:none)', 'E712:') + + " check we only get one error + call assert_fails('call max([#{}, [1]])', ['E728:', 'E728:']) + call assert_fails('call max(#{a: {}, b: [1]})', ['E728:', 'E728:']) endfunc func Test_min() @@ -100,6 +130,11 @@ func Test_min() call assert_fails('call min(1)', 'E712:') " call assert_fails('call min(v:none)', 'E712:') + call assert_fails('call min([1, {}])', 'E728:') + + " check we only get one error + call assert_fails('call min([[1], #{}])', ['E745:', 'E745:']) + call assert_fails('call min(#{a: [1], b: #{}})', ['E745:', 'E745:']) endfunc func Test_strwidth() @@ -188,7 +223,7 @@ func Test_str2nr() if has('float') call assert_fails('call str2nr(1.2)', 'E806:') endif - call assert_fails('call str2nr(10, [])', 'E474:') + call assert_fails('call str2nr(10, [])', 'E745:') endfunc func Test_strftime() @@ -381,6 +416,8 @@ func Test_strpart() call assert_equal('abcdefg', 'abcdefg'->strpart(-2)) call assert_equal('fg', strpart('abcdefg', 5, 4)) call assert_equal('defg', strpart('abcdefg', 3)) + call assert_equal('', strpart('abcdefg', 10)) + call assert_fails("let s=strpart('abcdef', [])", 'E745:') call assert_equal('lép', strpart('éléphant', 2, 4)) call assert_equal('léphant', strpart('éléphant', 2)) @@ -550,6 +587,16 @@ endfunc func Test_tr() call assert_equal('foo', tr('bar', 'bar', 'foo')) call assert_equal('zxy', 'cab'->tr('abc', 'xyz')) + call assert_fails("let s=tr([], 'abc', 'def')", 'E730:') + call assert_fails("let s=tr('abc', [], 'def')", 'E730:') + call assert_fails("let s=tr('abc', 'abc', [])", 'E730:') + call assert_fails("let s=tr('abcd', 'abcd', 'def')", 'E475:') + " set encoding=latin1 + call assert_fails("let s=tr('abcd', 'abcd', 'def')", 'E475:') + call assert_equal('hEllO', tr('hello', 'eo', 'EO')) + call assert_equal('hello', tr('hello', 'xy', 'ab')) + call assert_fails('call tr("abc", "123", "₁₂")', 'E475:') + set encoding=utf8 endfunc " Tests for the mode() function @@ -764,13 +811,41 @@ func Test_mode() delfunction OperatorFunc endfunc +" Test for append() func Test_append() enew! split call append(0, ["foo"]) + call append(1, []) + call append(1, v:_null_list) + call assert_equal(['foo', ''], getline(1, '$')) split only undo + undo + + " Using $ instead of '$' must give an error + call assert_fails("call append($, 'foobar')", 'E116:') + + call assert_fails("call append({}, '')", ['E728:', 'E728:']) +endfunc + +" Test for setline() +func Test_setline() + new + call setline(0, ["foo"]) + call setline(0, []) + call setline(0, v:_null_list) + call setline(1, ["bar"]) + call setline(1, []) + call setline(1, v:_null_list) + call setline(2, []) + call setline(2, v:_null_list) + call setline(3, []) + call setline(3, v:_null_list) + call setline(2, ["baz"]) + call assert_equal(['bar', 'baz'], getline(1, '$')) + close! endfunc func Test_getbufvar() @@ -804,6 +879,10 @@ func Test_getbufvar() call assert_equal(0, getbufvar(bnr, '&autoindent')) call assert_equal(0, getbufvar(bnr, '&autoindent', 1)) + " Set and get a buffer-local variable + call setbufvar(bnr, 'bufvar_test', ['one', 'two']) + call assert_equal(['one', 'two'], getbufvar(bnr, 'bufvar_test')) + " Open new window with forced option values set fileformats=unix,dos new ++ff=dos ++bin ++enc=iso-8859-2 @@ -842,6 +921,8 @@ func Test_stridx() call assert_equal(-1, stridx('hello', 'l', 10)) call assert_equal(2, stridx('hello', 'll')) call assert_equal(-1, stridx('hello', 'hello world')) + call assert_fails("let n=stridx('hello', [])", 'E730:') + call assert_fails("let n=stridx([], 'l')", 'E730:') endfunc func Test_strridx() @@ -858,6 +939,8 @@ func Test_strridx() call assert_equal(-1, strridx('hello', 'l', -1)) call assert_equal(2, strridx('hello', 'll')) call assert_equal(-1, strridx('hello', 'hello world')) + call assert_fails("let n=strridx('hello', [])", 'E730:') + call assert_fails("let n=strridx([], 'l')", 'E730:') endfunc func Test_match_func() @@ -867,6 +950,13 @@ func Test_match_func() call assert_equal(-1, match('testing', 'ing', 8)) call assert_equal(1, match(['vim', 'testing', 'execute'], 'ing')) call assert_equal(-1, match(['vim', 'testing', 'execute'], 'img')) + call assert_fails("let x=match('vim', [])", 'E730:') + call assert_equal(3, match(['a', 'b', 'c', 'a'], 'a', 1)) + call assert_equal(-1, match(['a', 'b', 'c', 'a'], 'a', 5)) + call assert_equal(4, match('testing', 'ing', -1)) + call assert_fails("let x=match('testing', 'ing', 0, [])", 'E745:') + call assert_equal(-1, match(v:_null_list, 2)) + call assert_equal(-1, match('abc', '\\%(')) endfunc func Test_matchend() @@ -973,6 +1063,7 @@ func Test_byte2line_line2byte() bw! endfunc +" Test for byteidx() and byteidxcomp() functions func Test_byteidx() let a = '.é.' " one char of two bytes call assert_equal(0, byteidx(a, 0)) @@ -992,6 +1083,7 @@ func Test_byteidx() call assert_equal(4, b->byteidx(2)) call assert_equal(5, b->byteidx(3)) call assert_equal(-1, b->byteidx(4)) + call assert_fails("call byteidx([], 0)", 'E730:') call assert_equal(0, b->byteidxcomp(0)) call assert_equal(1, b->byteidxcomp(1)) @@ -999,6 +1091,7 @@ func Test_byteidx() call assert_equal(4, b->byteidxcomp(3)) call assert_equal(5, b->byteidxcomp(4)) call assert_equal(-1, b->byteidxcomp(5)) + call assert_fails("call byteidxcomp([], 0)", 'E730:') endfunc " Test for charidx() @@ -1009,7 +1102,9 @@ func Test_charidx() call assert_equal(2, charidx(a, 4)) call assert_equal(3, charidx(a, 7)) call assert_equal(-1, charidx(a, 8)) + call assert_equal(-1, charidx(a, -1)) call assert_equal(-1, charidx('', 0)) + call assert_equal(-1, charidx(v:_null_string, 0)) " count composing characters call assert_equal(0, charidx(a, 0, 1)) @@ -1022,8 +1117,8 @@ func Test_charidx() call assert_fails('let x = charidx([], 1)', 'E474:') call assert_fails('let x = charidx("abc", [])', 'E474:') call assert_fails('let x = charidx("abc", 1, [])', 'E474:') - call assert_fails('let x = charidx("abc", 1, -1)', 'E474:') - call assert_fails('let x = charidx("abc", 1, 2)', 'E474:') + call assert_fails('let x = charidx("abc", 1, -1)', 'E1023:') + call assert_fails('let x = charidx("abc", 1, 2)', 'E1023:') endfunc func Test_count() @@ -1195,6 +1290,7 @@ func Test_col() norm gg4|mx6|mY2| call assert_equal(2, col('.')) call assert_equal(7, col('$')) + call assert_equal(2, col('v')) call assert_equal(4, col("'x")) call assert_equal(6, col("'Y")) call assert_equal(2, [1, 2]->col()) @@ -1205,6 +1301,47 @@ func Test_col() call assert_equal(0, col([2, '$'])) call assert_equal(0, col([1, 100])) call assert_equal(0, col([1])) + call assert_equal(0, col(v:_null_list)) + call assert_fails('let c = col({})', 'E1222:') + call assert_fails('let c = col(".", [])', 'E1210:') + + " test for getting the visual start column + func T() + let g:Vcol = col('v') + return '' + endfunc + let g:Vcol = 0 + xmap <expr> <F2> T() + exe "normal gg3|ve\<F2>" + call assert_equal(3, g:Vcol) + xunmap <F2> + delfunc T + + " Test for the visual line start and end marks '< and '> + call setline(1, ['one', 'one two', 'one two three']) + "normal! ggVG + call feedkeys("ggVG\<Esc>", 'xt') + call assert_equal(1, col("'<")) + call assert_equal(14, col("'>")) + " Delete the last line of the visually selected region + $d + call assert_notequal(14, col("'>")) + + " Test with 'virtualedit' + set virtualedit=all + call cursor(1, 10) + call assert_equal(4, col('.')) + set virtualedit& + + " Test for getting the column number in another window + let winid = win_getid() + new + call win_execute(winid, 'normal 1G$') + call assert_equal(3, col('.', winid)) + call win_execute(winid, 'normal 2G') + call assert_equal(8, col('$', winid)) + call assert_equal(0, col('.', 5001)) + bw! endfunc @@ -1248,12 +1385,15 @@ endfunc " Test for the inputdialog() function func Test_inputdialog() - CheckNotGui - - call feedkeys(":let v=inputdialog('Q:', 'xx', 'yy')\<CR>\<CR>", 'xt') - call assert_equal('xx', v) - call feedkeys(":let v=inputdialog('Q:', 'xx', 'yy')\<CR>\<Esc>", 'xt') - call assert_equal('yy', v) + if has('gui_running') + call assert_fails('let v=inputdialog([], "xx")', 'E730:') + call assert_fails('let v=inputdialog("Q", [])', 'E730:') + else + call feedkeys(":let v=inputdialog('Q:', 'xx', 'yy')\<CR>\<CR>", 'xt') + call assert_equal('xx', v) + call feedkeys(":let v=inputdialog('Q:', 'xx', 'yy')\<CR>\<Esc>", 'xt') + call assert_equal('yy', v) + endif endfunc " Test for inputlist() @@ -1297,11 +1437,28 @@ func Test_inputlist() call assert_fails('call inputlist("")', 'E686:') endfunc +func Test_range_inputlist() + " flush out any garbage left in the buffer + while getchar(0) + endwhile + + call feedkeys(":let result = inputlist(range(10))\<CR>1\<CR>", 'x') + call assert_equal(1, result) + call feedkeys(":let result = inputlist(range(3, 10))\<CR>1\<CR>", 'x') + call assert_equal(1, result) + + unlet result +endfunc + func Test_balloon_show() CheckFeature balloon_eval " This won't do anything but must not crash either. call balloon_show('hi!') + if !has('gui_running') + call balloon_show(range(3)) + call balloon_show([]) + endif endfunc func Test_shellescape() @@ -1612,11 +1769,11 @@ func Test_libcall_libcallnr() call assert_equal(4, 'abcd'->libcallnr(libc, 'strlen')) call assert_equal(char2nr('A'), char2nr('a')->libcallnr(libc, 'toupper')) - call assert_fails("call libcall(libc, 'Xdoesnotexist_', '')", 'E364:') - call assert_fails("call libcallnr(libc, 'Xdoesnotexist_', '')", 'E364:') + call assert_fails("call libcall(libc, 'Xdoesnotexist_', '')", ['', 'E364:']) + call assert_fails("call libcallnr(libc, 'Xdoesnotexist_', '')", ['', 'E364:']) - call assert_fails("call libcall('Xdoesnotexist_', 'getenv', 'HOME')", 'E364:') - call assert_fails("call libcallnr('Xdoesnotexist_', 'strlen', 'abcd')", 'E364:') + call assert_fails("call libcall('Xdoesnotexist_', 'getenv', 'HOME')", ['', 'E364:']) + call assert_fails("call libcallnr('Xdoesnotexist_', 'strlen', 'abcd')", ['', 'E364:']) endfunc sandbox function Fsandbox() @@ -1633,6 +1790,10 @@ func Test_func_sandbox() call assert_fails('call Fsandbox()', 'E48:') delfunc Fsandbox + + " From a sandbox try to set a predefined variable (which cannot be modified + " from a sandbox) + call assert_fails('sandbox let v:lnum = 10', 'E794:') endfunc func EditAnotherFile() @@ -1715,9 +1876,8 @@ endfunc func Test_confirm() " requires a UI to be active throw 'Skipped: use test/functional/vimscript/input_spec.lua' - if !has('unix') || has('gui_running') - return - endif + CheckUnix + CheckNotGui call feedkeys('o', 'L') let a = confirm('Press O to proceed') @@ -1732,7 +1892,7 @@ func Test_confirm() call assert_equal(2, a) " confirm() should return 0 when pressing CTRL-C. - call feedkeys("\<C-c>", 'L') + call feedkeys("\<C-C>", 'L') let a = confirm('Are you sure?', "&Yes\n&No") call assert_equal(0, a) @@ -1827,6 +1987,7 @@ func Test_call() call assert_equal(3, 'len'->call([123])) call assert_fails("call call('len', 123)", 'E714:') call assert_equal(0, call('', [])) + call assert_equal(0, call('len', v:_null_list)) function Mylen() dict return len(self.data) @@ -1834,11 +1995,15 @@ func Test_call() let mydict = {'data': [0, 1, 2, 3], 'len': function("Mylen")} eval mydict.len->call([], mydict)->assert_equal(4) call assert_fails("call call('Mylen', [], 0)", 'E715:') + call assert_fails('call foo', 'E107:') endfunc func Test_char2nr() call assert_equal(12354, char2nr('あ', 1)) call assert_equal(120, 'x'->char2nr()) + " set encoding=latin1 + call assert_equal(120, 'x'->char2nr()) + set encoding=utf-8 endfunc func Test_charclass() @@ -1915,6 +2080,274 @@ func Test_bufadd_bufload() call delete('XotherName') endfunc +func Test_range() + " destructuring + let [x, y] = range(2) + call assert_equal([0, 1], [x, y]) + + " index + call assert_equal(4, range(1, 10)[3]) + + " add() + call assert_equal([0, 1, 2, 3], add(range(3), 3)) + call assert_equal([0, 1, 2, [0, 1, 2]], add([0, 1, 2], range(3))) + call assert_equal([0, 1, 2, [0, 1, 2]], add(range(3), range(3))) + + " append() + new + call append('.', range(5)) + call assert_equal(['', '0', '1', '2', '3', '4'], getline(1, '$')) + bwipe! + + " appendbufline() + new + call appendbufline(bufnr(''), '.', range(5)) + call assert_equal(['0', '1', '2', '3', '4', ''], getline(1, '$')) + bwipe! + + " call() + func TwoArgs(a, b) + return [a:a, a:b] + endfunc + call assert_equal([0, 1], call('TwoArgs', range(2))) + + " col() + new + call setline(1, ['foo', 'bar']) + call assert_equal(2, col(range(1, 2))) + bwipe! + + " complete() + execute "normal! a\<C-r>=[complete(col('.'), range(10)), ''][1]\<CR>" + " complete_info() + execute "normal! a\<C-r>=[complete(col('.'), range(10)), ''][1]\<CR>\<C-r>=[complete_info(range(5)), ''][1]\<CR>" + + " copy() + call assert_equal([1, 2, 3], copy(range(1, 3))) + + " count() + call assert_equal(0, count(range(0), 3)) + call assert_equal(0, count(range(2), 3)) + call assert_equal(1, count(range(5), 3)) + + " cursor() + new + call setline(1, ['aaa', 'bbb', 'ccc']) + call cursor(range(1, 2)) + call assert_equal([2, 1], [col('.'), line('.')]) + bwipe! + + " deepcopy() + call assert_equal([1, 2, 3], deepcopy(range(1, 3))) + + " empty() + call assert_true(empty(range(0))) + call assert_false(empty(range(2))) + + " execute() + new + call setline(1, ['aaa', 'bbb', 'ccc']) + call execute(range(3)) + call assert_equal(2, line('.')) + bwipe! + + " extend() + call assert_equal([1, 2, 3, 4], extend([1], range(2, 4))) + call assert_equal([1, 2, 3, 4], extend(range(1, 1), range(2, 4))) + call assert_equal([1, 2, 3, 4], extend(range(1, 1), [2, 3, 4])) + + " filter() + call assert_equal([1, 3], filter(range(5), 'v:val % 2')) + + " funcref() + call assert_equal([0, 1], funcref('TwoArgs', range(2))()) + + " function() + call assert_equal([0, 1], function('TwoArgs', range(2))()) + + " garbagecollect() + let thelist = [1, range(2), 3] + let otherlist = range(3) + call test_garbagecollect_now() + + " get() + call assert_equal(4, get(range(1, 10), 3)) + call assert_equal(-1, get(range(1, 10), 42, -1)) + + " index() + call assert_equal(1, index(range(1, 5), 2)) + call assert_fails("echo index([1, 2], 1, [])", 'E745:') + + " insert() + call assert_equal([42, 1, 2, 3, 4, 5], insert(range(1, 5), 42)) + call assert_equal([42, 1, 2, 3, 4, 5], insert(range(1, 5), 42, 0)) + call assert_equal([1, 42, 2, 3, 4, 5], insert(range(1, 5), 42, 1)) + call assert_equal([1, 2, 3, 4, 42, 5], insert(range(1, 5), 42, 4)) + call assert_equal([1, 2, 3, 4, 42, 5], insert(range(1, 5), 42, -1)) + call assert_equal([1, 2, 3, 4, 5, 42], insert(range(1, 5), 42, 5)) + + " join() + call assert_equal('0 1 2 3 4', join(range(5))) + + " json_encode() + " call assert_equal('[0,1,2,3]', json_encode(range(4))) + call assert_equal('[0, 1, 2, 3]', json_encode(range(4))) + + " len() + call assert_equal(0, len(range(0))) + call assert_equal(2, len(range(2))) + call assert_equal(5, len(range(0, 12, 3))) + call assert_equal(4, len(range(3, 0, -1))) + + " list2str() + call assert_equal('ABC', list2str(range(65, 67))) + call assert_fails('let s = list2str(5)', 'E474:') + + " lock() + let thelist = range(5) + lockvar thelist + + " map() + call assert_equal([0, 2, 4, 6, 8], map(range(5), 'v:val * 2')) + + " match() + call assert_equal(3, match(range(5), 3)) + + " matchaddpos() + highlight MyGreenGroup ctermbg=green guibg=green + call matchaddpos('MyGreenGroup', range(line('.'), line('.'))) + + " matchend() + call assert_equal(4, matchend(range(5), '4')) + call assert_equal(3, matchend(range(1, 5), '4')) + call assert_equal(-1, matchend(range(1, 5), '42')) + + " matchstrpos() + call assert_equal(['4', 4, 0, 1], matchstrpos(range(5), '4')) + call assert_equal(['4', 3, 0, 1], matchstrpos(range(1, 5), '4')) + call assert_equal(['', -1, -1, -1], matchstrpos(range(1, 5), '42')) + + " max() reverse() + call assert_equal(0, max(range(0))) + call assert_equal(0, max(range(10, 9))) + call assert_equal(9, max(range(10))) + call assert_equal(18, max(range(0, 20, 3))) + call assert_equal(20, max(range(20, 0, -3))) + call assert_equal(99999, max(range(100000))) + call assert_equal(99999, max(range(99999, 0, -1))) + call assert_equal(99999, max(reverse(range(100000)))) + call assert_equal(99999, max(reverse(range(99999, 0, -1)))) + + " min() reverse() + call assert_equal(0, min(range(0))) + call assert_equal(0, min(range(10, 9))) + call assert_equal(5, min(range(5, 10))) + call assert_equal(5, min(range(5, 10, 3))) + call assert_equal(2, min(range(20, 0, -3))) + call assert_equal(0, min(range(100000))) + call assert_equal(0, min(range(99999, 0, -1))) + call assert_equal(0, min(reverse(range(100000)))) + call assert_equal(0, min(reverse(range(99999, 0, -1)))) + + " remove() + call assert_equal(1, remove(range(1, 10), 0)) + call assert_equal(2, remove(range(1, 10), 1)) + call assert_equal(9, remove(range(1, 10), 8)) + call assert_equal(10, remove(range(1, 10), 9)) + call assert_equal(10, remove(range(1, 10), -1)) + call assert_equal([3, 4, 5], remove(range(1, 10), 2, 4)) + + " repeat() + call assert_equal([0, 1, 2, 0, 1, 2], repeat(range(3), 2)) + call assert_equal([0, 1, 2], repeat(range(3), 1)) + call assert_equal([], repeat(range(3), 0)) + call assert_equal([], repeat(range(5, 4), 2)) + call assert_equal([], repeat(range(5, 4), 0)) + + " reverse() + call assert_equal([2, 1, 0], reverse(range(3))) + call assert_equal([0, 1, 2, 3], reverse(range(3, 0, -1))) + call assert_equal([9, 8, 7, 6, 5, 4, 3, 2, 1, 0], reverse(range(10))) + call assert_equal([20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10], reverse(range(10, 20))) + call assert_equal([16, 13, 10], reverse(range(10, 18, 3))) + call assert_equal([19, 16, 13, 10], reverse(range(10, 19, 3))) + call assert_equal([19, 16, 13, 10], reverse(range(10, 20, 3))) + call assert_equal([11, 14, 17, 20], reverse(range(20, 10, -3))) + call assert_equal([], reverse(range(0))) + + " TODO: setpos() + " new + " call setline(1, repeat([''], bufnr(''))) + " call setline(bufnr('') + 1, repeat('x', bufnr('') * 2 + 6)) + " call setpos('x', range(bufnr(''), bufnr('') + 3)) + " bwipe! + + " setreg() + call setreg('a', range(3)) + call assert_equal("0\n1\n2\n", getreg('a')) + + " settagstack() + call settagstack(1, #{items : range(4)}) + + " sign_define() + call assert_fails("call sign_define(range(5))", "E715:") + call assert_fails("call sign_placelist(range(5))", "E715:") + + " sign_undefine() + " call assert_fails("call sign_undefine(range(5))", "E908:") + call assert_fails("call sign_undefine(range(5))", "E155:") + + " sign_unplacelist() + call assert_fails("call sign_unplacelist(range(5))", "E715:") + + " sort() + call assert_equal([0, 1, 2, 3, 4, 5], sort(range(5, 0, -1))) + + " string() + call assert_equal('[0, 1, 2, 3, 4]', string(range(5))) + + " taglist() with 'tagfunc' + func TagFunc(pattern, flags, info) + return range(10) + endfunc + set tagfunc=TagFunc + call assert_fails("call taglist('asdf')", 'E987:') + set tagfunc= + + " term_start() + if has('terminal') && has('termguicolors') + call assert_fails('call term_start(range(3, 4))', 'E474:') + let g:terminal_ansi_colors = range(16) + if has('win32') + let cmd = "cmd /c dir" + else + let cmd = "ls" + endif + call assert_fails('call term_start("' .. cmd .. '", #{term_finish: "close"})', 'E475:') + unlet g:terminal_ansi_colors + endif + + " type() + call assert_equal(v:t_list, type(range(5))) + + " uniq() + call assert_equal([0, 1, 2, 3, 4], uniq(range(5))) + + " errors + call assert_fails('let x=range(2, 8, 0)', 'E726:') + call assert_fails('let x=range(3, 1)', 'E727:') + call assert_fails('let x=range(1, 3, -2)', 'E727:') + call assert_fails('let x=range([])', 'E745:') + call assert_fails('let x=range(1, [])', 'E745:') + call assert_fails('let x=range(1, 4, [])', 'E745:') +endfunc + +func Test_garbagecollect_now_fails() + let v:testing = 0 + call assert_fails('call test_garbagecollect_now()', 'E1142:') + let v:testing = 1 +endfunc + " Test for the eval() function func Test_eval() call assert_fails("call eval('5 a')", 'E488:') @@ -1958,6 +2391,13 @@ func Test_nr2char() call assert_equal("\x80\xfc\b" .. nr2char(0x40000000), eval('"\<M-' .. nr2char(0x40000000) .. '>"')) endfunc +" Test for screenattr(), screenchar() and screenchars() functions +func Test_screen_functions() + call assert_equal(-1, screenattr(-1, -1)) + call assert_equal(-1, screenchar(-1, -1)) + call assert_equal([], screenchars(-1, -1)) +endfunc + " Test for getcurpos() and setpos() func Test_getcurpos_setpos() new @@ -2048,6 +2488,7 @@ endfunc func Test_glob() call assert_equal('', glob(v:_null_string)) call assert_equal('', globpath(v:_null_string, v:_null_string)) + call assert_fails("let x = globpath(&rtp, 'syntax/c.vim', [])", 'E745:') call writefile([], 'Xglob1') call writefile([], 'XGLOB2') @@ -2073,4 +2514,46 @@ func Test_default_arg_value() call assert_equal('msg', HasDefault()) endfunc +" Test for gettext() +func Test_gettext() + call assert_fails('call gettext(1)', 'E475:') +endfunc + +func Test_builtin_check() + call assert_fails('let g:["trim"] = {x -> " " .. x}', 'E704:') + call assert_fails('let g:.trim = {x -> " " .. x}', 'E704:') + call assert_fails('let l:["trim"] = {x -> " " .. x}', 'E704:') + call assert_fails('let l:.trim = {x -> " " .. x}', 'E704:') + let lines =<< trim END + vim9script + var s:trim = (x) => " " .. x + END + call CheckScriptFailure(lines, 'E704:') + + call assert_fails('call extend(g:, #{foo: { -> "foo" }})', 'E704:') + let g:bar = 123 + call extend(g:, #{bar: { -> "foo" }}, "keep") + call assert_fails('call extend(g:, #{bar: { -> "foo" }}, "force")', 'E704:') + unlet g:bar + + call assert_fails('call extend(l:, #{foo: { -> "foo" }})', 'E704:') + let bar = 123 + call extend(l:, #{bar: { -> "foo" }}, "keep") + call assert_fails('call extend(l:, #{bar: { -> "foo" }}, "force")', 'E704:') + unlet bar + + call assert_fails('call extend(g:, #{foo: function("extend")})', 'E704:') + let g:bar = 123 + call extend(g:, #{bar: function("extend")}, "keep") + call assert_fails('call extend(g:, #{bar: function("extend")}, "force")', 'E704:') + unlet g:bar + + call assert_fails('call extend(l:, #{foo: function("extend")})', 'E704:') + let bar = 123 + call extend(l:, #{bar: function("extend")}, "keep") + call assert_fails('call extend(l:, #{bar: function("extend")}, "force")', 'E704:') + unlet bar +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_getcwd.vim b/src/nvim/testdir/test_getcwd.vim index a75583cd2c..073f8303dc 100644 --- a/src/nvim/testdir/test_getcwd.vim +++ b/src/nvim/testdir/test_getcwd.vim @@ -24,7 +24,7 @@ endfunc " Do all test in a separate window to avoid E211 when we recursively " delete the Xtopdir directory during cleanup -function SetUp() +func SetUp() set visualbell set nocp viminfo+=nviminfo diff --git a/src/nvim/testdir/test_getvar.vim b/src/nvim/testdir/test_getvar.vim index 5a96548893..e6b6341fce 100644 --- a/src/nvim/testdir/test_getvar.vim +++ b/src/nvim/testdir/test_getvar.vim @@ -133,11 +133,20 @@ func Test_get_lambda() call assert_equal([], get(l:L, 'args')) endfunc +func s:FooBar() +endfunc + " get({func}, {what} [, {default}]) func Test_get_func() let l:F = function('tr') call assert_equal('tr', get(l:F, 'name')) call assert_equal(l:F, get(l:F, 'func')) + + let Fb_func = function('s:FooBar') + call assert_match('<SNR>\d\+_FooBar', get(Fb_func, 'name')) + let Fb_ref = funcref('s:FooBar') + call assert_match('<SNR>\d\+_FooBar', get(Fb_ref, 'name')) + call assert_equal({'func has': 'no dict'}, get(l:F, 'dict', {'func has': 'no dict'})) call assert_equal(0, get(l:F, 'dict')) call assert_equal([], get(l:F, 'args')) diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim index b2bb189688..e369645328 100644 --- a/src/nvim/testdir/test_gf.vim +++ b/src/nvim/testdir/test_gf.vim @@ -226,6 +226,32 @@ func Test_gf_includeexpr() delfunc IncFunc endfunc +" Test for using a script-local function for 'includeexpr' +func Test_includeexpr_scriptlocal_func() + func! s:IncludeFunc() + let g:IncludeFname = v:fname + return '' + endfunc + set includeexpr=s:IncludeFunc() + call assert_equal(expand('<SID>') .. 'IncludeFunc()', &includeexpr) + new | only + call setline(1, 'TestFile1') + let g:IncludeFname = '' + call assert_fails('normal! gf', 'E447:') + call assert_equal('TestFile1', g:IncludeFname) + bw! + set includeexpr=<SID>IncludeFunc() + call assert_equal(expand('<SID>') .. 'IncludeFunc()', &includeexpr) + new | only + call setline(1, 'TestFile2') + let g:IncludeFname = '' + call assert_fails('normal! gf', 'E447:') + call assert_equal('TestFile2', g:IncludeFname) + set includeexpr& + delfunc s:IncludeFunc + bw! +endfunc + " Check that expanding directories can handle more than 255 entries. func Test_gf_subdirs_wildcard() let cwd = getcwd() diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim index cb6851250c..44a8784348 100644 --- a/src/nvim/testdir/test_global.vim +++ b/src/nvim/testdir/test_global.vim @@ -39,7 +39,7 @@ endfunc func Test_global_error() call assert_fails('g\\a', 'E10:') call assert_fails('g', 'E148:') - call assert_fails('g/\(/y', 'E476:') + call assert_fails('g/\(/y', 'E54:') endfunc " Test for printing lines using :g with different search patterns @@ -72,6 +72,18 @@ func Test_global_print() close! endfunc +func Test_global_empty_pattern() + " populate history + silent g/hello/ + + redir @a + g// + redir END + + call assert_match('Pattern not found: hello', @a) + " ^~~~~ this was previously empty +endfunc + " Test for global command with newline character func Test_global_newline() new @@ -91,6 +103,7 @@ endfunc " Test for interrupting :global using Ctrl-C func Test_interrupt_global() CheckRunVimInTerminal + let lines =<< trim END cnoremap ; <Cmd>sleep 10<CR> call setline(1, repeat(['foo'], 5)) @@ -100,14 +113,14 @@ func Test_interrupt_global() call term_sendkeys(buf, ":g/foo/norm :\<C-V>;\<CR>") " Wait for :sleep to start - call term_wait(buf) + call TermWait(buf, 100) call term_sendkeys(buf, "\<C-C>") call WaitForAssert({-> assert_match('Interrupted', term_getline(buf, 6))}, 1000) " Also test in Ex mode call term_sendkeys(buf, "gQg/foo/norm :\<C-V>;\<CR>") " Wait for :sleep to start - call term_wait(buf) + call TermWait(buf, 100) call term_sendkeys(buf, "\<C-C>") call WaitForAssert({-> assert_match('Interrupted', term_getline(buf, 5))}, 1000) diff --git a/src/nvim/testdir/test_hardcopy.vim b/src/nvim/testdir/test_hardcopy.vim deleted file mode 100644 index e390bd5cc8..0000000000 --- a/src/nvim/testdir/test_hardcopy.vim +++ /dev/null @@ -1,204 +0,0 @@ -" Test :hardcopy - -source check.vim - -func Test_printoptions() - edit test_hardcopy.vim - syn on - - for opt in ['left:5in,right:10pt,top:8mm,bottom:2pc', - \ 'left:2in,top:30pt,right:16mm,bottom:3pc', - \ 'header:3,syntax:y,number:y,wrap:n', - \ 'header:3,syntax:n,number:y,wrap:y', - \ 'header:0,syntax:a,number:y,wrap:y', - \ 'duplex:short,collate:n,jobsplit:y,portrait:n', - \ 'duplex:long,collate:y,jobsplit:n,portrait:y', - \ 'duplex:off,collate:y,jobsplit:y,portrait:y', - \ 'paper:10x14', - \ 'paper:A3', - \ 'paper:A4', - \ 'paper:A5', - \ 'paper:B4', - \ 'paper:B5', - \ 'paper:executive', - \ 'paper:folio', - \ 'paper:ledger', - \ 'paper:legal', - \ 'paper:letter', - \ 'paper:quarto', - \ 'paper:statement', - \ 'paper:tabloid', - \ 'formfeed:y', - \ ''] - exe 'set printoptions=' .. opt - if has('postscript') - 1,50hardcopy > Xhardcopy_printoptions - let lines = readfile('Xhardcopy_printoptions') - call assert_true(len(lines) > 20, opt) - call assert_true(lines[0] =~ 'PS-Adobe', opt) - call delete('Xhardcopy_printoptions') - endif - endfor - - call assert_fails('set printoptions=paper', 'E550:') - call assert_fails('set printoptions=shredder:on', 'E551:') - call assert_fails('set printoptions=left:no', 'E552:') - set printoptions& - bwipe -endfunc - -func Test_printmbfont() - " Print a help page which contains tabs, underlines (etc) to recover more code. - help syntax.txt - syn on - - for opt in [':WadaMin-Regular,b:WadaMin-Bold,i:WadaMin-Italic,o:WadaMin-Bold-Italic,c:yes,a:no', - \ ''] - exe 'set printmbfont=' .. opt - if has('postscript') - hardcopy > Xhardcopy_printmbfont - let lines = readfile('Xhardcopy_printmbfont') - call assert_true(len(lines) > 20, opt) - call assert_true(lines[0] =~ 'PS-Adobe', opt) - call delete('Xhardcopy_printmbfont') - endif - endfor - set printmbfont& - bwipe -endfunc - -func Test_printmbcharset() - CheckFeature postscript - - " digraph.txt has plenty of non-latin1 characters. - help digraph.txt - set printmbcharset=ISO10646 printencoding=utf-8 - for courier in ['yes', 'no'] - for ascii in ['yes', 'no'] - exe 'set printmbfont=r:WadaMin-Regular,b:WadaMin-Bold,i:WadaMin-Italic,o:WadaMin-BoldItalic' - \ .. ',c:' .. courier .. ',a:' .. ascii - hardcopy > Xhardcopy_printmbcharset - let lines = readfile('Xhardcopy_printmbcharset') - call assert_true(len(lines) > 20) - call assert_true(lines[0] =~ 'PS-Adobe') - endfor - endfor - - set printmbcharset=does-not-exist printencoding=utf-8 printmbfont=r:WadaMin-Regular - call assert_fails('hardcopy > Xhardcopy_printmbcharset', 'E456:') - - set printmbcharset=GB_2312-80 printencoding=utf-8 printmbfont=r:WadaMin-Regular - call assert_fails('hardcopy > Xhardcopy_printmbcharset', 'E673:') - - set printmbcharset=ISO10646 printencoding=utf-8 printmbfont= - call assert_fails('hardcopy > Xhardcopy_printmbcharset', 'E675:') - - call delete('Xhardcopy_printmbcharset') - set printmbcharset& printencoding& printmbfont& - bwipe -endfunc - -func Test_printexpr() - CheckFeature postscript - - " Not a very useful printexpr value, but enough to test - " hardcopy with 'printexpr'. - function PrintFile(fname) - call writefile(['Test printexpr: ' .. v:cmdarg], - \ 'Xhardcopy_printexpr') - call delete(a:fname) - return 0 - endfunc - set printexpr=PrintFile(v:fname_in) - - help help - hardcopy dummy args - call assert_equal(['Test printexpr: dummy args'], - \ readfile('Xhardcopy_printexpr')) - call delete('Xhardcopy_printexpr') - - " Function returns 1 to test print failure. - function PrintFails(fname) - call delete(a:fname) - return 1 - endfunc - set printexpr=PrintFails(v:fname_in) - call assert_fails('hardcopy', 'E365:') - - set printexpr& - bwipe -endfunc - -func Test_errors() - CheckFeature postscript - - edit test_hardcopy.vim - call assert_fails('hardcopy >', 'E324:') - bwipe -endfunc - -func Test_dark_background() - edit test_hardcopy.vim - syn on - - for bg in ['dark', 'light'] - exe 'set background=' .. bg - - if has('postscript') - hardcopy > Xhardcopy_dark_background - let lines = readfile('Xhardcopy_dark_background') - call assert_true(len(lines) > 20) - call assert_true(lines[0] =~ 'PS-Adobe') - call delete('Xhardcopy_dark_background') - endif - endfor - - set background& - bwipe -endfun - -func Test_empty_buffer() - CheckFeature postscript - - new - call assert_equal("\nNo text to be printed", execute('hardcopy')) - bwipe -endfunc - -func Test_printheader_parsing() - " Only test that this doesn't throw an error. - set printheader=%<%f\ %h%m%r%=%-14.(%l,%c%V%)\ %P - set printheader=%<%f%h%m%r%=%b\ 0x%B\ \ %l,%c%V\ %P - set printheader=%<%f%=\ [%1*%M%*%n%R%H]\ %-19(%3l,%02c%03V%)%O'%02b' - set printheader=...%r%{VarExists('b:gzflag','\ [GZ]')}%h... - set printheader= - set printheader& -endfunc - -func Test_fname_with_spaces() - CheckFeature postscript - - split t\ e\ s\ t.txt - call setline(1, ['just', 'some', 'text']) - hardcopy > %.ps - call assert_true(filereadable('t e s t.txt.ps')) - call delete('t e s t.txt.ps') - bwipe! -endfunc - -func Test_illegal_byte() - CheckFeature postscript - if &enc != 'utf-8' - return - endif - - new - " conversion of 0xff will fail, this used to cause a crash - call setline(1, "\xff") - hardcopy >Xpstest - - bwipe! - call delete('Xpstest') -endfunc - -" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_help.vim b/src/nvim/testdir/test_help.vim index 19c0fcd820..08dd3dcb9a 100644 --- a/src/nvim/testdir/test_help.vim +++ b/src/nvim/testdir/test_help.vim @@ -1,5 +1,23 @@ " Tests for :help +source check.vim +source vim9.vim + +func SetUp() + let s:vimruntime = $VIMRUNTIME + let s:runtimepath = &runtimepath + " Set $VIMRUNTIME to $BUILD_DIR/runtime and remove the original $VIMRUNTIME + " path from &runtimepath so that ":h local-additions" won't pick up builtin + " help files. + let $VIMRUNTIME = expand($BUILD_DIR) .. '/runtime' + set runtimepath-=../../../runtime +endfunc + +func TearDown() + let $VIMRUNTIME = s:vimruntime + let &runtimepath = s:runtimepath +endfunc + func Test_help_restore_snapshot() help set buftype= @@ -79,16 +97,42 @@ func Test_help_local_additions() call writefile(['*mydoc-ext.txt* my extended awesome doc'], 'Xruntime/doc/mydoc-ext.txt') let rtp_save = &rtp set rtp+=./Xruntime - help - 1 - call search('mydoc.txt') - call assert_equal('|mydoc.txt| my awesome doc', getline('.')) - 1 - call search('mydoc-ext.txt') - call assert_equal('|mydoc-ext.txt| my extended awesome doc', getline('.')) + help local-additions + let lines = getline(line(".") + 1, search("^$") - 1) + call assert_equal([ + \ '|mydoc-ext.txt| my extended awesome doc', + \ '|mydoc.txt| my awesome doc' + \ ], lines) + call delete('Xruntime/doc/mydoc-ext.txt') + close + + call mkdir('Xruntime-ja/doc', 'p') + call writefile(["local-additions\thelp.jax\t/*local-additions*"], 'Xruntime-ja/doc/tags-ja') + call writefile(['*help.txt* This is jax file', '', + \ 'LOCAL ADDITIONS: *local-additions*', ''], 'Xruntime-ja/doc/help.jax') + call writefile(['*work.txt* This is jax file'], 'Xruntime-ja/doc/work.jax') + call writefile(['*work2.txt* This is jax file'], 'Xruntime-ja/doc/work2.jax') + set rtp+=./Xruntime-ja + + help local-additions@en + let lines = getline(line(".") + 1, search("^$") - 1) + call assert_equal([ + \ '|mydoc.txt| my awesome doc' + \ ], lines) + close + + help local-additions@ja + let lines = getline(line(".") + 1, search("^$") - 1) + call assert_equal([ + \ '|mydoc.txt| my awesome doc', + \ '|help.txt| This is jax file', + \ '|work.txt| This is jax file', + \ '|work2.txt| This is jax file', + \ ], lines) close call delete('Xruntime', 'rf') + call delete('Xruntime-ja', 'rf') let &rtp = rtp_save endfunc @@ -98,6 +142,7 @@ func Test_help_completion() endfunc " Test for the :helptags command +" NOTE: if you run tests as root this will fail. Don't run tests as root! func Test_helptag_cmd() call mkdir('Xdir/a/doc', 'p') @@ -111,28 +156,12 @@ func Test_helptag_cmd() call assert_equal(["help-tags\ttags\t1"], readfile('Xdir/tags')) call delete('Xdir/tags') - " The following tests fail on FreeBSD for some reason - if has('unix') && !has('bsd') - " Read-only tags file - call mkdir('Xdir/doc', 'p') - call writefile([''], 'Xdir/doc/tags') - call writefile([], 'Xdir/doc/sample.txt') - call setfperm('Xdir/doc/tags', 'r-xr--r--') - call assert_fails('helptags Xdir/doc', 'E152:', getfperm('Xdir/doc/tags')) - - let rtp = &rtp - let &rtp = 'Xdir' - helptags ALL - let &rtp = rtp - - call delete('Xdir/doc/tags') - - " No permission to read the help file - call setfperm('Xdir/a/doc/sample.txt', '-w-------') - call assert_fails('helptags Xdir', 'E153:', getfperm('Xdir/a/doc/sample.txt')) - call delete('Xdir/a/doc/sample.txt') - call delete('Xdir/tags') - endif + " Test parsing tags + call writefile(['*tag1*', 'Example: >', ' *notag*', 'Example end: *tag2*'], + \ 'Xdir/a/doc/sample.txt') + helptags Xdir + call assert_equal(["tag1\ta/doc/sample.txt\t/*tag1*", + \ "tag2\ta/doc/sample.txt\t/*tag2*"], readfile('Xdir/tags')) " Duplicate tags in the help file call writefile(['*tag1*', '*tag1*', '*tag2*'], 'Xdir/a/doc/sample.txt') @@ -141,9 +170,35 @@ func Test_helptag_cmd() call delete('Xdir', 'rf') endfunc +func Test_helptag_cmd_readonly() + CheckUnix + CheckNotRoot + + " Read-only tags file + call mkdir('Xdir/doc', 'p') + call writefile([''], 'Xdir/doc/tags') + call writefile([], 'Xdir/doc/sample.txt') + call setfperm('Xdir/doc/tags', 'r-xr--r--') + call assert_fails('helptags Xdir/doc', 'E152:', getfperm('Xdir/doc/tags')) + + let rtp = &rtp + let &rtp = 'Xdir' + helptags ALL + let &rtp = rtp + + call delete('Xdir/doc/tags') + + " No permission to read the help file + call mkdir('Xdir/b/doc', 'p') + call writefile([], 'Xdir/b/doc/sample.txt') + call setfperm('Xdir/b/doc/sample.txt', '-w-------') + call assert_fails('helptags Xdir', 'E153:', getfperm('Xdir/b/doc/sample.txt')) + call delete('Xdir', 'rf') +endfunc + " Test for setting the 'helpheight' option in the help window func Test_help_window_height() - let &cmdheight = &lines - 24 + let &cmdheight = &lines - 23 set helpheight=10 help set helpheight=14 @@ -160,5 +215,15 @@ func Test_help_long_argument() endtry endfunc +func Test_help_using_visual_match() + let lines =<< trim END + call setline(1, ' ') + /^ + exe "normal \<C-V>\<C-V>" + h5\%V] + END + call CheckScriptFailure(lines, 'E149:') +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 e84726bbfc..eae1a241e3 100644 --- a/src/nvim/testdir/test_help_tagjump.vim +++ b/src/nvim/testdir/test_help_tagjump.vim @@ -1,17 +1,5 @@ " Tests for :help! {subject} -func SetUp() - " v:progpath is …/build/bin/nvim and we need …/build/runtime - " to be added to &rtp - let builddir = fnamemodify(exepath(v:progpath), ':h:h') - let s:rtp = &rtp - let &rtp .= printf(',%s/runtime', builddir) -endfunc - -func TearDown() - let &rtp = s:rtp -endfunc - func Test_help_tagjump() help call assert_equal("help", &filetype) @@ -38,18 +26,18 @@ func Test_help_tagjump() call assert_true(getline('.') =~ '\*quotestar\*') helpclose - " The test result is different in vim. There ":help ??" will jump to the - " falsy operator ??, which hasn't been ported to neovim yet. Instead, neovim - " jumps to the tag "g??". This test result needs to be changed if neovim - " ports the falsy operator. - help ?? + " help sm?le + help ch?ckhealth call assert_equal("help", &filetype) - call assert_true(getline('.') =~ '\*g??\*') + " call assert_true(getline('.') =~ '\*:smile\*') + call assert_true(getline('.') =~ '\*:checkhealth\*') helpclose - help ch?ckhealth + help ?? call assert_equal("help", &filetype) - call assert_true(getline('.') =~ '\*:checkhealth\*') + " *??* tag needs patch 8.2.1794 + " call assert_true(getline('.') =~ '\*??\*') + call assert_true(getline('.') =~ '\*g??\*') helpclose help :? diff --git a/src/nvim/testdir/test_hide.vim b/src/nvim/testdir/test_hide.vim index 41b1a4ad7c..b3ce395523 100644 --- a/src/nvim/testdir/test_hide.vim +++ b/src/nvim/testdir/test_hide.vim @@ -1,6 +1,6 @@ " Tests for :hide command/modifier and 'hidden' option -function SetUp() +func SetUp() let s:save_hidden = &hidden let s:save_bufhidden = &bufhidden let s:save_autowrite = &autowrite diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim index e84c45c635..8a102f2e65 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -536,9 +536,7 @@ func Test_termguicolors() endfunc func Test_cursorline_after_yank() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckScreendump call writefile([ \ 'set cul rnu', @@ -578,9 +576,7 @@ func Test_put_before_cursorline() endfunc func Test_cursorline_with_visualmode() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckScreendump call writefile([ \ 'set cul', @@ -745,8 +741,18 @@ endfunc " Test for :highlight command errors func Test_highlight_cmd_errors() if has('gui_running') || has('nvim') + " This test doesn't fail in the MS-Windows console version. + call assert_fails('hi Xcomment ctermfg=fg', 'E419:') + call assert_fails('hi Xcomment ctermfg=bg', 'E420:') call assert_fails('hi ' .. repeat('a', 201) .. ' ctermfg=black', 'E1249:') endif + + " Try using a very long terminal code. Define a dummy terminal code for this + " test. + let &t_fo = "\<Esc>1;" + let c = repeat("t_fo,", 100) . "t_fo" + " call assert_fails('exe "hi Xgroup1 start=" . c', 'E422:') + let &t_fo = "" endfunc " Test for using RGB color values in a highlight group @@ -813,11 +819,11 @@ func Test_highlight_clear_restores_context() let patContextDefault = fnamemodify(scriptContextDefault, ':t') .. ' line 1' let patContextRelink = fnamemodify(scriptContextRelink, ':t') .. ' line 2' - exec "source" scriptContextDefault + exec 'source ' .. scriptContextDefault let hlContextDefault = execute("verbose hi Context") call assert_match(patContextDefault, hlContextDefault) - exec "source" scriptContextRelink + exec 'source ' .. scriptContextRelink let hlContextRelink = execute("verbose hi Context") call assert_match(patContextRelink, hlContextRelink) diff --git a/src/nvim/testdir/test_increment.vim b/src/nvim/testdir/test_increment.vim index 2559654f25..3c2b88ef9f 100644 --- a/src/nvim/testdir/test_increment.vim +++ b/src/nvim/testdir/test_increment.vim @@ -476,6 +476,10 @@ func Test_visual_increment_20() exec "norm! \<C-A>" call assert_equal(["b"], getline(1, '$')) call assert_equal([0, 1, 1, 0], getpos('.')) + " decrement a and A and increment z and Z + call setline(1, ['a', 'A', 'z', 'Z']) + exe "normal 1G\<C-X>2G\<C-X>3G\<C-A>4G\<C-A>" + call assert_equal(['a', 'A', 'z', 'Z'], getline(1, '$')) endfunc " 21) block-wise increment on part of hexadecimal @@ -566,12 +570,14 @@ endfunc " 1) <ctrl-a> " 0b11111111111111111111111111111111 func Test_visual_increment_26() - set nrformats+=alpha + set nrformats+=bin call setline(1, ["0b11111111111111111111111111111110"]) exec "norm! \<C-V>$\<C-A>" call assert_equal(["0b11111111111111111111111111111111"], getline(1, '$')) call assert_equal([0, 1, 1, 0], getpos('.')) - set nrformats-=alpha + exec "norm! \<C-V>$\<C-X>" + call assert_equal(["0b11111111111111111111111111111110"], getline(1, '$')) + set nrformats-=bin endfunc " 27) increment with 'rightreft', if supported @@ -772,7 +778,6 @@ func Test_normal_increment_03() endfunc func Test_increment_empty_line() - new call setline(1, ['0', '0', '0', '0', '0', '0', '']) exe "normal Gvgg\<C-A>" call assert_equal(['1', '1', '1', '1', '1', '1', ''], getline(1, 7)) @@ -783,8 +788,13 @@ func Test_increment_empty_line() exe "normal! c\<C-A>l" exe "normal! c\<C-X>l" call assert_equal('one two', getline(1)) +endfunc - bwipe! +" Try incrementing/decrementing a non-digit/alpha character +func Test_increment_special_char() + call setline(1, '!') + call assert_beeps("normal \<C-A>") + call assert_beeps("normal \<C-X>") endfunc " Try incrementing/decrementing a number when nrformats contains unsigned @@ -867,4 +877,21 @@ func Test_normal_increment_with_virtualedit() set virtualedit& endfunc +" Test for incrementing a signed hexadecimal and octal number +func Test_normal_increment_signed_hexoct_nr() + new + " negative sign before a hex number should be ignored + call setline(1, ["-0x9"]) + exe "norm \<C-A>" + call assert_equal(["-0xa"], getline(1, '$')) + exe "norm \<C-X>" + call assert_equal(["-0x9"], getline(1, '$')) + call setline(1, ["-007"]) + exe "norm \<C-A>" + call assert_equal(["-010"], getline(1, '$')) + exe "norm \<C-X>" + call assert_equal(["-007"], getline(1, '$')) + bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index f706322a85..ec1379a378 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -1,5 +1,8 @@ +" Test for insert completion + source screendump.vim source check.vim +source vim9.vim " Test for insert expansion func Test_ins_complete() @@ -188,17 +191,17 @@ func s:CompleteDone_CompleteFuncDict( findstart, base ) endif return { - \ 'words': [ - \ { - \ 'word': 'aword', - \ 'abbr': 'wrd', - \ 'menu': 'extra text', - \ 'info': 'words are cool', - \ 'kind': 'W', - \ 'user_data': ['one', 'two'] - \ } - \ ] - \ } + \ 'words': [ + \ { + \ 'word': 'aword', + \ 'abbr': 'wrd', + \ 'menu': 'extra text', + \ 'info': 'words are cool', + \ 'kind': 'W', + \ 'user_data': ['one', 'two'] + \ } + \ ] + \ } endfunc func s:CompleteDone_CheckCompletedItemNone() @@ -258,16 +261,16 @@ func s:CompleteDone_CompleteFuncDictNoUserData(findstart, base) endif return { - \ 'words': [ - \ { - \ 'word': 'aword', - \ 'abbr': 'wrd', - \ 'menu': 'extra text', - \ 'info': 'words are cool', - \ 'kind': 'W' - \ } - \ ] - \ } + \ 'words': [ + \ { + \ 'word': 'aword', + \ 'abbr': 'wrd', + \ 'menu': 'extra text', + \ 'info': 'words are cool', + \ 'kind': 'W', + \ } + \ ] + \ } endfunc func s:CompleteDone_CheckCompletedItemDictNoUserData() @@ -578,6 +581,33 @@ func Test_pum_with_folds_two_tabs() call delete('Xpumscript') endfunc +func Test_pum_with_preview_win() + CheckScreendump + + let lines =<< trim END + funct Omni_test(findstart, base) + if a:findstart + return col(".") - 1 + endif + return [#{word: "one", info: "1info"}, #{word: "two", info: "2info"}, #{word: "three", info: "3info"}] + endfunc + set omnifunc=Omni_test + set completeopt+=longest + END + + call writefile(lines, 'Xpreviewscript') + let buf = RunVimInTerminal('-S Xpreviewscript', #{rows: 12}) + call term_wait(buf, 100) + call term_sendkeys(buf, "Gi\<C-X>\<C-O>") + call term_wait(buf, 200) + call term_sendkeys(buf, "\<C-N>") + call VerifyScreenDump(buf, 'Test_pum_with_preview_win', {}) + + call term_sendkeys(buf, "\<Esc>") + call StopVimInTerminal(buf) + call delete('Xpreviewscript') +endfunc + " Test for inserting the tag search pattern in insert mode func Test_ins_compl_tag_sft() call writefile([ @@ -1285,6 +1315,813 @@ func Test_no_mapping_for_ctrl_x_key() bwipe! endfunc +" Test for different ways of setting the 'completefunc' option +func Test_completefunc_callback() + func CompleteFunc1(callnr, findstart, base) + call add(g:CompleteFunc1Args, [a:callnr, a:findstart, a:base]) + return a:findstart ? 0 : [] + endfunc + func CompleteFunc2(findstart, base) + call add(g:CompleteFunc2Args, [a:findstart, a:base]) + return a:findstart ? 0 : [] + endfunc + + let lines =<< trim END + #" Test for using a global function name + LET &completefunc = 'g:CompleteFunc2' + new + call setline(1, 'global') + LET g:CompleteFunc2Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'global']], g:CompleteFunc2Args) + bw! + + #" Test for using a function() + set completefunc=function('g:CompleteFunc1',\ [10]) + new + call setline(1, 'one') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[10, 1, ''], [10, 0, 'one']], g:CompleteFunc1Args) + bw! + + #" Using a funcref variable to set 'completefunc' + VAR Fn = function('g:CompleteFunc1', [11]) + LET &completefunc = Fn + new + call setline(1, 'two') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[11, 1, ''], [11, 0, 'two']], g:CompleteFunc1Args) + bw! + + #" Using string(funcref_variable) to set 'completefunc' + LET Fn = function('g:CompleteFunc1', [12]) + LET &completefunc = string(Fn) + new + call setline(1, 'two') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[12, 1, ''], [12, 0, 'two']], g:CompleteFunc1Args) + bw! + + #" Test for using a funcref() + set completefunc=funcref('g:CompleteFunc1',\ [13]) + new + call setline(1, 'three') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[13, 1, ''], [13, 0, 'three']], g:CompleteFunc1Args) + bw! + + #" Using a funcref variable to set 'completefunc' + LET Fn = funcref('g:CompleteFunc1', [14]) + LET &completefunc = Fn + new + call setline(1, 'four') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[14, 1, ''], [14, 0, 'four']], g:CompleteFunc1Args) + bw! + + #" Using a string(funcref_variable) to set 'completefunc' + LET Fn = funcref('g:CompleteFunc1', [15]) + LET &completefunc = string(Fn) + new + call setline(1, 'four') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[15, 1, ''], [15, 0, 'four']], g:CompleteFunc1Args) + bw! + + #" Test for using a lambda function with set + VAR optval = "LSTART a, b LMIDDLE CompleteFunc1(16, a, b) LEND" + LET optval = substitute(optval, ' ', '\\ ', 'g') + exe "set completefunc=" .. optval + new + call setline(1, 'five') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[16, 1, ''], [16, 0, 'five']], g:CompleteFunc1Args) + bw! + + #" Set 'completefunc' to a lambda expression + LET &completefunc = LSTART a, b LMIDDLE CompleteFunc1(17, a, b) LEND + new + call setline(1, 'six') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[17, 1, ''], [17, 0, 'six']], g:CompleteFunc1Args) + bw! + + #" Set 'completefunc' to string(lambda_expression) + LET &completefunc = 'LSTART a, b LMIDDLE CompleteFunc1(18, a, b) LEND' + new + call setline(1, 'six') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[18, 1, ''], [18, 0, 'six']], g:CompleteFunc1Args) + bw! + + #" Set 'completefunc' to a variable with a lambda expression + VAR Lambda = LSTART a, b LMIDDLE CompleteFunc1(19, a, b) LEND + LET &completefunc = Lambda + new + call setline(1, 'seven') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[19, 1, ''], [19, 0, 'seven']], g:CompleteFunc1Args) + bw! + + #" Set 'completefunc' to a string(variable with a lambda expression) + LET Lambda = LSTART a, b LMIDDLE CompleteFunc1(20, a, b) LEND + LET &completefunc = string(Lambda) + new + call setline(1, 'seven') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[20, 1, ''], [20, 0, 'seven']], g:CompleteFunc1Args) + bw! + + #" Test for using a lambda function with incorrect return value + LET Lambda = LSTART a, b LMIDDLE strlen(a) LEND + LET &completefunc = Lambda + new + call setline(1, 'eight') + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + bw! + + #" Test for clearing the 'completefunc' option + set completefunc='' + set completefunc& + call assert_fails("set completefunc=function('abc')", "E700:") + call assert_fails("set completefunc=funcref('abc')", "E700:") + + #" set 'completefunc' to a non-existing function + set completefunc=CompleteFunc2 + call setline(1, 'five') + call assert_fails("set completefunc=function('NonExistingFunc')", 'E700:') + call assert_fails("LET &completefunc = function('NonExistingFunc')", 'E700:') + LET g:CompleteFunc2Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'five']], g:CompleteFunc2Args) + bw! + END + call CheckLegacyAndVim9Success(lines) + + " Test for using a script-local function name + func s:CompleteFunc3(findstart, base) + call add(g:CompleteFunc3Args, [a:findstart, a:base]) + return a:findstart ? 0 : [] + endfunc + set completefunc=s:CompleteFunc3 + new + call setline(1, 'script1') + let g:CompleteFunc3Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'script1']], g:CompleteFunc3Args) + bw! + + let &completefunc = 's:CompleteFunc3' + new + call setline(1, 'script2') + let g:CompleteFunc3Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'script2']], g:CompleteFunc3Args) + bw! + delfunc s:CompleteFunc3 + + " invalid return value + let &completefunc = {a -> 'abc'} + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + + " Using Vim9 lambda expression in legacy context should fail + " set completefunc=(a,\ b)\ =>\ CompleteFunc1(21,\ a,\ b) + new | only + let g:CompleteFunc1Args = [] + " call assert_fails('call feedkeys("A\<C-X>\<C-U>\<Esc>", "x")', 'E117:') + call assert_equal([], g:CompleteFunc1Args) + + " set 'completefunc' to a partial with dict. This used to cause a crash. + func SetCompleteFunc() + let params = {'complete': function('g:DictCompleteFunc')} + let &completefunc = params.complete + endfunc + func g:DictCompleteFunc(_) dict + endfunc + call SetCompleteFunc() + new + call SetCompleteFunc() + bw + call test_garbagecollect_now() + new + set completefunc= + wincmd w + set completefunc= + %bw! + delfunc g:DictCompleteFunc + delfunc SetCompleteFunc + + " Vim9 tests + let lines =<< trim END + vim9script + + def Vim9CompleteFunc(callnr: number, findstart: number, base: string): any + add(g:Vim9completeFuncArgs, [callnr, findstart, base]) + return findstart ? 0 : [] + enddef + + # Test for using a def function with completefunc + set completefunc=function('Vim9CompleteFunc',\ [60]) + new | only + setline(1, 'one') + g:Vim9completeFuncArgs = [] + feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + assert_equal([[60, 1, ''], [60, 0, 'one']], g:Vim9completeFuncArgs) + bw! + + # Test for using a global function name + &completefunc = g:CompleteFunc2 + new | only + setline(1, 'two') + g:CompleteFunc2Args = [] + feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + assert_equal([[1, ''], [0, 'two']], g:CompleteFunc2Args) + bw! + + # Test for using a script-local function name + def s:LocalCompleteFunc(findstart: number, base: string): any + add(g:LocalCompleteFuncArgs, [findstart, base]) + return findstart ? 0 : [] + enddef + &completefunc = s:LocalCompleteFunc + new | only + setline(1, 'three') + g:LocalCompleteFuncArgs = [] + feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + assert_equal([[1, ''], [0, 'three']], g:LocalCompleteFuncArgs) + bw! + END + call CheckScriptSuccess(lines) + + " cleanup + set completefunc& + delfunc CompleteFunc1 + delfunc CompleteFunc2 + unlet g:CompleteFunc1Args g:CompleteFunc2Args + %bw! +endfunc + +" Test for different ways of setting the 'omnifunc' option +func Test_omnifunc_callback() + func OmniFunc1(callnr, findstart, base) + call add(g:OmniFunc1Args, [a:callnr, a:findstart, a:base]) + return a:findstart ? 0 : [] + endfunc + func OmniFunc2(findstart, base) + call add(g:OmniFunc2Args, [a:findstart, a:base]) + return a:findstart ? 0 : [] + endfunc + + let lines =<< trim END + #" Test for using a function name + LET &omnifunc = 'g:OmniFunc2' + new + call setline(1, 'zero') + LET g:OmniFunc2Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'zero']], g:OmniFunc2Args) + bw! + + #" Test for using a function() + set omnifunc=function('g:OmniFunc1',\ [10]) + new + call setline(1, 'one') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[10, 1, ''], [10, 0, 'one']], g:OmniFunc1Args) + bw! + + #" Using a funcref variable to set 'omnifunc' + VAR Fn = function('g:OmniFunc1', [11]) + LET &omnifunc = Fn + new + call setline(1, 'two') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[11, 1, ''], [11, 0, 'two']], g:OmniFunc1Args) + bw! + + #" Using a string(funcref_variable) to set 'omnifunc' + LET Fn = function('g:OmniFunc1', [12]) + LET &omnifunc = string(Fn) + new + call setline(1, 'two') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[12, 1, ''], [12, 0, 'two']], g:OmniFunc1Args) + bw! + + #" Test for using a funcref() + set omnifunc=funcref('g:OmniFunc1',\ [13]) + new + call setline(1, 'three') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[13, 1, ''], [13, 0, 'three']], g:OmniFunc1Args) + bw! + + #" Use let to set 'omnifunc' to a funcref + LET Fn = funcref('g:OmniFunc1', [14]) + LET &omnifunc = Fn + new + call setline(1, 'four') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[14, 1, ''], [14, 0, 'four']], g:OmniFunc1Args) + bw! + + #" Using a string(funcref) to set 'omnifunc' + LET Fn = funcref("g:OmniFunc1", [15]) + LET &omnifunc = string(Fn) + new + call setline(1, 'four') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[15, 1, ''], [15, 0, 'four']], g:OmniFunc1Args) + bw! + + #" Test for using a lambda function with set + VAR optval = "LSTART a, b LMIDDLE OmniFunc1(16, a, b) LEND" + LET optval = substitute(optval, ' ', '\\ ', 'g') + exe "set omnifunc=" .. optval + new + call setline(1, 'five') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[16, 1, ''], [16, 0, 'five']], g:OmniFunc1Args) + bw! + + #" Set 'omnifunc' to a lambda expression + LET &omnifunc = LSTART a, b LMIDDLE OmniFunc1(17, a, b) LEND + new + call setline(1, 'six') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[17, 1, ''], [17, 0, 'six']], g:OmniFunc1Args) + bw! + + #" Set 'omnifunc' to a string(lambda_expression) + LET &omnifunc = 'LSTART a, b LMIDDLE OmniFunc1(18, a, b) LEND' + new + call setline(1, 'six') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[18, 1, ''], [18, 0, 'six']], g:OmniFunc1Args) + bw! + + #" Set 'omnifunc' to a variable with a lambda expression + VAR Lambda = LSTART a, b LMIDDLE OmniFunc1(19, a, b) LEND + LET &omnifunc = Lambda + new + call setline(1, 'seven') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[19, 1, ''], [19, 0, 'seven']], g:OmniFunc1Args) + bw! + + #" Set 'omnifunc' to a string(variable with a lambda expression) + LET Lambda = LSTART a, b LMIDDLE OmniFunc1(20, a, b) LEND + LET &omnifunc = string(Lambda) + new + call setline(1, 'seven') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[20, 1, ''], [20, 0, 'seven']], g:OmniFunc1Args) + bw! + + #" Test for using a lambda function with incorrect return value + LET Lambda = LSTART a, b LMIDDLE strlen(a) LEND + LET &omnifunc = Lambda + new + call setline(1, 'eight') + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + bw! + + #" Test for clearing the 'omnifunc' option + set omnifunc='' + set omnifunc& + call assert_fails("set omnifunc=function('abc')", "E700:") + call assert_fails("set omnifunc=funcref('abc')", "E700:") + + #" set 'omnifunc' to a non-existing function + set omnifunc=OmniFunc2 + call setline(1, 'nine') + call assert_fails("set omnifunc=function('NonExistingFunc')", 'E700:') + call assert_fails("LET &omnifunc = function('NonExistingFunc')", 'E700:') + LET g:OmniFunc2Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'nine']], g:OmniFunc2Args) + bw! + END + call CheckLegacyAndVim9Success(lines) + + " Test for using a script-local function name + func s:OmniFunc3(findstart, base) + call add(g:OmniFunc3Args, [a:findstart, a:base]) + return a:findstart ? 0 : [] + endfunc + set omnifunc=s:OmniFunc3 + new + call setline(1, 'script1') + let g:OmniFunc3Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'script1']], g:OmniFunc3Args) + bw! + + let &omnifunc = 's:OmniFunc3' + new + call setline(1, 'script2') + let g:OmniFunc3Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'script2']], g:OmniFunc3Args) + bw! + delfunc s:OmniFunc3 + + " invalid return value + let &omnifunc = {a -> 'abc'} + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + + " Using Vim9 lambda expression in legacy context should fail + " set omnifunc=(a,\ b)\ =>\ OmniFunc1(21,\ a,\ b) + new | only + let g:OmniFunc1Args = [] + " call assert_fails('call feedkeys("A\<C-X>\<C-O>\<Esc>", "x")', 'E117:') + call assert_equal([], g:OmniFunc1Args) + + " set 'omnifunc' to a partial with dict. This used to cause a crash. + func SetOmniFunc() + let params = {'omni': function('g:DictOmniFunc')} + let &omnifunc = params.omni + endfunc + func g:DictOmniFunc(_) dict + endfunc + call SetOmniFunc() + new + call SetOmniFunc() + bw + call test_garbagecollect_now() + new + set omnifunc= + wincmd w + set omnifunc= + %bw! + delfunc g:DictOmniFunc + delfunc SetOmniFunc + + " Vim9 tests + let lines =<< trim END + vim9script + + def Vim9omniFunc(callnr: number, findstart: number, base: string): any + add(g:Vim9omniFunc_Args, [callnr, findstart, base]) + return findstart ? 0 : [] + enddef + + # Test for using a def function with omnifunc + set omnifunc=function('Vim9omniFunc',\ [60]) + new | only + setline(1, 'one') + g:Vim9omniFunc_Args = [] + feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + assert_equal([[60, 1, ''], [60, 0, 'one']], g:Vim9omniFunc_Args) + bw! + + # Test for using a global function name + &omnifunc = g:OmniFunc2 + new | only + setline(1, 'two') + g:OmniFunc2Args = [] + feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + assert_equal([[1, ''], [0, 'two']], g:OmniFunc2Args) + bw! + + # Test for using a script-local function name + def s:LocalOmniFunc(findstart: number, base: string): any + add(g:LocalOmniFuncArgs, [findstart, base]) + return findstart ? 0 : [] + enddef + &omnifunc = s:LocalOmniFunc + new | only + setline(1, 'three') + g:LocalOmniFuncArgs = [] + feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + assert_equal([[1, ''], [0, 'three']], g:LocalOmniFuncArgs) + bw! + END + call CheckScriptSuccess(lines) + + " cleanup + set omnifunc& + delfunc OmniFunc1 + delfunc OmniFunc2 + unlet g:OmniFunc1Args g:OmniFunc2Args + %bw! +endfunc + +" Test for different ways of setting the 'thesaurusfunc' option +func Test_thesaurusfunc_callback() + func TsrFunc1(callnr, findstart, base) + call add(g:TsrFunc1Args, [a:callnr, a:findstart, a:base]) + return a:findstart ? 0 : [] + endfunc + func TsrFunc2(findstart, base) + call add(g:TsrFunc2Args, [a:findstart, a:base]) + return a:findstart ? 0 : ['sunday'] + endfunc + + let lines =<< trim END + #" Test for using a function name + LET &thesaurusfunc = 'g:TsrFunc2' + new + call setline(1, 'zero') + LET g:TsrFunc2Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'zero']], g:TsrFunc2Args) + bw! + + #" Test for using a function() + set thesaurusfunc=function('g:TsrFunc1',\ [10]) + new + call setline(1, 'one') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[10, 1, ''], [10, 0, 'one']], g:TsrFunc1Args) + bw! + + #" Using a funcref variable to set 'thesaurusfunc' + VAR Fn = function('g:TsrFunc1', [11]) + LET &thesaurusfunc = Fn + new + call setline(1, 'two') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[11, 1, ''], [11, 0, 'two']], g:TsrFunc1Args) + bw! + + #" Using a string(funcref_variable) to set 'thesaurusfunc' + LET Fn = function('g:TsrFunc1', [12]) + LET &thesaurusfunc = string(Fn) + new + call setline(1, 'two') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[12, 1, ''], [12, 0, 'two']], g:TsrFunc1Args) + bw! + + #" Test for using a funcref() + set thesaurusfunc=funcref('g:TsrFunc1',\ [13]) + new + call setline(1, 'three') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[13, 1, ''], [13, 0, 'three']], g:TsrFunc1Args) + bw! + + #" Using a funcref variable to set 'thesaurusfunc' + LET Fn = funcref('g:TsrFunc1', [14]) + LET &thesaurusfunc = Fn + new + call setline(1, 'four') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[14, 1, ''], [14, 0, 'four']], g:TsrFunc1Args) + bw! + + #" Using a string(funcref_variable) to set 'thesaurusfunc' + LET Fn = funcref('g:TsrFunc1', [15]) + LET &thesaurusfunc = string(Fn) + new + call setline(1, 'four') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[15, 1, ''], [15, 0, 'four']], g:TsrFunc1Args) + bw! + + #" Test for using a lambda function + VAR optval = "LSTART a, b LMIDDLE TsrFunc1(16, a, b) LEND" + LET optval = substitute(optval, ' ', '\\ ', 'g') + exe "set thesaurusfunc=" .. optval + new + call setline(1, 'five') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[16, 1, ''], [16, 0, 'five']], g:TsrFunc1Args) + bw! + + #" Test for using a lambda function with set + LET &thesaurusfunc = LSTART a, b LMIDDLE TsrFunc1(17, a, b) LEND + new + call setline(1, 'six') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[17, 1, ''], [17, 0, 'six']], g:TsrFunc1Args) + bw! + + #" Set 'thesaurusfunc' to a string(lambda expression) + LET &thesaurusfunc = 'LSTART a, b LMIDDLE TsrFunc1(18, a, b) LEND' + new + call setline(1, 'six') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[18, 1, ''], [18, 0, 'six']], g:TsrFunc1Args) + bw! + + #" Set 'thesaurusfunc' to a variable with a lambda expression + VAR Lambda = LSTART a, b LMIDDLE TsrFunc1(19, a, b) LEND + LET &thesaurusfunc = Lambda + new + call setline(1, 'seven') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[19, 1, ''], [19, 0, 'seven']], g:TsrFunc1Args) + bw! + + #" Set 'thesaurusfunc' to a string(variable with a lambda expression) + LET Lambda = LSTART a, b LMIDDLE TsrFunc1(20, a, b) LEND + LET &thesaurusfunc = string(Lambda) + new + call setline(1, 'seven') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[20, 1, ''], [20, 0, 'seven']], g:TsrFunc1Args) + bw! + + #" Test for using a lambda function with incorrect return value + LET Lambda = LSTART a, b LMIDDLE strlen(a) LEND + LET &thesaurusfunc = Lambda + new + call setline(1, 'eight') + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + bw! + + #" Test for clearing the 'thesaurusfunc' option + set thesaurusfunc='' + set thesaurusfunc& + call assert_fails("set thesaurusfunc=function('abc')", "E700:") + call assert_fails("set thesaurusfunc=funcref('abc')", "E700:") + + #" set 'thesaurusfunc' to a non-existing function + set thesaurusfunc=TsrFunc2 + call setline(1, 'ten') + call assert_fails("set thesaurusfunc=function('NonExistingFunc')", 'E700:') + call assert_fails("LET &thesaurusfunc = function('NonExistingFunc')", 'E700:') + LET g:TsrFunc2Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'ten']], g:TsrFunc2Args) + bw! + + #" Use a buffer-local value and a global value + set thesaurusfunc& + setlocal thesaurusfunc=function('g:TsrFunc1',\ [22]) + call setline(1, 'sun') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", "x") + call assert_equal('sun', getline(1)) + call assert_equal([[22, 1, ''], [22, 0, 'sun']], g:TsrFunc1Args) + new + call setline(1, 'sun') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", "x") + call assert_equal('sun', getline(1)) + call assert_equal([], g:TsrFunc1Args) + set thesaurusfunc=function('g:TsrFunc1',\ [23]) + wincmd w + call setline(1, 'sun') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", "x") + call assert_equal('sun', getline(1)) + call assert_equal([[22, 1, ''], [22, 0, 'sun']], g:TsrFunc1Args) + :%bw! + END + call CheckLegacyAndVim9Success(lines) + + " Test for using a script-local function name + func s:TsrFunc3(findstart, base) + call add(g:TsrFunc3Args, [a:findstart, a:base]) + return a:findstart ? 0 : [] + endfunc + set tsrfu=s:TsrFunc3 + new + call setline(1, 'script1') + let g:TsrFunc3Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'script1']], g:TsrFunc3Args) + bw! + + let &tsrfu = 's:TsrFunc3' + new + call setline(1, 'script2') + let g:TsrFunc3Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'script2']], g:TsrFunc3Args) + bw! + delfunc s:TsrFunc3 + + " invalid return value + let &thesaurusfunc = {a -> 'abc'} + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + + " Using Vim9 lambda expression in legacy context should fail + " set thesaurusfunc=(a,\ b)\ =>\ TsrFunc1(21,\ a,\ b) + new | only + let g:TsrFunc1Args = [] + " call assert_fails('call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")', 'E117:') + call assert_equal([], g:TsrFunc1Args) + bw! + + " set 'thesaurusfunc' to a partial with dict. This used to cause a crash. + func SetTsrFunc() + let params = {'thesaurus': function('g:DictTsrFunc')} + let &thesaurusfunc = params.thesaurus + endfunc + func g:DictTsrFunc(_) dict + endfunc + call SetTsrFunc() + new + call SetTsrFunc() + bw + call test_garbagecollect_now() + new + set thesaurusfunc= + wincmd w + %bw! + delfunc SetTsrFunc + + " set buffer-local 'thesaurusfunc' to a partial with dict. This used to + " cause a crash. + func SetLocalTsrFunc() + let params = {'thesaurus': function('g:DictTsrFunc')} + let &l:thesaurusfunc = params.thesaurus + endfunc + call SetLocalTsrFunc() + call test_garbagecollect_now() + call SetLocalTsrFunc() + set thesaurusfunc= + bw! + delfunc g:DictTsrFunc + delfunc SetLocalTsrFunc + + " Vim9 tests + let lines =<< trim END + vim9script + + def Vim9tsrFunc(callnr: number, findstart: number, base: string): any + add(g:Vim9tsrFunc_Args, [callnr, findstart, base]) + return findstart ? 0 : [] + enddef + + # Test for using a def function with thesaurusfunc + set thesaurusfunc=function('Vim9tsrFunc',\ [60]) + new | only + setline(1, 'one') + g:Vim9tsrFunc_Args = [] + feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + assert_equal([[60, 1, ''], [60, 0, 'one']], g:Vim9tsrFunc_Args) + bw! + + # Test for using a global function name + &thesaurusfunc = g:TsrFunc2 + new | only + setline(1, 'two') + g:TsrFunc2Args = [] + feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + assert_equal([[1, ''], [0, 'two']], g:TsrFunc2Args) + bw! + + # Test for using a script-local function name + def s:LocalTsrFunc(findstart: number, base: string): any + add(g:LocalTsrFuncArgs, [findstart, base]) + return findstart ? 0 : [] + enddef + &thesaurusfunc = s:LocalTsrFunc + new | only + setline(1, 'three') + g:LocalTsrFuncArgs = [] + feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + assert_equal([[1, ''], [0, 'three']], g:LocalTsrFuncArgs) + bw! + END + call CheckScriptSuccess(lines) + + " cleanup + set thesaurusfunc& + delfunc TsrFunc1 + delfunc TsrFunc2 + unlet g:TsrFunc1Args g:TsrFunc2Args + %bw! +endfunc + func FooBarComplete(findstart, base) if a:findstart return col('.') - 1 diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim index c178c87d3e..025eb016a8 100644 --- a/src/nvim/testdir/test_lambda.vim +++ b/src/nvim/testdir/test_lambda.vim @@ -62,7 +62,8 @@ endfunc function Test_lambda_fails() call assert_equal(3, {a, b -> a + b}(1, 2)) call assert_fails('echo {a, a -> a + a}(1, 2)', 'E853:') - call assert_fails('echo {a, b -> a + b)}(1, 2)', 'E15:') + call assert_fails('echo {a, b -> a + b)}(1, 2)', 'E451:') + echo assert_fails('echo 10->{a -> a + 2}', 'E107:') endfunc func Test_not_lambda() @@ -125,7 +126,7 @@ func Test_lambda_closure_counter() endfunc let l:F = s:foo() - call garbagecollect() + call test_garbagecollect_now() call assert_equal(1, l:F()) call assert_equal(2, l:F()) call assert_equal(3, l:F()) @@ -208,9 +209,9 @@ func Test_lambda_circular_reference() endfunc call s:Foo() - call garbagecollect() + call test_garbagecollect_now() let i = 0 | while i < 10000 | call s:Foo() | let i+= 1 | endwhile - call garbagecollect() + call test_garbagecollect_now() endfunc func Test_lambda_combination() @@ -239,11 +240,16 @@ func Test_closure_counter() endfunc let l:F = s:foo() - call garbagecollect() + call test_garbagecollect_now() call assert_equal(1, l:F()) call assert_equal(2, l:F()) call assert_equal(3, l:F()) call assert_equal(4, l:F()) + + call assert_match("^\n function <SNR>\\d\\+_bar() closure" + \ .. "\n1 let x += 1" + \ .. "\n2 return x" + \ .. "\n endfunction$", execute('func s:bar')) endfunc func Test_closure_unlet() @@ -257,7 +263,7 @@ func Test_closure_unlet() endfunc call assert_false(has_key(s:foo(), 'x')) - call garbagecollect() + call test_garbagecollect_now() endfunc func LambdaFoo() @@ -294,7 +300,7 @@ func Test_named_function_closure() endfunc call Afoo() call assert_equal(14, s:Abar()) - call garbagecollect() + call test_garbagecollect_now() call assert_equal(14, s:Abar()) endfunc diff --git a/src/nvim/testdir/test_langmap.vim b/src/nvim/testdir/test_langmap.vim index 4f831aa40b..aaed77e109 100644 --- a/src/nvim/testdir/test_langmap.vim +++ b/src/nvim/testdir/test_langmap.vim @@ -49,6 +49,41 @@ func Test_langmap() call feedkeys(';', 'tx') call assert_equal(5, col('.')) + set langmap=RL + let g:counter = 0 + nnoremap L;L <Cmd>let g:counter += 1<CR> + nnoremap <C-L> <Cmd>throw 'This mapping should not be triggered'<CR> + + " 'langmap' is applied to keys without modifiers when matching a mapping + call feedkeys('R;R', 'tx') + call assert_equal(1, g:counter) + nunmap L;L + unlet g:counter + + delete + call assert_equal('', getline(1)) + undo + call assert_equal('Hello World', getline(1)) + " 'langmap' does not change Ctrl-R to Ctrl-L for consistency + call feedkeys("\<*C-R>", 'tx') + call assert_equal('', getline(1)) + + set langmap=6L + undo + setlocal bufhidden=hide + let oldbuf = bufnr() + enew + call assert_notequal(oldbuf, bufnr()) + " 'langmap' does not change Ctrl-6 to Ctrl-L for consistency + " Ctrl-6 becomes Ctrl-^ after merging the Ctrl modifier + call feedkeys("\<*C-6>", 'tx') + call assert_equal(oldbuf, bufnr()) + setlocal bufhidden& + + nunmap <C-L> + set langmap& quit! endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_legacy_filetype.vim b/src/nvim/testdir/test_legacy_filetype.vim deleted file mode 100644 index 772faaadb0..0000000000 --- a/src/nvim/testdir/test_legacy_filetype.vim +++ /dev/null @@ -1,4 +0,0 @@ -let g:do_legacy_filetype = 1 -filetype on - -source test_filetype.vim diff --git a/src/nvim/testdir/test_let.vim b/src/nvim/testdir/test_let.vim index 6cb736a38a..35745e9c6a 100644 --- a/src/nvim/testdir/test_let.vim +++ b/src/nvim/testdir/test_let.vim @@ -6,6 +6,10 @@ func Test_let() let Test104#numvar = function('tr') call assert_equal("function('tr')", string(Test104#numvar)) + let foo#tr = function('tr') + call assert_equal("function('tr')", string(foo#tr)) + unlet foo#tr + let a = 1 let b = 2 @@ -25,9 +29,62 @@ func Test_let() let s = "\na #1\nb #2" call assert_equal(s, out) + " Test for displaying a string variable + let s = 'vim' + let out = execute('let s') + let s = "\ns vim" + call assert_equal(s, out) + + " Test for displaying a list variable + let l = [1, 2] + let out = execute('let l') + let s = "\nl [1, 2]" + call assert_equal(s, out) + + " Test for displaying a dict variable + let d = {'k' : 'v'} + let out = execute('let d') + let s = "\nd {'k': 'v'}" + call assert_equal(s, out) + + " Test for displaying a function reference variable + let F = function('min') + let out = execute('let F') + let s = "\nF *min()" + call assert_equal(s, out) + let x = 0 if 0 | let x = 1 | endif call assert_equal(0, x) + + " Display a list item using an out of range index + let l = [10] + call assert_fails('let l[1]', 'E684:') + + " List special variable dictionaries + let g:Test_Global_Var = 5 + call assert_match("\nTest_Global_Var #5", execute('let g:')) + unlet g:Test_Global_Var + + let b:Test_Buf_Var = 8 + call assert_match("\nb:Test_Buf_Var #8", execute('let b:')) + unlet b:Test_Buf_Var + + let w:Test_Win_Var = 'foo' + call assert_equal("\nw:Test_Win_Var foo", execute('let w:')) + unlet w:Test_Win_Var + + let t:Test_Tab_Var = 'bar' + call assert_equal("\nt:Test_Tab_Var bar", execute('let t:')) + unlet t:Test_Tab_Var + + let s:Test_Script_Var = [7] + call assert_match("\ns:Test_Script_Var \\[7]", execute('let s:')) + unlet s:Test_Script_Var + + let l:Test_Local_Var = {'k' : 5} + call assert_match("\nl:Test_Local_Var {'k': 5}", execute('let l:')) + call assert_match("v:errors []", execute('let v:')) endfunc func s:set_arg1(a) abort @@ -201,16 +258,68 @@ func Test_let_option_error() let &fillchars = _w endfunc +" Errors with the :let statement func Test_let_errors() let s = 'abcd' call assert_fails('let s[1] = 5', 'E689:') let l = [1, 2, 3] call assert_fails('let l[:] = 5', 'E709:') + + call assert_fails('let x:lnum=5', ['E121:', 'E488:']) + call assert_fails('let v:=5', 'E461:') + call assert_fails('let [a]', 'E474:') + call assert_fails('let [a, b] = [', 'E697:') + call assert_fails('let [a, b] = [10, 20', 'E696:') + call assert_fails('let [a, b] = 10', 'E714:') + call assert_fails('let [a, , b] = [10, 20]', 'E475:') + call assert_fails('let [a, b&] = [10, 20]', 'E475:') + call assert_fails('let $ = 10', 'E475:') + call assert_fails('let $FOO[1] = "abc"', 'E18:') + call assert_fails('let &buftype[1] = "nofile"', 'E18:') + let s = "var" + let var = 1 + call assert_fails('let var += [1,2]', 'E734:') + call assert_fails('let {s}.1 = 2', 'E1203:') + call assert_fails('let a[1] = 5', 'E121:') + let l = [[1,2]] + call assert_fails('let l[:][0] = [5]', 'E708:') + let d = {'k' : 4} + call assert_fails('let d.# = 5', 'E488:') + call assert_fails('let d.m += 5', 'E716:') + call assert_fails('let m = d[{]', 'E15:') + let l = [1, 2] + call assert_fails('let l[2] = 0', 'E684:') + call assert_fails('let l[0:1] = [1, 2, 3]', 'E710:') + call assert_fails('let l[-2:-3] = [3, 4]', 'E684:') + call assert_fails('let l[0:4] = [5, 6]', 'E711:') + call assert_fails('let l -= 2', 'E734:') + call assert_fails('let l += 2', 'E734:') + call assert_fails('let g:["a;b"] = 10', 'E461:') + call assert_fails('let g:.min = function("max")', 'E704:') + call assert_fails('let g:cos = "" | let g:.cos = {-> 42}', 'E704:') + if has('channel') + let ch = test_null_channel() + call assert_fails('let ch += 1', 'E734:') + endif + call assert_fails('let name = "a" .. "b",', 'E488: Trailing characters: ,') + + " This test works only when the language is English + if v:lang == "C" || v:lang =~ '^[Ee]n' + call assert_fails('let [a ; b;] = [10, 20]', + \ 'Double ; in list of variables') + endif endfunc func Test_let_heredoc_fails() call assert_fails('let v =<< marker', 'E991:') + try + exe "let v =<< TEXT | abc | TEXT" + call assert_report('No exception thrown') + catch /E488:/ + catch + call assert_report("Caught exception: " .. v:exception) + endtry let text =<< trim END func WrongSyntax() @@ -243,6 +352,10 @@ func Test_let_heredoc_fails() call writefile(text, 'XheredocBadMarker') call assert_fails('source XheredocBadMarker', 'E221:') call delete('XheredocBadMarker') + + call writefile(['let v =<< TEXT', 'abc'], 'XheredocMissingMarker') + call assert_fails('source XheredocMissingMarker', 'E990:') + call delete('XheredocMissingMarker') endfunc func Test_let_heredoc_trim_no_indent_marker() @@ -361,3 +474,5 @@ E END call assert_equal([' x', ' \y', ' z'], [a, b, c]) endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_lispwords.vim b/src/nvim/testdir/test_lispindent.vim index 4144fb0521..2d6060bba3 100644 --- a/src/nvim/testdir/test_lispwords.vim +++ b/src/nvim/testdir/test_lispindent.vim @@ -86,6 +86,37 @@ func Test_lisp_indent() set nolisp endfunc +func Test_lispindent_negative() + " in legacy script there is no error + call assert_equal(-1, lispindent(-1)) +endfunc + +func Test_lispindent_with_indentexpr() + enew + setl ai lisp nocin indentexpr=11 + exe "normal a(x\<CR>1\<CR>2)\<Esc>" + let expected = ['(x', ' 1', ' 2)'] + call assert_equal(expected, getline(1, 3)) + " with Lisp indenting the first line is not indented + normal 1G=G + call assert_equal(expected, getline(1, 3)) + + %del + setl lispoptions=expr:1 indentexpr=5 + exe "normal a(x\<CR>1\<CR>2)\<Esc>" + let expected_expr = ['(x', ' 1', ' 2)'] + call assert_equal(expected_expr, getline(1, 3)) + normal 2G2<<=G + call assert_equal(expected_expr, getline(1, 3)) + + setl lispoptions=expr:0 + " with Lisp indenting the first line is not indented + normal 1G3<<=G + call assert_equal(expected, getline(1, 3)) + + bwipe! +endfunc + func Test_lisp_indent_works() " This was reading beyond the end of the line new diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim index 9cef6905a5..9ecd83265a 100644 --- a/src/nvim/testdir/test_listdict.vim +++ b/src/nvim/testdir/test_listdict.vim @@ -31,6 +31,12 @@ func Test_list_slice() call assert_equal([1, 'as''d', [1, 2, function('strlen')]], l[:-2]) call assert_equal([1, 'as''d', [1, 2, function('strlen')], {'a': 1}], l[0:8]) call assert_equal([], l[8:-1]) + call assert_equal([], l[0:-10]) + " perform an operation on a list slice + let l = [1, 2, 3] + let l[:1] += [1, 2] + let l[2:] -= [1] + call assert_equal([2, 4, 2], l) endfunc " List identity @@ -104,6 +110,8 @@ func Test_list_range_assign() let l = [0] let l[:] = [1, 2] call assert_equal([1, 2], l) + let l[-4:-1] = [5, 6] + call assert_equal([5, 6], l) endfunc " Test removing items in list @@ -143,6 +151,20 @@ func Test_list_func_remove() call assert_fails("call remove(l, l)", 'E745:') endfunc +" List add() function +func Test_list_add() + let l = [] + call add(l, 1) + call add(l, [2, 3]) + call add(l, []) + call add(l, v:_null_list) + call add(l, {'k' : 3}) + call add(l, {}) + call add(l, v:_null_dict) + call assert_equal([1, [2, 3], [], [], {'k' : 3}, {}, {}], l) + " call assert_equal(1, add(v:_null_list, 4)) +endfunc + " Tests for Dictionary type func Test_dict() @@ -166,6 +188,19 @@ func Test_dict() call filter(d, 'v:key =~ ''[ac391]''') call assert_equal({'c': 'ccc', '1': 99, '3': 33, '-1': {'a': 1}}, d) + " duplicate key + call assert_fails("let d = {'k' : 10, 'k' : 20}", 'E721:') + " missing comma + call assert_fails("let d = {'k' : 10 'k' : 20}", 'E722:') + " missing curly brace + call assert_fails("let d = {'k' : 10,", 'E723:') + " invalid key + call assert_fails('let d = #{++ : 10}', 'E15:') + " wrong type for key + call assert_fails('let d={[] : 10}', 'E730:') + " undefined variable as value + call assert_fails("let d={'k' : i}", 'E121:') + " allow key starting with number at the start, not a curly expression call assert_equal({'1foo': 77}, #{1foo: 77}) @@ -254,6 +289,16 @@ func Test_dict_func() call assert_equal('xxx3', Fn('xxx')) endfunc +func Test_dict_assign() + let d = {} + let d.1 = 1 + let d._ = 2 + call assert_equal({'1': 1, '_': 2}, d) + + let n = 0 + call assert_fails('let n.key = 3', 'E1203: Dot can only be used on a dictionary: n.key = 3') +endfunc + " Function in script-local List or Dict func Test_script_local_dict_func() let g:dict = {} @@ -266,7 +311,7 @@ func Test_script_local_dict_func() unlet g:dict endfunc -" Test removing items in la dictionary +" Test removing items in a dictionary func Test_dict_func_remove() let d = {1:'a', 2:'b', 3:'c'} call assert_equal('b', remove(d, 2)) @@ -301,17 +346,18 @@ func Test_dict_deepcopy() let l = [4, d, 6] let d[3] = l let dc = deepcopy(d) - call assert_fails('call deepcopy(d, 1)', 'E698') + call assert_fails('call deepcopy(d, 1)', 'E698:') let l2 = [0, l, l, 3] let l[1] = l2 let l3 = deepcopy(l2) call assert_true(l3[1] is l3[2]) + call assert_fails("call deepcopy([1, 2], 2)", 'E1023:') endfunc " Locked variables func Test_list_locked_var() let expected = [ - \ [['0000-000', 'ppppppp'], + \ [['1000-000', 'ppppppF'], \ ['0000-000', 'ppppppp'], \ ['0000-000', 'ppppppp']], \ [['1000-000', 'ppppppF'], @@ -338,7 +384,7 @@ func Test_list_locked_var() exe "unlockvar " . depth . " l" endif let ps = islocked("l").islocked("l[1]").islocked("l[1][1]").islocked("l[1][1][0]").'-'.islocked("l[2]").islocked("l[2]['6']").islocked("l[2]['6'][7]") - call assert_equal(expected[depth][u][0], ps) + call assert_equal(expected[depth][u][0], ps, 'depth: ' .. depth) let ps = '' try let l[1][1][0] = 99 @@ -382,15 +428,20 @@ func Test_list_locked_var() catch let ps .= 'F' endtry - call assert_equal(expected[depth][u][1], ps) + call assert_equal(expected[depth][u][1], ps, 'depth: ' .. depth) endfor endfor + call assert_fails("let x=islocked('a b')", 'E488:') + let mylist = [1, 2, 3] + call assert_fails("let x = islocked('mylist[1:2]')", 'E786:') + let mydict = {'k' : 'v'} + call assert_fails("let x = islocked('mydict.a')", 'E716:') endfunc " Unletting locked variables func Test_list_locked_var_unlet() let expected = [ - \ [['0000-000', 'ppppppp'], + \ [['1000-000', 'ppppppp'], \ ['0000-000', 'ppppppp'], \ ['0000-000', 'ppppppp']], \ [['1000-000', 'ppFppFp'], @@ -418,7 +469,7 @@ func Test_list_locked_var_unlet() exe "unlockvar " . depth . " l" endif let ps = islocked("l").islocked("l[1]").islocked("l[1][1]").islocked("l[1][1][0]").'-'.islocked("l[2]").islocked("l[2]['6']").islocked("l[2]['6'][7]") - call assert_equal(expected[depth][u][0], ps) + call assert_equal(expected[depth][u][0], ps, 'depth: ' .. depth) let ps = '' try unlet l[2]['6'][7] @@ -465,6 +516,11 @@ func Test_list_locked_var_unlet() call assert_equal(expected[depth][u][1], ps) endfor endfor + " Deleting a list range should fail if the range is locked + let l = [1, 2, 3, 4] + lockvar l[1:2] + call assert_fails('unlet l[1:2]', 'E741:') + unlet l endfunc " Locked variables and :unlet or list / dict functions @@ -574,6 +630,18 @@ func Test_let_lock_list() unlet l endfunc +" Locking part of the list +func Test_let_lock_list_items() + let l = [1, 2, 3, 4] + lockvar l[2:] + call assert_equal(0, islocked('l[0]')) + call assert_equal(1, islocked('l[2]')) + call assert_equal(1, islocked('l[3]')) + call assert_fails('let l[2] = 10', 'E741:') + call assert_fails('let l[3] = 20', 'E741:') + unlet l +endfunc + " lockvar/islocked() triggering script autoloading func Test_lockvar_script_autoload() let old_rtp = &rtp @@ -605,6 +673,9 @@ func Test_func_arg_list() call s:arg_list_test(1, 2, [3, 4], {5: 6}) endfunc +func Test_dict_item_locked() +endfunc + " Tests for reverse(), sort(), uniq() func Test_reverse_sort_uniq() let l = ['-0', 'A11', 2, 2, 'xaaa', 4, 'foo', 'foo6', 'foo', [0, 1, 2], 'x8', [0, 1, 2], 1.5] @@ -627,7 +698,10 @@ func Test_reverse_sort_uniq() endif call assert_fails('call reverse("")', 'E899:') - call assert_fails('call uniq([1, 2], {x, y -> []})', 'E882:') + call assert_fails('call uniq([1, 2], {x, y -> []})', 'E745:') + call assert_fails("call sort([1, 2], function('min'), 1)", "E715:") + call assert_fails("call sort([1, 2], function('invalid_func'))", "E700:") + call assert_fails("call sort([1, 2], function('min'))", "E118:") endfunc " reduce a list or a blob @@ -675,7 +749,7 @@ func Test_reduce() call assert_equal(42, reduce(v:_null_blob, function('add'), 42)) endfunc -" splitting a string to a List +" splitting a string to a List using split() func Test_str_split() call assert_equal(['aa', 'bb'], split(' aa bb ')) call assert_equal(['aa', 'bb'], split(' aa bb ', '\W\+', 0)) @@ -686,6 +760,9 @@ func Test_str_split() call assert_equal(['aa', '', 'bb', 'cc', ''], split('aa,,bb, cc,', ',\s*', 1)) call assert_equal(['a', 'b', 'c'], split('abc', '\zs')) call assert_equal(['', 'a', '', 'b', '', 'c', ''], split('abc', '\zs', 1)) + call assert_fails("call split('abc', [])", 'E730:') + call assert_fails("call split('abc', 'b', [])", 'E745:') + call assert_equal(['abc'], split('abc', '\\%(')) endfunc " compare recursively linked list and dict @@ -697,6 +774,12 @@ func Test_listdict_compare() call assert_true(d == d) call assert_false(l != deepcopy(l)) call assert_false(d != deepcopy(d)) + + " comparison errors + call assert_fails('echo [1, 2] =~ {}', 'E691:') + call assert_fails('echo [1, 2] =~ [1, 2]', 'E692:') + call assert_fails('echo {} =~ 5', 'E735:') + call assert_fails('echo {} =~ {}', 'E736:') endfunc " compare complex recursively linked list and dict @@ -870,6 +953,67 @@ func Test_scope_dict() call s:check_scope_dict('v', v:true) endfunc +" Test for deep nesting of lists (> 100) +func Test_deep_nested_list() + let deep_list = [] + let l = deep_list + for i in range(102) + let newlist = [] + call add(l, newlist) + let l = newlist + endfor + call add(l, 102) + + call assert_fails('let m = deepcopy(deep_list)', 'E698:') + call assert_fails('lockvar 110 deep_list', 'E743:') + call assert_fails('unlockvar 110 deep_list', 'E743:') + " Nvim implements :echo very differently + " call assert_fails('let x = execute("echo deep_list")', 'E724:') + call test_garbagecollect_now() + unlet deep_list +endfunc + +" Test for deep nesting of dicts (> 100) +func Test_deep_nested_dict() + let deep_dict = {} + let d = deep_dict + for i in range(102) + let newdict = {} + let d.k = newdict + let d = newdict + endfor + let d.k = 'v' + + call assert_fails('let m = deepcopy(deep_dict)', 'E698:') + call assert_fails('lockvar 110 deep_dict', 'E743:') + call assert_fails('unlockvar 110 deep_dict', 'E743:') + " Nvim implements :echo very differently + " call assert_fails('let x = execute("echo deep_dict")', 'E724:') + call test_garbagecollect_now() + unlet deep_dict +endfunc + +" List and dict indexing tests +func Test_listdict_index() + call assert_fails('echo function("min")[0]', 'E695:') + call assert_fails('echo v:true[0]', 'E909:') + let d = {'k' : 10} + call assert_fails('echo d.', 'E15:') + call assert_fails('echo d[1:2]', 'E719:') + call assert_fails("let v = [4, 6][{-> 1}]", 'E729:') + call assert_fails("let v = range(5)[2:[]]", 'E730:') + call assert_fails("let v = range(5)[2:{-> 2}(]", ['E15:', 'E116:']) + call assert_fails("let v = range(5)[2:3", 'E111:') + call assert_fails("let l = insert([1,2,3], 4, 10)", 'E684:') + call assert_fails("let l = insert([1,2,3], 4, -10)", 'E684:') + call assert_fails("let l = insert([1,2,3], 4, [])", 'E745:') + let l = [1, 2, 3] + call assert_fails("let l[i] = 3", 'E121:') + call assert_fails("let l[1.1] = 4", 'E806:') + call assert_fails("let l[:i] = [4, 5]", 'E121:') + call assert_fails("let l[:3.2] = [4, 5]", 'E806:') +endfunc + " Test for a null list func Test_null_list() let l = v:_null_list @@ -906,3 +1050,21 @@ func Test_null_list() call assert_equal(1, islocked('l')) unlockvar l endfunc + +" Test for a null dict +func Test_null_dict() + call assert_equal(v:_null_dict, v:_null_dict) + let d = v:_null_dict + call assert_equal({}, d) + call assert_equal(0, len(d)) + call assert_equal(1, empty(d)) + call assert_equal(0, items(d)) + call assert_equal(0, keys(d)) + call assert_equal(0, values(d)) + call assert_false(has_key(d, 'k')) + call assert_equal('{}', string(d)) + call assert_fails('let x = v:_null_dict[10]') + call assert_equal({}, {}) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_makeencoding.vim b/src/nvim/testdir/test_makeencoding.vim index c53c07d991..e297bdc228 100644 --- a/src/nvim/testdir/test_makeencoding.vim +++ b/src/nvim/testdir/test_makeencoding.vim @@ -107,3 +107,19 @@ func Test_make() lclose endfor endfunc + +" Test for an error file with a long line that needs an encoding conversion +func Test_longline_conversion() + new + call setline(1, ['Xfile:10:' .. repeat("\xe0", 2000)]) + write ++enc=latin1 Xerr.out + bw! + set errorformat& + set makeencoding=latin1 + cfile Xerr.out + call assert_equal(repeat("\u00e0", 2000), getqflist()[0].text) + call delete('Xerr.out') + set makeencoding& +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim index bde3624adf..5c5a65d4ca 100644 --- a/src/nvim/testdir/test_mapping.vim +++ b/src/nvim/testdir/test_mapping.vim @@ -395,7 +395,9 @@ func Test_motionforce_omap() endfunc func Test_error_in_map_expr() - if !has('terminal') || (has('win32') && has('gui_running')) + " Unlike CheckRunVimInTerminal this does work in a win32 console + CheckFeature terminal + if has('win32') && has('gui_running') throw 'Skipped: cannot run Vim in a terminal window' endif @@ -765,6 +767,11 @@ func Test_mapcomplete() mapclear endfunc +func GetAbbrText() + unabbr hola + return 'hello' +endfunc + " Test for <expr> in abbreviation func Test_expr_abbr() new @@ -777,10 +784,17 @@ func Test_expr_abbr() " invalid <expr> abbreviation abbr <expr> hte GetAbbr() call assert_fails('normal ihte ', 'E117:') - call assert_equal(' ', getline(1)) + call assert_equal('', getline(1)) unabbr <expr> hte - close! + " evaluating the expression deletes the abbreviation + abbr <expr> hola GetAbbrText() + call assert_equal('GetAbbrText()', maparg('hola', 'i', '1')) + call feedkeys("ahola \<Esc>", 'xt') + call assert_equal('hello ', getline('.')) + call assert_equal('', maparg('hola', 'i', '1')) + + bwipe! endfunc " Test for storing mappings in different modes in a vimrc file @@ -1048,6 +1062,30 @@ func Test_mouse_drag_mapped_start_select() set mouse& endfunc +func Test_mouse_drag_statusline() + set laststatus=2 + set mouse=a + func ClickExpr() + call Ntest_setmouse(&lines - 1, 1) + return "\<LeftMouse>" + endfunc + func DragExpr() + call Ntest_setmouse(&lines - 2, 1) + return "\<LeftDrag>" + endfunc + nnoremap <expr> <F2> ClickExpr() + nnoremap <expr> <F3> DragExpr() + + " this was causing a crash in win_drag_status_line() + call feedkeys("\<F2>:tabnew\<CR>\<F3>", 'tx') + + nunmap <F2> + nunmap <F3> + delfunc ClickExpr + delfunc DragExpr + set laststatus& mouse& +endfunc + " Test for mapping <LeftDrag> in Insert mode func Test_mouse_drag_insert_map() set mouse=a diff --git a/src/nvim/testdir/test_marks.vim b/src/nvim/testdir/test_marks.vim index 74e63d9d69..a7ccca498c 100644 --- a/src/nvim/testdir/test_marks.vim +++ b/src/nvim/testdir/test_marks.vim @@ -91,6 +91,17 @@ func Test_setpos() call assert_equal([0, 1, 21341234, 0], getpos("'a")) call assert_equal(4, virtcol("'a")) + " Test with invalid buffer number, line number and column number + call cursor(2, 2) + call setpos('.', [-1, 1, 1, 0]) + call assert_equal([2, 2], [line('.'), col('.')]) + call setpos('.', [0, -1, 1, 0]) + call assert_equal([2, 2], [line('.'), col('.')]) + call setpos('.', [0, 1, -1, 0]) + call assert_equal([2, 2], [line('.'), col('.')]) + + call assert_fails("call setpos('ab', [0, 1, 1, 0])", 'E474:') + bwipe! call win_gotoid(twowin) bwipe! @@ -293,4 +304,17 @@ func Test_getmarklist() close! endfunc +" This was using freed memory +func Test_jump_mark_autocmd() + next 00 + edit 0 + sargument + au BufEnter 0 all + sil norm + + au! BufEnter + bwipe! +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_match.vim b/src/nvim/testdir/test_match.vim index 4f22e54563..600b6132a9 100644 --- a/src/nvim/testdir/test_match.vim +++ b/src/nvim/testdir/test_match.vim @@ -160,12 +160,14 @@ endfunc func Test_matchadd_error() call clearmatches() " Nvim: not an error anymore: + " call assert_fails("call matchadd('GroupDoesNotExist', 'X')", 'E28:') call matchadd('GroupDoesNotExist', 'X') call assert_equal([{'group': 'GroupDoesNotExist', 'pattern': 'X', 'priority': 10, 'id': 1206}], getmatches()) - call assert_fails("call matchadd('Search', '\\(')", 'E475:') + call assert_fails("call matchadd('Search', '\\(')", 'E54:') call assert_fails("call matchadd('Search', 'XXX', 1, 123, 1)", 'E715:') call assert_fails("call matchadd('Error', 'XXX', 1, 3)", 'E798:') call assert_fails("call matchadd('Error', 'XXX', 1, 0)", 'E799:') + call assert_fails("call matchadd('Error', 'XXX', [], 0)", 'E745:') endfunc func Test_matchaddpos() @@ -305,7 +307,10 @@ func Test_matchaddpos_error() call assert_fails("call matchaddpos('Error', [1], 1, 123, 1)", 'E715:') call assert_fails("call matchaddpos('Error', [1], 1, 5, {'window':12345})", 'E957:') " Why doesn't the following error have an error code E...? + " call assert_fails("call matchaddpos('Error', [{}])", 'E290:') call assert_fails("call matchaddpos('Error', [{}])", 'E5031:') + call assert_equal(-1, matchaddpos('Error', v:_null_list)) + call assert_fails("call matchaddpos('Error', [1], [], 1)", 'E745:') endfunc func OtherWindowCommon() @@ -322,9 +327,8 @@ func OtherWindowCommon() endfunc func Test_matchdelete_other_window() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckScreendump + let buf = OtherWindowCommon() call term_sendkeys(buf, ":call matchdelete(mid, winid)\<CR>") call VerifyScreenDump(buf, 'Test_matchdelete_1', {}) @@ -339,9 +343,7 @@ func Test_matchdelete_error() endfunc func Test_matchclear_other_window() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckRunVimInTerminal let buf = OtherWindowCommon() call term_sendkeys(buf, ":call clearmatches(winid)\<CR>") call VerifyScreenDump(buf, 'Test_matchclear_1', {}) @@ -351,9 +353,7 @@ func Test_matchclear_other_window() endfunc func Test_matchadd_other_window() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckRunVimInTerminal let buf = OtherWindowCommon() call term_sendkeys(buf, ":call matchadd('Search', 'Hello', 1, -1, #{window: winid})\<CR>") call term_sendkeys(buf, ":\<CR>") @@ -363,5 +363,80 @@ func Test_matchadd_other_window() call delete('XscriptMatchCommon') endfunc +func Test_match_in_linebreak() + CheckRunVimInTerminal + + let lines =<< trim END + set breakindent linebreak breakat+=] + call printf('%s]%s', repeat('x', 50), repeat('x', 70))->setline(1) + call matchaddpos('ErrorMsg', [[1, 51]]) + END + call writefile(lines, 'XscriptMatchLinebreak') + let buf = RunVimInTerminal('-S XscriptMatchLinebreak', #{rows: 10}) + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_match_linebreak', {}) + + call StopVimInTerminal(buf) + call delete('XscriptMatchLinebreak') +endfunc + +func Test_match_with_incsearch() + CheckRunVimInTerminal + + let lines =<< trim END + set incsearch + call setline(1, range(20)) + call matchaddpos('ErrorMsg', [3]) + END + call writefile(lines, 'XmatchWithIncsearch') + let buf = RunVimInTerminal('-S XmatchWithIncsearch', #{rows: 6}) + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_match_with_incsearch_1', {}) + + call term_sendkeys(buf, ":s/0") + call VerifyScreenDump(buf, 'Test_match_with_incsearch_2', {}) + + call term_sendkeys(buf, "\<CR>") + call StopVimInTerminal(buf) + call delete('XmatchWithIncsearch') +endfunc + +" Test for deleting matches outside of the screen redraw top/bottom lines +" This should cause a redraw of those lines. +func Test_matchdelete_redraw() + new + call setline(1, range(1, 500)) + call cursor(250, 1) + let m1 = matchaddpos('Search', [[250]]) + let m2 = matchaddpos('Search', [[10], [450]]) + redraw! + let m3 = matchaddpos('Search', [[240], [260]]) + call matchdelete(m2) + let m = getmatches() + call assert_equal(2, len(m)) + call assert_equal([250], m[0].pos1) + redraw! + call matchdelete(m1) + call assert_equal(1, len(getmatches())) + bw! +endfunc + +func Test_match_tab_with_linebreak() + CheckRunVimInTerminal + + let lines =<< trim END + set linebreak + call setline(1, "\tix") + call matchadd('ErrorMsg', '\t') + END + call writefile(lines, 'XscriptMatchTabLinebreak') + let buf = RunVimInTerminal('-S XscriptMatchTabLinebreak', #{rows: 10}) + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_match_tab_linebreak', {}) + + call StopVimInTerminal(buf) + call delete('XscriptMatchTabLinebreak') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_matchfuzzy.vim b/src/nvim/testdir/test_matchfuzzy.vim index c836bc87aa..b46550fbc3 100644 --- a/src/nvim/testdir/test_matchfuzzy.vim +++ b/src/nvim/testdir/test_matchfuzzy.vim @@ -6,9 +6,7 @@ source check.vim " Test for matchfuzzy() func Test_matchfuzzy() call assert_fails('call matchfuzzy(10, "abc")', 'E686:') - " Needs v8.2.1183; match the final error that's thrown for now - " call assert_fails('call matchfuzzy(["abc"], [])', 'E730:') - call assert_fails('call matchfuzzy(["abc"], [])', 'E475:') + call assert_fails('call matchfuzzy(["abc"], [])', 'E730:') call assert_fails("let x = matchfuzzy(v:_null_list, 'foo')", 'E686:') call assert_fails('call matchfuzzy(["abc"], v:_null_string)', 'E475:') call assert_equal([], matchfuzzy([], 'abc')) @@ -75,12 +73,9 @@ func Test_matchfuzzy() call assert_fails("let x = matchfuzzy(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:') call assert_equal([], matchfuzzy(l, 'cam')) " Nvim's callback implementation is different, so E6000 is expected instead, - " but we need v8.2.1183 to assert it " call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E921:') - " call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E6000:') - call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E475:') - " call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E730:') - call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E475:') + call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E6000:') + call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E730:') call assert_fails("let x = matchfuzzy(l, 'cam', v:_null_dict)", 'E715:') call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : v:_null_string})", 'E475:') " Nvim doesn't have null functions @@ -155,12 +150,9 @@ func Test_matchfuzzypos() call assert_fails("let x = matchfuzzypos(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:') call assert_equal([[], [], []], matchfuzzypos(l, 'cam')) " Nvim's callback implementation is different, so E6000 is expected instead, - " but we need v8.2.1183 to assert it " call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E921:') - " call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E6000:') - call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E475:') - " call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E730:') - call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E475:') + call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E6000:') + call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E730:') call assert_fails("let x = matchfuzzypos(l, 'cam', v:_null_dict)", 'E715:') call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : v:_null_string})", 'E475:') " Nvim doesn't have null functions diff --git a/src/nvim/testdir/test_menu.vim b/src/nvim/testdir/test_menu.vim index db7ec92bf8..a1121632e6 100644 --- a/src/nvim/testdir/test_menu.vim +++ b/src/nvim/testdir/test_menu.vim @@ -153,7 +153,7 @@ func Test_menu_errors() call assert_fails('menu Test.Foo.Bar', 'E327:') call assert_fails('cmenu Test.Foo', 'E328:') call assert_fails('emenu x Test.Foo', 'E475:') - call assert_fails('emenu Test.Foo.Bar', 'E334:') + call assert_fails('emenu Test.Foo.Bar', 'E327:') call assert_fails('menutranslate Test', 'E474:') silent! unmenu Foo @@ -252,6 +252,7 @@ func Test_menu_info() nmenu Test.abc <Nop> call assert_equal('<Nop>', menu_info('Test.abc').rhs) call assert_fails('call menu_info([])', 'E730:') + call assert_fails('call menu_info("", [])', 'E730:') nunmenu Test " Test for defining menus in different modes @@ -429,7 +430,7 @@ func Test_menu_special() nunmenu Test.Sign endfunc -" Test for "icon=filname" in a toolbar +" Test for "icon=filename" in a toolbar func Test_menu_icon() CheckFeature toolbar nmenu icon=myicon.xpm Toolbar.Foo :echo "Foo"<CR> diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim index 42a1fdcfe2..bfdebdac79 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -353,7 +353,7 @@ func Test_echo_verbose_system() " display a page and go back, results in exactly the same view call term_sendkeys(buf, ' ') - call TermWait(buf) + call TermWait(buf, 50) call term_sendkeys(buf, 'b') call VerifyScreenDump(buf, 'Test_verbose_system_1', {}) @@ -366,7 +366,7 @@ func Test_echo_verbose_system() call VerifyScreenDump(buf, 'Test_verbose_system_2', {}) call term_sendkeys(buf, ' ') - call TermWait(buf) + call TermWait(buf, 50) call term_sendkeys(buf, 'b') call VerifyScreenDump(buf, 'Test_verbose_system_2', {}) diff --git a/src/nvim/testdir/test_method.vim b/src/nvim/testdir/test_method.vim index cdf688b857..ca3b736429 100644 --- a/src/nvim/testdir/test_method.vim +++ b/src/nvim/testdir/test_method.vim @@ -35,6 +35,7 @@ func Test_list_method() call assert_equal(v:t_list, l->type()) call assert_equal([1, 2, 3], [1, 1, 2, 3, 3]->uniq()) call assert_fails('eval l->values()', 'E715:') + call assert_fails('echo []->len', 'E107:') endfunc func Test_dict_method() @@ -130,9 +131,9 @@ func Test_method_syntax() eval [1, 2, 3] \ ->sort( \ ) - call assert_fails('eval [1, 2, 3]-> sort()', 'E260:') + call assert_fails('eval [1, 2, 3]-> sort()', 'E15:') call assert_fails('eval [1, 2, 3]->sort ()', 'E274:') - call assert_fails('eval [1, 2, 3]-> sort ()', 'E260:') + call assert_fails('eval [1, 2, 3]-> sort ()', 'E15:') endfunc func Test_method_lambda() @@ -152,6 +153,22 @@ endfunc func Test_method_not_supported() call assert_fails('eval 123->changenr()', 'E276:') + call assert_fails('echo "abc"->invalidfunc()', 'E117:') + " Test for too many or too few arguments to a method + call assert_fails('let n="abc"->len(2)', 'E118:') + call assert_fails('let n=10->setwinvar()', 'E119:') endfunc -" vim: shiftwidth=2 sts=2 expandtab +" Test for passing optional arguments to methods +func Test_method_args() + let v:errors = [] + let n = 10->assert_inrange(1, 5, "Test_assert_inrange") + if v:errors[0] !~ 'Test_assert_inrange' + call assert_report(v:errors[0]) + else + " Test passed + let v:errors = [] + endif +endfunc + +" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index ccc775560f..972397cb91 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -851,9 +851,7 @@ func Test_mksession_shortmess_with_A() edit Xtestfile write let fname = swapname('%') - " readblob() needs patch 8.2.2343 - " let cont = readblob(fname) - let cont = readfile(fname, 'B') + let cont = readblob(fname) set sessionoptions-=options mksession Xtestsession bwipe! @@ -992,6 +990,38 @@ func Test_altfile() call assert_equal('Xtwoalt', bufname('#')) only bwipe! + call delete('Xtest_altfile') +endfunc + +" Test for creating views with manual folds +func Test_mkview_manual_fold() + call writefile(range(1,10), 'Xfile') + new Xfile + " create recursive folds + 5,6fold + 4,7fold + mkview Xview + normal zE + source Xview + call assert_equal([-1, 4, 4, 4, 4, -1], [foldclosed(3), foldclosed(4), + \ foldclosed(5), foldclosed(6), foldclosed(7), foldclosed(8)]) + " open one level of fold + 4foldopen + mkview! Xview + normal zE + source Xview + call assert_equal([-1, -1, 5, 5, -1, -1], [foldclosed(3), foldclosed(4), + \ foldclosed(5), foldclosed(6), foldclosed(7), foldclosed(8)]) + " open all the folds + %foldopen! + mkview! Xview + normal zE + source Xview + call assert_equal([-1, -1, -1, -1, -1, -1], [foldclosed(3), foldclosed(4), + \ foldclosed(5), foldclosed(6), foldclosed(7), foldclosed(8)]) + call delete('Xfile') + call delete('Xview') + bw! endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 4f842189b6..7c90b444e5 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -3,6 +3,7 @@ source shared.vim source check.vim source view_util.vim +source vim9.vim source screendump.vim func Setup_NewWindow() @@ -35,14 +36,14 @@ func CountSpaces(type, ...) else silent exe "normal! `[v`]y" endif - let g:a=strlen(substitute(@@, '[^ ]', '', 'g')) + let g:a = strlen(substitute(@@, '[^ ]', '', 'g')) let &selection = sel_save let @@ = reg_save endfunc func OpfuncDummy(type, ...) " for testing operatorfunc - let g:opt=&linebreak + let g:opt = &linebreak if a:0 " Invoked from Visual mode, use gv command. silent exe "normal! gvy" @@ -53,7 +54,7 @@ func OpfuncDummy(type, ...) endif " Create a new dummy window new - let g:bufnr=bufnr('%') + let g:bufnr = bufnr('%') endfunc func Test_normal00_optrans() @@ -120,6 +121,39 @@ func Test_normal01_keymodel() call feedkeys("Vkk\<Up>yy", 'tx') call assert_equal(['47', '48', '49', '50'], getreg(0, 0, 1)) + " Test for using special keys to start visual selection + %d + call setline(1, ['red fox tail', 'red fox tail', 'red fox tail']) + set keymodel=startsel + " Test for <S-PageUp> and <S-PageDown> + call cursor(1, 1) + call feedkeys("\<S-PageDown>y", 'xt') + call assert_equal([0, 1, 1, 0], getpos("'<")) + call assert_equal([0, 3, 1, 0], getpos("'>")) + call feedkeys("Gz\<CR>8|\<S-PageUp>y", 'xt') + call assert_equal([0, 2, 1, 0], getpos("'<")) + call assert_equal([0, 3, 8, 0], getpos("'>")) + " Test for <S-C-Home> and <S-C-End> + call cursor(2, 12) + call feedkeys("\<S-C-Home>y", 'xt') + call assert_equal([0, 1, 1, 0], getpos("'<")) + call assert_equal([0, 2, 12, 0], getpos("'>")) + call cursor(1, 4) + call feedkeys("\<S-C-End>y", 'xt') + call assert_equal([0, 1, 4, 0], getpos("'<")) + call assert_equal([0, 3, 13, 0], getpos("'>")) + " Test for <S-C-Left> and <S-C-Right> + call cursor(2, 5) + call feedkeys("\<S-C-Right>y", 'xt') + call assert_equal([0, 2, 5, 0], getpos("'<")) + call assert_equal([0, 2, 9, 0], getpos("'>")) + call cursor(2, 9) + call feedkeys("\<S-C-Left>y", 'xt') + call assert_equal([0, 2, 5, 0], getpos("'<")) + call assert_equal([0, 2, 9, 0], getpos("'>")) + + set keymodel& + " clean up bw! endfunc @@ -140,16 +174,15 @@ func Test_normal03_join() $ :j 10 call assert_equal('100', getline('.')) + call assert_beeps('normal GVJ') " clean up bw! endfunc +" basic filter test func Test_normal04_filter() - " basic filter test " only test on non windows platform - if has('win32') - return - endif + CheckNotMSWindows call Setup_NewWindow() 1 call feedkeys("!!sed -e 's/^/| /'\n", 'tx') @@ -222,12 +255,49 @@ func Test_normal_formatexpr_returns_nonzero() close! endfunc +" Test for using a script-local function for 'formatexpr' +func Test_formatexpr_scriptlocal_func() + func! s:Format() + let g:FormatArgs = [v:lnum, v:count] + endfunc + set formatexpr=s:Format() + call assert_equal(expand('<SID>') .. 'Format()', &formatexpr) + new | only + call setline(1, range(1, 40)) + let g:FormatArgs = [] + normal! 2GVjgq + call assert_equal([2, 2], g:FormatArgs) + bw! + set formatexpr=<SID>Format() + call assert_equal(expand('<SID>') .. 'Format()', &formatexpr) + new | only + call setline(1, range(1, 40)) + let g:FormatArgs = [] + normal! 4GVjgq + call assert_equal([4, 2], g:FormatArgs) + bw! + let &formatexpr = 's:Format()' + new | only + call setline(1, range(1, 40)) + let g:FormatArgs = [] + normal! 6GVjgq + call assert_equal([6, 2], g:FormatArgs) + bw! + let &formatexpr = '<SID>Format()' + new | only + call setline(1, range(1, 40)) + let g:FormatArgs = [] + normal! 8GVjgq + call assert_equal([8, 2], g:FormatArgs) + setlocal formatexpr= + delfunc s:Format + bw! +endfunc + +" basic test for formatprg func Test_normal06_formatprg() - " basic test for formatprg " only test on non windows platform - if has('win32') - return - endif + CheckNotMSWindows " uses sed to number non-empty lines call writefile(['#!/bin/sh', 'sed ''/./=''|sed ''/./{', 'N', 's/\n/ /', '}'''], 'Xsed_format.sh') @@ -240,16 +310,24 @@ func Test_normal06_formatprg() set formatprg=./Xsed_format.sh norm! gggqG call assert_equal(expected, getline(1, '$')) - bw! + %d - 10new call setline(1, text) set formatprg=donothing setlocal formatprg=./Xsed_format.sh norm! gggqG call assert_equal(expected, getline(1, '$')) - bw! + %d + + " Check for the command-line ranges added to 'formatprg' + set formatprg=cat + call setline(1, ['one', 'two', 'three', 'four', 'five']) + call feedkeys('gggqG', 'xt') + call assert_equal('.,$!cat', @:) + call feedkeys('2Ggq2j', 'xt') + call assert_equal('.,.+2!cat', @:) + bw! " clean up set formatprg= setlocal formatprg= @@ -263,18 +341,16 @@ func Test_normal07_internalfmt() 10new call setline(1, list) set tw=12 - norm! gggqG + norm! ggVGgq call assert_equal(['1 2 3', '4 5 6', '7 8 9', '10 11 '], getline(1, '$')) " clean up set tw=0 bw! endfunc +" basic tests for foldopen/folddelete func Test_normal08_fold() - " basic tests for foldopen/folddelete - if !has("folding") - return - endif + CheckFeature folding call Setup_NewWindow() 50 setl foldenable fdm=marker @@ -352,70 +428,6 @@ func Test_normal09a_operatorfunc() norm V10j,, call assert_equal(22, g:a) - " Use a lambda function for 'opfunc' - unmap <buffer> ,, - call cursor(1, 1) - let g:a=0 - nmap <buffer><silent> ,, :set opfunc={type\ ->\ CountSpaces(type)}<CR>g@ - vmap <buffer><silent> ,, :<C-U>call CountSpaces(visualmode(), 1)<CR> - 50 - norm V2j,, - call assert_equal(6, g:a) - norm V,, - call assert_equal(2, g:a) - norm ,,l - call assert_equal(0, g:a) - 50 - exe "norm 0\<c-v>10j2l,," - call assert_equal(11, g:a) - 50 - norm V10j,, - call assert_equal(22, g:a) - - " use a partial function for 'opfunc' - let g:OpVal = 0 - func! Test_opfunc1(x, y, type) - let g:OpVal = a:x + a:y - endfunc - set opfunc=function('Test_opfunc1',\ [5,\ 7]) - normal! g@l - call assert_equal(12, g:OpVal) - " delete the function and try to use g@ - delfunc Test_opfunc1 - call test_garbagecollect_now() - call assert_fails('normal! g@l', 'E117:') - set opfunc= - - " use a funcref for 'opfunc' - let g:OpVal = 0 - func! Test_opfunc2(x, y, type) - let g:OpVal = a:x + a:y - endfunc - set opfunc=funcref('Test_opfunc2',\ [4,\ 3]) - normal! g@l - call assert_equal(7, g:OpVal) - " delete the function and try to use g@ - delfunc Test_opfunc2 - call test_garbagecollect_now() - call assert_fails('normal! g@l', 'E933:') - set opfunc= - - " Try to use a function with two arguments for 'operatorfunc' - let g:OpVal = 0 - func! Test_opfunc3(x, y) - let g:OpVal = 4 - endfunc - set opfunc=Test_opfunc3 - call assert_fails('normal! g@l', 'E119:') - call assert_equal(0, g:OpVal) - set opfunc= - delfunc Test_opfunc3 - unlet g:OpVal - - " Try to use a lambda function with two arguments for 'operatorfunc' - set opfunc={x,\ y\ ->\ 'done'} - call assert_fails('normal! g@l', 'E119:') - " clean up unmap <buffer> ,, set opfunc= @@ -484,6 +496,227 @@ func Test_normal09c_operatorfunc() set operatorfunc= endfunc +" Test for different ways of setting the 'operatorfunc' option +func Test_opfunc_callback() + new + func OpFunc1(callnr, type) + let g:OpFunc1Args = [a:callnr, a:type] + endfunc + func OpFunc2(type) + let g:OpFunc2Args = [a:type] + endfunc + + let lines =<< trim END + #" Test for using a function name + LET &opfunc = 'g:OpFunc2' + LET g:OpFunc2Args = [] + normal! g@l + call assert_equal(['char'], g:OpFunc2Args) + + #" Test for using a function() + set opfunc=function('g:OpFunc1',\ [10]) + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([10, 'char'], g:OpFunc1Args) + + #" Using a funcref variable to set 'operatorfunc' + VAR Fn = function('g:OpFunc1', [11]) + LET &opfunc = Fn + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([11, 'char'], g:OpFunc1Args) + + #" Using a string(funcref_variable) to set 'operatorfunc' + LET Fn = function('g:OpFunc1', [12]) + LET &operatorfunc = string(Fn) + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([12, 'char'], g:OpFunc1Args) + + #" Test for using a funcref() + set operatorfunc=funcref('g:OpFunc1',\ [13]) + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([13, 'char'], g:OpFunc1Args) + + #" Using a funcref variable to set 'operatorfunc' + LET Fn = funcref('g:OpFunc1', [14]) + LET &opfunc = Fn + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([14, 'char'], g:OpFunc1Args) + + #" Using a string(funcref_variable) to set 'operatorfunc' + LET Fn = funcref('g:OpFunc1', [15]) + LET &opfunc = string(Fn) + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([15, 'char'], g:OpFunc1Args) + + #" Test for using a lambda function using set + VAR optval = "LSTART a LMIDDLE OpFunc1(16, a) LEND" + LET optval = substitute(optval, ' ', '\\ ', 'g') + exe "set opfunc=" .. optval + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([16, 'char'], g:OpFunc1Args) + + #" Test for using a lambda function using LET + LET &opfunc = LSTART a LMIDDLE OpFunc1(17, a) LEND + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([17, 'char'], g:OpFunc1Args) + + #" Set 'operatorfunc' to a string(lambda expression) + LET &opfunc = 'LSTART a LMIDDLE OpFunc1(18, a) LEND' + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([18, 'char'], g:OpFunc1Args) + + #" Set 'operatorfunc' to a variable with a lambda expression + VAR Lambda = LSTART a LMIDDLE OpFunc1(19, a) LEND + LET &opfunc = Lambda + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([19, 'char'], g:OpFunc1Args) + + #" Set 'operatorfunc' to a string(variable with a lambda expression) + LET Lambda = LSTART a LMIDDLE OpFunc1(20, a) LEND + LET &opfunc = string(Lambda) + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([20, 'char'], g:OpFunc1Args) + + #" Try to use 'operatorfunc' after the function is deleted + func g:TmpOpFunc1(type) + let g:TmpOpFunc1Args = [21, a:type] + endfunc + LET &opfunc = function('g:TmpOpFunc1') + delfunc g:TmpOpFunc1 + call test_garbagecollect_now() + LET g:TmpOpFunc1Args = [] + call assert_fails('normal! g@l', 'E117:') + call assert_equal([], g:TmpOpFunc1Args) + + #" Try to use a function with two arguments for 'operatorfunc' + func g:TmpOpFunc2(x, y) + let g:TmpOpFunc2Args = [a:x, a:y] + endfunc + set opfunc=TmpOpFunc2 + LET g:TmpOpFunc2Args = [] + call assert_fails('normal! g@l', 'E119:') + call assert_equal([], g:TmpOpFunc2Args) + delfunc TmpOpFunc2 + + #" Try to use a lambda function with two arguments for 'operatorfunc' + LET &opfunc = LSTART a, b LMIDDLE OpFunc1(22, b) LEND + LET g:OpFunc1Args = [] + call assert_fails('normal! g@l', 'E119:') + call assert_equal([], g:OpFunc1Args) + + #" Test for clearing the 'operatorfunc' option + set opfunc='' + set opfunc& + call assert_fails("set opfunc=function('abc')", "E700:") + call assert_fails("set opfunc=funcref('abc')", "E700:") + + #" set 'operatorfunc' to a non-existing function + LET &opfunc = function('g:OpFunc1', [23]) + call assert_fails("set opfunc=function('NonExistingFunc')", 'E700:') + call assert_fails("LET &opfunc = function('NonExistingFunc')", 'E700:') + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([23, 'char'], g:OpFunc1Args) + END + call CheckTransLegacySuccess(lines) + + " Test for using a script-local function name + func s:OpFunc3(type) + let g:OpFunc3Args = [a:type] + endfunc + set opfunc=s:OpFunc3 + let g:OpFunc3Args = [] + normal! g@l + call assert_equal(['char'], g:OpFunc3Args) + + let &opfunc = 's:OpFunc3' + let g:OpFunc3Args = [] + normal! g@l + call assert_equal(['char'], g:OpFunc3Args) + delfunc s:OpFunc3 + + " Using Vim9 lambda expression in legacy context should fail + " set opfunc=(a)\ =>\ OpFunc1(24,\ a) + let g:OpFunc1Args = [] + " call assert_fails('normal! g@l', 'E117:') + call assert_equal([], g:OpFunc1Args) + + " set 'operatorfunc' to a partial with dict. This used to cause a crash. + func SetOpFunc() + let operator = {'execute': function('OperatorExecute')} + let &opfunc = operator.execute + endfunc + func OperatorExecute(_) dict + endfunc + call SetOpFunc() + call test_garbagecollect_now() + set operatorfunc= + delfunc SetOpFunc + delfunc OperatorExecute + + " Vim9 tests + let lines =<< trim END + vim9script + + def g:Vim9opFunc(val: number, type: string): void + g:OpFunc1Args = [val, type] + enddef + + # Test for using a def function with opfunc + set opfunc=function('g:Vim9opFunc',\ [60]) + g:OpFunc1Args = [] + normal! g@l + assert_equal([60, 'char'], g:OpFunc1Args) + + # Test for using a global function name + &opfunc = g:OpFunc2 + g:OpFunc2Args = [] + normal! g@l + assert_equal(['char'], g:OpFunc2Args) + bw! + + # Test for using a script-local function name + def s:LocalOpFunc(type: string): void + g:LocalOpFuncArgs = [type] + enddef + &opfunc = s:LocalOpFunc + g:LocalOpFuncArgs = [] + normal! g@l + assert_equal(['char'], g:LocalOpFuncArgs) + bw! + END + call CheckScriptSuccess(lines) + + " setting 'opfunc' to a script local function outside of a script context + " should fail + let cleanup =<< trim END + call writefile([execute('messages')], 'Xtest.out') + qall + END + call writefile(cleanup, 'Xverify.vim') + call RunVim([], [], "-c \"set opfunc=s:abc\" -S Xverify.vim") + call assert_match('E81: Using <SID> not in a', readfile('Xtest.out')[0]) + call delete('Xtest.out') + call delete('Xverify.vim') + + " cleanup + set opfunc& + delfunc OpFunc1 + delfunc OpFunc2 + unlet g:OpFunc1Args g:OpFunc2Args + %bw! +endfunc + func Test_normal10_expand() " Test for expand() 10new @@ -506,6 +739,14 @@ func Test_normal10_expand() call assert_equal(expected[i], expand('<cexpr>'), 'i == ' . i) endfor + " Test for <cexpr> in state.val and ptr->val + call setline(1, 'x = state.val;') + call cursor(1, 10) + call assert_equal('state.val', expand('<cexpr>')) + call setline(1, 'x = ptr->val;') + call cursor(1, 9) + call assert_equal('ptr->val', expand('<cexpr>')) + if executable('echo') " Test expand(`...`) i.e. backticks command expansion. " MS-Windows has a trailing space. @@ -514,11 +755,25 @@ func Test_normal10_expand() " Test expand(`=...`) i.e. backticks expression expansion call assert_equal('5', expand('`=2+3`')) + call assert_equal('3.14', expand('`=3.14`')) " clean up bw! endfunc +" Test for expand() in latin1 encoding +func Test_normal_expand_latin1() + new + let save_enc = &encoding + " set encoding=latin1 + call setline(1, 'val = item->color;') + call cursor(1, 11) + call assert_equal('color', expand("<cword>")) + call assert_equal('item->color', expand("<cexpr>")) + let &encoding = save_enc + bw! +endfunc + func Test_normal11_showcmd() " test for 'showcmd' 10new @@ -543,6 +798,13 @@ func Test_normal11_showcmd() redraw! call assert_match('1-3$', Screenline(&lines)) call feedkeys("v", 'xt') + " test for visually selecting the end of line + call setline(1, ["foobar"]) + call feedkeys("$vl", 'xt') + redraw! + call assert_match('2$', Screenline(&lines)) + call feedkeys("y", 'xt') + call assert_equal("r\n", @") bw! endfunc @@ -1024,6 +1286,22 @@ func Test_vert_scroll_cmds() close! endfunc +func Test_scroll_in_ex_mode() + " This was using invalid memory because w_botline was invalid. + let lines =<< trim END + diffsplit + norm os00( + call writefile(['done'], 'Xdone') + qa! + END + call writefile(lines, 'Xscript') + call assert_equal(1, RunVim([], [], '--clean -X -Z -e -s -S Xscript')) + call assert_equal(['done'], readfile('Xdone')) + + call delete('Xscript') + call delete('Xdone') +endfunc + " Test for the 'sidescroll' option func Test_sidescroll_opt() new @@ -1431,10 +1709,8 @@ func Test_normal18_z_fold() endfunc func Test_normal20_exmode() - if !has("unix") - " Reading from redirected file doesn't work on MS-Windows - return - endif + " Reading from redirected file doesn't work on MS-Windows + CheckNotMSWindows call writefile(['1a', 'foo', 'bar', '.', 'w! Xfile2', 'q!'], 'Xscript') call writefile(['1', '2'], 'Xfile') call system(GetVimCommand() .. ' -e -s < Xscript Xfile') @@ -1587,7 +1863,7 @@ func Test_normal23_K() call setline(1, '---') call assert_fails('normal! ggv2lK', 'E349:') call setline(1, ['abc', 'xyz']) - call assert_fails("normal! gg2lv2h\<C-]>", 'E426:') + call assert_fails("normal! gg2lv2h\<C-]>", 'E433:') call assert_beeps("normal! ggVjK") " clean up @@ -2062,6 +2338,16 @@ func Test_normal30_changecase() call assert_equal(['aaaaaa', 'AAAAaa'], getline(1, 2)) set whichwrap& + " try changing the case with a double byte encoding (DBCS) + %bw! + let enc = &enc + " set encoding=cp932 + call setline(1, "\u8470") + normal ~ + normal gU$gu$gUgUg~g~gugu + call assert_equal("\u8470", getline(1)) + let &encoding = enc + " clean up bw! endfunc @@ -2153,6 +2439,19 @@ func Test_normal31_r_cmd() " r command should fail in operator pending mode call assert_beeps('normal! cr') + " replace a tab character in visual mode + %d + call setline(1, ["a\tb", "c\td", "e\tf"]) + normal gglvjjrx + call assert_equal(['axx', 'xxx', 'xxf'], getline(1, '$')) + + " replace with a multibyte character (with multiple composing characters) + %d + new + call setline(1, 'aaa') + exe "normal $ra\u0328\u0301" + call assert_equal("aaa\u0328\u0301", getline(1)) + " clean up set noautoindent bw! @@ -2177,9 +2476,7 @@ endfunc " Test for g`, g;, g,, g&, gv, gk, gj, gJ, g0, g^, g_, gm, g$, gM, g CTRL-G, " gi and gI commands func Test_normal33_g_cmd2() - if !has("jumplist") - return - endif + CheckFeature jumplist call Setup_NewWindow() " Test for g` clearjumps @@ -2638,7 +2935,6 @@ endfunc " Test for cw cW ce func Test_normal39_cw() " Test for cw and cW on whitespace - " and cpo+=w setting new set tw=0 call append(0, 'here are some words') @@ -2646,14 +2942,6 @@ func Test_normal39_cw() call assert_equal('hereZZZare some words', getline('.')) norm! 1gg0elcWYYY call assert_equal('hereZZZareYYYsome words', getline('.')) - " Nvim: no "w" flag in 'cpoptions'. - " set cpo+=w - " call setline(1, 'here are some words') - " norm! 1gg0elcwZZZ - " call assert_equal('hereZZZ are some words', getline('.')) - " norm! 1gg2elcWYYY - " call assert_equal('hereZZZ areYYY some words', getline('.')) - set cpo-=w norm! 2gg0cwfoo call assert_equal('foo', getline('.')) @@ -2813,24 +3101,6 @@ func Test_normal47_visual_buf_wipe() set nomodified endfunc -func Test_normal47_autocmd() - " disabled, does not seem to be possible currently - throw "Skipped: not possible to test cursorhold autocmd while waiting for input in normal_cmd" - new - call append(0, repeat('-',20)) - au CursorHold * call feedkeys('2l', '') - 1 - set updatetime=20 - " should delete 12 chars (d12l) - call feedkeys('d1', '!') - call assert_equal('--------', getline(1)) - - " clean up - au! CursorHold - set updatetime=4000 - bw! -endfunc - func Test_normal48_wincmd() new exe "norm! \<c-w>c" @@ -2848,9 +3118,8 @@ func Test_normal49_counts() endfunc func Test_normal50_commandline() - if !has("timers") || !has("cmdline_hist") - return - endif + CheckFeature timers + CheckFeature cmdline_hist func! DoTimerWork(id) call assert_equal('[Command Line]', bufname('')) " should fail, with E11, but does fail with E23? @@ -2879,9 +3148,7 @@ func Test_normal50_commandline() endfunc func Test_normal51_FileChangedRO() - if !has("autocmd") - return - endif + CheckFeature autocmd call writefile(['foo'], 'Xreadonly.log') new Xreadonly.log setl ro @@ -2896,9 +3163,7 @@ func Test_normal51_FileChangedRO() endfunc func Test_normal52_rl() - if !has("rightleft") - return - endif + CheckFeature rightleft new call setline(1, 'abcde fghij klmnopq') norm! 1gg$ @@ -2930,22 +3195,6 @@ func Test_normal52_rl() bw! endfunc -func Test_normal53_digraph() - if !has('digraphs') - return - endif - new - call setline(1, 'abcdefgh|') - exe "norm! 1gg0f\<c-k>!!" - call assert_equal(9, col('.')) - set cpo+=D - exe "norm! 1gg0f\<c-k>!!" - call assert_equal(1, col('.')) - - set cpo-=D - bw! -endfunc - func Test_normal54_Ctrl_bsl() new call setline(1, 'abcdefghijklmn') @@ -3266,46 +3515,6 @@ func Test_normal_gk_gj() set cpoptions& number& numberwidth& wrap& endfunc -" Test for cursor movement with '-' in 'cpoptions' -func Test_normal_cpo_minus() - throw 'Skipped: Nvim does not support cpoptions flag "-"' - new - call setline(1, ['foo', 'bar', 'baz']) - let save_cpo = &cpo - set cpo+=- - call assert_beeps('normal 10j') - call assert_equal(1, line('.')) - normal G - call assert_beeps('normal 10k') - call assert_equal(3, line('.')) - call assert_fails(10, 'E16:') - let &cpo = save_cpo - close! -endfunc - -" Test for displaying dollar when changing text ('$' flag in 'cpoptions') -func Test_normal_cpo_dollar() - throw 'Skipped: use test/functional/legacy/cpoptions_spec.lua' - new - let g:Line = '' - func SaveFirstLine() - let g:Line = Screenline(1) - return '' - endfunc - inoremap <expr> <buffer> <F2> SaveFirstLine() - call test_override('redraw_flag', 1) - set cpo+=$ - call setline(1, 'one two three') - redraw! - exe "normal c2w\<F2>vim" - call assert_equal('one tw$ three', g:Line) - call assert_equal('vim three', getline(1)) - set cpo-=$ - call test_override('ALL', 0) - delfunc SaveFirstLine - %bw! -endfunc - " Test for using : to run a multi-line Ex command in operator pending mode func Test_normal_yank_with_excmd() new @@ -3370,6 +3579,31 @@ func Test_normal_colon_op() close! endfunc +" Test for d and D commands +func Test_normal_delete_cmd() + new + " D in an empty line + call setline(1, '') + normal D + call assert_equal('', getline(1)) + " D in an empty line in virtualedit mode + set virtualedit=all + normal D + call assert_equal('', getline(1)) + set virtualedit& + " delete to a readonly register + call setline(1, ['abcd']) + call assert_beeps('normal ":d2l') + + " D and d with 'nomodifiable' + call setline(1, ['abcd']) + setlocal nomodifiable + call assert_fails('normal D', 'E21:') + call assert_fails('normal d$', 'E21:') + + close! +endfunc + " Test for deleting or changing characters across lines with 'whichwrap' " containing 's'. Should count <EOL> as one character. func Test_normal_op_across_lines() @@ -3477,6 +3711,27 @@ func Test_normal_percent_jump() close! endfunc +" Test for << and >> commands to shift text by 'shiftwidth' +func Test_normal_shift_rightleft() + new + call setline(1, ['one', '', "\t", ' two', "\tthree", ' four']) + set shiftwidth=2 tabstop=8 + normal gg6>> + call assert_equal([' one', '', "\t ", ' two', "\t three", "\tfour"], + \ getline(1, '$')) + normal ggVG2>> + call assert_equal([' one', '', "\t ", "\ttwo", + \ "\t three", "\t four"], getline(1, '$')) + normal gg6<< + call assert_equal([' one', '', "\t ", ' two', "\t three", + \ "\t four"], getline(1, '$')) + normal ggVG2<< + call assert_equal(['one', '', "\t", ' two', "\tthree", ' four'], + \ getline(1, '$')) + set shiftwidth& tabstop& + bw! +endfunc + " Some commands like yy, cc, dd, >>, << and !! accept a count after " typing the first letter of the command. func Test_normal_count_after_operator() @@ -3540,4 +3795,37 @@ func Test_normal_count_out_of_range() bwipe! endfunc +" Test that mouse shape is restored to Normal mode after failed "c" operation. +func Test_mouse_shape_after_failed_change() + CheckFeature mouseshape + CheckCanRunGui + + let lines =<< trim END + set mouseshape+=o:busy + setlocal nomodifiable + let g:mouse_shapes = [] + + func SaveMouseShape(timer) + let g:mouse_shapes += [getmouseshape()] + endfunc + + func SaveAndQuit(timer) + call writefile(g:mouse_shapes, 'Xmouseshapes') + quit + endfunc + + call timer_start(50, {_ -> feedkeys('c')}) + call timer_start(100, 'SaveMouseShape') + call timer_start(150, {_ -> feedkeys('c')}) + call timer_start(200, 'SaveMouseShape') + call timer_start(250, 'SaveAndQuit') + END + call writefile(lines, 'Xmouseshape.vim', 'D') + call RunVim([], [], "-g -S Xmouseshape.vim") + sleep 300m + call assert_equal(['busy', 'arrow'], readfile('Xmouseshapes')) + + call delete('Xmouseshapes') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index ada6d2406b..f51de94bac 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -157,6 +157,20 @@ func Test_path_keep_commas() set path& endfunc +func Test_path_too_long() + exe 'set path=' .. repeat('x', 10000) + call assert_fails('find x', 'E854:') + set path& +endfunc + +func Test_signcolumn() + CheckFeature signs + call assert_equal("auto", &signcolumn) + set signcolumn=yes + set signcolumn=no + call assert_fails('set signcolumn=nope') +endfunc + func Test_filetype_valid() set ft=valid_name call assert_equal("valid_name", &filetype) @@ -263,7 +277,7 @@ func Test_set_completion() call feedkeys(":setglobal di\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"setglobal dictionary diff diffexpr diffopt digraph directory display', @:) - " Expand boolan options. When doing :set no<Tab> + " Expand boolean options. When doing :set no<Tab> " vim displays the options names without "no" but completion uses "no...". call feedkeys(":set nodi\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"set nodiff digraph', @:) @@ -399,6 +413,7 @@ func Test_set_errors() call assert_fails('set pyxversion=6', 'E474:') endif call assert_fails("let &tabstop='ab'", 'E521:') + call assert_fails('set spellcapcheck=%\\(', 'E54:') call assert_fails('set sessionoptions=curdir,sesdir', 'E474:') call assert_fails('set foldmarker={{{,', 'E474:') call assert_fails('set sessionoptions=sesdir,curdir', 'E474:') @@ -432,9 +447,8 @@ endfunc " Must be executed before other tests that set 'term'. func Test_000_term_option_verbose() - if has('nvim') || has('gui_running') - return - endif + throw "Skipped: Nvim does not support setting 'term'" + CheckNotGui call CheckWasNotSet('t_cm') @@ -881,7 +895,6 @@ endfunc " Test for the default CDPATH option func Test_opt_default_cdpath() - CheckFeature file_in_path let after =<< trim [CODE] call assert_equal(',/path/to/dir1,/path/to/dir2', &cdpath) call writefile(v:errors, 'Xtestout') @@ -1117,6 +1130,35 @@ func Test_opt_reset_scroll() call delete('Xscroll') endfunc +" Check that VIM_POSIX env variable influences default value of 'cpo' and 'shm' +func Test_VIM_POSIX() + throw 'Skipped: Nvim does not support $VIM_POSIX' + let saved_VIM_POSIX = getenv("VIM_POSIX") + + call setenv('VIM_POSIX', "1") + let after =<< trim [CODE] + call writefile([&cpo, &shm], 'X_VIM_POSIX') + qall + [CODE] + if RunVim([], after, '') + call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZ$!%*-+<>#{|&/\.;', + \ 'AS'], readfile('X_VIM_POSIX')) + endif + + call setenv('VIM_POSIX', v:null) + let after =<< trim [CODE] + call writefile([&cpo, &shm], 'X_VIM_POSIX') + qall + [CODE] + if RunVim([], after, '') + call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZ$!%*-+<>;', + \ 'S'], readfile('X_VIM_POSIX')) + endif + + call delete('X_VIM_POSIX') + call setenv('VIM_POSIX', saved_VIM_POSIX) +endfunc + " Test for setting an option to a Vi or Vim default func Test_opt_default() throw 'Skipped: Nvim has different defaults' @@ -1196,6 +1238,30 @@ func Test_opt_cdhome() set cdhome& endfunc +func Test_set_completion_2() + CheckOption termguicolors + + " Test default option completion + set wildoptions= + call feedkeys(":set termg\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set termguicolors', @:) + + call feedkeys(":set notermg\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set notermguicolors', @:) + + " Test fuzzy option completion + set wildoptions=fuzzy + call feedkeys(":set termg\<C-A>\<C-B>\"\<CR>", 'tx') + " Nvim doesn't have 'termencoding' + " call assert_equal('"set termguicolors termencoding', @:) + call assert_equal('"set termguicolors', @:) + + call feedkeys(":set notermg\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set notermguicolors', @:) + + set wildoptions= +endfunc + func Test_switchbuf_reset() set switchbuf=useopen sblast @@ -1222,5 +1288,17 @@ func Test_keywordprg_empty() let &keywordprg = k endfunc +" check that the very first buffer created does not have 'endoffile' set +func Test_endoffile_default() + let after =<< trim [CODE] + call writefile([execute('set eof?')], 'Xtestout') + qall! + [CODE] + if RunVim([], after, '') + call assert_equal(["\nnoendoffile"], readfile('Xtestout')) + endif + call delete('Xtestout') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim index 8c90f21600..3020668f1b 100644 --- a/src/nvim/testdir/test_partial.vim +++ b/src/nvim/testdir/test_partial.vim @@ -82,6 +82,9 @@ func Test_partial_dict() let dict = {"tr": function('tr', ['hello', 'h', 'H'])} call assert_equal("Hello", dict.tr()) + + call assert_fails("let F=function('setloclist', 10)", "E923:") + call assert_fails("let F=function('setloclist', [], [])", "E922:") endfunc func Test_partial_implicit() @@ -354,3 +357,5 @@ func Test_compare_partials() call assert_true(F1 isnot# F1d1) " Partial /= non-partial call assert_true(d1.f1 isnot# d1.f1) " handle_subscript creates new partial each time endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index 067e5d14e5..791cce4431 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -359,7 +359,7 @@ func Test_completefunc_opens_new_window_one() /^one call assert_fails('call feedkeys("A\<C-X>\<C-U>\<C-N>\<Esc>", "x")', 'E565:') call assert_equal(winid, win_getid()) - call assert_equal('oneDEF', getline(1)) + call assert_equal('onedef', getline(1)) q! endfunc @@ -384,9 +384,7 @@ func Test_completefunc_opens_new_window_two() /^two call assert_fails('call feedkeys("A\<C-X>\<C-U>\<C-N>\<Esc>", "x")', 'E565:') call assert_equal(winid, win_getid()) - " v8.2.1919 hasn't been ported yet - " call assert_equal('twodef', getline(1)) - call assert_equal('twoDEF', getline(1)) + call assert_equal('twodef', getline(1)) q! endfunc @@ -677,9 +675,9 @@ func Test_complete_CTRLN_startofbuffer() endfunc func Test_popup_and_window_resize() - if !has('terminal') || has('gui_running') - return - endif + CheckFeature terminal + CheckNotGui + let h = winheight(0) if h < 15 return @@ -864,7 +862,6 @@ func Test_popup_position() endfunc func Test_popup_command() - CheckScreendump CheckFeature menu menu Test.Foo Foo @@ -872,6 +869,23 @@ func Test_popup_command() call assert_fails('popup Test.Foo.X', 'E327:') call assert_fails('popup Foo', 'E337:') unmenu Test.Foo +endfunc + +func Test_popup_command_dump() + CheckFeature menu + CheckScreendump + + let script =<< trim END + func StartTimer() + call timer_start(100, {-> ChangeMenu()}) + endfunc + func ChangeMenu() + aunmenu PopUp.&Paste + nnoremenu 1.40 PopUp.&Paste :echomsg "pasted"<CR> + echomsg 'changed' + endfunc + END + call writefile(script, 'XtimerScript') let lines =<< trim END one two three four five @@ -879,12 +893,12 @@ func Test_popup_command() one more two three four five END call writefile(lines, 'Xtest') - let buf = RunVimInTerminal('Xtest', {}) + let buf = RunVimInTerminal('-S XtimerScript Xtest', {}) call term_sendkeys(buf, ":source $VIMRUNTIME/menu.vim\<CR>") call term_sendkeys(buf, "/X\<CR>:popup PopUp\<CR>") call VerifyScreenDump(buf, 'Test_popup_command_01', {}) - " Select a word + " go to the Paste entry in the menu call term_sendkeys(buf, "jj") call VerifyScreenDump(buf, 'Test_popup_command_02', {}) @@ -893,8 +907,20 @@ func Test_popup_command() call VerifyScreenDump(buf, 'Test_popup_command_03', {}) call term_sendkeys(buf, "\<Esc>") + + " Set a timer to change a menu entry while it's displayed. The text should + " not change but the command does. Making the screendump also verifies that + " "changed" shows up, which means the timer triggered + call term_sendkeys(buf, "/X\<CR>:call StartTimer() | popup PopUp\<CR>") + call VerifyScreenDump(buf, 'Test_popup_command_04', {}) + + " Select the Paste entry, executes the changed menu item. + call term_sendkeys(buf, "jj\<CR>") + call VerifyScreenDump(buf, 'Test_popup_command_05', {}) + call StopVimInTerminal(buf) call delete('Xtest') + call delete('XtimerScript') endfunc func Test_popup_complete_backwards() @@ -948,9 +974,9 @@ func Test_complete_o_tab() endfunc func Test_menu_only_exists_in_terminal() - if !exists(':tlmenu') || has('gui_running') - return - endif + CheckCommand tlmenu + CheckNotGui + tlnoremenu &Edit.&Paste<Tab>"+gP <C-W>"+ aunmenu * try diff --git a/src/nvim/testdir/test_profile.vim b/src/nvim/testdir/test_profile.vim index 4225b91bc4..9165f7bace 100644 --- a/src/nvim/testdir/test_profile.vim +++ b/src/nvim/testdir/test_profile.vim @@ -403,6 +403,47 @@ func Test_profile_completion() call feedkeys(":profile start test_prof\<C-A>\<C-B>\"\<CR>", 'tx') call assert_match('^"profile start.* test_profile\.vim', @:) + + call feedkeys(":profile file test_prof\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_match('"profile file test_profile\.vim', @:) + call feedkeys(":profile file test_prof\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_match('"profile file test_profile\.vim', @:) + call feedkeys(":profile file test_prof \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_match('"profile file test_prof ', @:) + call feedkeys(":profile file X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_match('"profile file X1B2C3', @:) + + func Xprof_test() + endfunc + call feedkeys(":profile func Xprof\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profile func Xprof_test', @:) + call feedkeys(":profile func Xprof\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profile func Xprof_test', @:) + call feedkeys(":profile func Xprof \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profile func Xprof ', @:) + call feedkeys(":profile func X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profile func X1B2C3', @:) + + call feedkeys(":profdel \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profdel file func', @:) + call feedkeys(":profdel fu\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profdel func', @:) + call feedkeys(":profdel he\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profdel he', @:) + call feedkeys(":profdel here \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profdel here ', @:) + call feedkeys(":profdel file test_prof\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profdel file test_profile.vim', @:) + call feedkeys(":profdel file X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profdel file X1B2C3', @:) + call feedkeys(":profdel func Xprof\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profdel func Xprof_test', @:) + call feedkeys(":profdel func Xprof_test \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profdel func Xprof_test ', @:) + call feedkeys(":profdel func X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profdel func X1B2C3', @:) + + delfunc Xprof_test endfunc func Test_profile_errors() diff --git a/src/nvim/testdir/test_prompt_buffer.vim b/src/nvim/testdir/test_prompt_buffer.vim index 9b8a776c95..b8f6c5240c 100644 --- a/src/nvim/testdir/test_prompt_buffer.vim +++ b/src/nvim/testdir/test_prompt_buffer.vim @@ -180,6 +180,8 @@ func Test_prompt_buffer_edit() call assert_beeps('normal! S') call assert_beeps("normal! \<C-A>") call assert_beeps("normal! \<C-X>") + call assert_beeps("normal! dp") + call assert_beeps("normal! do") " pressing CTRL-W in the prompt buffer should trigger the window commands call assert_equal(1, winnr()) exe "normal A\<C-W>\<C-W>" diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 8c9e39570f..8dc4173d60 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1,6 +1,7 @@ " Test for the quickfix feature. source check.vim +source vim9.vim CheckFeature quickfix source screendump.vim @@ -104,9 +105,15 @@ func XlistTests(cchar) call assert_true(v:errmsg ==# 'E42: No Errors') " Populate the list and then try - Xgetexpr ['non-error 1', 'Xtestfile1:1:3:Line1', - \ 'non-error 2', 'Xtestfile2:2:2:Line2', - \ 'non-error| 3', 'Xtestfile3:3:1:Line3'] + let lines =<< trim END + non-error 1 + Xtestfile1:1:3:Line1 + non-error 2 + Xtestfile2:2:2:Line2 + non-error| 3 + Xtestfile3:3:1:Line3 + END + Xgetexpr lines " List only valid entries let l = split(execute('Xlist', ''), "\n") @@ -261,6 +268,7 @@ func XwindowTests(cchar) " Opening the location list window without any errors should fail if a:cchar == 'l' call assert_fails('lopen', 'E776:') + call assert_fails('lwindow', 'E776:') endif " Create a list with no valid entries @@ -271,8 +279,12 @@ func XwindowTests(cchar) call assert_true(winnr('$') == 1) " Create a list with valid entries - Xgetexpr ['Xtestfile1:1:3:Line1', 'Xtestfile2:2:2:Line2', - \ 'Xtestfile3:3:1:Line3'] + let lines =<< trim END + Xtestfile1:1:3:Line1 + Xtestfile2:2:2:Line2 + Xtestfile3:3:1:Line3 + END + Xgetexpr lines " Open the window Xwindow @@ -335,8 +347,12 @@ func XwindowTests(cchar) if a:cchar == 'c' " Opening the quickfix window in multiple tab pages should reuse the " quickfix buffer - Xgetexpr ['Xtestfile1:1:3:Line1', 'Xtestfile2:2:2:Line2', - \ 'Xtestfile3:3:1:Line3'] + let lines =<< trim END + Xtestfile1:1:3:Line1 + Xtestfile2:2:2:Line2 + Xtestfile3:3:1:Line3 + END + Xgetexpr lines Xopen let qfbufnum = bufnr('%') tabnew @@ -371,14 +387,16 @@ func Test_copenHeight_tabline() set tabline& showtabline& endfunc - " Tests for the :cfile, :lfile, :caddfile, :laddfile, :cgetfile and :lgetfile " commands. func XfileTests(cchar) call s:setup_commands(a:cchar) - call writefile(['Xtestfile1:700:10:Line 700', - \ 'Xtestfile2:800:15:Line 800'], 'Xqftestfile1') + let lines =<< trim END + Xtestfile1:700:10:Line 700 + Xtestfile2:800:15:Line 800 + END + call writefile(lines, 'Xqftestfile1') enew! Xfile Xqftestfile1 @@ -402,8 +420,11 @@ func XfileTests(cchar) call assert_true(len(l) == 3 && \ l[2].lnum == 900 && l[2].col == 30 && l[2].text ==# 'Line 900') - call writefile(['Xtestfile1:222:77:Line 222', - \ 'Xtestfile2:333:88:Line 333'], 'Xqftestfile1') + let lines =<< trim END + Xtestfile1:222:77:Line 222 + Xtestfile2:333:88:Line 333 + END + call writefile(lines, 'Xqftestfile1') enew! Xgetfile Xqftestfile1 @@ -433,8 +454,11 @@ func XbufferTests(cchar) call s:setup_commands(a:cchar) enew! - silent! call setline(1, ['Xtestfile7:700:10:Line 700', - \ 'Xtestfile8:800:15:Line 800']) + let lines =<< trim END + Xtestfile7:700:10:Line 700 + Xtestfile8:800:15:Line 800 + END + silent! call setline(1, lines) Xbuffer! let l = g:Xgetlist() call assert_true(len(l) == 2 && @@ -442,8 +466,11 @@ func XbufferTests(cchar) \ l[1].lnum == 800 && l[1].col == 15 && l[1].text ==# 'Line 800') enew! - silent! call setline(1, ['Xtestfile9:900:55:Line 900', - \ 'Xtestfile10:950:66:Line 950']) + let lines =<< trim END + Xtestfile9:900:55:Line 900 + Xtestfile10:950:66:Line 950 + END + silent! call setline(1, lines) Xgetbuffer let l = g:Xgetlist() call assert_true(len(l) == 2 && @@ -451,8 +478,11 @@ func XbufferTests(cchar) \ l[1].lnum == 950 && l[1].col == 66 && l[1].text ==# 'Line 950') enew! - silent! call setline(1, ['Xtestfile11:700:20:Line 700', - \ 'Xtestfile12:750:25:Line 750']) + let lines =<< trim END + Xtestfile11:700:20:Line 700 + Xtestfile12:750:25:Line 750 + END + silent! call setline(1, lines) Xaddbuffer let l = g:Xgetlist() call assert_true(len(l) == 4 && @@ -522,12 +552,15 @@ func Xtest_browse(cchar) call s:create_test_file('Xqftestfile1') call s:create_test_file('Xqftestfile2') - Xgetexpr ['Xqftestfile1:5:Line5', - \ 'Xqftestfile1:6:Line6', - \ 'Xqftestfile2:10:Line10', - \ 'Xqftestfile2:11:Line11', - \ 'RegularLine1', - \ 'RegularLine2'] + let lines =<< trim END + Xqftestfile1:5:Line5 + Xqftestfile1:6:Line6 + Xqftestfile2:10:Line10 + Xqftestfile2:11:Line11 + RegularLine1 + RegularLine2 + END + Xgetexpr lines Xfirst call assert_fails('-5Xcc', 'E16:') @@ -577,10 +610,13 @@ func Xtest_browse(cchar) call assert_equal(5, line('.')) " Jumping to an error from the error window using cc command - Xgetexpr ['Xqftestfile1:5:Line5', - \ 'Xqftestfile1:6:Line6', - \ 'Xqftestfile2:10:Line10', - \ 'Xqftestfile2:11:Line11'] + let lines =<< trim END + Xqftestfile1:5:Line5 + Xqftestfile1:6:Line6 + Xqftestfile2:10:Line10 + Xqftestfile2:11:Line11 + END + Xgetexpr lines Xopen 10Xcc call assert_equal(11, line('.')) @@ -705,7 +741,7 @@ func s:test_xhelpgrep(cchar) " Search for non existing help string call assert_fails('Xhelpgrep a1b2c3', 'E480:') " Invalid regular expression - call assert_fails('Xhelpgrep \@<!', 'E480:') + call assert_fails('Xhelpgrep \@<!', 'E866:') endfunc func Test_helpgrep() @@ -714,6 +750,35 @@ func Test_helpgrep() call s:test_xhelpgrep('l') endfunc +" When running the :helpgrep command, if an autocmd modifies the 'cpoptions' +" value, then Vim crashes. (issue fixed by 7.2b-004 and 8.2.4453) +func Test_helpgrep_restore_cpo_aucmd() + let save_cpo = &cpo + augroup QF_Test + au! + autocmd BufNew * set cpo=acd + augroup END + + helpgrep quickfix + call assert_equal('acd', &cpo) + %bw! + + set cpo&vim + augroup QF_Test + au! + autocmd BufReadPost * set cpo= + augroup END + + helpgrep buffer + call assert_equal('', &cpo) + + augroup QF_Test + au! + augroup END + %bw! + let &cpo = save_cpo +endfunc + func Test_errortitle() augroup QfBufWinEnter au! @@ -1079,20 +1144,21 @@ func s:dir_stack_tests(cchar) let save_efm=&efm set efm=%DEntering\ dir\ '%f',%f:%l:%m,%XLeaving\ dir\ '%f' - 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'" - \] + let lines =<< trim END + 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' + END Xexpr "" for l in lines @@ -1126,19 +1192,19 @@ func Test_efm_dirstack() call mkdir('dir1/c') call mkdir('dir2') - 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" - \ ] - + let lines =<< trim END + 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 + END call writefile(lines, 'habits1.txt') call writefile(lines, 'dir1/a/habits2.txt') call writefile(lines, 'dir1/a/b/habits3.txt') @@ -1164,7 +1230,13 @@ func Xefm_ignore_continuations(cchar) \ '%-Wignored %m %l,' . \ '%+Cmore ignored %m %l,' . \ '%Zignored end' - Xgetexpr ['ignored warning 1', 'more ignored continuation 2', 'ignored end', 'error resync 4'] + let lines =<< trim END + ignored warning 1 + more ignored continuation 2 + ignored end + error resync 4 + END + Xgetexpr lines let l = map(g:Xgetlist(), '[v:val.text, v:val.valid, v:val.lnum, v:val.type]') call assert_equal([['resync', 1, 4, 'E']], l) @@ -1210,8 +1282,14 @@ func Xinvalid_efm_Tests(cchar) set efm= call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E378:') + " Empty directory name. When there is an error in parsing new entries, make + " sure the previous quickfix list is made the current list. + set efm& + cexpr ["one", "two"] + let qf_id = getqflist(#{id: 0}).id set efm=%DEntering\ dir\ abc,%f:%l:%m call assert_fails('Xexpr ["Entering dir abc", "abc.txt:1:Hello world"]', 'E379:') + call assert_equal(qf_id, getqflist(#{id: 0}).id) let &efm = save_efm endfunc @@ -1402,8 +1480,14 @@ func Test_efm_error_type() " error type set efm=%f:%l:%t:%m - cexpr ["Xfile1:10:E:msg1", "Xfile1:20:W:msg2", "Xfile1:30:I:msg3", - \ "Xfile1:40:N:msg4", "Xfile1:50:R:msg5"] + let lines =<< trim END + Xfile1:10:E:msg1 + Xfile1:20:W:msg2 + Xfile1:30:I:msg3 + Xfile1:40:N:msg4 + Xfile1:50:R:msg5 + END + cexpr lines let output = split(execute('clist'), "\n") call assert_equal([ \ ' 1 Xfile1:10 error: msg1', @@ -1414,8 +1498,14 @@ func Test_efm_error_type() " error type and a error number set efm=%f:%l:%t:%n:%m - cexpr ["Xfile1:10:E:2:msg1", "Xfile1:20:W:4:msg2", "Xfile1:30:I:6:msg3", - \ "Xfile1:40:N:8:msg4", "Xfile1:50:R:3:msg5"] + let lines =<< trim END + Xfile1:10:E:2:msg1 + Xfile1:20:W:4:msg2 + Xfile1:30:I:6:msg3 + Xfile1:40:N:8:msg4 + Xfile1:50:R:3:msg5 + END + cexpr lines let output = split(execute('clist'), "\n") call assert_equal([ \ ' 1 Xfile1:10 error 2: msg1', @@ -1440,8 +1530,13 @@ func Test_efm_end_lnum_col() " 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 lines =<< trim END + 1)msg1 + Xfile1:14-24:1-2 + 2)msg2 + Xfile1:24-34:3-4 + END + cexpr lines let output = split(execute('clist'), "\n") call assert_equal([ \ ' 1 Xfile1:14-24 col 1-2 error 1: msg1', @@ -1465,7 +1560,7 @@ func XquickfixChangedByAutocmd(cchar) endfunc endif - augroup testgroup + augroup QF_Test au! autocmd BufReadCmd test_changed.txt call ReadFunc() augroup END @@ -1479,7 +1574,24 @@ func XquickfixChangedByAutocmd(cchar) endfor call assert_fails('Xrewind', ErrorNr . ':') - augroup! testgroup + augroup QF_Test + au! + augroup END + + if a:cchar == 'c' + cexpr ["Xtest1:1:Line"] + cwindow + only + augroup QF_Test + au! + autocmd WinEnter * call setqflist([], 'f') + augroup END + call assert_fails('exe "normal \<CR>"', 'E925:') + augroup QF_Test + au! + augroup END + endif + %bw! endfunc func Test_quickfix_was_changed_by_autocmd() @@ -1617,6 +1729,9 @@ func SetXlistTests(cchar, bnum) \ " {'bufnr':999, 'lnum':5}])", 'E92:') call g:Xsetlist([[1, 2,3]]) call assert_equal(0, len(g:Xgetlist())) + call assert_fails('call g:Xsetlist([], [])', 'E928:') + call g:Xsetlist([v:_null_dict]) + call assert_equal([], g:Xgetlist()) endfunc func Test_setqflist() @@ -1831,12 +1946,12 @@ func Test_cgetfile_on_long_lines() " Problematic values if the line is longer than 4096 bytes. Then 1024 bytes " are read at a time. for len in [4078, 4079, 4080, 5102, 5103, 5104, 6126, 6127, 6128, 7150, 7151, 7152] - let lines = [ - \ '/tmp/file1:1:1:aaa', - \ '/tmp/file2:1:1:%s', - \ '/tmp/file3:1:1:bbb', - \ '/tmp/file4:1:1:ccc', - \ ] + let lines =<< trim END + /tmp/file1:1:1:aaa + /tmp/file2:1:1:%s + /tmp/file3:1:1:bbb + /tmp/file4:1:1:ccc + END let lines[1] = substitute(lines[1], '%s', repeat('x', len), '') call writefile(lines, 'Xcqetfile.txt') cgetfile Xcqetfile.txt @@ -1863,12 +1978,15 @@ func Test_switchbuf() let file1_winid = win_getid() new Xqftestfile2 let file2_winid = win_getid() - cgetexpr ['Xqftestfile1:5:Line5', - \ 'Xqftestfile1:6:Line6', - \ 'Xqftestfile2:10:Line10', - \ 'Xqftestfile2:11:Line11', - \ 'Xqftestfile3:15:Line15', - \ 'Xqftestfile3:16:Line16'] + let lines =<< trim END + Xqftestfile1:5:Line5 + Xqftestfile1:6:Line6 + Xqftestfile2:10:Line10 + Xqftestfile2:11:Line11 + Xqftestfile3:15:Line15 + Xqftestfile3:16:Line16 + END + cgetexpr lines new let winid = win_getid() @@ -2530,21 +2648,23 @@ func Test_Autocmd() silent! cexpr non_existing_func() silent! caddexpr non_existing_func() silent! cgetexpr non_existing_func() - let l = ['precexpr', - \ 'postcexpr', - \ 'precaddexpr', - \ 'postcaddexpr', - \ 'precgetexpr', - \ 'postcgetexpr', - \ 'precexpr', - \ 'postcexpr', - \ 'precaddexpr', - \ 'postcaddexpr', - \ 'precgetexpr', - \ 'postcgetexpr', - \ 'precexpr', - \ 'precaddexpr', - \ 'precgetexpr'] + let l =<< trim END + precexpr + postcexpr + precaddexpr + postcaddexpr + precgetexpr + postcgetexpr + precexpr + postcexpr + precaddexpr + postcaddexpr + precgetexpr + postcgetexpr + precexpr + precaddexpr + precgetexpr + END call assert_equal(l, g:acmds) let g:acmds = [] @@ -2562,15 +2682,17 @@ func Test_Autocmd() exe 'silent! cgetbuffer ' . bnum exe 'silent! caddbuffer ' . bnum enew! - let l = ['precbuffer', - \ 'postcbuffer', - \ 'precgetbuffer', - \ 'postcgetbuffer', - \ 'precaddbuffer', - \ 'postcaddbuffer', - \ 'precbuffer', - \ 'precgetbuffer', - \ 'precaddbuffer'] + let l =<< trim END + precbuffer + postcbuffer + precgetbuffer + postcgetbuffer + precaddbuffer + postcaddbuffer + precbuffer + precgetbuffer + precaddbuffer + END call assert_equal(l, g:acmds) call writefile(['Xtest:1:Line1'], 'Xtest') @@ -2585,24 +2707,26 @@ func Test_Autocmd() silent! cfile do_not_exist silent! caddfile do_not_exist silent! cgetfile do_not_exist - let l = ['precfile', - \ 'postcfile', - \ 'precaddfile', - \ 'postcaddfile', - \ 'precgetfile', - \ 'postcgetfile', - \ 'precfile', - \ 'postcfile', - \ 'precaddfile', - \ 'postcaddfile', - \ 'precgetfile', - \ 'postcgetfile', - \ 'precfile', - \ 'postcfile', - \ 'precaddfile', - \ 'postcaddfile', - \ 'precgetfile', - \ 'postcgetfile'] + let l =<< trim END + precfile + postcfile + precaddfile + postcaddfile + precgetfile + postcgetfile + precfile + postcfile + precaddfile + postcaddfile + precgetfile + postcgetfile + precfile + postcfile + precaddfile + postcaddfile + precgetfile + postcgetfile + END call assert_equal(l, g:acmds) let g:acmds = [] @@ -2615,20 +2739,22 @@ func Test_Autocmd() set makeprg= silent! make set makeprg& - let l = ['prehelpgrep', - \ 'posthelpgrep', - \ 'prehelpgrep', - \ 'posthelpgrep', - \ 'previmgrep', - \ 'postvimgrep', - \ 'previmgrepadd', - \ 'postvimgrepadd', - \ 'previmgrep', - \ 'postvimgrep', - \ 'previmgrepadd', - \ 'postvimgrepadd', - \ 'premake', - \ 'postmake'] + let l =<< trim END + prehelpgrep + posthelpgrep + prehelpgrep + posthelpgrep + previmgrep + postvimgrep + previmgrepadd + postvimgrepadd + previmgrep + postvimgrep + previmgrepadd + postvimgrepadd + premake + postmake + END call assert_equal(l, g:acmds) if has('unix') @@ -2648,22 +2774,24 @@ func Test_Autocmd() silent lgrep Grep_Autocmd_Text test_quickfix.vim silent lgrepadd GrepAdd_Autocmd_Text test_quickfix.vim set grepprg&vim - let l = ['pregrep', - \ 'postgrep', - \ 'pregrepadd', - \ 'postgrepadd', - \ 'pregrep', - \ 'postgrep', - \ 'pregrepadd', - \ 'postgrepadd', - \ 'pregrep', - \ 'postgrep', - \ 'pregrepadd', - \ 'postgrepadd', - \ 'prelgrep', - \ 'postlgrep', - \ 'prelgrepadd', - \ 'postlgrepadd'] + let l =<< trim END + pregrep + postgrep + pregrepadd + postgrepadd + pregrep + postgrep + pregrepadd + postgrepadd + pregrep + postgrep + pregrepadd + postgrepadd + prelgrep + postlgrep + prelgrepadd + postlgrepadd + END call assert_equal(l, g:acmds) endif @@ -2822,15 +2950,16 @@ func Test_cwindow_highlight() CheckScreendump let lines =<< trim END - call setline(1, ['some', 'text', 'with', 'matches']) - write XCwindow - vimgrep e XCwindow - redraw - cwindow 4 + call setline(1, ['some', 'text', 'with', 'matches']) + write XCwindow + vimgrep e XCwindow + redraw + cwindow 4 END call writefile(lines, 'XtestCwindow') let buf = RunVimInTerminal('-S XtestCwindow', #{rows: 12}) call VerifyScreenDump(buf, 'Test_quickfix_cwindow_1', {}) + call term_sendkeys(buf, ":cnext\<CR>") call VerifyScreenDump(buf, 'Test_quickfix_cwindow_2', {}) @@ -2843,10 +2972,13 @@ endfunc func XvimgrepTests(cchar) call s:setup_commands(a:cchar) - call writefile(['Editor:VIM vim', - \ 'Editor:Emacs EmAcS', - \ 'Editor:Notepad NOTEPAD'], 'Xtestfile1') - call writefile(['Linux', 'MacOS', 'MS-Windows'], 'Xtestfile2') + let lines =<< trim END + Editor:VIM vim + Editor:Emacs EmAcS + Editor:Notepad NOTEPAD + END + call writefile(lines, 'Xtestfile1') + call writefile(['Linux', 'macOS', 'MS-Windows'], 'Xtestfile2') " Error cases call assert_fails('Xvimgrep /abc *', 'E682:') @@ -2860,7 +2992,7 @@ func XvimgrepTests(cchar) Xexpr "" Xvimgrepadd Notepad Xtestfile1 - Xvimgrepadd MacOS Xtestfile2 + Xvimgrepadd macOS Xtestfile2 let l = g:Xgetlist() call assert_equal(2, len(l)) call assert_equal('Editor:Notepad NOTEPAD', l[0].text) @@ -2890,6 +3022,19 @@ func XvimgrepTests(cchar) call assert_equal(0, getbufinfo('Xtestfile1')[0].loaded) call assert_equal([], getbufinfo('Xtestfile2')) + " Test for opening the dummy buffer used by vimgrep in a window. The new + " window should be closed + %bw! + augroup QF_Test + au! + autocmd BufReadPre * exe "sb " .. expand("<abuf>") + augroup END + call assert_fails("Xvimgrep /sublime/ Xtestfile1", 'E480:') + call assert_equal(1, winnr('$')) + augroup QF_Test + au! + augroup END + call delete('Xtestfile1') call delete('Xtestfile2') endfunc @@ -3093,7 +3238,7 @@ func Test_cclose_from_copen() endfunc func Test_cclose_in_autocmd() - " Problem is only triggered if "starting" is zero, so that the OptionsSet + " Problem is only triggered if "starting" is zero, so that the OptionSet " event will be triggered. " call test_override('starting', 1) augroup QF_Test @@ -3108,7 +3253,7 @@ func Test_cclose_in_autocmd() " call test_override('starting', 0) endfunc -" Check that ":file" without an argument is possible even when curbuf is locked +" Check that ":file" without an argument is possible even when "curbuf->b_ro_locked" " is set. func Test_file_from_copen() " Works without argument. @@ -3154,6 +3299,21 @@ func Test_resize_from_copen() endtry endfunc +func Test_filetype_autocmd() + " this changes the location list while it is in use to fill a buffer + lexpr '' + lopen + augroup FT_loclist + au FileType * call setloclist(0, [], 'f') + augroup END + silent! lolder + lexpr '' + + augroup FT_loclist + au! FileType + augroup END +endfunc + func Test_vimgrep_with_textlock() new @@ -3297,9 +3457,9 @@ func Xmultidirstack_tests(cchar) let l1 = g:Xgetlist({'nr':1, 'items':1}) let l2 = g:Xgetlist({'nr':2, 'items':1}) - call assert_equal(expand('Xone/a/one.txt'), bufname(l1.items[1].bufnr)) + call assert_equal('Xone/a/one.txt', bufname(l1.items[1].bufnr)) call assert_equal(3, l1.items[1].lnum) - call assert_equal(expand('Xtwo/a/two.txt'), bufname(l2.items[1].bufnr)) + call assert_equal('Xtwo/a/two.txt', bufname(l2.items[1].bufnr)) call assert_equal(5, l2.items[1].lnum) endfunc @@ -3343,14 +3503,15 @@ func Xmultifilestack_tests(cchar) " error line ends with a file stack. let efm_val = 'Error\ l%l\ in\ %f,' let efm_val .= '%-P%>(%f%r,Error\ l%l\ in\ %m,%-Q)%r' - let l = g:Xgetlist({'lines' : [ - \ '(one.txt', - \ 'Error l4 in one.txt', - \ ') (two.txt', - \ 'Error l6 in two.txt', - \ ')', - \ 'Error l8 in one.txt' - \ ], 'efm' : efm_val}) + let lines =<< trim END + (one.txt + Error l4 in one.txt + ) (two.txt + Error l6 in two.txt + ) + Error l8 in one.txt + END + let l = g:Xgetlist({'lines': lines, 'efm' : efm_val}) call assert_equal(3, len(l.items)) call assert_equal('one.txt', bufname(l.items[0].bufnr)) call assert_equal(4, l.items[0].lnum) @@ -3628,7 +3789,15 @@ func Xqfjump_tests(cchar) call g:Xsetlist([], 'f') setlocal buftype=nofile new - call g:Xsetlist([], ' ', {'lines' : ['F1:1:1:Line1', 'F1:2:2:Line2', 'F2:1:1:Line1', 'F2:2:2:Line2', 'F3:1:1:Line1', 'F3:2:2:Line2']}) + let lines =<< trim END + F1:1:1:Line1 + F1:2:2:Line2 + F2:1:1:Line1 + F2:2:2:Line2 + F3:1:1:Line1 + F3:2:2:Line2 + END + call g:Xsetlist([], ' ', {'lines': lines}) Xopen let winid = win_getid() wincmd p @@ -3764,6 +3933,22 @@ func Xgetlist_empty_tests(cchar) endif endfunc +func Test_empty_list_quickfixtextfunc() + " This was crashing. Can only reproduce by running it in a separate Vim + " instance. + let lines =<< trim END + func s:Func(o) + cgetexpr '0' + endfunc + cope + let &quickfixtextfunc = 's:Func' + cgetfile [ex + END + call writefile(lines, 'Xquickfixtextfunc') + call RunVim([], [], '-e -s -S Xquickfixtextfunc -c qa') + call delete('Xquickfixtextfunc') +endfunc + func Test_getqflist() call Xgetlist_empty_tests('c') call Xgetlist_empty_tests('l') @@ -3949,8 +4134,8 @@ endfunc func Test_lvimgrep_crash2() au BufNewFile x sfind - call assert_fails('lvimgrep x x', 'E480:') - call assert_fails('lvimgrep x x x', 'E480:') + call assert_fails('lvimgrep x x', 'E471:') + call assert_fails('lvimgrep x x x', 'E471:') au! BufNewFile endfunc @@ -4061,14 +4246,19 @@ endfunc " The following test used to crash Vim func Test_lhelpgrep_autocmd() lhelpgrep quickfix - autocmd QuickFixCmdPost * call setloclist(0, [], 'f') + augroup QF_Test + au! + autocmd QuickFixCmdPost * call setloclist(0, [], 'f') + augroup END lhelpgrep buffer call assert_equal('help', &filetype) call assert_equal(0, getloclist(0, {'nr' : '$'}).nr) lhelpgrep tabpage call assert_equal('help', &filetype) call assert_equal(1, getloclist(0, {'nr' : '$'}).nr) - au! QuickFixCmdPost + augroup QF_Test + au! + augroup END new | only augroup QF_Test @@ -4081,7 +4271,7 @@ func Test_lhelpgrep_autocmd() wincmd w call assert_fails('helpgrep quickfix', 'E925:') augroup QF_Test - au! BufEnter + au! augroup END new | only @@ -4091,7 +4281,7 @@ func Test_lhelpgrep_autocmd() augroup END call assert_fails('helpgrep quickfix', 'E925:') augroup QF_Test - au! BufEnter + au! augroup END new | only @@ -4101,10 +4291,43 @@ func Test_lhelpgrep_autocmd() augroup END call assert_fails('lhelpgrep quickfix', 'E926:') augroup QF_Test - au! BufEnter + au! augroup END + " Replace the contents of a help window location list when it is still in + " use. new | only + lhelpgrep quickfix + wincmd w + augroup QF_Test + au! + autocmd WinEnter * call setloclist(0, [], 'r') + augroup END + call assert_fails('lhelpgrep win_getid', 'E926:') + augroup QF_Test + au! + augroup END + + %bw! +endfunc + +" The following test used to crash Vim +func Test_lhelpgrep_autocmd_free_loclist() + %bw! + lhelpgrep quickfix + wincmd w + augroup QF_Test + au! + autocmd WinEnter * call setloclist(0, [], 'f') + augroup END + lhelpgrep win_getid + wincmd w + wincmd w + wincmd w + augroup QF_Test + au! + augroup END + %bw! endfunc " Test for shortening/simplifying the file name when opening the @@ -4822,9 +5045,20 @@ func Xtest_below(cchar) endif " Test for lines with multiple quickfix entries - Xexpr ["X1:5:L5", "X2:5:1:L5_1", "X2:5:2:L5_2", "X2:5:3:L5_3", - \ "X2:10:1:L10_1", "X2:10:2:L10_2", "X2:10:3:L10_3", - \ "X2:15:1:L15_1", "X2:15:2:L15_2", "X2:15:3:L15_3", "X3:3:L3"] + let lines =<< trim END + X1:5:L5 + X2:5:1:L5_1 + X2:5:2:L5_2 + X2:5:3:L5_3 + X2:10:1:L10_1 + X2:10:2:L10_2 + X2:10:3:L10_3 + X2:15:1:L15_1 + X2:15:2:L15_2 + X2:15:3:L15_3 + X3:3:L3 + END + Xexpr lines edit +1 X2 Xbelow 2 call assert_equal(['X2', 10, 1], [@%, line('.'), col('.')]) @@ -4888,33 +5122,32 @@ func Test_cbelow() endfunc func Test_quickfix_count() - let commands = [ - \ 'cNext', - \ 'cNfile', - \ 'cabove', - \ 'cbelow', - \ 'cfirst', - \ 'clast', - \ 'cnewer', - \ 'cnext', - \ 'cnfile', - \ 'colder', - \ 'cprevious', - \ 'crewind', - \ - \ 'lNext', - \ 'lNfile', - \ 'labove', - \ 'lbelow', - \ 'lfirst', - \ 'llast', - \ 'lnewer', - \ 'lnext', - \ 'lnfile', - \ 'lolder', - \ 'lprevious', - \ 'lrewind', - \ ] + let commands =<< trim END + cNext + cNfile + cabove + cbelow + cfirst + clast + cnewer + cnext + cnfile + colder + cprevious + crewind + lNext + lNfile + labove + lbelow + lfirst + llast + lnewer + lnext + lnfile + lolder + lprevious + lrewind + END for cmd in commands call assert_fails('-1' .. cmd, 'E16:') call assert_fails('.' .. cmd, 'E16:') @@ -5096,6 +5329,29 @@ func Test_lhelpgrep_from_help_window() new | only! endfunc +" Test for the crash fixed by 7.3.715 +func Test_setloclist_crash() + %bw! + let g:BufNum = bufnr() + augroup QF_Test + au! + au BufUnload * call setloclist(0, [{'bufnr':g:BufNum, 'lnum':1, 'col':1, 'text': 'tango down'}]) + augroup END + + try + lvimgrep /.*/ *.mak + catch /E926:/ + endtry + call assert_equal('tango down', getloclist(0, {'items' : 0}).items[0].text) + call assert_equal(1, getloclist(0, {'size' : 0}).size) + + augroup QF_Test + au! + augroup END + unlet g:BufNum + %bw! +endfunc + " Test for adding an invalid entry with the quickfix window open and making " sure that the window contents are not changed func Test_add_invalid_entry_with_qf_window() @@ -5295,6 +5551,7 @@ func Xtest_getqflist_by_idx(cchar) call assert_equal('L20', l[0].text) call assert_equal([], g:Xgetlist({'idx' : -1, 'items' : 0}).items) call assert_equal([], g:Xgetlist({'idx' : 3, 'items' : 0}).items) + call assert_equal({}, g:Xgetlist(#{idx: "abc"})) %bwipe! endfunc @@ -5353,6 +5610,19 @@ func Xtest_qftextfunc(cchar) call assert_equal('F1|10 col 2-7| green', getline(1)) call assert_equal('F1|20-25 col 4-8| blue', getline(2)) Xclose + + set efm=%f:%l:%c:%m + set quickfixtextfunc=Tqfexpr + " Update the list with only the cwindow + Xwindow + only + call g:Xsetlist([ + \ { 'filename': 'F2', 'lnum': 20, 'col': 2, + \ 'end_col': 7, 'text': 'red'} + \ ]) + call assert_equal(['F2-L20C2-red'], getline(1, '$')) + new + Xclose set efm& set quickfixtextfunc& @@ -5476,6 +5746,155 @@ func Test_qftextfunc() call Xtest_qftextfunc('l') endfunc +func Test_qftextfunc_callback() + let lines =<< trim END + set efm=%f:%l:%c:%m + + #" Test for using a function name + LET &qftf = 'g:Tqfexpr' + cexpr "F0:0:0:L0" + copen + call assert_equal('F0-L0C0-L0', getline(1)) + cclose + + #" Test for using a function() + set qftf=function('g:Tqfexpr') + cexpr "F1:1:1:L1" + copen + call assert_equal('F1-L1C1-L1', getline(1)) + cclose + + #" Using a funcref variable to set 'quickfixtextfunc' + VAR Fn = function('g:Tqfexpr') + LET &qftf = Fn + cexpr "F2:2:2:L2" + copen + call assert_equal('F2-L2C2-L2', getline(1)) + cclose + + #" Using string(funcref_variable) to set 'quickfixtextfunc' + LET Fn = function('g:Tqfexpr') + LET &qftf = string(Fn) + cexpr "F3:3:3:L3" + copen + call assert_equal('F3-L3C3-L3', getline(1)) + cclose + + #" Test for using a funcref() + set qftf=funcref('g:Tqfexpr') + cexpr "F4:4:4:L4" + copen + call assert_equal('F4-L4C4-L4', getline(1)) + cclose + + #" Using a funcref variable to set 'quickfixtextfunc' + LET Fn = funcref('g:Tqfexpr') + LET &qftf = Fn + cexpr "F5:5:5:L5" + copen + call assert_equal('F5-L5C5-L5', getline(1)) + cclose + + #" Using a string(funcref_variable) to set 'quickfixtextfunc' + LET Fn = funcref('g:Tqfexpr') + LET &qftf = string(Fn) + cexpr "F5:5:5:L5" + copen + call assert_equal('F5-L5C5-L5', getline(1)) + cclose + + #" Test for using a lambda function with set + VAR optval = "LSTART a LMIDDLE Tqfexpr(a) LEND" + LET optval = substitute(optval, ' ', '\\ ', 'g') + exe "set qftf=" .. optval + cexpr "F6:6:6:L6" + copen + call assert_equal('F6-L6C6-L6', getline(1)) + cclose + + #" Set 'quickfixtextfunc' to a lambda expression + LET &qftf = LSTART a LMIDDLE Tqfexpr(a) LEND + cexpr "F7:7:7:L7" + copen + call assert_equal('F7-L7C7-L7', getline(1)) + cclose + + #" Set 'quickfixtextfunc' to string(lambda_expression) + LET &qftf = "LSTART a LMIDDLE Tqfexpr(a) LEND" + cexpr "F8:8:8:L8" + copen + call assert_equal('F8-L8C8-L8', getline(1)) + cclose + + #" Set 'quickfixtextfunc' to a variable with a lambda expression + VAR Lambda = LSTART a LMIDDLE Tqfexpr(a) LEND + LET &qftf = Lambda + cexpr "F9:9:9:L9" + copen + call assert_equal('F9-L9C9-L9', getline(1)) + cclose + + #" Set 'quickfixtextfunc' to a string(variable with a lambda expression) + LET Lambda = LSTART a LMIDDLE Tqfexpr(a) LEND + LET &qftf = string(Lambda) + cexpr "F9:9:9:L9" + copen + call assert_equal('F9-L9C9-L9', getline(1)) + cclose + END + call CheckLegacyAndVim9Success(lines) + + " Test for using a script-local function name + func s:TqfFunc2(info) + let g:TqfFunc2Args = [a:info.start_idx, a:info.end_idx] + return '' + endfunc + let g:TqfFunc2Args = [] + set quickfixtextfunc=s:TqfFunc2 + cexpr "F10:10:10:L10" + cclose + call assert_equal([1, 1], g:TqfFunc2Args) + + let &quickfixtextfunc = 's:TqfFunc2' + cexpr "F11:11:11:L11" + cclose + call assert_equal([1, 1], g:TqfFunc2Args) + delfunc s:TqfFunc2 + + " set 'quickfixtextfunc' to a partial with dict. This used to cause a crash. + func SetQftfFunc() + let params = {'qftf': function('g:DictQftfFunc')} + let &quickfixtextfunc = params.qftf + endfunc + func g:DictQftfFunc(_) dict + endfunc + call SetQftfFunc() + new + call SetQftfFunc() + bw + call test_garbagecollect_now() + new + set qftf= + wincmd w + set qftf= + :%bw! + + " set per-quickfix list 'quickfixtextfunc' to a partial with dict. This used + " to cause a crash. + let &qftf = '' + func SetLocalQftfFunc() + let params = {'qftf': function('g:DictQftfFunc')} + call setqflist([], 'a', {'quickfixtextfunc' : params.qftf}) + endfunc + call SetLocalQftfFunc() + call test_garbagecollect_now() + call setqflist([], 'a', {'quickfixtextfunc' : ''}) + delfunc g:DictQftfFunc + delfunc SetQftfFunc + delfunc SetLocalQftfFunc + set efm& +endfunc + " Test for updating a location list for some other window and check that " 'qftextfunc' uses the correct location list. func Test_qftextfunc_other_loclist() @@ -5541,9 +5960,12 @@ func Test_locationlist_open_in_newtab() %bwipe! - lgetexpr ['Xqftestfile1:5:Line5', - \ 'Xqftestfile2:10:Line10', - \ 'Xqftestfile3:16:Line16'] + let lines =<< trim END + Xqftestfile1:5:Line5 + Xqftestfile2:10:Line10 + Xqftestfile3:16:Line16 + END + lgetexpr lines silent! llast call assert_equal(1, tabpagenr('$')) @@ -5584,6 +6006,21 @@ func Test_win_gettype() lclose endfunc +fun Test_vimgrep_nomatch() + call XexprTests('c') + call g:Xsetlist([{'lnum':10,'text':'Line1'}]) + copen + if has("win32") + call assert_fails('vimgrep foo *.zzz', 'E479:') + let expected = [{'lnum': 10, 'bufnr': 0, 'end_lnum': 0, 'pattern': '', 'valid': 0, 'vcol': 0, 'nr': 0, 'module': '', 'type': '', 'end_col': 0, 'col': 0, 'text': 'Line1'}] + else + call assert_fails('vimgrep foo *.zzz', 'E480:') + let expected = [] + endif + call assert_equal(expected, getqflist()) + cclose +endfunc + " Test for opening the quickfix window in two tab pages and then closing one " of the quickfix windows. This should not make the quickfix buffer unlisted. " (github issue #9300). @@ -5652,7 +6089,7 @@ func Test_lopen_bwipe_all() qall! END call writefile(lines, 'Xscript') - if RunVim([], [], '--clean -n -S Xscript') + if RunVim([], [], '-u NONE -n -X -Z -e -m -s -S Xscript') call assert_equal(['done'], readfile('Xresult')) endif @@ -5660,5 +6097,140 @@ func Test_lopen_bwipe_all() call delete('Xresult') endfunc +" Test for calling setqflist() function recursively +func Test_recursive_setqflist() + augroup QF_Test + au! + autocmd BufWinEnter quickfix call setqflist([], 'r') + augroup END + + copen + call assert_fails("call setqflist([], 'a')", 'E952:') + + augroup QF_Test + au! + augroup END + %bw! +endfunc + +" Test for failure to create a new window when selecting a file from the +" quickfix window +func Test_cwindow_newwin_fails() + cgetexpr ["Xfile1:10:L10", "Xfile1:20:L20"] + cwindow + only + let qf_wid = win_getid() + " create the maximum number of scratch windows + let hor_win_count = (&lines - 1)/2 + let hor_split_count = hor_win_count - 1 + for s in range(1, hor_split_count) | new | set buftype=nofile | endfor + call win_gotoid(qf_wid) + call assert_fails('exe "normal \<CR>"', 'E36:') + %bw! +endfunc + +" Test for updating the location list when only the location list window is +" present and the corresponding file window is closed. +func Test_loclist_update_with_llwin_only() + %bw! + new + wincmd w + lexpr ["Xfile1:1:Line1"] + lopen + wincmd p + close + call setloclist(2, [], 'r', {'lines': ["Xtest2:2:Line2"]}) + call assert_equal(['Xtest2|2| Line2'], getbufline(winbufnr(2), 1, '$')) + %bw! +endfunc + +" Test for getting the quickfix list after a buffer with an error is wiped out +func Test_getqflist_wiped_out_buffer() + %bw! + cexpr ["Xtest1:34:Wiped out"] + let bnum = bufnr('Xtest1') + call assert_equal(bnum, getqflist()[0].bufnr) + bw Xtest1 + call assert_equal(0, getqflist()[0].bufnr) + %bw! +endfunc + +" Test for the status message that is displayed when opening a new quickfix +" list +func Test_qflist_statusmsg() + cexpr "1\n2" + cexpr "1\n2\n3\ntest_quickfix.vim:1:msg" + call assert_equal('(4 of 4): msg', v:statusmsg) + call setqflist([], 'f') + %bw! + + " When creating a new quickfix list, if an autocmd changes the quickfix list + " in the stack, then an error message should be displayed. + augroup QF_Test + au! + au BufEnter test_quickfix.vim colder + augroup END + cexpr "1\n2" + call assert_fails('cexpr "1\n2\n3\ntest_quickfix.vim:1:msg"', 'E925:') + call setqflist([], 'f') + augroup QF_Test + au! + augroup END + %bw! + + augroup QF_Test + au! + au BufEnter test_quickfix.vim caddexpr "4" + augroup END + call assert_fails('cexpr "1\n2\n3\ntest_quickfix.vim:1:msg"', 'E925:') + call setqflist([], 'f') + augroup QF_Test + au! + augroup END + %bw! +endfunc + +func Test_quickfixtextfunc_recursive() + func s:QFTfunc(o) + cgete '0' + endfunc + copen + let &quickfixtextfunc = 's:QFTfunc' + cex "" + + let &quickfixtextfunc = '' + cclose +endfunc + +" Test for replacing the location list from an autocmd. This used to cause a +" read from freed memory. +func Test_loclist_replace_autocmd() + %bw! + call setloclist(0, [], 'f') + let s:bufnr = bufnr() + cal setloclist(0, [{'0': 0, '': ''}]) + au BufEnter * cal setloclist(1, [{'t': ''}, {'bufnr': s:bufnr}], 'r') + lopen + try + exe "norm j\<CR>" + catch + endtry + lnext + %bw! + call setloclist(0, [], 'f') +endfunc + +func s:QfTf(_) +endfunc + +func Test_setqflist_cb_arg() + " This was changing the callback name in the dictionary. + let d = #{quickfixtextfunc: 's:QfTf'} + call setqflist([], 'a', d) + call assert_equal('s:QfTf', d.quickfixtextfunc) + + call setqflist([], 'f') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_random.vim b/src/nvim/testdir/test_random.vim index 6d3f7dcfd9..5fdbfe9cd8 100644 --- a/src/nvim/testdir/test_random.vim +++ b/src/nvim/testdir/test_random.vim @@ -1,5 +1,8 @@ " Tests for srand() and rand() +source check.vim +source shared.vim + func Test_Rand() let r = srand(123456789) call assert_equal([1573771921, 319883699, 2742014374, 1324369493], r) @@ -9,18 +12,9 @@ func Test_Rand() 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 + " using /dev/urandom or used time, result is different each time + call assert_notequal(s, srand()) " Nvim does not support test_srand_seed " call test_srand_seed(123456789) @@ -33,13 +27,11 @@ func Test_Rand() endif call assert_fails('echo srand([1])', 'E745:') call assert_fails('echo rand("burp")', 'E475:') - call assert_fails('echo rand([1, 2, 3])', 'E475:') - call assert_fails('echo rand([[1], 2, 3, 4])', 'E475:') - call assert_fails('echo rand([1, [2], 3, 4])', 'E475:') - call assert_fails('echo rand([1, 2, [3], 4])', 'E475:') - call assert_fails('echo rand([1, 2, 3, [4]])', 'E475:') - - " call test_settime(0) + call assert_fails('echo rand([1, 2, 3])', 'E730:') + call assert_fails('echo rand([[1], 2, 3, 4])', 'E730:') + call assert_fails('echo rand([1, [2], 3, 4])', 'E730:') + call assert_fails('echo rand([1, 2, [3], 4])', 'E730:') + call assert_fails('echo rand([1, 2, 3, [4]])', 'E730:') endfunc func Test_issue_5587() @@ -48,4 +40,20 @@ func Test_issue_5587() call rand() endfunc +func Test_srand() + CheckNotGui + + let cmd = GetVimCommand() .. ' -V -es -c "echo rand()" -c qa!' + let bad = 0 + for _ in range(10) + echo cmd + let result1 = system(cmd) + let result2 = system(cmd) + if result1 ==# result2 + let bad += 1 + endif + endfor + call assert_inrange(0, 4, bad) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_recover.vim b/src/nvim/testdir/test_recover.vim index fc073cacd2..92e22687af 100644 --- a/src/nvim/testdir/test_recover.vim +++ b/src/nvim/testdir/test_recover.vim @@ -1,5 +1,7 @@ " Test :recover +source check.vim + func Test_recover_root_dir() " This used to access invalid memory. split Xtest @@ -23,6 +25,21 @@ func Test_recover_root_dir() set dir& endfunc +" Make a copy of the current swap file to "Xswap". +" Return the name of the swap file. +func CopySwapfile() + preserve + " get the name of the swap file + let swname = split(execute("swapname"))[0] + let swname = substitute(swname, '[[:blank:][:cntrl:]]*\(.\{-}\)[[:blank:][:cntrl:]]*$', '\1', '') + " make a copy of the swap file in Xswap + set binary + exe 'sp ' . swname + w! Xswap + set nobinary + return swname +endfunc + " Inserts 10000 lines with text to fill the swap file with two levels of pointer " blocks. Then recovers from the swap file and checks all text is restored. " @@ -40,15 +57,9 @@ func Test_swap_file() let i += 1 endwhile $delete - preserve - " get the name of the swap file - let swname = split(execute("swapname"))[0] - let swname = substitute(swname, '[[:blank:][:cntrl:]]*\(.\{-}\)[[:blank:][:cntrl:]]*$', '\1', '') - " make a copy of the swap file in Xswap - set binary - exe 'sp ' . swname - w! Xswap - set nobinary + + let swname = CopySwapfile() + new only! bwipe! Xtest @@ -69,3 +80,388 @@ func Test_swap_file() set undolevels& enew! | only endfunc + +func Test_nocatch_process_still_running() + let g:skipped_reason = 'test_override() is N/A' + return + " sysinfo.uptime probably only works on Linux + if !has('linux') + let g:skipped_reason = 'only works on Linux' + return + endif + " the GUI dialog can't be handled + if has('gui_running') + let g:skipped_reason = 'only works in the terminal' + return + endif + + " don't intercept existing swap file here + au! SwapExists + + " Edit a file and grab its swapfile. + edit Xswaptest + call setline(1, ['a', 'b', 'c']) + let swname = CopySwapfile() + + " Forget we edited this file + new + only! + bwipe! Xswaptest + + call rename('Xswap', swname) + call feedkeys('e', 'tL') + redir => editOutput + edit Xswaptest + redir END + call assert_match('E325: ATTENTION', editOutput) + call assert_match('file name: .*Xswaptest', editOutput) + call assert_match('process ID: \d* (STILL RUNNING)', editOutput) + + " Forget we edited this file + new + only! + bwipe! Xswaptest + + " pretend we rebooted + call test_override("uptime", 0) + sleep 1 + + call feedkeys('e', 'tL') + redir => editOutput + edit Xswaptest + redir END + call assert_match('E325: ATTENTION', editOutput) + call assert_notmatch('(STILL RUNNING)', editOutput) + + call test_override("ALL", 0) + call delete(swname) +endfunc + +" Test for :recover with multiple swap files +func Test_recover_multiple_swap_files() + CheckUnix + new Xfile1 + call setline(1, ['a', 'b', 'c']) + preserve + let b = readblob(swapname('')) + call writefile(b, '.Xfile1.swm') + call writefile(b, '.Xfile1.swn') + call writefile(b, '.Xfile1.swo') + %bw! + call feedkeys(":recover Xfile1\<CR>3\<CR>q", 'xt') + call assert_equal(['a', 'b', 'c'], getline(1, '$')) + " try using out-of-range number to select a swap file + bw! + call feedkeys(":recover Xfile1\<CR>4\<CR>q", 'xt') + call assert_equal('Xfile1', @%) + call assert_equal([''], getline(1, '$')) + bw! + call feedkeys(":recover Xfile1\<CR>0\<CR>q", 'xt') + call assert_equal('Xfile1', @%) + call assert_equal([''], getline(1, '$')) + bw! + + call delete('.Xfile1.swm') + call delete('.Xfile1.swn') + call delete('.Xfile1.swo') +endfunc + +" Test for :recover using an empty swap file +func Test_recover_empty_swap_file() + CheckUnix + call writefile([], '.Xfile1.swp') + let msg = execute('recover Xfile1') + call assert_match('Unable to read block 0 from .Xfile1.swp', msg) + call assert_equal('Xfile1', @%) + bw! + + " make sure there are no old swap files laying around + for f in glob('.sw?', 0, 1) + call delete(f) + endfor + + " :recover from an empty buffer + call assert_fails('recover', 'E305:') + call delete('.Xfile1.swp') +endfunc + +" Test for :recover using a corrupted swap file +" Refer to the comments in the memline.c file for the swap file headers +" definition. +func Test_recover_corrupted_swap_file() + CheckUnix + + " recover using a partial swap file + call writefile(0z1234, '.Xfile1.swp') + call assert_fails('recover Xfile1', 'E295:') + bw! + + " recover using invalid content in the swap file + call writefile([repeat('1', 2*1024)], '.Xfile1.swp') + call assert_fails('recover Xfile1', 'E307:') + call delete('.Xfile1.swp') + + " :recover using a swap file with a corrupted header + edit Xfile1 + preserve + let sn = swapname('') + let b = readblob(sn) + let save_b = copy(b) + bw! + + " Not all fields are written in a system-independent manner. Detect whether + " the test is running on a little or big-endian system, so the correct + " corruption values can be set. + " The B0_MAGIC_LONG field may be 32-bit or 64-bit, depending on the system, + " even though the value stored is only 32-bits. Therefore, need to check + " both the high and low 32-bits to compute these values. + let little_endian = (b[1008:1011] == 0z33323130) || (b[1012:1015] == 0z33323130) + let system_64bit = little_endian ? (b[1012:1015] == 0z00000000) : (b[1008:1011] == 0z00000000) + + " clear the B0_MAGIC_LONG field + if system_64bit + let b[1008:1015] = 0z00000000.00000000 + else + let b[1008:1011] = 0z00000000 + endif + call writefile(b, sn) + let msg = execute('recover Xfile1') + call assert_match('the file has been damaged', msg) + call assert_equal('Xfile1', @%) + call assert_equal([''], getline(1, '$')) + bw! + + " reduce the page size + let b = copy(save_b) + let b[12:15] = 0z00010000 + call writefile(b, sn) + let msg = execute('recover Xfile1') + call assert_match('page size is smaller than minimum value', msg) + call assert_equal('Xfile1', @%) + call assert_equal([''], getline(1, '$')) + bw! + + " clear the pointer ID + let b = copy(save_b) + let b[4096:4097] = 0z0000 + call writefile(b, sn) + call assert_fails('recover Xfile1', 'E310:') + call assert_equal('Xfile1', @%) + call assert_equal([''], getline(1, '$')) + bw! + + " set the number of pointers in a pointer block to zero + let b = copy(save_b) + let b[4098:4099] = 0z0000 + call writefile(b, sn) + call assert_fails('recover Xfile1', 'E312:') + call assert_equal('Xfile1', @%) + call assert_equal(['???EMPTY BLOCK'], getline(1, '$')) + bw! + + " set the block number in a pointer entry to a negative number + let b = copy(save_b) + if system_64bit + let b[4104:4111] = little_endian ? 0z00000000.00000080 : 0z80000000.00000000 + else + let b[4104:4107] = little_endian ? 0z00000080 : 0z80000000 + endif + call writefile(b, sn) + call assert_fails('recover Xfile1', 'E312:') + call assert_equal('Xfile1', @%) + call assert_equal(['???LINES MISSING'], getline(1, '$')) + bw! + + " clear the data block ID + let b = copy(save_b) + let b[8192:8193] = 0z0000 + call writefile(b, sn) + call assert_fails('recover Xfile1', 'E312:') + call assert_equal('Xfile1', @%) + call assert_equal(['???BLOCK MISSING'], getline(1, '$')) + bw! + + " set the number of lines in the data block to zero + let b = copy(save_b) + if system_64bit + let b[8208:8215] = 0z00000000.00000000 + else + let b[8208:8211] = 0z00000000 + endif + call writefile(b, sn) + call assert_fails('recover Xfile1', 'E312:') + call assert_equal('Xfile1', @%) + call assert_equal(['??? from here until ???END lines may have been inserted/deleted', + \ '???END'], getline(1, '$')) + bw! + + " use an invalid text start for the lines in a data block + let b = copy(save_b) + if system_64bit + let b[8216:8219] = 0z00000000 + else + let b[8212:8215] = 0z00000000 + endif + call writefile(b, sn) + call assert_fails('recover Xfile1', 'E312:') + call assert_equal('Xfile1', @%) + call assert_equal(['???'], getline(1, '$')) + bw! + + " use an incorrect text end (db_txt_end) for the data block + let b = copy(save_b) + let b[8204:8207] = little_endian ? 0z80000000 : 0z00000080 + call writefile(b, sn) + call assert_fails('recover Xfile1', 'E312:') + call assert_equal('Xfile1', @%) + call assert_equal(['??? from here until ???END lines may be messed up', '', + \ '???END'], getline(1, '$')) + bw! + + " remove the data block + let b = copy(save_b) + call writefile(b[:8191], sn) + call assert_fails('recover Xfile1', 'E312:') + call assert_equal('Xfile1', @%) + call assert_equal(['???MANY LINES MISSING'], getline(1, '$')) + + bw! + call delete(sn) +endfunc + +" Test for :recover using an encrypted swap file +func Test_recover_encrypted_swap_file() + CheckFeature cryptv + CheckUnix + + " Recover an encrypted file from the swap file without the original file + new Xfile1 + call feedkeys(":X\<CR>vim\<CR>vim\<CR>", 'xt') + call setline(1, ['aaa', 'bbb', 'ccc']) + preserve + let b = readblob('.Xfile1.swp') + call writefile(b, '.Xfile1.swm') + bw! + call feedkeys(":recover Xfile1\<CR>vim\<CR>\<CR>", 'xt') + call assert_equal(['aaa', 'bbb', 'ccc'], getline(1, '$')) + bw! + call delete('.Xfile1.swm') + + " Recover an encrypted file from the swap file with the original file + new Xfile1 + call feedkeys(":X\<CR>vim\<CR>vim\<CR>", 'xt') + call setline(1, ['aaa', 'bbb', 'ccc']) + update + call setline(1, ['111', '222', '333']) + preserve + let b = readblob('.Xfile1.swp') + call writefile(b, '.Xfile1.swm') + bw! + call feedkeys(":recover Xfile1\<CR>vim\<CR>\<CR>", 'xt') + call assert_equal(['111', '222', '333'], getline(1, '$')) + call assert_true(&modified) + bw! + call delete('.Xfile1.swm') + call delete('Xfile1') +endfunc + +" Test for :recover using a unreadable swap file +func Test_recover_unreadble_swap_file() + CheckUnix + CheckNotRoot + new Xfile1 + let b = readblob('.Xfile1.swp') + call writefile(b, '.Xfile1.swm') + bw! + call setfperm('.Xfile1.swm', '-w-------') + call assert_fails('recover Xfile1', 'E306:') + call delete('.Xfile1.swm') +endfunc + +" Test for using :recover when the original file and the swap file have the +" same contents. +func Test_recover_unmodified_file() + CheckUnix + call writefile(['aaa', 'bbb', 'ccc'], 'Xfile1') + edit Xfile1 + preserve + let b = readblob('.Xfile1.swp') + %bw! + call writefile(b, '.Xfile1.swz') + let msg = execute('recover Xfile1') + call assert_equal(['aaa', 'bbb', 'ccc'], getline(1, '$')) + call assert_false(&modified) + call assert_match('Buffer contents equals file contents', msg) + bw! + call delete('Xfile1') + call delete('.Xfile1.swz') +endfunc + +" Test for recovering a file when editing a symbolically linked file +func Test_recover_symbolic_link() + CheckUnix + call writefile(['aaa', 'bbb', 'ccc'], 'Xfile1') + silent !ln -s Xfile1 Xfile2 + edit Xfile2 + call assert_equal('.Xfile1.swp', fnamemodify(swapname(''), ':t')) + preserve + let b = readblob('.Xfile1.swp') + %bw! + call writefile([], 'Xfile1') + call writefile(b, '.Xfile1.swp') + silent! recover Xfile2 + call assert_equal(['aaa', 'bbb', 'ccc'], getline(1, '$')) + call assert_true(&modified) + update + %bw! + call assert_equal(['aaa', 'bbb', 'ccc'], readfile('Xfile1')) + call delete('Xfile1') + call delete('Xfile2') + call delete('.Xfile1.swp') +endfunc + +" Test for recovering a file when an autocmd moves the cursor to an invalid +" line. This used to result in an internal error (E315) which is fixed +" by 8.2.2966. +func Test_recover_invalid_cursor_pos() + call writefile([], 'Xfile1') + edit Xfile1 + preserve + let b = readblob('.Xfile1.swp') + bw! + augroup Test + au! + au BufReadPost Xfile1 normal! 3G + augroup END + call writefile(range(1, 3), 'Xfile1') + call writefile(b, '.Xfile1.swp') + try + recover Xfile1 + catch /E308:/ + " this test is for the :E315 internal error. + " ignore the 'E308: Original file may have been changed' error + endtry + redraw! + augroup Test + au! + augroup END + augroup! Test + call delete('Xfile1') + call delete('.Xfile1.swp') +endfunc + +" Test for recovering a buffer without a name +func Test_noname_buffer() + new + call setline(1, ['one', 'two']) + preserve + let sn = swapname('') + let b = readblob(sn) + bw! + call writefile(b, sn) + exe "recover " .. sn + call assert_equal(['one', 'two'], getline(1, '$')) + call delete(sn) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim index d08a980787..ece6ae518e 100644 --- a/src/nvim/testdir/test_regexp_latin.vim +++ b/src/nvim/testdir/test_regexp_latin.vim @@ -101,8 +101,33 @@ func Test_multi_failure() set re=2 call assert_fails('/a**', 'E871:') call assert_fails('/a*\+', 'E871:') - call assert_fails('/a\{a}', 'E870:') + call assert_fails('/a\{a}', 'E554:') + set re=0 +endfunc + +func Test_column_success_failure() + new + call setline(1, 'xbar') + + set re=1 + %s/\%>0v./A/ + call assert_equal('Abar', getline(1)) + call assert_fails('/\%v', 'E71:') + call assert_fails('/\%>v', 'E71:') + call assert_fails('/\%c', 'E71:') + call assert_fails('/\%<c', 'E71:') + call assert_fails('/\%l', 'E71:') + set re=2 + %s/\%>0v./B/ + call assert_equal('Bbar', getline(1)) + call assert_fails('/\%v', 'E1273:') + call assert_fails('/\%>v', 'E1273:') + call assert_fails('/\%c', 'E1273:') + call assert_fails('/\%<c', 'E1273:') + call assert_fails('/\%l', 'E1273:') + set re=0 + bwipe! endfunc func Test_recursive_addstate() @@ -146,6 +171,10 @@ func Test_regexp_single_line_pat() call add(tl, [2, 'c*', 'abdef', '']) call add(tl, [2, 'bc\+', 'abccccdef', 'bcccc']) call add(tl, [2, 'bc\+', 'abdef']) " no match + " match escape character in a string + call add(tl, [2, '.\e.', "one\<Esc>two", "e\<Esc>t"]) + " match backspace character in a string + call add(tl, [2, '.\b.', "one\<C-H>two", "e\<C-H>t"]) " match newline character in a string call add(tl, [2, 'o\nb', "foo\nbar", "o\nb"]) @@ -895,6 +924,8 @@ func Test_regexp_error() call assert_fails("call matchlist('x x', '\\%#=2 \\zs*')", 'E888:') call assert_fails("call matchlist('x x', '\\%#=2 \\ze*')", 'E888:') call assert_fails('exe "normal /\\%#=1\\%[x\\%[x]]\<CR>"', 'E369:') + call assert_fails("call matchstr('abcd', '\\%o841\\%o142')", 'E678:') + call assert_equal('', matchstr('abcd', '\%o181\%o142')) endfunc " Test for using the last substitute string pattern (~) @@ -1027,6 +1058,28 @@ func Test_using_invalid_visual_position() bwipe! endfunc +func Test_using_two_engines_pattern() + new + call setline(1, ['foobar=0', 'foobar=1', 'foobar=2']) + " \%#= at the end of the pattern + for i in range(0, 2) + for j in range(0, 2) + exe "set re=" .. i + call cursor(j + 1, 7) + call assert_fails("%s/foobar\\%#=" .. j, 'E1281:') + endfor + endfor + set re=0 + + " \%#= at the start of the pattern + for i in range(0, 2) + call cursor(i + 1, 7) + exe ":%s/\\%#=" .. i .. "foobar=" .. i .. "/xx" + endfor + call assert_equal(['xx', 'xx', 'xx'], getline(1, '$')) + bwipe! +endfunc + func Test_recursive_substitute_expr() new func Repl() diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index 11dd3badb6..bbf1aa53b5 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -263,8 +263,16 @@ func Test_get_register() call assert_equal('', getreg("\<C-F>")) call assert_equal('', getreg("\<C-W>")) call assert_equal('', getreg("\<C-L>")) + " Change the last used register to '"' for the next test + normal! ""yy + let @" = 'happy' + call assert_equal('happy', getreg()) + call assert_equal('happy', getreg('')) call assert_equal('', getregtype('!')) + call assert_fails('echo getregtype([])', 'E730:') + call assert_equal('v', getregtype()) + call assert_equal('v', getregtype('')) " Test for inserting an invalid register content call assert_beeps('exe "normal i\<C-R>!"') @@ -277,7 +285,9 @@ func Test_get_register() " Test for inserting a multi-line register in the command line call feedkeys(":\<C-R>r\<Esc>", 'xt') - call assert_equal("a\rb", histget(':', -1)) " Modified because of #6137 + " Nvim: no trailing CR because of #6137 + " call assert_equal("a\rb\r", histget(':', -1)) + call assert_equal("a\rb", histget(':', -1)) call assert_fails('let r = getreg("=", [])', 'E745:') call assert_fails('let r = getreg("=", 1, [])', 'E745:') @@ -289,6 +299,7 @@ endfunc func Test_set_register() call assert_fails("call setreg('#', 200)", 'E86:') + " call assert_fails("call setreg('a', test_unknown())", 'E908:') edit Xfile_alt_1 let b1 = bufnr('') @@ -349,6 +360,12 @@ func Test_set_register() normal 0".gP call assert_equal('abcabcabc', getline(1)) + let @"='' + call setreg('', '1') + call assert_equal('1', @") + call setreg('@', '2') + call assert_equal('2', @") + enew! endfunc @@ -474,6 +491,21 @@ func Test_get_reginfo() let info = getreginfo('"') call assert_equal('z', info.points_to) + let @a="a1b2" + nnoremap <F2> <Cmd>let g:RegInfo = getreginfo()<CR> + exe "normal \"a\<F2>" + call assert_equal({'regcontents': ['a1b2'], 'isunnamed': v:false, + \ 'regtype': 'v'}, g:RegInfo) + nunmap <F2> + unlet g:RegInfo + + " The type of "isunnamed" was VAR_SPECIAL but should be VAR_BOOL. Can only + " be noticed when using json_encod(). + call setreg('a', 'foo') + let reginfo = getreginfo('a') + let expected = #{regcontents: ['foo'], isunnamed: v:false, regtype: 'v'} + call assert_equal(json_encode(expected), json_encode(reginfo)) + bwipe! endfunc diff --git a/src/nvim/testdir/test_reltime.vim b/src/nvim/testdir/test_reltime.vim index b381f1ddbb..f4ce5de118 100644 --- a/src/nvim/testdir/test_reltime.vim +++ b/src/nvim/testdir/test_reltime.vim @@ -24,4 +24,8 @@ func Test_reltime() call assert_true(reltimefloat(differs) < 0.1) call assert_true(reltimefloat(differs) > 0.0) + call assert_equal(0, reltime({})) + call assert_equal(0, reltime({}, {})) endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_retab.vim b/src/nvim/testdir/test_retab.vim index 1650a03876..a4f95053c0 100644 --- a/src/nvim/testdir/test_retab.vim +++ b/src/nvim/testdir/test_retab.vim @@ -1,4 +1,7 @@ " Test :retab + +source check.vim + func SetUp() new call setline(1, "\ta \t b c ") @@ -81,19 +84,32 @@ func Test_retab_error() call assert_fails('ret 80000000000000000000', 'E475:') endfunc +func RetabLoop() + while 1 + set ts=4000 + retab 4 + endwhile +endfunc + func Test_retab_endless() - new + " inside try/catch we can catch the error message call setline(1, "\t0\t") let caught = 'no' try - while 1 - set ts=4000 - retab 4 - endwhile - catch /E1240/ - let caught = 'yes' + call RetabLoop() + catch /E1240:/ + let caught = v:exception endtry - bwipe! + call assert_match('E1240:', caught) + + set tabstop& +endfunc + +func Test_nocatch_retab_endless() + " when not inside try/catch an interrupt is generated to get out of loops + call setline(1, "\t0\t") + call assert_fails('call RetabLoop()', ['E1240:', 'Interrupted']) + set tabstop& endfunc diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 0cf55c7d0b..885043accf 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -294,6 +294,9 @@ func Test_searchpair() new call setline(1, ['other code', 'here [', ' [', ' " cursor here', ' ]]']) + " should not give an error for using "42" + call assert_equal(0, searchpair('a', 'b', 'c', '', 42)) + 4 call assert_equal(3, searchpair('\[', '', ']', 'bW')) call assert_equal([0, 3, 2, 0], getpos('.')) @@ -897,9 +900,7 @@ func Test_incsearch_cmdline_modifier() endfunc func Test_incsearch_scrolling() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckRunVimInTerminal call assert_equal(0, &scrolloff) call writefile([ \ 'let dots = repeat(".", 120)', @@ -1376,6 +1377,22 @@ func Test_subst_word_under_cursor() set noincsearch endfunc +func Test_search_skip_all_matches() + enew + call setline(1, ['no match here', + \ 'match this line', + \ 'nope', + \ 'match in this line', + \ 'last line', + \ ]) + call cursor(1, 1) + let lnum = search('this', '', 0, 0, 'getline(".") =~ "this line"') + " Only check that no match is found. Previously it searched forever. + call assert_equal(0, lnum) + + bwipe! +endfunc + func Test_search_undefined_behaviour() CheckFeature terminal @@ -1649,8 +1666,8 @@ func Test_search_with_no_last_pat() call assert_fails(";//p", 'E35:') call assert_fails("??p", 'E35:') call assert_fails(";??p", 'E35:') - call assert_fails('g//p', 'E476:') - call assert_fails('v//p', 'E476:') + call assert_fails('g//p', ['E35:', 'E476:']) + call assert_fails('v//p', ['E35:', 'E476:']) call writefile(v:errors, 'Xresult') qall! [SCRIPT] @@ -1671,8 +1688,8 @@ func Test_search_tilde_pat() call assert_fails('exe "normal /~\<CR>"', 'E33:') call assert_fails('exe "normal ?~\<CR>"', 'E33:') set regexpengine=2 - call assert_fails('exe "normal /~\<CR>"', 'E383:') - call assert_fails('exe "normal ?~\<CR>"', 'E383:') + call assert_fails('exe "normal /~\<CR>"', ['E33:', 'E383:']) + call assert_fails('exe "normal ?~\<CR>"', ['E33:', 'E383:']) set regexpengine& call writefile(v:errors, 'Xresult') qall! @@ -1752,6 +1769,25 @@ func Test_invalid_regexp() call assert_fails("call search('\\%#=3ab')", 'E864:') endfunc +" Test for searching a very complex pattern in a string. Should switch the +" regexp engine from NFA to the old engine. +func Test_regexp_switch_engine() + let l = readfile('samples/re.freeze.txt') + let v = substitute(l[4], '..\@<!', '', '') + call assert_equal(l[4], v) +endfunc + +" Test for the \%V atom to search within visually selected text +func Test_search_in_visual_area() + new + call setline(1, ['foo bar1', 'foo bar2', 'foo bar3', 'foo bar4']) + exe "normal 2GVjo/\\%Vbar\<CR>\<Esc>" + call assert_equal([2, 5], [line('.'), col('.')]) + exe "normal 2GVj$?\\%Vbar\<CR>\<Esc>" + call assert_equal([3, 5], [line('.'), col('.')]) + close! +endfunc + " Test for searching with 'smartcase' and 'ignorecase' func Test_search_smartcase() new @@ -1796,7 +1832,7 @@ func Test_search_smartcase_utf8() set ignorecase& smartcase& let &encoding = save_enc - close! + bwipe! endfunc " Test searching past the end of a file @@ -1805,7 +1841,29 @@ func Test_search_past_eof() call setline(1, ['Line']) exe "normal /\\n\\zs\<CR>" call assert_equal([1, 4], [line('.'), col('.')]) - close! + bwipe! +endfunc + +" Test setting the start of the match and still finding a next match in the +" same line. +func Test_search_set_start_same_line() + new + set cpo-=c + + call setline(1, ['1', '2', '3 .', '4', '5']) + exe "normal /\\_s\\zs\\S\<CR>" + call assert_equal([2, 1], [line('.'), col('.')]) + exe 'normal n' + call assert_equal([3, 1], [line('.'), col('.')]) + exe 'normal n' + call assert_equal([3, 3], [line('.'), col('.')]) + exe 'normal n' + call assert_equal([4, 1], [line('.'), col('.')]) + exe 'normal n' + call assert_equal([5, 1], [line('.'), col('.')]) + + set cpo+=c + bwipe! endfunc " Test for various search offsets @@ -1927,6 +1985,100 @@ func Test_incsearch_highlighting_newline() bw endfunc +func Test_incsearch_substitute_dump2() + CheckOption incsearch + CheckScreendump + + call writefile([ + \ 'set incsearch hlsearch scrolloff=0', + \ 'for n in range(1, 4)', + \ ' call setline(n, "foo " . n)', + \ 'endfor', + \ 'call setline(5, "abc|def")', + \ '3', + \ ], 'Xis_subst_script2') + let buf = RunVimInTerminal('-S Xis_subst_script2', {'rows': 9, 'cols': 70}) + + call term_sendkeys(buf, ':%s/\vabc|') + sleep 100m + call VerifyScreenDump(buf, 'Test_incsearch_sub_01', {}) + call term_sendkeys(buf, "\<Esc>") + + " The following should not be highlighted + call term_sendkeys(buf, ':1,5s/\v|') + sleep 100m + call VerifyScreenDump(buf, 'Test_incsearch_sub_02', {}) + + + call StopVimInTerminal(buf) + call delete('Xis_subst_script2') +endfunc + +func Test_pattern_is_uppercase_smartcase() + new + let input=['abc', 'ABC', 'Abc', 'abC'] + call setline(1, input) + call cursor(1,1) + " default, matches firstline + %s/abc//g + call assert_equal(['', 'ABC', 'Abc', 'abC'], + \ getline(1, '$')) + + set smartcase ignorecase + sil %d + call setline(1, input) + call cursor(1,1) + " with smartcase and incsearch set, matches everything + %s/abc//g + call assert_equal(['', '', '', ''], getline(1, '$')) + + sil %d + call setline(1, input) + call cursor(1,1) + " with smartcase and incsearch set and found an uppercase letter, + " match only that. + %s/abC//g + call assert_equal(['abc', 'ABC', 'Abc', ''], + \ getline(1, '$')) + + sil %d + call setline(1, input) + call cursor(1,1) + exe "norm! vG$\<esc>" + " \%V should not be detected as uppercase letter + %s/\%Vabc//g + call assert_equal(['', '', '', ''], getline(1, '$')) + + call setline(1, input) + call cursor(1,1) + exe "norm! vG$\<esc>" + " \v%V should not be detected as uppercase letter + %s/\v%Vabc//g + call assert_equal(['', '', '', ''], getline(1, '$')) + + call setline(1, input) + call cursor(1,1) + exe "norm! vG$\<esc>" + " \v%VabC should be detected as uppercase letter + %s/\v%VabC//g + call assert_equal(['abc', 'ABC', 'Abc', ''], + \ getline(1, '$')) + + call setline(1, input) + call cursor(1,1) + " \Vabc should match everything + %s/\Vabc//g + call assert_equal(['', '', '', ''], getline(1, '$')) + + call setline(1, input + ['_abc']) + " _ matches normally + %s/\v_.*//g + call assert_equal(['abc', 'ABC', 'Abc', 'abC', ''], getline(1, '$')) + + set smartcase& ignorecase& + bw! +endfunc + func Test_no_last_search_pattern() CheckOption incsearch diff --git a/src/nvim/testdir/test_search_stat.vim b/src/nvim/testdir/test_search_stat.vim index 89e09cf85b..77bd50ada2 100644 --- a/src/nvim/testdir/test_search_stat.vim +++ b/src/nvim/testdir/test_search_stat.vim @@ -260,6 +260,14 @@ endfunc func Test_searchcount_fails() call assert_fails('echo searchcount("boo!")', 'E715:') + call assert_fails('echo searchcount({"timeout" : []})', 'E745:') + call assert_fails('echo searchcount({"maxcount" : []})', 'E745:') + call assert_fails('echo searchcount({"pattern" : []})', 'E730:') + call assert_fails('echo searchcount({"pos" : 1})', 'E475:') + call assert_fails('echo searchcount({"pos" : [1]})', 'E475:') + call assert_fails('echo searchcount({"pos" : [[], 2, 3]})', 'E745:') + call assert_fails('echo searchcount({"pos" : [1, [], 3]})', 'E745:') + call assert_fails('echo searchcount({"pos" : [1, 2, []]})', 'E745:') endfunc func Test_searchcount_in_statusline() diff --git a/src/nvim/testdir/test_selectmode.vim b/src/nvim/testdir/test_selectmode.vim index f2cab45450..041f0592f1 100644 --- a/src/nvim/testdir/test_selectmode.vim +++ b/src/nvim/testdir/test_selectmode.vim @@ -34,6 +34,9 @@ func Test_selectmode_start() set selectmode=cmd call feedkeys('gvabc', 'xt') call assert_equal('abctdef', getline(1)) + " arrow keys without shift should not start selection + call feedkeys("A\<Home>\<Right>\<Left>ro", 'xt') + call assert_equal('roabctdef', getline(1)) set selectmode= keymodel= bw! endfunc diff --git a/src/nvim/testdir/test_shell.vim b/src/nvim/testdir/test_shell.vim new file mode 100644 index 0000000000..8b9c7a5b12 --- /dev/null +++ b/src/nvim/testdir/test_shell.vim @@ -0,0 +1,209 @@ +" Test for the shell related options ('shell', 'shellcmdflag', 'shellpipe', +" 'shellquote', 'shellredir', 'shellxescape', and 'shellxquote') + +source check.vim +source shared.vim + +func Test_shell_options() + " The expected value of 'shellcmdflag', 'shellpipe', 'shellquote', + " 'shellredir', 'shellxescape', 'shellxquote' for the supported shells. + let shells = [] + if has('unix') + let shells += [['sh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''], + \ ['ksh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''], + \ ['mksh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''], + \ ['zsh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''], + \ ['zsh-beta', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''], + \ ['bash', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''], + \ ['fish', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''], + \ ['ash', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''], + \ ['dash', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''], + \ ['csh', '-c', '|& tee', '', '>&', '', ''], + \ ['tcsh', '-c', '|& tee', '', '>&', '', '']] + endif + if has('win32') + let shells += [['cmd', '/s /c', '>%s 2>&1', '', '>%s 2>&1', '', '"']] + endif + + " start a new Vim instance with 'shell' set to each of the supported shells + " and check the default shell option settings + let after =<< trim END + let l = [&shell, &shellcmdflag, &shellpipe, &shellquote] + let l += [&shellredir, &shellxescape, &shellxquote] + call writefile([json_encode(l)], 'Xtestout') + qall! + END + for e in shells + if RunVim([], after, '--cmd "set shell=' .. e[0] .. '"') + call assert_equal(e, json_decode(readfile('Xtestout')[0])) + endif + endfor + + " Test shellescape() for each of the shells. + for e in shells + exe 'set shell=' .. e[0] + if e[0] =~# '.*csh$' || e[0] =~# '.*csh.exe$' + let str1 = "'cmd \"arg1\" '\\''arg2'\\'' \\!%#'" + let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\\\!\\%\\#'" + elseif e[0] =~# '.*powershell$' || e[0] =~# '.*powershell.exe$' + let str1 = "'cmd \"arg1\" ''arg2'' !%#'" + let str2 = "'cmd \"arg1\" ''arg2'' \\!\\%\\#'" + else + let str1 = "'cmd \"arg1\" '\\''arg2'\\'' !%#'" + let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\!\\%\\#'" + endif + call assert_equal(str1, shellescape("cmd \"arg1\" 'arg2' !%#"), e[0]) + call assert_equal(str2, shellescape("cmd \"arg1\" 'arg2' !%#", 1), e[0]) + + " Try running an external command with the shell. + if executable(e[0]) + " set the shell options for the current 'shell' + let [&shellcmdflag, &shellpipe, &shellquote, &shellredir, + \ &shellxescape, &shellxquote] = e[1:6] + new + r !echo hello + call assert_equal('hello', substitute(getline(2), '\W', '', 'g'), e[0]) + bwipe! + endif + endfor + set shell& shellcmdflag& shellpipe& shellquote& + set shellredir& shellxescape& shellxquote& + call delete('Xtestout') +endfunc + +" Test for the 'shell' option +func Test_shell() + throw 'Skipped: Nvim missing :shell currently' + CheckUnix + let save_shell = &shell + set shell= + let caught_e91 = 0 + try + shell + catch /E91:/ + let caught_e91 = 1 + endtry + call assert_equal(1, caught_e91) + let &shell = save_shell +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 the 'shellescape' option +func Test_shellescape() + let save_shell = &shell + set shell=bash + call assert_equal("'text'", shellescape('text')) + call assert_equal("'te\"xt'", 'te"xt'->shellescape()) + call assert_equal("'te'\\''xt'", shellescape("te'xt")) + + call assert_equal("'te%xt'", shellescape("te%xt")) + call assert_equal("'te\\%xt'", shellescape("te%xt", 1)) + call assert_equal("'te#xt'", shellescape("te#xt")) + call assert_equal("'te\\#xt'", shellescape("te#xt", 1)) + call assert_equal("'te!xt'", shellescape("te!xt")) + call assert_equal("'te\\!xt'", shellescape("te!xt", 1)) + + call assert_equal("'te\nxt'", shellescape("te\nxt")) + call assert_equal("'te\\\nxt'", shellescape("te\nxt", 1)) + set shell=tcsh + call assert_equal("'te\\!xt'", shellescape("te!xt")) + call assert_equal("'te\\\\!xt'", shellescape("te!xt", 1)) + call assert_equal("'te\\\nxt'", shellescape("te\nxt")) + call assert_equal("'te\\\\\nxt'", shellescape("te\nxt", 1)) + + let &shell = save_shell +endfunc + +" Test for 'shellslash' +func Test_shellslash() + CheckOption shellslash + let save_shellslash = &shellslash + " The shell and cmdflag, and expected slash in tempname with shellslash set or + " unset. The assert checks the file separator before the leafname. + " ".*\\\\[^\\\\]*$" + let shells = [['cmd', '/c', '/', '/'], + \ ['powershell', '-Command', '/', '/'], + \ ['sh', '-c', '/', '/']] + for e in shells + exe 'set shell=' .. e[0] .. ' | set shellcmdflag=' .. e[1] + set noshellslash + let file = tempname() + call assert_match('^.\+' .. e[2] .. '[^' .. e[2] .. ']\+$', file, e[0] .. ' ' .. e[1] .. ' nossl') + set shellslash + let file = tempname() + call assert_match('^.\+' .. e[3] .. '[^' .. e[3] .. ']\+$', file, e[0] .. ' ' .. e[1] .. ' ssl') + endfor + let &shellslash = save_shellslash +endfunc + +" Test for 'shellxquote' +func Test_shellxquote() + CheckUnix + + let save_shell = &shell + let save_sxq = &shellxquote + let save_sxe = &shellxescape + + call writefile(['#!/bin/sh', 'echo "Cmd: [$*]" > Xlog'], 'Xtestshell') + call setfperm('Xtestshell', "r-x------") + set shell=./Xtestshell + + set shellxquote=\\" + call feedkeys(":!pwd\<CR>\<CR>", 'xt') + call assert_equal(['Cmd: [-c "pwd"]'], readfile('Xlog')) + + set shellxquote=( + call feedkeys(":!pwd\<CR>\<CR>", 'xt') + call assert_equal(['Cmd: [-c (pwd)]'], readfile('Xlog')) + + set shellxquote=\\"( + call feedkeys(":!pwd\<CR>\<CR>", 'xt') + call assert_equal(['Cmd: [-c "(pwd)"]'], readfile('Xlog')) + + set shellxescape=\"&<<()@^ + set shellxquote=( + call feedkeys(":!pwd\"&<<{}@^\<CR>\<CR>", 'xt') + call assert_equal(['Cmd: [-c (pwd^"^&^<^<{}^@^^)]'], readfile('Xlog')) + + let &shell = save_shell + let &shellxquote = save_sxq + let &shellxescape = save_sxe + call delete('Xtestshell') + call delete('Xlog') +endfunc + +" Test for using the shell set in the $SHELL environment variable +func Test_set_shell() + let after =<< trim [CODE] + call writefile([&shell], "Xtestout") + quit! + [CODE] + + if has('win32') + let $SHELL = 'C:\with space\cmd.exe' + let expected = '"C:\with space\cmd.exe"' + else + let $SHELL = '/bin/with space/sh' + let expected = '"/bin/with space/sh"' + endif + + if RunVimPiped([], after, '', '') + let lines = readfile('Xtestout') + call assert_equal(expected, lines[0]) + endif + call delete('Xtestout') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_signals.vim b/src/nvim/testdir/test_signals.vim index 338c0d79ff..c291c68e0d 100644 --- a/src/nvim/testdir/test_signals.vim +++ b/src/nvim/testdir/test_signals.vim @@ -16,8 +16,9 @@ endfunc " Test signal WINCH (window resize signal) func Test_signal_WINCH() throw 'skipped: Nvim cannot avoid terminal resize' - if has('gui_running') || !HasSignal('WINCH') - return + CheckNotGui + if !HasSignal('WINCH') + throw 'Skipped: WINCH signal not supported' endif " We do not actually want to change the size of the terminal. @@ -52,7 +53,7 @@ endfunc " Test signal PWR, which should update the swap file. func Test_signal_PWR() if !HasSignal('PWR') - return + throw 'Skipped: PWR signal not supported' endif " Set a very large 'updatetime' and 'updatecount', so that we can be sure @@ -78,6 +79,33 @@ func Test_signal_PWR() set updatetime& updatecount& endfunc +" Test signal INT. Handler sets got_int. It should be like typing CTRL-C. +func Test_signal_INT() + CheckRunVimInTerminal + if !HasSignal('INT') + throw 'Skipped: INT signal not supported' + endif + + " Skip the rest of the test when running with valgrind as signal INT is not + " received somehow by Vim when running with valgrind. + let cmd = GetVimCommand() + if cmd =~ 'valgrind' + throw 'Skipped: cannot test signal INT with valgrind' + endif + + let buf = RunVimInTerminal('', {'rows': 6}) + let pid_vim = term_getjob(buf)->job_info().process + + " Check that an endless loop in Vim is interrupted by signal INT. + call term_sendkeys(buf, ":while 1 | endwhile\n") + call WaitForAssert({-> assert_equal(':while 1 | endwhile', term_getline(buf, 6))}) + exe 'silent !kill -s INT ' .. pid_vim + call term_sendkeys(buf, ":call setline(1, 'INTERUPTED')\n") + call WaitForAssert({-> assert_equal('INTERUPTED', term_getline(buf, 1))}) + + call StopVimInTerminal(buf) +endfunc + " Test a deadly signal. " " There are several deadly signals: SISEGV, SIBUS, SIGTERM... @@ -91,9 +119,7 @@ func Test_deadly_signal_TERM() if !HasSignal('TERM') throw 'Skipped: TERM signal not supported' endif - if !CanRunVimInTerminal() - throw 'Skipped: cannot run vim in terminal' - endif + CheckRunVimInTerminal let cmd = GetVimCommand() if cmd =~ 'valgrind' throw 'Skipped: cannot test signal TERM with valgrind' @@ -128,8 +154,7 @@ func Test_deadly_signal_TERM() call assert_equal(['foo'], getline(1, '$')) let result = readfile('XautoOut') - call assert_match('VimLeavePre triggered', result[0]) - call assert_match('VimLeave triggered', result[1]) + call assert_equal(["VimLeavePre triggered", "VimLeave triggered"], result) %bwipe! call delete('.Xsig_TERM.swp') diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim index ff9ba3d8ed..8311955a15 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 culhl=Search icon=../../pixmaps/stock_vim_find_help.png') + call Sign_command_ignore_error('sign define Sign2 text=xy texthl=Title linehl=Error culhl=Search numhl=Number 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 culhl=Search$', a) + \ 'linehl=Error texthl=Title culhl=Search numhl=Number$', a) let a=execute('sign list Sign1') call assert_equal("\nsign Sign1 text=x ", a) @@ -127,26 +127,34 @@ func Test_sign() 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 + sign define SignY texthl=TextHl culhl=CulHl linehl=LineHl numhl=NumHl let sl = sign_getdefined('SignY')[0] call assert_equal('TextHl', sl.texthl) call assert_equal('CulHl', sl.culhl) call assert_equal('LineHl', sl.linehl) + call assert_equal('NumHl', sl.numhl) - sign define SignY texthl= culhl=CulHl linehl=LineHl + sign define SignY texthl= culhl=CulHl linehl=LineHl numhl=NumHl 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) + call assert_equal('NumHl', sl.numhl) sign define SignY linehl= let sl = sign_getdefined('SignY')[0] call assert_false(has_key(sl, 'linehl')) call assert_equal('CulHl', sl.culhl) + call assert_equal('NumHl', sl.numhl) sign define SignY culhl= let sl = sign_getdefined('SignY')[0] call assert_false(has_key(sl, 'culhl')) + call assert_equal('NumHl', sl.numhl) + + sign define SignY numhl= + let sl = sign_getdefined('SignY')[0] + call assert_false(has_key(sl, 'numhl')) sign undefine SignY @@ -158,7 +166,7 @@ func Test_sign() sign define Sign5 text=X\ linehl=Comment sign undefine Sign5 - sign define Sign5 linehl=Comment text=X\ + sign define Sign5 linehl=Comment text=X\ sign undefine Sign5 " define sign with backslash @@ -417,8 +425,8 @@ func Test_sign_funcs() 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' : '=>'}], + 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 @@ -483,13 +491,13 @@ func Test_sign_funcs() call assert_fails('call sign_place(5, "", "sign1", "@", {"lnum" : 10})', \ 'E158:') call assert_fails('call sign_place(5, "", "sign1", [], {"lnum" : 10})', - \ 'E158:') + \ 'E730:') call assert_fails('call sign_place(21, "", "sign1", "Xsign", \ {"lnum" : -1})', 'E474:') call assert_fails('call sign_place(22, "", "sign1", "Xsign", \ {"lnum" : 0})', 'E474:') call assert_fails('call sign_place(22, "", "sign1", "Xsign", - \ {"lnum" : []})', 'E474:') + \ {"lnum" : []})', 'E745:') call assert_equal(-1, sign_place(1, "*", "sign1", "Xsign", {"lnum" : 10})) " Tests for sign_getplaced() @@ -535,9 +543,9 @@ func Test_sign_funcs() call assert_equal(15, sign_place(15, '', 'sign1', 'Xsign', {'lnum' : 20})) call assert_equal(15, sign_place(15, '', 'sign2', 'Xsign')) call assert_equal([{'bufnr' : bufnr(''), 'signs' : - \ [{'id' : 15, 'group' : '', 'lnum' : 20, 'name' : 'sign2', - \ 'priority' : 10}]}], - \ sign_getplaced()) + \ [{'id' : 15, 'group' : '', 'lnum' : 20, 'name' : 'sign2', + \ 'priority' : 10}]}], + \ sign_getplaced()) " Tests for sign_undefine() call assert_equal(0, sign_undefine("sign1")) @@ -1165,7 +1173,7 @@ func Test_sign_unplace() call delete("Xsign2") endfunc -" Tests for auto-generating the sign identifier +" Tests for auto-generating the sign identifier. func Test_aaa_sign_id_autogen() enew | only call sign_unplace('*') @@ -1650,10 +1658,34 @@ func Test_sign_lnum_adjust() " changes made by this function. let &undolevels=&undolevels + " Nvim: make sign adjustment when deleting lines match Vim + set signcolumn=yes:1 + " Delete the line with the sign call deletebufline('', 4) let l = sign_getplaced(bufnr('')) - call assert_equal(0, len(l[0].signs)) + call assert_equal(4, l[0].signs[0].lnum) + + " Undo the delete operation + undo + let l = sign_getplaced(bufnr('')) + call assert_equal(5, l[0].signs[0].lnum) + + " Break the undo + let &undolevels=&undolevels + + " Delete few lines at the end of the buffer including the line with the sign + " Sign line number should not change (as it is placed outside of the buffer) + call deletebufline('', 3, 6) + let l = sign_getplaced(bufnr('')) + call assert_equal(5, l[0].signs[0].lnum) + + " Undo the delete operation. Sign should be restored to the previous line + undo + let l = sign_getplaced(bufnr('')) + call assert_equal(5, l[0].signs[0].lnum) + + set signcolumn& sign unplace * group=* sign undefine sign1 @@ -1731,7 +1763,7 @@ func Test_sign_jump_func() call assert_fails("call sign_jump(5, 'g5', 'foo')", 'E157:') call assert_fails('call sign_jump([], "", "foo")', 'E745:') call assert_fails('call sign_jump(2, [], "foo")', 'E730:') - call assert_fails('call sign_jump(2, "", {})', 'E158:') + call assert_fails('call sign_jump(2, "", {})', 'E731:') call assert_fails('call sign_jump(2, "", "baz")', 'E158:') sign unplace * group=* @@ -1741,9 +1773,7 @@ endfunc " Test for correct cursor position after the sign column appears or disappears. func Test_sign_cursor_position() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckRunVimInTerminal let lines =<< trim END call setline(1, [repeat('x', 75), 'mmmm', 'yyyy']) @@ -1790,8 +1820,8 @@ func Test_sign_numcol() set number set signcolumn=number sign define sign1 text==> - sign place 10 line=1 name=sign1 sign define sign2 text=V + sign place 10 line=1 name=sign1 redraw! call assert_equal("=> 01234", s:ScreenLine(1, 1, 8)) diff --git a/src/nvim/testdir/test_sleep.vim b/src/nvim/testdir/test_sleep.vim index f71855fd4b..a428f380b0 100644 --- a/src/nvim/testdir/test_sleep.vim +++ b/src/nvim/testdir/test_sleep.vim @@ -21,6 +21,7 @@ func! Test_sleep_bang() call s:assert_takes_longer('sl 50m', 50) call s:assert_takes_longer('sl! 50m', 50) call s:assert_takes_longer('1sleep', 1000) + call s:assert_takes_longer('normal 1gs', 1000) endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_sort.vim b/src/nvim/testdir/test_sort.vim index 9895ad754c..534393b724 100644 --- a/src/nvim/testdir/test_sort.vim +++ b/src/nvim/testdir/test_sort.vim @@ -80,7 +80,7 @@ func Test_sort_default() call assert_equal(['2', 'A', 'AA', 'a', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"], '')) call assert_equal(['2', 'A', 'AA', 'a', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"], 0)) call assert_equal(['2', 'A', 'a', 'AA', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"], 1)) - call assert_fails('call sort([3.3, 1, "2"], 3)', "E474") + call assert_fails('call sort([3.3, 1, "2"], 3)', "E474:") endfunc " Tests for the ":sort" command. @@ -1360,7 +1360,8 @@ func Test_sort_cmd() call setline(1, ['line1', 'line2']) call assert_fails('sort no', 'E474:') call assert_fails('sort c', 'E475:') - call assert_fails('sort #pat%', 'E682:') + call assert_fails('sort #pat%', 'E654:') + call assert_fails('sort /\%(/', 'E53:') enew! endfunc diff --git a/src/nvim/testdir/test_source.vim b/src/nvim/testdir/test_source.vim index ba6fd5ad95..d4d96e36bf 100644 --- a/src/nvim/testdir/test_source.vim +++ b/src/nvim/testdir/test_source.vim @@ -1,5 +1,8 @@ " Tests for the :source command. +source check.vim +source view_util.vim + func Test_source_autocmd() call writefile([ \ 'let did_source = 1', @@ -87,4 +90,24 @@ func Test_source_autocmd_sfile() call delete('Xscript.vim') endfunc +func Test_source_error() + call assert_fails('scriptencoding utf-8', 'E167:') + call assert_fails('finish', 'E168:') + " call assert_fails('scriptversion 2', 'E984:') +endfunc + +" Test for sourcing a script recursively +func Test_nested_script() + CheckRunVimInTerminal + call writefile([':source! Xscript.vim', ''], 'Xscript.vim') + let buf = RunVimInTerminal('', {'rows': 6}) + call term_wait(buf) + call term_sendkeys(buf, ":set noruler\n") + call term_sendkeys(buf, ":source! Xscript.vim\n") + call term_wait(buf) + call WaitForAssert({-> assert_match('E22: Scripts nested too deep\s*', term_getline(buf, 6))}) + call delete('Xscript.vim') + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index 8ab8204b10..c840e834b9 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -147,7 +147,7 @@ func Test_spell_file_missing() augroup TestSpellFileMissing autocmd! SpellFileMissing * bwipe augroup END - call assert_fails('set spell spelllang=ab_cd', 'E797:') + call assert_fails('set spell spelllang=ab_cd', 'E937:') " clean up augroup TestSpellFileMissing @@ -159,6 +159,19 @@ func Test_spell_file_missing() %bwipe! endfunc +func Test_spell_file_missing_bwipe() + " this was using a window that was wiped out in a SpellFileMissing autocmd + set spelllang=xy + au SpellFileMissing * n0 + set spell + au SpellFileMissing * bw + snext somefile + + au! SpellFileMissing + bwipe! + set nospell spelllang=en +endfunc + func Test_spelldump() " In case the spell file is not found avoid getting the download dialog, we " would get stuck at the prompt. @@ -329,6 +342,11 @@ func Test_spellsuggest() call assert_equal(['Third'], spellsuggest('THird', 1)) call assert_equal(['All'], spellsuggest('ALl', 1)) + " Special suggestion for repeated 'the the'. + call assert_inrange(0, 2, index(spellsuggest('the the', 3), 'the')) + call assert_inrange(0, 2, index(spellsuggest('the the', 3), 'the')) + call assert_inrange(0, 2, index(spellsuggest('The the', 3), 'The')) + call assert_fails("call spellsuggest('maxch', [])", 'E745:') call assert_fails("call spellsuggest('maxch', 2, [])", 'E745:') @@ -474,6 +492,35 @@ func Test_spellsuggest_option_expr() bwipe! endfunc +" Test for 'spellsuggest' expr errrors +func Test_spellsuggest_expr_errors() + " 'spellsuggest' + func MySuggest() + return range(3) + endfunc + set spell spellsuggest=expr:MySuggest() + call assert_equal([], spellsuggest('baord', 3)) + + " Test for 'spellsuggest' expression returning a non-list value + func! MySuggest2() + return 'good' + endfunc + set spellsuggest=expr:MySuggest2() + call assert_equal([], spellsuggest('baord')) + + " Test for 'spellsuggest' expression returning a list with dict values + func! MySuggest3() + return [[{}, {}]] + endfunc + set spellsuggest=expr:MySuggest3() + call assert_fails("call spellsuggest('baord')", 'E731:') + + set nospell spellsuggest& + delfunc MySuggest + delfunc MySuggest2 + delfunc MySuggest3 +endfunc + func Test_spellsuggest_timeout() set spellsuggest=timeout:30 set spellsuggest=timeout:-123 @@ -484,8 +531,23 @@ func Test_spellsuggest_timeout() call assert_fails('set spellsuggest=timeout:--9', 'E474:') endfunc +func Test_spellsuggest_visual_end_of_line() + let enc_save = &encoding + " set encoding=iso8859 + + " This was reading beyond the end of the line. + norm R00000000000 + sil norm 0 + sil! norm i00000) + sil! norm i00000) + call feedkeys("\<CR>") + norm z= + + let &encoding = enc_save +endfunc + func Test_spellinfo() - throw 'skipped: Nvim does not support enc=latin1' + throw 'Skipped: Nvim does not support enc=latin1' new let runtime = substitute($VIMRUNTIME, '\\', '/', 'g') @@ -1401,3 +1463,5 @@ let g:test_data_aff_sal = [ \"SAL ZZ- _", \"SAL Z S", \ ] + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index b30a5e7edb..1ee1d0dfe3 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -267,10 +267,9 @@ endfunc " Test the -V[N] argument to set the 'verbose' option to [N] func Test_V_arg() - if has('gui_running') - " Can't catch the output of gvim. - return - endif + " Can't catch the output of gvim. + CheckNotGui + let out = system(GetVimCommand() . ' --clean -es -X -V0 -c "set verbose?" -cq') call assert_equal(" verbose=0\n", out) @@ -521,9 +520,18 @@ func Test_geometry() call writefile([&columns, &lines, getwinposx(), getwinposy(), string(getwinpos())], "Xtest_geometry") qall [CODE] - if RunVim([], after, '-f -g -geometry 31x13+41+43') + " Some window managers have a bar at the top that pushes windows down, + " need to use at least 130, let's do 150 + if RunVim([], after, '-f -g -geometry 31x13+41+150') let lines = readfile('Xtest_geometry') - call assert_equal(['31', '13', '41', '43', '[41, 43]'], lines) + " Depending on the GUI library and the windowing system the final size + " might be a bit different, allow for some tolerance. Tuned based on + " actual failures. + call assert_inrange(31, 35, str2nr(lines[0])) + call assert_equal('13', lines[1]) + call assert_equal('41', lines[2]) + call assert_equal('150', lines[3]) + call assert_equal('[41, 150]', lines[4]) endif endif @@ -543,10 +551,9 @@ endfunc func Test_invalid_args() - if !has('unix') || has('gui_running') - " can't get output of Vim. - return - endif + " must be able to get the output of Vim. + CheckUnix + CheckNotGui for opt in ['-Y', '--does-not-exist'] let out = split(system(GetVimCommand() .. ' ' .. opt), "\n") @@ -622,6 +629,12 @@ func Test_invalid_args() endfor if has('gui_gtk') + let out = split(system(GetVimCommand() .. ' --socketid'), "\n") + call assert_equal(1, v:shell_error) + call assert_match('^VIM - Vi IMproved .* (.*)$', out[0]) + call assert_equal('Argument missing after: "--socketid"', out[1]) + call assert_equal('More info with: "vim -h"', out[2]) + for opt in ['--socketid x', '--socketid 0xg'] let out = split(system(GetVimCommand() .. ' ' .. opt), "\n") call assert_equal(1, v:shell_error) @@ -629,6 +642,7 @@ func Test_invalid_args() call assert_equal('Invalid argument for: "--socketid"', out[1]) call assert_equal('More info with: "vim -h"', out[2]) endfor + endif endfunc @@ -711,27 +725,6 @@ func Test_read_stdin() call delete('Xtestout') endfunc -func Test_set_shell() - let after =<< trim [CODE] - call writefile([&shell], "Xtestout") - quit! - [CODE] - - if has('win32') - let $SHELL = 'C:\with space\cmd.exe' - let expected = '"C:\with space\cmd.exe"' - else - let $SHELL = '/bin/with space/sh' - let expected = '"/bin/with space/sh"' - endif - - if RunVimPiped([], after, '', '') - let lines = readfile('Xtestout') - call assert_equal(expected, lines[0]) - endif - call delete('Xtestout') -endfunc - func Test_progpath() " Tests normally run with "./vim" or "../vim", these must have been expanded " to a full path. @@ -747,10 +740,9 @@ func Test_progpath() endfunc func Test_silent_ex_mode() - if !has('unix') || has('gui_running') - " can't get output of Vim. - return - endif + " must be able to get the output of Vim. + CheckUnix + CheckNotGui " This caused an ml_get error. let out = system(GetVimCommand() . ' -u NONE -es -c''set verbose=1|h|exe "%norm\<c-y>\<c-d>"'' -c cq') @@ -758,10 +750,9 @@ func Test_silent_ex_mode() endfunc func Test_default_term() - if !has('unix') || has('gui_running') - " can't get output of Vim. - return - endif + " must be able to get the output of Vim. + CheckUnix + CheckNotGui let save_term = $TERM let $TERM = 'unknownxxx' @@ -796,10 +787,17 @@ func Test_zzz_startinsert() call delete('Xtestout') endfunc +func Test_issue_3969() + " Can't catch the output of gvim. + CheckNotGui + + " Check that message is not truncated. + let out = system(GetVimCommand() . ' -es -X -V1 -c "echon ''hello''" -cq') + call assert_equal('hello', out) +endfunc + func Test_start_with_tabs() - if !CanRunVimInTerminal() - return - endif + CheckRunVimInTerminal let buf = RunVimInTerminal('-p a b c', {}) call VerifyScreenDump(buf, 'Test_start_with_tabs', {}) @@ -943,6 +941,7 @@ endfunc " Test for enabling the lisp mode on startup func Test_l_arg() + throw 'Skipped: Nvim -l arg differs from Vim' let after =<< trim [CODE] let s = 'lisp=' .. &lisp .. ', showmatch=' .. &showmatch call writefile([s], 'Xtestout') @@ -956,9 +955,7 @@ endfunc " Test for specifying a non-existing vimrc file using "-u" func Test_missing_vimrc() - if !CanRunVimInTerminal() - throw 'Skipped: cannot run vim in terminal' - endif + CheckRunVimInTerminal let after =<< trim [CODE] call assert_match('^E282:', v:errmsg) call writefile(v:errors, 'Xtestout') @@ -1016,6 +1013,7 @@ endfunc " Test for using the 'exrc' option func Test_exrc() + throw 'Skipped: Nvim requires user input for the exrc option' let after =<< trim [CODE] call assert_equal(1, &exrc) call assert_equal(1, &secure) @@ -1045,7 +1043,7 @@ func Test_io_not_a_terminal() \ 'Vim: Warning: Input is not from a terminal'], l) endfunc -" Test for --not-a-term avoiding escape codes. +" Test for not being a term avoiding escape codes. func Test_not_a_term() CheckUnix CheckNotGui @@ -1056,18 +1054,14 @@ func Test_not_a_term() let redir = &shellredir .. ' Xvimout' endif - " Without --not-a-term there are a few escape sequences. - " This will take 2 seconds because of the missing --not-a-term + " As nvim checks the environment by itself there will be no escape sequences + " This will also happen to take two (2) seconds. let cmd = GetVimProg() .. ' --cmd quit ' .. redir exe "silent !" . cmd - call assert_match("\<Esc>", readfile('Xvimout')->join()) + call assert_notmatch("\e", 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 delete('Xvimout') + " --not-a-term flag has thus been deleted endfunc @@ -1271,4 +1265,19 @@ func Test_progname() call delete('Xprogname', 'd') endfunc +" Test for doing a write from .vimrc +func Test_write_in_vimrc() + call writefile(['silent! write'], 'Xvimrc') + let after =<< trim [CODE] + call assert_match('E32: ', v:errmsg) + call writefile(v:errors, 'Xtestout') + qall + [CODE] + if RunVim([], after, '-u Xvimrc') + call assert_equal([], readfile('Xtestout')) + call delete('Xtestout') + endif + call delete('Xvimrc') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_startup_utf8.vim b/src/nvim/testdir/test_startup_utf8.vim index bb4304396e..2ee6ecc41d 100644 --- a/src/nvim/testdir/test_startup_utf8.vim +++ b/src/nvim/testdir/test_startup_utf8.vim @@ -63,9 +63,7 @@ func Test_read_fifo_utf8() endfunc func Test_detect_ambiwidth() - if !CanRunVimInTerminal() - throw 'Skipped: cannot run Vim in a terminal window' - endif + CheckRunVimInTerminal " Use the title termcap entries to output the escape sequence. call writefile([ diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index 6bde052442..990c852ccd 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -565,4 +565,45 @@ func Test_statusline_highlight_truncate() call delete('XTest_statusline') endfunc +func Test_statusline_showcmd() + CheckScreendump + + let lines =<< trim END + func MyStatusLine() + return '%S' + endfunc + + set laststatus=2 + set statusline=%!MyStatusLine() + set showcmdloc=statusline + call setline(1, ['a', 'b', 'c']) + set foldopen+=jump + 1,2fold + 3 + END + call writefile(lines, 'XTest_statusline', 'D') + + let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 6}) + + call term_sendkeys(buf, "g") + call VerifyScreenDump(buf, 'Test_statusline_showcmd_1', {}) + + " typing "gg" should open the fold + call term_sendkeys(buf, "g") + call VerifyScreenDump(buf, 'Test_statusline_showcmd_2', {}) + + call term_sendkeys(buf, "\<C-V>Gl") + call VerifyScreenDump(buf, 'Test_statusline_showcmd_3', {}) + + call term_sendkeys(buf, "\<Esc>1234") + call VerifyScreenDump(buf, 'Test_statusline_showcmd_4', {}) + + call term_sendkeys(buf, "\<Esc>:set statusline=\<CR>") + call term_sendkeys(buf, ":\<CR>") + call term_sendkeys(buf, "1234") + call VerifyScreenDump(buf, 'Test_statusline_showcmd_5', {}) + + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index 88a3c13d65..c99a0d456d 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -446,13 +446,19 @@ func Test_substitute_errors() call assert_fails('s/FOO/bar/', 'E486:') call assert_fails('s/foo/bar/@', 'E488:') - call assert_fails('s/\(/bar/', 'E476:') + call assert_fails('s/\(/bar/', 'E54:') call assert_fails('s afooabara', 'E146:') call assert_fails('s\\a', 'E10:') setl nomodifiable call assert_fails('s/foo/bar/', 'E21:') + call assert_fails("let s=substitute([], 'a', 'A', 'g')", 'E730:') + call assert_fails("let s=substitute('abcda', [], 'A', 'g')", 'E730:') + call assert_fails("let s=substitute('abcda', 'a', [], 'g')", 'E730:') + call assert_fails("let s=substitute('abcda', 'a', 'A', [])", 'E730:') + call assert_fails("let s=substitute('abc', '\\%(', 'A', 'g')", 'E53:') + bwipe! endfunc @@ -488,6 +494,9 @@ func Test_sub_replace_1() call assert_equal("x\<C-M>x", substitute('xXx', 'X', "\r", '')) call assert_equal("YyyY", substitute('Y', 'Y', '\L\uyYy\l\EY', '')) call assert_equal("zZZz", substitute('Z', 'Z', '\U\lZzZ\u\Ez', '')) + " \v or \V after $ + call assert_equal('abxx', substitute('abcd', 'xy$\v|cd$', 'xx', '')) + call assert_equal('abxx', substitute('abcd', 'xy$\V\|cd\$', 'xx', '')) endfunc func Test_sub_replace_2() @@ -833,9 +842,9 @@ endfunc func Test_sub_with_no_last_pat() let lines =<< trim [SCRIPT] call assert_fails('~', 'E33:') - call assert_fails('s//abc/g', 'E476:') - call assert_fails('s\/bar', 'E476:') - call assert_fails('s\&bar&', 'E476:') + call assert_fails('s//abc/g', 'E35:') + call assert_fails('s\/bar', 'E35:') + call assert_fails('s\&bar&', 'E33:') call writefile(v:errors, 'Xresult') qall! [SCRIPT] @@ -862,6 +871,40 @@ endfunc func Test_substitute() call assert_equal('a1a2a3a', substitute('123', '\zs', 'a', 'g')) + " Substitute with special keys + call assert_equal("a\<End>c", substitute('abc', "a.c", "a\<End>c", '')) +endfunc + +func Test_substitute_expr() + let g:val = 'XXX' + call assert_equal('XXX', substitute('yyy', 'y*', '\=g:val', '')) + call assert_equal('XXX', substitute('yyy', 'y*', {-> g:val}, '')) + call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)', + \ '\=nr2char("0x" . submatch(1))', 'g')) + call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)', + \ {-> nr2char("0x" . submatch(1))}, 'g')) + + call assert_equal('231', substitute('123', '\(.\)\(.\)\(.\)', + \ {-> submatch(2) . submatch(3) . submatch(1)}, '')) + + func Recurse() + return substitute('yyy', 'y\(.\)y', {-> submatch(1)}, '') + endfunc + " recursive call works + call assert_equal('-y-x-', substitute('xxx', 'x\(.\)x', {-> '-' . Recurse() . '-' . submatch(1) . '-'}, '')) + + call assert_fails("let s=submatch([])", 'E745:') + call assert_fails("let s=submatch(2, [])", 'E745:') +endfunc + +func Test_invalid_submatch() + " This was causing invalid memory access in Vim-7.4.2232 and older + call assert_fails("call substitute('x', '.', {-> submatch(10)}, '')", 'E935:') + call assert_fails('eval submatch(-1)', 'E935:') + call assert_equal('', submatch(0)) + call assert_equal('', submatch(1)) + call assert_equal([], submatch(0, 1)) + call assert_equal([], submatch(1, 1)) endfunc func Test_submatch_list_concatenate() @@ -870,6 +913,44 @@ func Test_submatch_list_concatenate() call substitute('A1', pat, Rep, '')->assert_equal("[['A1'], ['1']]") endfunc +func Test_substitute_expr_arg() + call assert_equal('123456789-123456789=', substitute('123456789', + \ '\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)', + \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) + + call assert_equal('123456-123456=789', substitute('123456789', + \ '\(.\)\(.\)\(.\)\(a*\)\(n*\)\(.\)\(.\)\(.\)\(x*\)', + \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) + + call assert_equal('123456789-123456789x=', substitute('123456789', + \ '\(.\)\(.\)\(.*\)', + \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . 'x' . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) + + call assert_fails("call substitute('xxx', '.', {m -> string(add(m, 'x'))}, '')", 'E742:') + call assert_fails("call substitute('xxx', '.', {m -> string(insert(m, 'x'))}, '')", 'E742:') + call assert_fails("call substitute('xxx', '.', {m -> string(extend(m, ['x']))}, '')", 'E742:') + call assert_fails("call substitute('xxx', '.', {m -> string(remove(m, 1))}, '')", 'E742:') +endfunc + +" Test for using a function to supply the substitute string +func Test_substitute_using_func() + func Xfunc() + return '1234' + endfunc + call assert_equal('a1234f', substitute('abcdef', 'b..e', + \ function("Xfunc"), '')) + delfunc Xfunc +endfunc + +" Test for using submatch() with a multiline match +func Test_substitute_multiline_submatch() + new + call setline(1, ['line1', 'line2', 'line3', 'line4']) + %s/^line1\(\_.\+\)line4$/\=submatch(1)/ + call assert_equal(['', 'line2', 'line3', ''], getline(1, '$')) + close! +endfunc + func Test_substitute_skipped_range() new if 0 @@ -995,6 +1076,19 @@ func Test_sub_open_cmdline_win() call delete('Xresult') endfunc +" This was editing a script file from the expression +func Test_sub_edit_scriptfile() + new + norm o0000000000000000000000000000000000000000000000000000 + func EditScript() + silent! scr! Xfile + endfunc + s/\%')/\=EditScript() + + delfunc EditScript + bwipe! +endfunc + " Test for the 2-letter and 3-letter :substitute commands func Test_substitute_short_cmd() new diff --git a/src/nvim/testdir/test_swap.vim b/src/nvim/testdir/test_swap.vim index 34d1d585ce..cf46b4c5bd 100644 --- a/src/nvim/testdir/test_swap.vim +++ b/src/nvim/testdir/test_swap.vim @@ -203,8 +203,8 @@ func Test_swapfile_delete() " 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 + " deleted. Process 0x3fffffff most likely does not exist. + let swapfile_bytes[24:27] = 0zffffff3f call writefile(swapfile_bytes, swapfile_name) let s:swapname = '' split XswapfileText @@ -421,6 +421,161 @@ func Test_swap_symlink() call delete('Xswapdir', 'rf') endfunc +func s:get_unused_pid(base) + if has('job') + " Execute 'echo' as a temporary job, and return its pid as an unused pid. + if has('win32') + let cmd = 'cmd /c echo' + else + let cmd = 'echo' + endif + let j = job_start(cmd) + while job_status(j) ==# 'run' + sleep 10m + endwhile + if job_status(j) ==# 'dead' + return job_info(j).process + endif + endif + " Must add four for MS-Windows to see it as a different one. + return a:base + 4 +endfunc + +func s:blob_to_pid(b) + return a:b[3] * 16777216 + a:b[2] * 65536 + a:b[1] * 256 + a:b[0] +endfunc + +func s:pid_to_blob(i) + let b = 0z + let b[0] = and(a:i, 0xff) + let b[1] = and(a:i / 256, 0xff) + let b[2] = and(a:i / 65536, 0xff) + let b[3] = and(a:i / 16777216, 0xff) + return b +endfunc + +func Test_swap_auto_delete() + " Create a valid swapfile by editing a file with a special extension. + split Xtest.scr + call setline(1, ['one', 'two', 'three']) + write " file is written, not modified + write " write again to make sure the swapfile is created + " read the swapfile as a Blob + let swapfile_name = swapname('%') + let swapfile_bytes = readfile(swapfile_name, 'B') + + " Forget about the file, recreate the swap file, then edit it again. The + " swap file should be automatically deleted. + bwipe! + " Change the process ID to avoid the "still running" warning. + let swapfile_bytes[24:27] = s:pid_to_blob(s:get_unused_pid( + \ s:blob_to_pid(swapfile_bytes[24:27]))) + call writefile(swapfile_bytes, swapfile_name) + edit Xtest.scr + " will end up using the same swap file after deleting the existing one + call assert_equal(swapfile_name, swapname('%')) + bwipe! + + " create the swap file again, but change the host name so that it won't be + " deleted + autocmd! SwapExists + augroup test_swap_recover_ext + autocmd! + autocmd SwapExists * let v:swapchoice = 'e' + augroup END + + " change the host name + let swapfile_bytes[28 + 40] = swapfile_bytes[28 + 40] + 2 + call writefile(swapfile_bytes, swapfile_name) + edit Xtest.scr + call assert_equal(1, filereadable(swapfile_name)) + " will use another same swap file name + call assert_notequal(swapfile_name, swapname('%')) + bwipe! + + call delete('Xtest.scr') + call delete(swapfile_name) + augroup test_swap_recover_ext + autocmd! + augroup END + augroup! test_swap_recover_ext +endfunc + +" Test for renaming a buffer when the swap file is deleted out-of-band +func Test_missing_swap_file() + CheckUnix + new Xfile2 + call delete(swapname('')) + call assert_fails('file Xfile3', 'E301:') + call assert_equal('Xfile3', bufname()) + call assert_true(bufexists('Xfile2')) + call assert_true(bufexists('Xfile3')) + %bw! +endfunc + +" Test for :preserve command +func Test_preserve() + new Xfile4 + setlocal noswapfile + call assert_fails('preserve', 'E313:') + bw! +endfunc + +" Test for the v:swapchoice variable +func Test_swapchoice() + call writefile(['aaa', 'bbb'], 'Xfile5') + edit Xfile5 + preserve + let swapfname = swapname('') + let b = readblob(swapfname) + bw! + call writefile(b, swapfname) + + autocmd! SwapExists + + " Test for v:swapchoice = 'o' (readonly) + augroup test_swapchoice + autocmd! + autocmd SwapExists * let v:swapchoice = 'o' + augroup END + edit Xfile5 + call assert_true(&readonly) + call assert_equal(['aaa', 'bbb'], getline(1, '$')) + %bw! + call assert_true(filereadable(swapfname)) + + " Test for v:swapchoice = 'a' (abort) + augroup test_swapchoice + autocmd! + autocmd SwapExists * let v:swapchoice = 'a' + augroup END + try + edit Xfile5 + catch /^Vim:Interrupt$/ + endtry + call assert_equal('', @%) + call assert_true(bufexists('Xfile5')) + %bw! + call assert_true(filereadable(swapfname)) + + " Test for v:swapchoice = 'd' (delete) + augroup test_swapchoice + autocmd! + autocmd SwapExists * let v:swapchoice = 'd' + augroup END + edit Xfile5 + call assert_equal('Xfile5', @%) + %bw! + call assert_false(filereadable(swapfname)) + + call delete('Xfile5') + call delete(swapfname) + augroup test_swapchoice + autocmd! + augroup END + augroup! test_swapchoice +endfunc + func Test_no_swap_file() call assert_equal("\nNo swap file", execute('swapname')) endfunc diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index ccff01486e..45230c4208 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -113,6 +113,9 @@ func Test_syntime() let a = execute('syntime report') call assert_equal("\nNo Syntax items defined for this buffer", a) + let a = execute('syntime clear') + call assert_equal("\nNo Syntax items defined for this buffer", a) + view samples/memfile_test.c setfiletype cpp redraw @@ -171,6 +174,10 @@ func Test_syntax_list() let a = execute('syntax list') call assert_equal("\nNo Syntax items defined for this buffer", a) + syntax keyword Type int containedin=g1 skipwhite skipempty skipnl nextgroup=Abc + let exp = "Type xxx containedin=g1 nextgroup=Abc skipnl skipwhite skipempty int" + call assert_equal(exp, split(execute("syntax list"), "\n")[1]) + bd endfunc @@ -347,6 +354,18 @@ func Test_syntax_arg_skipped() syn clear endfunc +" Check for an error. Used when multiple errors are thrown and we are checking +" for an earliest error. +func AssertFails(cmd, errcode) + let save_exception = '' + try + exe a:cmd + catch + let save_exception = v:exception + endtry + call assert_match(a:errcode, save_exception) +endfunc + func Test_syntax_invalid_arg() call assert_fails('syntax case asdf', 'E390:') if has('conceal') @@ -354,11 +373,51 @@ func Test_syntax_invalid_arg() endif call assert_fails('syntax spell asdf', 'E390:') call assert_fails('syntax clear @ABCD', 'E391:') - call assert_fails('syntax include @Xxx', 'E397:') - call assert_fails('syntax region X start="{"', 'E399:') + call assert_fails('syntax include random_file', 'E484:') + call assert_fails('syntax include <afile>', 'E495:') call assert_fails('syntax sync x', 'E404:') call assert_fails('syntax keyword Abc a[', 'E789:') call assert_fails('syntax keyword Abc a[bc]d', 'E890:') + call assert_fails('syntax cluster Abc add=A add=', 'E406:') + + " Test for too many \z\( and unmatched \z\( + " Not able to use assert_fails() here because both E50:/E879: and E475: + " messages are emitted. + set regexpengine=1 + call AssertFails("syntax region MyRegion start='\\z\\(' end='\\*/'", 'E52:') + + let cmd = "syntax region MyRegion start='" + let cmd ..= repeat("\\z\\(.\\)", 10) .. "' end='\*/'" + call AssertFails(cmd, 'E50:') + + set regexpengine=2 + call AssertFails("syntax region MyRegion start='\\z\\(' end='\\*/'", 'E54:') + + let cmd = "syntax region MyRegion start='" + let cmd ..= repeat("\\z\\(.\\)", 10) .. "' end='\*/'" + call AssertFails(cmd, 'E879:') + set regexpengine& + + call AssertFails('syntax keyword cMyItem grouphere G1', 'E393:') + call AssertFails('syntax sync match Abc grouphere MyItem "abc"', 'E394:') + call AssertFails('syn keyword Type contains int', 'E395:') + call assert_fails('syntax include @Xxx', 'E397:') + call AssertFails('syntax region X start', 'E398:') + call assert_fails('syntax region X start="{"', 'E399:') + call AssertFails('syntax cluster contains=Abc', 'E400:') + call AssertFails("syntax match Character /'.'", 'E401:') + call AssertFails("syntax match Character /'.'/a", 'E402:') + call assert_fails('syntax sync linecont /\%(/', 'E53:') + call assert_fails('syntax sync linecont /pat', 'E404:') + call assert_fails('syntax sync linecont', 'E404:') + call assert_fails('syntax sync linecont /pat1/ linecont /pat2/', 'E403:') + call assert_fails('syntax sync minlines=a', 'E404:') + call AssertFails('syntax match ABC /x/ contains=', 'E406:') + call AssertFails("syntax match Character contains /'.'/", 'E405:') + call AssertFails('syntax match ccFoo "Foo" nextgroup=ALLBUT,F', 'E407:') + call AssertFails('syntax region Block start="{" contains=F,ALLBUT', 'E408:') + call AssertFails("syntax match Characters contains=a.*x /'.'/", 'E409:') + call assert_fails('syntax match Search /abc/ contains=ALLBUT,/\%(/', 'E53:') endfunc func Test_syn_sync() @@ -386,6 +445,7 @@ func Test_syn_clear() hi clear Foo call assert_equal('Foo', synIDattr(hlID("Foo"), "name")) hi clear Bar + call assert_fails('syntax clear invalid_syngroup', 'E28:') endfunc func Test_invalid_name() @@ -479,15 +539,16 @@ func Test_conceal() call assert_match('16 ', ScreenLines(2, 7)[0]) call assert_equal([[0, '', 0], [1, '', 1], [1, '', 1], [1, '', 2], [1, '', 2], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)')) + call AssertFails("syntax match Entity '&' conceal cchar=\<Tab>", 'E844:') + syn clear set conceallevel& bw! endfunc func Test_bg_detection() - if has('gui_running') - return - endif + CheckNotGui + " auto-detection of &bg, make sure sure it isn't set anywhere before " this test hi Normal ctermbg=0 @@ -564,15 +625,15 @@ func Test_synstack_synIDtrans() call assert_equal(['cComment', 'cTodo'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")')) call assert_equal(['Comment', 'Todo'], map(synstack(line("."), col(".")), 'synIDattr(synIDtrans(v:val), "name")')) + call assert_fails("let n=synIDtrans([])", 'E745:') + syn clear bw! endfunc " Check highlighting for a small piece of C code with a screen dump. func Test_syntax_c() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckRunVimInTerminal call writefile([ \ '/* comment line at the top */', \ 'int main(int argc, char **argv) { // another comment', @@ -607,6 +668,24 @@ func Test_syntax_c() call delete('Xtest.c') endfun +" Test \z(...) along with \z1 +func Test_syn_zsub() + new + syntax on + call setline(1, 'xxx start foo xxx not end foo xxx end foo xxx') + let l:expected = ' ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ' + + for l:re in [0, 1, 2] + " Example taken from :help :syn-ext-match + syntax region Z start="start \z(\I\i*\)" skip="not end \z1" end="end \z1" + eval AssertHighlightGroups(1, 1, l:expected, 1, 'regexp=' .. l:re) + syntax clear Z + endfor + + set re& + bw! +endfunc + " Using \z() in a region with NFA failing should not crash. func Test_syn_wrong_z_one() new diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim index bfa8a277bd..6c8373b335 100644 --- a/src/nvim/testdir/test_system.vim +++ b/src/nvim/testdir/test_system.vim @@ -142,40 +142,4 @@ func Test_system_with_shell_quote() endtry endfunc -" Test for 'shellxquote' -func Test_Shellxquote() - CheckUnix - - let save_shell = &shell - let save_sxq = &shellxquote - let save_sxe = &shellxescape - - call writefile(['#!/bin/sh', 'echo "Cmd: [$*]" > Xlog'], 'Xtestshell') - call setfperm('Xtestshell', "r-x------") - set shell=./Xtestshell - - set shellxquote=\\" - call feedkeys(":!pwd\<CR>\<CR>", 'xt') - call assert_equal(['Cmd: [-c "pwd"]'], readfile('Xlog')) - - set shellxquote=( - call feedkeys(":!pwd\<CR>\<CR>", 'xt') - call assert_equal(['Cmd: [-c (pwd)]'], readfile('Xlog')) - - set shellxquote=\\"( - call feedkeys(":!pwd\<CR>\<CR>", 'xt') - call assert_equal(['Cmd: [-c "(pwd)"]'], readfile('Xlog')) - - set shellxescape=\"&<<()@^ - set shellxquote=( - call feedkeys(":!pwd\"&<<{}@^\<CR>\<CR>", 'xt') - call assert_equal(['Cmd: [-c (pwd^"^&^<^<{}^@^^)]'], readfile('Xlog')) - - let &shell = save_shell - let &shellxquote = save_sxq - let &shellxescape = save_sxe - call delete('Xtestshell') - call delete('Xlog') -endfunc - " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_tabline.vim b/src/nvim/testdir/test_tabline.vim index e58a412c5a..d9bef09067 100644 --- a/src/nvim/testdir/test_tabline.vim +++ b/src/nvim/testdir/test_tabline.vim @@ -1,6 +1,9 @@ " Test for tabline source shared.vim +source view_util.vim +source check.vim +source screendump.vim func TablineWithCaughtError() let s:func_in_tabline_called = 1 @@ -147,4 +150,58 @@ func Test_tabline_20_format_items_no_overrun() set showtabline& tabline& endfunc +func Test_mouse_click_in_tab() + " This used to crash because TabPageIdxs[] was not initialized + let lines =<< trim END + tabnew + set mouse=a + exe "norm \<LeftMouse>" + END + call writefile(lines, 'Xclickscript') + call RunVim([], [], "-e -s -S Xclickscript -c qa") + + call delete('Xclickscript') +endfunc + +func Test_tabline_showcmd() + CheckScreendump + + let lines =<< trim END + func MyTabLine() + return '%S' + endfunc + + set showtabline=2 + set tabline=%!MyTabLine() + set showcmdloc=tabline + call setline(1, ['a', 'b', 'c']) + set foldopen+=jump + 1,2fold + 3 + END + call writefile(lines, 'XTest_tabline', 'D') + + let buf = RunVimInTerminal('-S XTest_tabline', {'rows': 6}) + + call term_sendkeys(buf, "g") + call VerifyScreenDump(buf, 'Test_tabline_showcmd_1', {}) + + " typing "gg" should open the fold + call term_sendkeys(buf, "g") + call VerifyScreenDump(buf, 'Test_tabline_showcmd_2', {}) + + call term_sendkeys(buf, "\<C-V>Gl") + call VerifyScreenDump(buf, 'Test_tabline_showcmd_3', {}) + + call term_sendkeys(buf, "\<Esc>1234") + call VerifyScreenDump(buf, 'Test_tabline_showcmd_4', {}) + + call term_sendkeys(buf, "\<Esc>:set tabline=\<CR>") + call term_sendkeys(buf, ":\<CR>") + call term_sendkeys(buf, "1234") + call VerifyScreenDump(buf, 'Test_tabline_showcmd_5', {}) + + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim index 6d468ec9de..b97aa409d8 100644 --- a/src/nvim/testdir/test_tabpage.vim +++ b/src/nvim/testdir/test_tabpage.vim @@ -591,9 +591,7 @@ func Test_tabs() endfunc func Test_tabpage_cmdheight() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckRunVimInTerminal call writefile([ \ 'set laststatus=2', \ 'set cmdheight=2', @@ -623,6 +621,17 @@ func Test_tabpage_close_cmdwin() tabonly endfunc +" Pressing <C-PageUp> in insert mode should go to the previous tab page +" and <C-PageDown> should go to the next tab page +func Test_tabpage_Ctrl_Pageup() + tabnew + call feedkeys("i\<C-PageUp>", 'xt') + call assert_equal(1, tabpagenr()) + call feedkeys("i\<C-PageDown>", 'xt') + call assert_equal(2, tabpagenr()) + %bw! +endfunc + " Return the terminal key code for selecting a tab page from the tabline. This " sequence contains the following codes: a CSI (0x9b), KS_TABLINE (0xf0), " KS_FILLER (0x58) and then the tab page number. diff --git a/src/nvim/testdir/test_tagfunc.vim b/src/nvim/testdir/test_tagfunc.vim index ffc1d63b90..cba96d3504 100644 --- a/src/nvim/testdir/test_tagfunc.vim +++ b/src/nvim/testdir/test_tagfunc.vim @@ -1,5 +1,9 @@ " Test 'tagfunc' +source vim9.vim +source check.vim +source screendump.vim + func TagFunc(pat, flag, info) let g:tagfunc_args = [a:pat, a:flag, a:info] let tags = [] @@ -85,7 +89,7 @@ func Test_tagfunc() return v:null endfunc set tags= tfu=NullTagFunc - call assert_fails('tag nothing', 'E426') + call assert_fails('tag nothing', 'E433') delf NullTagFunc bwipe! @@ -117,4 +121,297 @@ func Test_tagfunc_settagstack() delfunc Mytagfunc2 endfunc +" Script local tagfunc callback function +func s:ScriptLocalTagFunc(pat, flags, info) + let g:ScriptLocalFuncArgs = [a:pat, a:flags, a:info] + return v:null +endfunc + +" Test for different ways of setting the 'tagfunc' option +func Test_tagfunc_callback() + func TagFunc1(callnr, pat, flags, info) + let g:TagFunc1Args = [a:callnr, a:pat, a:flags, a:info] + return v:null + endfunc + func TagFunc2(pat, flags, info) + let g:TagFunc2Args = [a:pat, a:flags, a:info] + return v:null + endfunc + + let lines =<< trim END + #" Test for using a function name + LET &tagfunc = 'g:TagFunc2' + new + LET g:TagFunc2Args = [] + call assert_fails('tag a10', 'E433:') + call assert_equal(['a10', '', {}], g:TagFunc2Args) + bw! + + #" Test for using a function() + set tagfunc=function('g:TagFunc1',\ [10]) + new + LET g:TagFunc1Args = [] + call assert_fails('tag a11', 'E433:') + call assert_equal([10, 'a11', '', {}], g:TagFunc1Args) + bw! + + #" Using a funcref variable to set 'tagfunc' + VAR Fn = function('g:TagFunc1', [11]) + LET &tagfunc = Fn + new + LET g:TagFunc1Args = [] + call assert_fails('tag a12', 'E433:') + call assert_equal([11, 'a12', '', {}], g:TagFunc1Args) + bw! + + #" Using a string(funcref_variable) to set 'tagfunc' + LET Fn = function('g:TagFunc1', [12]) + LET &tagfunc = string(Fn) + new + LET g:TagFunc1Args = [] + call assert_fails('tag a12', 'E433:') + call assert_equal([12, 'a12', '', {}], g:TagFunc1Args) + bw! + + #" Test for using a funcref() + set tagfunc=funcref('g:TagFunc1',\ [13]) + new + LET g:TagFunc1Args = [] + call assert_fails('tag a13', 'E433:') + call assert_equal([13, 'a13', '', {}], g:TagFunc1Args) + bw! + + #" Using a funcref variable to set 'tagfunc' + LET Fn = funcref('g:TagFunc1', [14]) + LET &tagfunc = Fn + new + LET g:TagFunc1Args = [] + call assert_fails('tag a14', 'E433:') + call assert_equal([14, 'a14', '', {}], g:TagFunc1Args) + bw! + + #" Using a string(funcref_variable) to set 'tagfunc' + LET Fn = funcref('g:TagFunc1', [15]) + LET &tagfunc = string(Fn) + new + LET g:TagFunc1Args = [] + call assert_fails('tag a14', 'E433:') + call assert_equal([15, 'a14', '', {}], g:TagFunc1Args) + bw! + + #" Test for using a lambda function + VAR optval = "LSTART a, b, c LMIDDLE TagFunc1(16, a, b, c) LEND" + LET optval = substitute(optval, ' ', '\\ ', 'g') + exe "set tagfunc=" .. optval + new + LET g:TagFunc1Args = [] + call assert_fails('tag a17', 'E433:') + call assert_equal([16, 'a17', '', {}], g:TagFunc1Args) + bw! + + #" Set 'tagfunc' to a lambda expression + LET &tagfunc = LSTART a, b, c LMIDDLE TagFunc1(17, a, b, c) LEND + new + LET g:TagFunc1Args = [] + call assert_fails('tag a18', 'E433:') + call assert_equal([17, 'a18', '', {}], g:TagFunc1Args) + bw! + + #" Set 'tagfunc' to a string(lambda expression) + LET &tagfunc = 'LSTART a, b, c LMIDDLE TagFunc1(18, a, b, c) LEND' + new + LET g:TagFunc1Args = [] + call assert_fails('tag a18', 'E433:') + call assert_equal([18, 'a18', '', {}], g:TagFunc1Args) + bw! + + #" Set 'tagfunc' to a variable with a lambda expression + VAR Lambda = LSTART a, b, c LMIDDLE TagFunc1(19, a, b, c) LEND + LET &tagfunc = Lambda + new + LET g:TagFunc1Args = [] + call assert_fails("tag a19", "E433:") + call assert_equal([19, 'a19', '', {}], g:TagFunc1Args) + bw! + + #" Set 'tagfunc' to a string(variable with a lambda expression) + LET Lambda = LSTART a, b, c LMIDDLE TagFunc1(20, a, b, c) LEND + LET &tagfunc = string(Lambda) + new + LET g:TagFunc1Args = [] + call assert_fails("tag a19", "E433:") + call assert_equal([20, 'a19', '', {}], g:TagFunc1Args) + bw! + + #" Test for using a lambda function with incorrect return value + LET Lambda = LSTART a, b, c LMIDDLE strlen(a) LEND + LET &tagfunc = string(Lambda) + new + call assert_fails("tag a20", "E987:") + bw! + + #" Test for clearing the 'tagfunc' option + set tagfunc='' + set tagfunc& + call assert_fails("set tagfunc=function('abc')", "E700:") + call assert_fails("set tagfunc=funcref('abc')", "E700:") + + #" set 'tagfunc' to a non-existing function + LET &tagfunc = function('g:TagFunc2', [21]) + LET g:TagFunc2Args = [] + call assert_fails("set tagfunc=function('NonExistingFunc')", 'E700:') + call assert_fails("LET &tagfunc = function('NonExistingFunc')", 'E700:') + call assert_fails("tag axb123", 'E426:') + call assert_equal([], g:TagFunc2Args) + bw! + END + call CheckLegacyAndVim9Success(lines) + + " Test for using a script-local function name + func s:TagFunc3(pat, flags, info) + let g:TagFunc3Args = [a:pat, a:flags, a:info] + return v:null + endfunc + set tagfunc=s:TagFunc3 + new + let g:TagFunc3Args = [] + call assert_fails('tag a21', 'E433:') + call assert_equal(['a21', '', {}], g:TagFunc3Args) + bw! + let &tagfunc = 's:TagFunc3' + new + let g:TagFunc3Args = [] + call assert_fails('tag a22', 'E433:') + call assert_equal(['a22', '', {}], g:TagFunc3Args) + bw! + delfunc s:TagFunc3 + + " invalid return value + let &tagfunc = "{a -> 'abc'}" + call assert_fails("echo taglist('a')", "E987:") + + " Using Vim9 lambda expression in legacy context should fail + " set tagfunc=(a,\ b,\ c)\ =>\ g:TagFunc1(21,\ a,\ b,\ c) + new + let g:TagFunc1Args = [] + " call assert_fails("tag a17", "E117:") + call assert_equal([], g:TagFunc1Args) + bw! + + " Test for using a script local function + set tagfunc=<SID>ScriptLocalTagFunc + new + let g:ScriptLocalFuncArgs = [] + call assert_fails('tag a15', 'E433:') + call assert_equal(['a15', '', {}], g:ScriptLocalFuncArgs) + bw! + + " Test for using a script local funcref variable + let Fn = function("s:ScriptLocalTagFunc") + let &tagfunc= Fn + new + let g:ScriptLocalFuncArgs = [] + call assert_fails('tag a16', 'E433:') + call assert_equal(['a16', '', {}], g:ScriptLocalFuncArgs) + bw! + + " Test for using a string(script local funcref variable) + let Fn = function("s:ScriptLocalTagFunc") + let &tagfunc= string(Fn) + new + let g:ScriptLocalFuncArgs = [] + call assert_fails('tag a16', 'E433:') + call assert_equal(['a16', '', {}], g:ScriptLocalFuncArgs) + bw! + + " set 'tagfunc' to a partial with dict. This used to cause a crash. + func SetTagFunc() + let params = {'tagfn': function('g:DictTagFunc')} + let &tagfunc = params.tagfn + endfunc + func g:DictTagFunc(_) dict + endfunc + call SetTagFunc() + new + call SetTagFunc() + bw + call test_garbagecollect_now() + new + set tagfunc= + wincmd w + set tagfunc= + :%bw! + delfunc g:DictTagFunc + delfunc SetTagFunc + + " Vim9 tests + let lines =<< trim END + vim9script + + def Vim9tagFunc(callnr: number, pat: string, flags: string, info: dict<any>): any + g:Vim9tagFuncArgs = [callnr, pat, flags, info] + return null + enddef + + # Test for using a def function with completefunc + set tagfunc=function('Vim9tagFunc',\ [60]) + new + g:Vim9tagFuncArgs = [] + assert_fails('tag a10', 'E433:') + assert_equal([60, 'a10', '', {}], g:Vim9tagFuncArgs) + + # Test for using a global function name + &tagfunc = g:TagFunc2 + new + g:TagFunc2Args = [] + assert_fails('tag a11', 'E433:') + assert_equal(['a11', '', {}], g:TagFunc2Args) + bw! + + # Test for using a script-local function name + def s:LocalTagFunc(pat: string, flags: string, info: dict<any> ): any + g:LocalTagFuncArgs = [pat, flags, info] + return null + enddef + &tagfunc = s:LocalTagFunc + new + g:LocalTagFuncArgs = [] + assert_fails('tag a12', 'E433:') + assert_equal(['a12', '', {}], g:LocalTagFuncArgs) + bw! + END + call CheckScriptSuccess(lines) + + " cleanup + delfunc TagFunc1 + delfunc TagFunc2 + set tagfunc& + %bw! +endfunc + +func Test_tagfunc_wipes_buffer() + func g:Tag0unc0(t,f,o) + bwipe + endfunc + set tagfunc=g:Tag0unc0 + new + cal assert_fails('tag 0', 'E987:') + + delfunc g:Tag0unc0 + set tagfunc= +endfunc + +func Test_tagfunc_closes_window() + split any + func MytagfuncClose(pat, flags, info) + close + return [{'name' : 'mytag', 'filename' : 'Xtest', 'cmd' : '1'}] + endfunc + set tagfunc=MytagfuncClose + call assert_fails('tag xyz', 'E1299:') + + set tagfunc= +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index 04c0218f74..be60a3535c 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -8,7 +8,7 @@ func Test_ptag_with_notagstack() CheckFeature quickfix set notagstack - call assert_fails('ptag does_not_exist_tag_name', 'E426') + call assert_fails('ptag does_not_exist_tag_name', 'E433') set tagstack&vim endfunc @@ -184,6 +184,10 @@ function Test_keyword_jump() call search("start") exe "normal! 5\<C-W>\<C-I>" call assert_equal(" start OK if found this line", getline('.')) + + " invalid tag search pattern + call assert_fails('tag /\%(/', 'E426:') + enew! | only call delete('Xtestfile') call delete('Xinclude') @@ -227,15 +231,13 @@ func Test_tag_symbolic() endfunc " Tests for tag search with !_TAG_FILE_ENCODING. -" Depends on the test83-tags2 and test83-tags3 files. func Test_tag_file_encoding() - throw 'skipped: Nvim removed test83-tags2, test83-tags3' if has('vms') - return + throw 'Skipped: does not work on VMS' endif if !has('iconv') || iconv("\x82\x60", "cp932", "utf-8") != "\uff21" - return + throw 'Skipped: iconv does not work' endif let save_enc = &encoding @@ -260,18 +262,31 @@ func Test_tag_file_encoding() " case2: new - set tags=test83-tags2 + let content = ['!_TAG_FILE_ENCODING cp932 //', + \ "\x82`\x82a\x82b Xtags2.txt /\x82`\x82a\x82b"] + call writefile(content, 'Xtags') + set tags=Xtags tag /.BC call assert_equal('Xtags2.txt', expand('%:t')) call assert_equal('ABC', getline('.')) + call delete('Xtags') close " case3: new - set tags=test83-tags3 + let contents = [ + \ "!_TAG_FILE_SORTED 1 //", + \ "!_TAG_FILE_ENCODING cp932 //"] + for i in range(1, 100) + call add(contents, 'abc' .. i + \ .. " Xtags3.txt /\x82`\x82a\x82b") + endfor + call writefile(contents, 'Xtags') + set tags=Xtags tag abc50 call assert_equal('Xtags3.txt', expand('%:t')) call assert_equal('ABC', getline('.')) + call delete('Xtags') close set tags& @@ -282,6 +297,7 @@ func Test_tag_file_encoding() call delete('Xtags1') endfunc +" Test for emacs-style tags file (TAGS) func Test_tagjump_etags() if !has('emacs_tags') return @@ -322,6 +338,7 @@ func Test_tagjump_etags() \ ], 'Xtags2') tag main call assert_equal(2, line('.')) + call assert_fails('tag bar', 'E426:') " corrupted tag line call writefile([ @@ -345,7 +362,28 @@ func Test_tagjump_etags() \ "Xmain.c,64", \ ";;;;\x7f1,0", \ ], 'Xtags') - call assert_fails('tag foo', 'E426:') + call assert_fails('tag foo', 'E431:') + + " end of file after a CTRL-L line + call writefile([ + \ "\x0c", + \ "Xmain.c,64", + \ "void foo() {}\x7ffoo\x011,0", + \ "\x0c", + \ ], 'Xtags') + call assert_fails('tag main', 'E426:') + + " error in an included tags file + call writefile([ + \ "\x0c", + \ "Xtags2,include" + \ ], 'Xtags') + call writefile([ + \ "\x0c", + \ "Xmain.c,64", + \ "void foo() {}", + \ ], 'Xtags2') + call assert_fails('tag foo', 'E431:') call delete('Xtags') call delete('Xtags2') @@ -371,6 +409,7 @@ func Test_getsettagstack() call assert_fails("call settagstack(1, {'items' : 10})", 'E714') call assert_fails("call settagstack(1, {'items' : []}, 10)", 'E928') call assert_fails("call settagstack(1, {'items' : []}, 'b')", 'E962') + call assert_equal(-1, settagstack(0, v:_null_dict)) set tags=Xtags call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", @@ -766,11 +805,11 @@ endfunc " Test for an unsorted tags file func Test_tag_sort() - call writefile([ + let l = [ \ "first\tXfoo\t1", \ "ten\tXfoo\t3", - \ "six\tXfoo\t2"], - \ 'Xtags') + \ "six\tXfoo\t2"] + call writefile(l, 'Xtags') set tags=Xtags let code =<< trim [CODE] int first() {} @@ -781,7 +820,14 @@ func Test_tag_sort() call assert_fails('tag first', 'E432:') + " When multiple tag files are not sorted, then message should be displayed + " multiple times + call writefile(l, 'Xtags2') + set tags=Xtags,Xtags2 + call assert_fails('tag first', ['E432:', 'E432:']) + call delete('Xtags') + call delete('Xtags2') call delete('Xfoo') set tags& %bwipe @@ -1099,7 +1145,7 @@ func Test_tselect_listing() call writefile([ \ "!_TAG_FILE_ENCODING\tutf-8\t//", \ "first\tXfoo\t1" .. ';"' .. "\tv\ttyperef:typename:int\tfile:", - \ "first\tXfoo\t2" .. ';"' .. "\tv\ttyperef:typename:char\tfile:"], + \ "first\tXfoo\t2" .. ';"' .. "\tkind:v\ttyperef:typename:char\tfile:"], \ 'Xtags') set tags=Xtags @@ -1422,4 +1468,148 @@ func Test_tag_length() set tags& taglength& endfunc +" Tests for errors in a tags file +func Test_tagfile_errors() + set tags=Xtags + + " missing search pattern or line number for a tag + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "foo\tXfile\t"], 'Xtags', 'b') + call writefile(['foo'], 'Xfile') + + enew + tag foo + call assert_equal('', @%) + let caught_431 = v:false + try + eval taglist('.*') + catch /:E431:/ + let caught_431 = v:true + endtry + call assert_equal(v:true, caught_431) + + " tag name and file name are not separated by a tab + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "foo Xfile 1"], 'Xtags') + call assert_fails('tag foo', 'E431:') + + " file name and search pattern are not separated by a tab + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "foo\tXfile 1;"], 'Xtags') + call assert_fails('tag foo', 'E431:') + + call delete('Xtags') + call delete('Xfile') + set tags& +endfunc + +" When :stag fails to open the file, should close the new window +func Test_stag_close_window_on_error() + new | only + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "foo\tXfile\t1"], 'Xtags') + call writefile(['foo'], 'Xfile') + call writefile([], '.Xfile.swp') + " Remove the catch-all that runtest.vim adds + au! SwapExists + augroup StagTest + au! + autocmd SwapExists Xfile let v:swapchoice='q' + augroup END + + stag foo + call assert_equal(1, winnr('$')) + call assert_equal('', @%) + + augroup StagTest + au! + augroup END + call delete('Xfile') + call delete('.Xfile.swp') + set tags& +endfunc + +" Test for 'tagbsearch' (binary search) +func Test_tagbsearch() + " If a tags file header says the tags are sorted, but the tags are actually + " unsorted, then binary search should fail and linear search should work. + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "!_TAG_FILE_SORTED\t1\t/0=unsorted, 1=sorted, 2=foldcase/", + \ "third\tXfoo\t3", + \ "second\tXfoo\t2", + \ "first\tXfoo\t1"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int second() {} + int third() {} + [CODE] + call writefile(code, 'Xfoo') + + enew + set tagbsearch + call assert_fails('tag first', 'E426:') + call assert_equal('', bufname()) + call assert_fails('tag second', 'E426:') + call assert_equal('', bufname()) + tag third + call assert_equal('Xfoo', bufname()) + call assert_equal(3, line('.')) + %bw! + + set notagbsearch + tag first + call assert_equal('Xfoo', bufname()) + call assert_equal(1, line('.')) + enew + tag second + call assert_equal('Xfoo', bufname()) + call assert_equal(2, line('.')) + enew + tag third + call assert_equal('Xfoo', bufname()) + call assert_equal(3, line('.')) + %bw! + + " If a tags file header says the tags are unsorted, but the tags are + " actually sorted, then binary search should work. + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "!_TAG_FILE_SORTED\t0\t/0=unsorted, 1=sorted, 2=foldcase/", + \ "first\tXfoo\t1", + \ "second\tXfoo\t2", + \ "third\tXfoo\t3"], + \ 'Xtags') + + set tagbsearch + tag first + call assert_equal('Xfoo', bufname()) + call assert_equal(1, line('.')) + enew + tag second + call assert_equal('Xfoo', bufname()) + call assert_equal(2, line('.')) + enew + tag third + call assert_equal('Xfoo', bufname()) + call assert_equal(3, line('.')) + %bw! + + " Binary search fails on EOF + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "!_TAG_FILE_SORTED\t1\t/0=unsorted, 1=sorted, 2=foldcase/", + \ "bar\tXfoo\t1", + \ "foo\tXfoo\t2"], + \ 'Xtags') + call assert_fails('tag bbb', 'E426:') + + call delete('Xtags') + call delete('Xfoo') + set tags& tagbsearch& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_taglist.vim b/src/nvim/testdir/test_taglist.vim index be46773256..0387ef2bd8 100644 --- a/src/nvim/testdir/test_taglist.vim +++ b/src/nvim/testdir/test_taglist.vim @@ -36,6 +36,14 @@ func Test_taglist() call assert_equal('d', cmd[0]['kind']) call assert_equal('call cursor(3, 4)', cmd[0]['cmd']) + " Use characters with value > 127 in the tag extra field. + call writefile([ + \ "vFoo\tXfoo\t4" .. ';"' .. "\ttypename:int\ta£££\tv", + \ ], 'Xtags') + call assert_equal('v', taglist('vFoo')[0].kind) + + call assert_fails("let l=taglist([])", 'E730:') + call delete('Xtags') set tags& bwipe @@ -82,13 +90,11 @@ func Test_taglist_ctags_etags() endfunc func Test_tags_too_long() - call assert_fails('tag ' . repeat('x', 1020), 'E426') + call assert_fails('tag ' . repeat('x', 1020), ['E433', 'E426']) tags endfunc func Test_tagfiles() - " Nvim: different default for 'tags'. - set tags=./tags,tags call assert_equal([], tagfiles()) call writefile(["FFoo\tXfoo\t1"], 'Xtags1') @@ -221,6 +227,11 @@ func Test_format_error() endtry call assert_true(caught_exception) + " no field after the filename for a tag + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "foo\tXfile"], 'Xtags') + call assert_fails("echo taglist('foo')", 'E431:') + set tags& call delete('Xtags') endfunc @@ -241,4 +252,30 @@ func Test_tag_complete_wildoptions() set tags& endfunc +func Test_tag_complete_with_overlong_line() + let tagslines =<< trim END + !_TAG_FILE_FORMAT 2 // + !_TAG_FILE_SORTED 1 // + !_TAG_FILE_ENCODING utf-8 // + inboundGSV a 1;" r + inboundGovernor a 2;" kind:⊢ type:forall (muxMode :: MuxMode) socket peerAddr versionNumber m a b. (MonadAsync m, MonadCatch m, MonadEvaluate m, MonadThrow m, MonadThrow (STM m), MonadTime m, MonadTimer m, MonadMask m, Ord peerAddr, HasResponder muxMode ~ True) => Tracer m (RemoteTransitionTrace peerAddr) -> Tracer m (InboundGovernorTrace peerAddr) -> ServerControlChannel muxMode peerAddr ByteString m a b -> DiffTime -> MuxConnectionManager muxMode socket peerAddr versionNumber ByteString m a b -> StrictTVar m InboundGovernorObservableState -> m Void + inboundGovernorCounters a 3;" kind:⊢ type:InboundGovernorState muxMode peerAddr m a b -> InboundGovernorCounters + END + call writefile(tagslines, 'Xtags') + set tags=Xtags + + " try with binary search + set tagbsearch + call feedkeys(":tag inbou\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"tag inboundGSV inboundGovernor inboundGovernorCounters', @:) + " try with linear search + set notagbsearch + call feedkeys(":tag inbou\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"tag inboundGSV inboundGovernor inboundGovernorCounters', @:) + set tagbsearch& + + call delete('Xtags') + set tags& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim index 4eb6e69adf..7a13de12f4 100644 --- a/src/nvim/testdir/test_textformat.vim +++ b/src/nvim/testdir/test_textformat.vim @@ -1044,6 +1044,22 @@ func Test_empty_matchpairs() bwipe! endfunc +func Test_mps_error() + let encoding_save = &encoding + + " for e in ['utf-8', 'latin1'] + for e in ['utf-8'] + exe 'set encoding=' .. e + + call assert_fails('set mps=<:', 'E474:', e) + call assert_fails('set mps=:>', 'E474:', e) + call assert_fails('set mps=<>', 'E474:', e) + call assert_fails('set mps=<:>_', 'E474:', e) + endfor + + let &encoding = encoding_save +endfunc + " Test for ra on multi-byte characters func Test_ra_multibyte() new @@ -1096,6 +1112,20 @@ func Test_fo_a_w() call feedkeys("iabc abc a abc\<Esc>k0weade", 'xt') call assert_equal(['abc abcde ', 'a abc'], getline(1, '$')) + " when a line ends with space, it is not broken up. + %d + call feedkeys("ione two to ", 'xt') + call assert_equal('one two to ', getline(1)) + + " when a line ends with spaces and backspace is used in the next line, the + " last space in the previous line should be removed. + %d + set backspace=indent,eol,start + call setline(1, ['one ', 'two']) + exe "normal 2Gi\<BS>" + call assert_equal(['one two'], getline(1, '$')) + set backspace& + " Test for 'a', 'w' and '1' options. setlocal textwidth=0 setlocal fo=1aw diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index b3a22614b0..f94ee6c9f3 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -3,6 +3,7 @@ source check.vim CheckFeature timers +source screendump.vim source shared.vim source term_util.vim source load.vim @@ -46,9 +47,6 @@ endfunc func Test_timer_repeat_many() let g:val = 0 let timer = timer_start(50, 'MyHandler', {'repeat': -1}) - if has('mac') - sleep 200m - endif sleep 200m call timer_stop(timer) call assert_inrange((has('mac') ? 1 : 2), LoadAdjust(5), g:val) @@ -266,8 +264,9 @@ endfunc func Test_timer_peek_and_get_char() if !has('unix') && !has('gui_running') - return + throw 'Skipped: cannot feed low-level input' endif + call timer_start(0, 'FeedAndPeek') let intr = timer_start(100, 'Interrupt') let c = getchar() @@ -277,9 +276,9 @@ endfunc func Test_timer_getchar_zero() if has('win32') && !has('gui_running') - " Console: no low-level input - return + throw 'Skipped: cannot feed low-level input' endif + CheckFunction reltimefloat " Measure the elapsed time to avoid a hang when it fails. let start = reltime() @@ -305,9 +304,7 @@ func Test_timer_ex_mode() endfunc func Test_timer_restore_count() - if !CanRunVimInTerminal() - throw 'Skipped: cannot run Vim in a terminal window' - endif + CheckRunVimInTerminal " Check that v:count is saved and restored, not changed by a timer. call writefile([ \ 'nnoremap <expr><silent> L v:count ? v:count . "l" : "l"', @@ -411,6 +408,30 @@ func Test_timer_invalid_callback() call assert_fails('call timer_start(0, "0")', 'E921') endfunc +func Test_timer_changing_function_list() + CheckRunVimInTerminal + + " Create a large number of functions. Should get the "more" prompt. + " The typing "G" triggers the timer, which changes the function table. + let lines =<< trim END + for func in map(range(1,99), "'Func' .. v:val") + exe "func " .. func .. "()" + endfunc + endfor + au CmdlineLeave : call timer_start(0, {-> 0}) + END + call writefile(lines, 'XTest_timerchange') + let buf = RunVimInTerminal('-S XTest_timerchange', #{rows: 10}) + call term_sendkeys(buf, ":fu\<CR>") + call WaitForAssert({-> assert_match('-- More --', term_getline(buf, 10))}) + call term_sendkeys(buf, "G") + call WaitForAssert({-> assert_match('E454', term_getline(buf, 9))}) + call term_sendkeys(buf, "\<Esc>") + + call StopVimInTerminal(buf) + call delete('XTest_timerchange') +endfunc + func Test_timer_using_win_execute_undo_sync() let bufnr1 = bufnr() new diff --git a/src/nvim/testdir/test_trycatch.vim b/src/nvim/testdir/test_trycatch.vim index d71bb5bbb8..ef20e03126 100644 --- a/src/nvim/testdir/test_trycatch.vim +++ b/src/nvim/testdir/test_trycatch.vim @@ -3,6 +3,7 @@ source check.vim source shared.vim +source vim9.vim "------------------------------------------------------------------------------- " Test environment {{{1 @@ -2000,15 +2001,34 @@ endfunc func Test_try_catch_errors() call assert_fails('throw |', 'E471:') call assert_fails("throw \n ", 'E471:') - call assert_fails('catch abc', 'E603:') + call assert_fails('catch abc', 'E654:') call assert_fails('try | let i = 1| finally | catch | endtry', 'E604:') call assert_fails('finally', 'E606:') call assert_fails('try | finally | finally | endtry', 'E607:') - " v8.2.3486 has been ported, but v8.2.1183 hasn't, so E170 appears here. - " call assert_fails('try | for i in range(5) | endif | endtry', 'E580:') - call assert_fails('try | for i in range(5) | endif | endtry', 'E170:') + call assert_fails('try | for i in range(5) | endif | endtry', 'E580:') call assert_fails('try | while v:true | endtry', 'E170:') call assert_fails('try | if v:true | endtry', 'E171:') + + " this was using a negative index in cstack[] + let lines =<< trim END + try + for + if + endwhile + if + finally + END + call CheckScriptFailure(lines, 'E690:') + + let lines =<< trim END + try + for + if + endwhile + if + endtry + END + call CheckScriptFailure(lines, 'E690:') endfunc " Test for verbose messages with :try :catch, and :finally {{{1 @@ -2223,6 +2243,23 @@ func Test_user_command_throw_in_function_call() unlet g:caught endfunc +" Test that after reporting an uncaught exception there is no error for a +" missing :endif +func Test_after_exception_no_endif_error() + function Throw() + throw "Failure" + endfunction + + function Foo() + if 1 + call Throw() + endif + endfunction + call assert_fails('call Foo()', ['E605:', 'E605:']) + delfunc Throw + delfunc Foo +endfunc + " Test for using throw in a called function with following endtry {{{1 func Test_user_command_function_call_with_endtry() let lines =<< trim END diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim index eb47af08d7..ee8b52caaf 100644 --- a/src/nvim/testdir/test_undo.vim +++ b/src/nvim/testdir/test_undo.vim @@ -3,6 +3,9 @@ " undo-able pieces. Do that by setting 'undolevels'. " Also tests :earlier and :later. +source check.vim +source screendump.vim + func Test_undotree() new @@ -773,4 +776,30 @@ func Test_undo_mark() bwipe! endfunc +func Test_undo_after_write() + " use a terminal to make undo work like when text is typed + CheckRunVimInTerminal + + let lines =<< trim END + edit Xtestfile.txt + set undolevels=100 undofile + imap . <Cmd>write<CR> + write + END + call writefile(lines, 'Xtest_undo_after_write', 'D') + let buf = RunVimInTerminal('-S Xtest_undo_after_write', #{rows: 6}) + + call term_sendkeys(buf, "Otest.\<CR>boo!!!\<Esc>") + sleep 100m + call term_sendkeys(buf, "u") + call VerifyScreenDump(buf, 'Test_undo_after_write_1', {}) + + call term_sendkeys(buf, "u") + call VerifyScreenDump(buf, 'Test_undo_after_write_2', {}) + + call StopVimInTerminal(buf) + call delete('Xtestfile.txt') +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_unlet.vim b/src/nvim/testdir/test_unlet.vim index b02bdaab3b..4779d17906 100644 --- a/src/nvim/testdir/test_unlet.vim +++ b/src/nvim/testdir/test_unlet.vim @@ -1,12 +1,9 @@ " Tests for :unlet func Test_read_only() - try - " this caused a crash - unlet v:count - catch - call assert_true(v:exception =~ ':E795:') - endtry + " these caused a crash + call assert_fails('unlet v:count', 'E795:') + call assert_fails('unlet v:errmsg', 'E795:') endfunc func Test_existing() @@ -18,15 +15,19 @@ endfunc func Test_not_existing() unlet! does_not_exist - try - unlet does_not_exist - catch - call assert_true(v:exception =~ ':E108:') - endtry + call assert_fails('unlet does_not_exist', 'E108:') endfunc func Test_unlet_fails() call assert_fails('unlet v:["count"]', 'E46:') + call assert_fails('unlet $', 'E475:') + let v = {} + call assert_fails('unlet v[:]', 'E719:') + let l = [] + call assert_fails("unlet l['k'", 'E111:') + let d = {'k' : 1} + call assert_fails("unlet d.k2", 'E716:') + call assert_fails("unlet {a};", 'E488:') endfunc func Test_unlet_env() @@ -62,3 +63,5 @@ func Test_unlet_complete() call feedkeys(":unlet $FOO\t\n", 'tx') call assert_true(!exists('$FOOBAR') || empty($FOOBAR)) endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_user_func.vim b/src/nvim/testdir/test_user_func.vim index c14624f5b4..4742293ed5 100644 --- a/src/nvim/testdir/test_user_func.vim +++ b/src/nvim/testdir/test_user_func.vim @@ -3,6 +3,9 @@ " Also test that a builtin function cannot be replaced. " Also test for regression when calling arbitrary expression. +source check.vim +source shared.vim + func Table(title, ...) let ret = a:title let idx = 1 @@ -83,10 +86,14 @@ func Test_user_func() normal o[(one again call assert_equal('1. one again', getline('.')) + " Try to overwrite a function in the global (g:) scope call assert_equal(3, max([1, 2, 3])) call assert_fails("call extend(g:, {'max': function('min')})", 'E704') call assert_equal(3, max([1, 2, 3])) + " Try to overwrite an user defined function with a function reference + call assert_fails("let Expr1 = function('min')", 'E705:') + " Regression: the first line below used to throw ?E110: Missing ')'? " Second is here just to prove that this line is correct when not skipping " rhs of &&. @@ -142,8 +149,8 @@ func Test_default_arg() call assert_equal(res.optional, 2) call assert_equal(res['0'], 1) - call assert_fails("call MakeBadFunc()", 'E989') - call assert_fails("fu F(a=1 ,) | endf", 'E475') + call assert_fails("call MakeBadFunc()", 'E989:') + call assert_fails("fu F(a=1 ,) | endf", 'E1068:') " Since neovim does not have v:none, the ability to use the default " argument with the intermediate argument set to v:none has been omitted. @@ -156,6 +163,16 @@ func Test_default_arg() \ .. "1 return deepcopy(a:)\n" \ .. " endfunction", \ execute('func Args2')) + + " Error in default argument expression + let l =<< trim END + func F1(x = y) + return a:x * 2 + endfunc + echo F1() + END + let @a = l->join("\n") + call assert_fails("exe @a", 'E121:') endfunc func s:addFoo(lead) @@ -175,4 +192,313 @@ func Test_function_list() call assert_fails("function Xabc", 'E123:') endfunc +" Test for <sfile>, <slnum> in a function +func Test_sfile_in_function() + func Xfunc() + call assert_match('..Test_sfile_in_function\[5]..Xfunc', expand('<sfile>')) + call assert_equal('2', expand('<slnum>')) + endfunc + call Xfunc() + delfunc Xfunc +endfunc + +" Test trailing text after :endfunction {{{1 +func Test_endfunction_trailing() + call assert_false(exists('*Xtest')) + + exe "func Xtest()\necho 'hello'\nendfunc\nlet done = 'yes'" + call assert_true(exists('*Xtest')) + call assert_equal('yes', done) + delfunc Xtest + unlet done + + exe "func Xtest()\necho 'hello'\nendfunc|let done = 'yes'" + call assert_true(exists('*Xtest')) + call assert_equal('yes', done) + delfunc Xtest + unlet done + + " trailing line break + exe "func Xtest()\necho 'hello'\nendfunc\n" + call assert_true(exists('*Xtest')) + delfunc Xtest + + set verbose=1 + exe "func Xtest()\necho 'hello'\nendfunc \" garbage" + call assert_notmatch('W22:', split(execute('1messages'), "\n")[0]) + call assert_true(exists('*Xtest')) + delfunc Xtest + + exe "func Xtest()\necho 'hello'\nendfunc garbage" + call assert_match('W22:', split(execute('1messages'), "\n")[0]) + call assert_true(exists('*Xtest')) + delfunc Xtest + set verbose=0 + + func Xtest(a1, a2) + echo a:a1 .. a:a2 + endfunc + set verbose=15 + redir @a + call Xtest(123, repeat('x', 100)) + redir END + call assert_match('calling Xtest(123, ''xxxxxxx.*x\.\.\.x.*xxxx'')', getreg('a')) + delfunc Xtest + set verbose=0 + + function Foo() + echo 'hello' + endfunction | echo 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' + delfunc Foo +endfunc + +func Test_delfunction_force() + delfunc! Xtest + delfunc! Xtest + func Xtest() + echo 'nothing' + endfunc + delfunc! Xtest + delfunc! Xtest + + " Try deleting the current function + call assert_fails('delfunc Test_delfunction_force', 'E131:') +endfunc + +func Test_function_defined_line() + CheckNotGui + + let lines =<< trim [CODE] + " F1 + func F1() + " F2 + func F2() + " + " + " + return + endfunc + " F3 + execute "func F3()\n\n\n\nreturn\nendfunc" + " F4 + execute "func F4()\n + \\n + \\n + \\n + \return\n + \endfunc" + endfunc + " F5 + execute "func F5()\n\n\n\nreturn\nendfunc" + " F6 + execute "func F6()\n + \\n + \\n + \\n + \return\n + \endfunc" + call F1() + verbose func F1 + verbose func F2 + verbose func F3 + verbose func F4 + verbose func F5 + verbose func F6 + qall! + [CODE] + + call writefile(lines, 'Xtest.vim') + let res = system(GetVimCommandClean() .. ' -es -X -S Xtest.vim') + call assert_equal(0, v:shell_error) + + let m = matchstr(res, 'function F1()[^[:print:]]*[[:print:]]*') + call assert_match(' line 2$', m) + + let m = matchstr(res, 'function F2()[^[:print:]]*[[:print:]]*') + call assert_match(' line 4$', m) + + let m = matchstr(res, 'function F3()[^[:print:]]*[[:print:]]*') + call assert_match(' line 11$', m) + + let m = matchstr(res, 'function F4()[^[:print:]]*[[:print:]]*') + call assert_match(' line 13$', m) + + let m = matchstr(res, 'function F5()[^[:print:]]*[[:print:]]*') + call assert_match(' line 21$', m) + + let m = matchstr(res, 'function F6()[^[:print:]]*[[:print:]]*') + call assert_match(' line 23$', m) + + call delete('Xtest.vim') +endfunc + +" Test for defining a function reference in the global scope +func Test_add_funcref_to_global_scope() + let x = g: + let caught_E862 = 0 + try + func x.Xfunc() + return 1 + endfunc + catch /E862:/ + let caught_E862 = 1 + endtry + call assert_equal(1, caught_E862) +endfunc + +func Test_funccall_garbage_collect() + func Func(x, ...) + call add(a:x, a:000) + endfunc + call Func([], []) + " Must not crash cause by invalid freeing + call test_garbagecollect_now() + call assert_true(v:true) + delfunc Func +endfunc + +" Test for script-local function +func <SID>DoLast() + call append(line('$'), "last line") +endfunc + +func s:DoNothing() + call append(line('$'), "nothing line") +endfunc + +func Test_script_local_func() + set nocp nomore viminfo+=nviminfo + new + nnoremap <buffer> _x :call <SID>DoNothing()<bar>call <SID>DoLast()<bar>delfunc <SID>DoNothing<bar>delfunc <SID>DoLast<cr> + + normal _x + call assert_equal('nothing line', getline(2)) + call assert_equal('last line', getline(3)) + close! + + " Try to call a script local function in global scope + let lines =<< trim [CODE] + :call assert_fails('call s:Xfunc()', 'E81:') + :call assert_fails('let x = call("<SID>Xfunc", [])', 'E120:') + :call writefile(v:errors, 'Xresult') + :qall + + [CODE] + call writefile(lines, 'Xscript') + if RunVim([], [], '-s Xscript') + call assert_equal([], readfile('Xresult')) + endif + call delete('Xresult') + call delete('Xscript') +endfunc + +" Test for errors in defining new functions +func Test_func_def_error() + call assert_fails('func Xfunc abc ()', 'E124:') + call assert_fails('func Xfunc(', 'E125:') + call assert_fails('func xfunc()', 'E128:') + + " Try to redefine a function that is in use + let caught_E127 = 0 + try + func! Test_func_def_error() + endfunc + catch /E127:/ + let caught_E127 = 1 + endtry + call assert_equal(1, caught_E127) + + " Try to define a function in a dict twice + let d = {} + let lines =<< trim END + func d.F1() + return 1 + endfunc + END + let l = join(lines, "\n") . "\n" + exe l + call assert_fails('exe l', 'E717:') + + " Define an autoload function with an incorrect file name + call writefile(['func foo#Bar()', 'return 1', 'endfunc'], 'Xscript') + call assert_fails('source Xscript', 'E746:') + call delete('Xscript') + + " Try to list functions using an invalid search pattern + call assert_fails('function /\%(/', 'E53:') +endfunc + +" Test for deleting a function +func Test_del_func() + call assert_fails('delfunction Xabc', 'E130:') + let d = {'a' : 10} + call assert_fails('delfunc d.a', 'E718:') + func d.fn() + return 1 + endfunc + + " cannot delete the dict function by number + let nr = substitute(execute('echo d'), '.*function(''\(\d\+\)'').*', '\1', '') + call assert_fails('delfunction g:' .. nr, 'E475: Invalid argument: g:') + + delfunc d.fn + call assert_equal({'a' : 10}, d) +endfunc + +" Test for calling return outside of a function +func Test_return_outside_func() + call writefile(['return 10'], 'Xscript') + call assert_fails('source Xscript', 'E133:') + call delete('Xscript') +endfunc + +" Test for errors in calling a function +func Test_func_arg_error() + " Too many arguments + call assert_fails("call call('min', range(1,20))", 'E118:') + call assert_fails("call call('min', range(1,21))", 'E699:') + call assert_fails('echo min(0,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,0,1)', + \ 'E740:') + + " Missing dict argument + func Xfunc() dict + return 1 + endfunc + call assert_fails('call Xfunc()', 'E725:') + delfunc Xfunc +endfunc + +func Test_func_dict() + let mydict = {'a': 'b'} + function mydict.somefunc() dict + return len(self) + endfunc + + call assert_equal("{'a': 'b', 'somefunc': function('3')}", string(mydict)) + call assert_equal(2, mydict.somefunc()) + call assert_match("^\n function \\d\\\+() dict" + \ .. "\n1 return len(self)" + \ .. "\n endfunction$", execute('func mydict.somefunc')) + call assert_fails('call mydict.nonexist()', 'E716:') +endfunc + +func Test_func_range() + new + call setline(1, range(1, 8)) + func FuncRange() range + echo a:firstline + echo a:lastline + endfunc + 3 + call assert_equal("\n3\n3", execute('call FuncRange()')) + call assert_equal("\n4\n6", execute('4,6 call FuncRange()')) + call assert_equal("\n function FuncRange() range" + \ .. "\n1 echo a:firstline" + \ .. "\n2 echo a:lastline" + \ .. "\n endfunction", + \ execute('function FuncRange')) + + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_usercommands.vim b/src/nvim/testdir/test_usercommands.vim index 1065dd16e2..6910361345 100644 --- a/src/nvim/testdir/test_usercommands.vim +++ b/src/nvim/testdir/test_usercommands.vim @@ -79,6 +79,19 @@ function Test_cmdmods() call assert_equal('silent!', g:mods) tab MyCmd call assert_equal('tab', g:mods) + 0tab MyCmd + call assert_equal('0tab', g:mods) + tab split + tab MyCmd + call assert_equal('tab', g:mods) + 1tab MyCmd + call assert_equal('1tab', g:mods) + tabprev + tab MyCmd + call assert_equal('tab', g:mods) + 2tab MyCmd + call assert_equal('2tab', g:mods) + 2tabclose topleft MyCmd call assert_equal('topleft', g:mods) to MyCmd @@ -310,6 +323,11 @@ func Test_CmdErrors() call assert_fails('com DoCmd :', 'E174:') comclear call assert_fails('delcom DoCmd', 'E184:') + + " These used to leak memory + call assert_fails('com! -complete=custom,CustomComplete _ :', 'E182:') + call assert_fails('com! -complete=custom,CustomComplete docmd :', 'E183:') + call assert_fails('com! -complete=custom,CustomComplete -xxx DoCmd :', 'E181:') endfunc func CustomComplete(A, L, P) @@ -317,7 +335,7 @@ func CustomComplete(A, L, P) endfunc func CustomCompleteList(A, L, P) - return [ "Monday", "Tuesday", "Wednesday", {}] + return [ "Monday", "Tuesday", "Wednesday", {}, v:_null_string] endfunc func Test_CmdCompletion() @@ -336,6 +354,14 @@ func Test_CmdCompletion() call feedkeys(":com -complete=co\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"com -complete=color command compiler', @:) + " try completion for unsupported argument values + call feedkeys(":com -newarg=\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"com -newarg=\t", @:) + + " command completion after the name in a user defined command + call feedkeys(":com MyCmd chist\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"com MyCmd chistory", @:) + command! DoCmd1 : command! DoCmd2 : call feedkeys(":com \<C-A>\<C-B>\"\<CR>", 'tx') @@ -347,6 +373,10 @@ func Test_CmdCompletion() call feedkeys(":delcom DoC\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"delcom DoCmd1 DoCmd2', @:) + " try argument completion for a command without completion + call feedkeys(":DoCmd1 \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"DoCmd1 \t", @:) + delcom DoCmd1 call feedkeys(":delcom DoC\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"delcom DoCmd2', @:) @@ -365,6 +395,21 @@ func Test_CmdCompletion() call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"DoCmd mswin xterm', @:) + " Test for file name completion + com! -nargs=1 -complete=file DoCmd : + call feedkeys(":DoCmd READM\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"DoCmd README.txt', @:) + + " Test for buffer name completion + com! -nargs=1 -complete=buffer DoCmd : + let bnum = bufadd('BufForUserCmd') + call setbufvar(bnum, '&buflisted', 1) + call feedkeys(":DoCmd BufFor\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"DoCmd BufForUserCmd', @:) + bwipe BufForUserCmd + call feedkeys(":DoCmd BufFor\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"DoCmd BufFor', @:) + com! -nargs=* -complete=custom,CustomComplete DoCmd : call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"DoCmd January February Mars', @:) @@ -387,6 +432,17 @@ func Test_CmdCompletion() com! -nargs=? -complete=custom,min DoCmd call assert_fails("call feedkeys(':DoCmd \t', 'tx')", 'E118:') + " custom completion for a pattern with a backslash + let g:ArgLead = '' + func! CustCompl(A, L, P) + let g:ArgLead = a:A + return ['one', 'two', 'three'] + endfunc + com! -nargs=? -complete=customlist,CustCompl DoCmd + call feedkeys(":DoCmd a\\\t", 'xt') + call assert_equal('a\', g:ArgLead) + delfunc CustCompl + delcom DoCmd endfunc @@ -604,6 +660,27 @@ func Test_command_list() call assert_equal("\nNo user-defined commands found", execute('command')) endfunc +" Test for a custom user completion returning the wrong value type +func Test_usercmd_custom() + func T1(a, c, p) + return "a\nb\n" + endfunc + command -nargs=* -complete=customlist,T1 TCmd1 + call feedkeys(":TCmd1 \<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"TCmd1 ', @:) + delcommand TCmd1 + delfunc T1 + + func T2(a, c, p) + return {} + endfunc + command -nargs=* -complete=customlist,T2 TCmd2 + call feedkeys(":TCmd2 \<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"TCmd2 ', @:) + delcommand TCmd2 + delfunc T2 +endfunc + func Test_delcommand_buffer() command Global echo 'global' command -buffer OneBuffer echo 'one' @@ -644,5 +721,30 @@ func Test_recursive_define() endwhile endfunc +" Test for using buffer-local ambiguous user-defined commands +func Test_buflocal_ambiguous_usercmd() + new + command -buffer -nargs=1 -complete=sign TestCmd1 echo "Hello" + command -buffer -nargs=1 -complete=sign TestCmd2 echo "World" + + call assert_fails("call feedkeys(':TestCmd\<CR>', 'xt')", 'E464:') + call feedkeys(":TestCmd \<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"TestCmd ', @:) + + delcommand TestCmd1 + delcommand TestCmd2 + bw! +endfunc + +" Test for using a multibyte character in a user command +func Test_multibyte_in_usercmd() + command SubJapanesePeriodToDot exe "%s/\u3002/./g" + new + call setline(1, "Hello\u3002") + SubJapanesePeriodToDot + call assert_equal('Hello.', getline(1)) + bw! + delcommand SubJapanesePeriodToDot +endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_utf8.vim b/src/nvim/testdir/test_utf8.vim index ab3503c282..e5f6d68720 100644 --- a/src/nvim/testdir/test_utf8.vim +++ b/src/nvim/testdir/test_utf8.vim @@ -2,6 +2,7 @@ source check.vim source view_util.vim +source screendump.vim " Visual block Insert adjusts for multi-byte char func Test_visual_block_insert() @@ -12,7 +13,7 @@ func Test_visual_block_insert() bwipeout! endfunc -" Test for built-in function strchars() +" Test for built-in functions strchars() and strcharlen() func Test_strchars() let inp = ["a", "あいa", "A\u20dd", "A\u20dd\u20dd", "\u20dd"] let exp = [[1, 1, 1], [3, 3, 3], [2, 2, 1], [3, 3, 1], [1, 1, 1]] @@ -21,6 +22,15 @@ func Test_strchars() call assert_equal(exp[i][1], inp[i]->strchars(0)) call assert_equal(exp[i][2], strchars(inp[i], 1)) endfor + + let exp = [1, 3, 1, 1, 1] + for i in range(len(inp)) + call assert_equal(exp[i], inp[i]->strcharlen()) + call assert_equal(exp[i], strcharlen(inp[i])) + endfor + + call assert_fails("let v=strchars('abc', [])", 'E745:') + call assert_fails("let v=strchars('abc', 2)", 'E1023:') endfunc " Test for customlist completion @@ -131,9 +141,13 @@ func Test_list2str_str2list_latin1() let save_encoding = &encoding " set encoding=latin1 - + let lres = str2list(s, 1) let sres = list2str(l, 1) + call assert_equal([65, 66, 67], str2list("ABC")) + + " Try converting a list to a string in latin-1 encoding + call assert_equal([1, 2, 3], str2list(list2str([1, 2, 3]))) let &encoding = save_encoding call assert_equal(l, lres) @@ -153,6 +167,39 @@ func Test_setcellwidths() call assert_equal(2, strwidth("\u1339")) call assert_equal(1, strwidth("\u133a")) + for aw in ['single', 'double'] + exe 'set ambiwidth=' . aw + " Handle \u0080 to \u009F as control chars even on MS-Windows. + set isprint=@,161-255 + + call setcellwidths([]) + " Control chars + call assert_equal(4, strwidth("\u0081")) + call assert_equal(6, strwidth("\uFEFF")) + " Ambiguous width chars + call assert_equal((aw == 'single') ? 1 : 2, strwidth("\u00A1")) + call assert_equal((aw == 'single') ? 1 : 2, strwidth("\u2010")) + + call setcellwidths([[0x81, 0x81, 1], [0xA1, 0xA1, 1], + \ [0x2010, 0x2010, 1], [0xFEFF, 0xFEFF, 1]]) + " Control chars + call assert_equal(4, strwidth("\u0081")) + call assert_equal(6, strwidth("\uFEFF")) + " Ambiguous width chars + call assert_equal(1, strwidth("\u00A1")) + call assert_equal(1, strwidth("\u2010")) + + call setcellwidths([[0x81, 0x81, 2], [0xA1, 0xA1, 2], + \ [0x2010, 0x2010, 2], [0xFEFF, 0xFEFF, 2]]) + " Control chars + call assert_equal(4, strwidth("\u0081")) + call assert_equal(6, strwidth("\uFEFF")) + " Ambiguous width chars + call assert_equal(2, strwidth("\u00A1")) + call assert_equal(2, strwidth("\u2010")) + endfor + set ambiwidth& isprint& + call setcellwidths([]) call assert_fails('call setcellwidths(1)', 'E714:') @@ -185,6 +232,42 @@ func Test_setcellwidths() call setcellwidths([]) endfunc +func Test_getcellwidths() + call setcellwidths([]) + call assert_equal([], getcellwidths()) + + let widthlist = [ + \ [0x1330, 0x1330, 2], + \ [9999, 10000, 1], + \ [0x1337, 0x1339, 2], + \] + let widthlistsorted = [ + \ [0x1330, 0x1330, 2], + \ [0x1337, 0x1339, 2], + \ [9999, 10000, 1], + \] + call setcellwidths(widthlist) + call assert_equal(widthlistsorted, getcellwidths()) + + call setcellwidths([]) +endfunc + +func Test_setcellwidths_dump() + CheckRunVimInTerminal + + let lines =<< trim END + call setline(1, "\ue5ffDesktop") + END + call writefile(lines, 'XCellwidths', 'D') + let buf = RunVimInTerminal('-S XCellwidths', {'rows': 6}) + call VerifyScreenDump(buf, 'Test_setcellwidths_dump_1', {}) + + call term_sendkeys(buf, ":call setcellwidths([[0xe5ff, 0xe5ff, 2]])\<CR>") + call VerifyScreenDump(buf, 'Test_setcellwidths_dump_2', {}) + + call StopVimInTerminal(buf) +endfunc + func Test_print_overlong() " Text with more composing characters than MB_MAXBYTES. new diff --git a/src/nvim/testdir/test_vartabs.vim b/src/nvim/testdir/test_vartabs.vim index 0acd7fc1e5..e12c71d521 100644 --- a/src/nvim/testdir/test_vartabs.vim +++ b/src/nvim/testdir/test_vartabs.vim @@ -97,7 +97,7 @@ func Test_vartabs() .retab! call assert_equal("\t\t\t\tl", getline(1)) - " Test for 'retab' with same vlaues as vts + " Test for 'retab' with same values as vts set ts=8 sts=0 vts=5,3,6,2 vsts= exe "norm! S l" .retab! 5,3,6,2 @@ -379,6 +379,8 @@ func Test_vartabs_shiftwidth() let lines = ScreenLines([1, 3], winwidth(0)) call s:compare_lines(expect4, lines) + call assert_fails('call shiftwidth([])', 'E745:') + " cleanup bw! bw! diff --git a/src/nvim/testdir/test_viminfo.vim b/src/nvim/testdir/test_viminfo.vim index 2d6d598011..e792db90ab 100644 --- a/src/nvim/testdir/test_viminfo.vim +++ b/src/nvim/testdir/test_viminfo.vim @@ -18,4 +18,9 @@ func Test_viminfo_option_error() call assert_fails('set viminfo=%10', 'E528:') endfunc +func Test_viminfo_oldfiles_newfile() + let v:oldfiles = v:_null_list + call assert_equal("\nNo old files", execute('oldfiles')) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index 3487a028ca..b0c4baf7c2 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -1,16 +1,15 @@ " Test various aspects of the Vim script language. -" Most of this was formerly in test49. +" Most of this was formerly in test49.vim (developed by Servatius Brandt +" <Servatius.Brandt@fujitsu-siemens.com>) source check.vim source shared.vim +source script_util.vim "------------------------------------------------------------------------------- " Test environment {{{1 "------------------------------------------------------------------------------- -com! XpathINIT let g:Xpath = '' -com! -nargs=1 -bar Xpath let g:Xpath = g:Xpath . <args> - " Append a message to the "messages" file func Xout(text) split messages @@ -20,67 +19,31 @@ endfunc com! -nargs=1 Xout call Xout(<args>) -" MakeScript() - Make a script file from a function. {{{2 -" -" Create a script that consists of the body of the function a:funcname. -" Replace any ":return" by a ":finish", any argument variable by a global -" variable, and every ":call" by a ":source" for the next following argument -" in the variable argument list. This function is useful if similar tests are -" to be made for a ":return" from a function call or a ":finish" in a script -" file. -func MakeScript(funcname, ...) - let script = tempname() - execute "redir! >" . script - execute "function" a:funcname - redir END - execute "edit" script - " Delete the "function" and the "endfunction" lines. Do not include the - " word "function" in the pattern since it might be translated if LANG is - " set. When MakeScript() is being debugged, this deletes also the debugging - " output of its line 3 and 4. - exec '1,/.*' . a:funcname . '(.*)/d' - /^\d*\s*endfunction\>/,$d - %s/^\d*//e - %s/return/finish/e - %s/\<a:\(\h\w*\)/g:\1/ge - normal gg0 - let cnt = 0 - while search('\<call\s*\%(\u\|s:\)\w*\s*(.*)', 'W') > 0 - let cnt = cnt + 1 - s/\<call\s*\%(\u\|s:\)\w*\s*(.*)/\='source ' . a:{cnt}/ - endwhile - g/^\s*$/d - write - bwipeout - return script -endfunc - -" ExecAsScript - Source a temporary script made from a function. {{{2 -" -" Make a temporary script file from the function a:funcname, ":source" it, and -" delete it afterwards. However, if an exception is thrown the file may remain, -" the caller should call DeleteTheScript() afterwards. -let s:script_name = '' -function! ExecAsScript(funcname) - " Make a script from the function passed as argument. - let s:script_name = MakeScript(a:funcname) - - " Source and delete the script. - exec "source" s:script_name - call delete(s:script_name) - let s:script_name = '' -endfunction - -function! DeleteTheScript() - if s:script_name - call delete(s:script_name) - let s:script_name = '' - endif +" Create a new instance of Vim and run the commands in 'test' and then 'verify' +" The commands in 'test' are expected to store the test results in the Xtest.out +" file. If the test passes successfully, then Xtest.out should be empty. +func RunInNewVim(test, verify) + let init =<< trim END + set cpo-=C " support line-continuation in sourced script + source script_util.vim + XpathINIT + XloopINIT + END + let cleanup =<< trim END + call writefile(v:errors, 'Xtest.out') + qall + END + call writefile(init, 'Xtest.vim') + call writefile(a:test, 'Xtest.vim', 'a') + call writefile(a:verify, 'Xverify.vim') + call writefile(cleanup, 'Xverify.vim', 'a') + call RunVim([], [], "-S Xtest.vim -S Xverify.vim") + call assert_equal([], readfile('Xtest.out')) + call delete('Xtest.out') + call delete('Xtest.vim') + call delete('Xverify.vim') endfunc -com! -nargs=1 -bar ExecAsScript call ExecAsScript(<f-args>) - - "------------------------------------------------------------------------------- " Test 1: :endwhile in function {{{1 " @@ -90,7 +53,7 @@ com! -nargs=1 -bar ExecAsScript call ExecAsScript(<f-args>) " tests will hang. "------------------------------------------------------------------------------- -function! T1_F() +func T1_F() Xpath 'a' let first = 1 while 1 @@ -104,9 +67,9 @@ function! T1_F() return endif endwhile -endfunction +endfunc -function! T1_G() +func T1_G() Xpath 'h' let first = 1 while 1 @@ -121,7 +84,7 @@ function! T1_G() endif if 1 " unmatched :if endwhile -endfunction +endfunc func Test_endwhile_function() XpathINIT @@ -175,7 +138,7 @@ endfunc " Test 3: :if, :elseif, :while, :continue, :break {{{1 "------------------------------------------------------------------------------- -function Test_if_while() +func Test_if_while() XpathINIT if 1 Xpath 'a' @@ -231,11 +194,21 @@ function Test_if_while() call assert_equal('ab3j3b2c2b1f1h1km', g:Xpath) endfunc +" Check double quote after skipped "elseif" does not give error E15 +func Test_skipped_elseif() + if "foo" ==? "foo" + let result = "first" + elseif "foo" ==? "foo" + let result = "second" + endif + call assert_equal('first', result) +endfunc + "------------------------------------------------------------------------------- " Test 4: :return {{{1 "------------------------------------------------------------------------------- -function! T4_F() +func T4_F() if 1 Xpath 'a' let loops = 3 @@ -253,15 +226,15 @@ function! T4_F() else Xpath 'g' endif -endfunction +endfunc -function Test_return() +func Test_return() XpathINIT call T4_F() Xpath '4' call assert_equal('ab3e3b2c24', g:Xpath) -endfunction +endfunc "------------------------------------------------------------------------------- @@ -271,14 +244,14 @@ endfunction " test as a script file (:return replaced by :finish). "------------------------------------------------------------------------------- -function Test_finish() +func Test_finish() XpathINIT ExecAsScript T4_F Xpath '5' call DeleteTheScript() call assert_equal('ab3e3b2c25', g:Xpath) -endfunction +endfunc @@ -412,7 +385,7 @@ delfunction G31 delfunction G32 delfunction G33 -function Test_defining_functions() +func Test_defining_functions() call assert_equal('ade2ie3ibcg0h1g1h2g2h3fg0h1g1h2g2h3m', g:test6_result) call assert_equal('F1G1F2G21G22G23F3G31G32G33', g:test6_calls) endfunc @@ -476,7 +449,7 @@ endfunc XpathINIT -function! T8_F() +func T8_F() if 1 Xpath 'a' while 1 @@ -508,9 +481,9 @@ function! T8_F() return novar " returns (default return value 0) Xpath 'q' return 1 " not reached -endfunction +endfunc -function! T8_G() abort +func T8_G() abort if 1 Xpath 'r' while 1 @@ -524,9 +497,9 @@ function! T8_G() abort Xpath 'x' return -4 " not reached -endfunction +endfunc -function! T8_H() abort +func T8_H() abort while 1 Xpath 'A' if 1 @@ -540,7 +513,7 @@ function! T8_H() abort Xpath 'F' return -4 " not reached -endfunction +endfunc " Aborted functions (T8_G and T8_H) return -1. let g:test8_sum = (T8_F() + 1) - 4 * T8_G() - 8 * T8_H() @@ -567,7 +540,7 @@ endfunc XpathINIT -function! F() abort +func F() abort Xpath 'a' let result = G() " not aborted Xpath 'b' @@ -575,30 +548,30 @@ function! F() abort Xpath 'c' endif return 1 -endfunction +endfunc -function! G() " no abort attribute +func G() " no abort attribute Xpath 'd' if H() != -1 " aborted Xpath 'e' endif Xpath 'f' return 2 -endfunction +endfunc -function! H() abort +func H() abort Xpath 'g' call I() " aborted Xpath 'h' return 4 -endfunction +endfunc -function! I() abort +func I() abort Xpath 'i' asdf " error Xpath 'j' return 8 -endfunction +endfunc if F() != 1 Xpath 'k' @@ -626,7 +599,7 @@ endfunc XpathINIT -function! MSG(enr, emsg) +func MSG(enr, emsg) let english = v:lang == "C" || v:lang =~ '^[Ee]n' if a:enr == "" Xout "TODO: Add message number for:" a:emsg @@ -710,10 +683,10 @@ XpathINIT let calls = 0 -function! P(num) +func P(num) let g:calls = g:calls + a:num " side effect on call return 0 -endfunction +endfunc if 1 Xpath 'a' @@ -1092,7 +1065,5387 @@ func Test_unmatched_if_in_while() endfunc "------------------------------------------------------------------------------- +" Test 18: Interrupt (Ctrl-C pressed) {{{1 +" +" On an interrupt, the script processing is terminated immediately. +"------------------------------------------------------------------------------- + +func Test_interrupt_while_if() + let test =<< trim [CODE] + try + if 1 + Xpath 'a' + while 1 + Xpath 'b' + if 1 + Xpath 'c' + call interrupt() + call assert_report('should not get here') + break + finish + endif | call assert_report('should not get here') + call assert_report('should not get here') + endwhile | call assert_report('should not get here') + call assert_report('should not get here') + endif | call assert_report('should not get here') + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'd' + endtry | Xpath 'e' + Xpath 'f' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdef', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_interrupt_try() + let test =<< trim [CODE] + try + try + Xpath 'a' + call interrupt() + call assert_report('should not get here') + endtry | call assert_report('should not get here') + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'b' + endtry | Xpath 'c' + Xpath 'd' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcd', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_interrupt_func_while_if() + let test =<< trim [CODE] + func F() + if 1 + Xpath 'a' + while 1 + Xpath 'b' + if 1 + Xpath 'c' + call interrupt() + call assert_report('should not get here') + break + return + endif | call assert_report('should not get here') + call assert_report('should not get here') + endwhile | call assert_report('should not get here') + call assert_report('should not get here') + endif | call assert_report('should not get here') + call assert_report('should not get here') + endfunc + + Xpath 'd' + try + call F() | call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'e' + endtry | Xpath 'f' + Xpath 'g' + [CODE] + let verify =<< trim [CODE] + call assert_equal('dabcefg', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_interrupt_func_try() + let test =<< trim [CODE] + func G() + try + Xpath 'a' + call interrupt() + call assert_report('should not get here') + endtry | call assert_report('should not get here') + call assert_report('should not get here') + endfunc + + Xpath 'b' + try + call G() | call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'c' + endtry | Xpath 'd' + Xpath 'e' + [CODE] + let verify =<< trim [CODE] + call assert_equal('bacde', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 19: Aborting on errors inside :try/:endtry {{{1 +" +" An error in a command dynamically enclosed in a :try/:endtry region +" aborts script processing immediately. It does not matter whether +" the failing command is outside or inside a function and whether a +" function has an "abort" attribute. +"------------------------------------------------------------------------------- + +func Test_try_error_abort_1() + let test =<< trim [CODE] + func F() abort + Xpath 'a' + asdf + call assert_report('should not get here') + endfunc + + try + Xpath 'b' + call F() + call assert_report('should not get here') + endtry | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('ba', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_try_error_abort_2() + let test =<< trim [CODE] + func G() + Xpath 'a' + asdf + call assert_report('should not get here') + endfunc + + try + Xpath 'b' + call G() + call assert_report('should not get here') + endtry | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('ba', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_try_error_abort_3() + let test =<< trim [CODE] + try + Xpath 'a' + asdf + call assert_report('should not get here') + endtry | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('a', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_try_error_abort_4() + let test =<< trim [CODE] + if 1 + try + Xpath 'a' + asdf + call assert_report('should not get here') + endtry | call assert_report('should not get here') + endif | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('a', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_try_error_abort_5() + let test =<< trim [CODE] + let p = 1 + while p + let p = 0 + try + Xpath 'a' + asdf + call assert_report('should not get here') + endtry | call assert_report('should not get here') + endwhile | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('a', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_try_error_abort_6() + let test =<< trim [CODE] + let p = 1 + Xpath 'a' + while p + Xpath 'b' + let p = 0 + try + Xpath 'c' + endwhile | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 20: Aborting on errors after :try/:endtry {{{1 +" +" When an error occurs after the last active :try/:endtry region has +" been left, termination behavior is as if no :try/:endtry has been +" seen. +"------------------------------------------------------------------------------- + +func Test_error_after_try_1() + let test =<< trim [CODE] + let p = 1 + while p + let p = 0 + Xpath 'a' + try + Xpath 'b' + endtry + asdf + call assert_report('should not get here') + endwhile | call assert_report('should not get here') + Xpath 'c' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_error_after_try_2() + let test =<< trim [CODE] + while 1 + try + Xpath 'a' + break + call assert_report('should not get here') + endtry + endwhile + Xpath 'b' + asdf + Xpath 'c' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_error_after_try_3() + let test =<< trim [CODE] + while 1 + try + Xpath 'a' + break + call assert_report('should not get here') + finally + Xpath 'b' + endtry + endwhile + Xpath 'c' + asdf + Xpath 'd' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcd', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_error_after_try_4() + let test =<< trim [CODE] + while 1 + try + Xpath 'a' + finally + Xpath 'b' + break + call assert_report('should not get here') + endtry + endwhile + Xpath 'c' + asdf + Xpath 'd' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcd', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_error_after_try_5() + let test =<< trim [CODE] + let p = 1 + while p + let p = 0 + try + Xpath 'a' + continue + call assert_report('should not get here') + endtry + endwhile + Xpath 'b' + asdf + Xpath 'c' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_error_after_try_6() + let test =<< trim [CODE] + let p = 1 + while p + let p = 0 + try + Xpath 'a' + continue + call assert_report('should not get here') + finally + Xpath 'b' + endtry + endwhile + Xpath 'c' + asdf + Xpath 'd' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcd', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_error_after_try_7() + let test =<< trim [CODE] + let p = 1 + while p + let p = 0 + try + Xpath 'a' + finally + Xpath 'b' + continue + call assert_report('should not get here') + endtry + endwhile + Xpath 'c' + asdf + Xpath 'd' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcd', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 21: :finally for :try after :continue/:break/:return/:finish {{{1 +" +" If a :try conditional stays inactive due to a preceding :continue, +" :break, :return, or :finish, its :finally clause should not be +" executed. +"------------------------------------------------------------------------------- + +func Test_finally_after_loop_ctrl_statement() + let test =<< trim [CODE] + func F() + let loops = 2 + while loops > 0 + XloopNEXT + let loops = loops - 1 + try + if loops == 1 + Xloop 'a' + continue + call assert_report('should not get here') + elseif loops == 0 + Xloop 'b' + break + call assert_report('should not get here') + endif + + try " inactive + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + finally + Xloop 'c' + endtry + call assert_report('should not get here') + endwhile + + try + Xpath 'd' + return + call assert_report('should not get here') + try " inactive + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + finally + Xpath 'e' + endtry + call assert_report('should not get here') + endfunc + + try + Xpath 'f' + call F() + Xpath 'g' + finish + call assert_report('should not get here') + try " inactive + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + finally + Xpath 'h' + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('fa2c2b3c3degh', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 22: :finally for a :try after an error/interrupt/:throw {{{1 +" +" If a :try conditional stays inactive due to a preceding error or +" interrupt or :throw, its :finally clause should not be executed. +"------------------------------------------------------------------------------- + +func Test_finally_after_error_in_func() + let test =<< trim [CODE] + func Error() + try + Xpath 'b' + asdf " aborting error, triggering error exception + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endfunc + + Xpath 'a' + call Error() + call assert_report('should not get here') + + if 1 " not active due to error + try " not active since :if inactive + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + endif + + try " not active due to error + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('ab', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_finally_after_interrupt() + let test =<< trim [CODE] + func Interrupt() + try + Xpath 'a' + call interrupt() " triggering interrupt exception + call assert_report('should not get here') + endtry + endfunc + + Xpath 'b' + try + call Interrupt() + catch /^Vim:Interrupt$/ + Xpath 'c' + finish + endtry + call assert_report('should not get here') + + if 1 " not active due to interrupt + try " not active since :if inactive + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + endif + + try " not active due to interrupt + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('bac', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_finally_after_throw() + let test =<< trim [CODE] + func Throw() + Xpath 'a' + throw 'xyz' + endfunc + + Xpath 'b' + call Throw() + call assert_report('should not get here') + + if 1 " not active due to :throw + try " not active since :if inactive + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + endif + + try " not active due to :throw + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('ba', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 23: :catch clauses for a :try after a :throw {{{1 +" +" If a :try conditional stays inactive due to a preceding :throw, +" none of its :catch clauses should be executed. +"------------------------------------------------------------------------------- + +func Test_catch_after_throw() + let test =<< trim [CODE] + try + Xpath 'a' + throw "xyz" + call assert_report('should not get here') + + if 1 " not active due to :throw + try " not active since :if inactive + call assert_report('should not get here') + catch /xyz/ + call assert_report('should not get here') + endtry + endif + catch /xyz/ + Xpath 'b' + endtry + + Xpath 'c' + throw "abc" + call assert_report('should not get here') + + try " not active due to :throw + call assert_report('should not get here') + catch /abc/ + call assert_report('should not get here') + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 24: :endtry for a :try after a :throw {{{1 +" +" If a :try conditional stays inactive due to a preceding :throw, +" its :endtry should not rethrow the exception to the next surrounding +" active :try conditional. +"------------------------------------------------------------------------------- + +func Test_endtry_after_throw() + let test =<< trim [CODE] + try " try 1 + try " try 2 + Xpath 'a' + throw "xyz" " makes try 2 inactive + call assert_report('should not get here') + + try " try 3 + call assert_report('should not get here') + endtry " no rethrow to try 1 + catch /xyz/ " should catch although try 2 inactive + Xpath 'b' + endtry + catch /xyz/ " try 1 active, but exception already caught + call assert_report('should not get here') + endtry + Xpath 'c' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 27: Executing :finally clauses after :return {{{1 +" +" For a :return command dynamically enclosed in a :try/:endtry region, +" :finally clauses are executed and the called function is ended. +"------------------------------------------------------------------------------- + +func T27_F() + try + Xpath 'a' + try + Xpath 'b' + return + call assert_report('should not get here') + finally + Xpath 'c' + endtry + Xpath 'd' + finally + Xpath 'e' + endtry + call assert_report('should not get here') +endfunc + +func T27_G() + try + Xpath 'f' + return + call assert_report('should not get here') + finally + Xpath 'g' + call T27_F() + Xpath 'h' + endtry + call assert_report('should not get here') +endfunc + +func T27_H() + try + Xpath 'i' + call T27_G() + Xpath 'j' + finally + Xpath 'k' + return + call assert_report('should not get here') + endtry + call assert_report('should not get here') +endfunction + +func Test_finally_after_return() + XpathINIT + try + Xpath 'l' + call T27_H() + Xpath 'm' + finally + Xpath 'n' + endtry + call assert_equal('lifgabcehjkmn', g:Xpath) +endfunc + +"------------------------------------------------------------------------------- +" Test 28: Executing :finally clauses after :finish {{{1 +" +" For a :finish command dynamically enclosed in a :try/:endtry region, +" :finally clauses are executed and the sourced file is finished. +" +" This test executes the bodies of the functions F, G, and H from the +" previous test as script files (:return replaced by :finish). +"------------------------------------------------------------------------------- + +func Test_finally_after_finish() + XpathINIT + + let scriptF = MakeScript("T27_F") + let scriptG = MakeScript("T27_G", scriptF) + let scriptH = MakeScript("T27_H", scriptG) + + try + Xpath 'A' + exec "source" scriptH + Xpath 'B' + finally + Xpath 'C' + endtry + Xpath 'D' + call assert_equal('AifgabcehjkBCD', g:Xpath) + call delete(scriptF) + call delete(scriptG) + call delete(scriptH) +endfunc + +"------------------------------------------------------------------------------- +" Test 29: Executing :finally clauses on errors {{{1 +" +" After an error in a command dynamically enclosed in a :try/:endtry +" region, :finally clauses are executed and the script processing is +" terminated. +"------------------------------------------------------------------------------- + +func Test_finally_after_error_1() + let test =<< trim [CODE] + func F() + while 1 + try + Xpath 'a' + while 1 + try + Xpath 'b' + asdf " error + call assert_report('should not get here') + finally + Xpath 'c' + endtry | call assert_report('should not get here') + call assert_report('should not get here') + break + endwhile + call assert_report('should not get here') + finally + Xpath 'd' + endtry | call assert_report('should not get here') + call assert_report('should not get here') + break + endwhile + call assert_report('should not get here') + endfunc + + while 1 + try + Xpath 'e' + while 1 + call F() + call assert_report('should not get here') + break + endwhile | call assert_report('should not get here') + call assert_report('should not get here') + finally + Xpath 'f' + endtry | call assert_report('should not get here') + endwhile | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('eabcdf', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_finally_after_error_2() + let test =<< trim [CODE] + func G() abort + if 1 + try + Xpath 'a' + asdf " error + call assert_report('should not get here') + finally + Xpath 'b' + endtry | Xpath 'c' + endif | Xpath 'd' + call assert_report('should not get here') + endfunc + + if 1 + try + Xpath 'e' + call G() + call assert_report('should not get here') + finally + Xpath 'f' + endtry | call assert_report('should not get here') + endif | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('eabf', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 30: Executing :finally clauses on interrupt {{{1 +" +" After an interrupt in a command dynamically enclosed in +" a :try/:endtry region, :finally clauses are executed and the +" script processing is terminated. +"------------------------------------------------------------------------------- + +func Test_finally_on_interrupt() + let test =<< trim [CODE] + func F() + try + Xloop 'a' + call interrupt() + call assert_report('should not get here') + finally + Xloop 'b' + endtry + call assert_report('should not get here') + endfunc + + try + try + Xpath 'c' + try + Xpath 'd' + call interrupt() + call assert_report('should not get here') + finally + Xpath 'e' + try + Xpath 'f' + try + Xpath 'g' + finally + Xpath 'h' + try + Xpath 'i' + call interrupt() + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + finally + Xpath 'j' + try + Xpath 'k' + call F() + call assert_report('should not get here') + finally + Xpath 'l' + try + Xpath 'm' + XloopNEXT + ExecAsScript F + call assert_report('should not get here') + finally + Xpath 'n' + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'o' + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('cdefghijka1b1lma2b2no', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 31: Executing :finally clauses after :throw {{{1 +" +" After a :throw dynamically enclosed in a :try/:endtry region, +" :finally clauses are executed and the script processing is +" terminated. +"------------------------------------------------------------------------------- + +func Test_finally_after_throw_2() + let test =<< trim [CODE] + func F() + try + Xloop 'a' + throw "exception" + call assert_report('should not get here') + finally + Xloop 'b' + endtry + call assert_report('should not get here') + endfunc + + try + Xpath 'c' + try + Xpath 'd' + throw "exception" + call assert_report('should not get here') + finally + Xpath 'e' + try + Xpath 'f' + try + Xpath 'g' + finally + Xpath 'h' + try + Xpath 'i' + throw "exception" + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + finally + Xpath 'j' + try + Xpath 'k' + call F() + call assert_report('should not get here') + finally + Xpath 'l' + try + Xpath 'm' + XloopNEXT + ExecAsScript F + call assert_report('should not get here') + finally + Xpath 'n' + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('cdefghijka1b1lma2b2n', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 34: :finally reason discarded by :continue {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by a :continue in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_after_continue() + let test =<< trim [CODE] + func C(jump) + XloopNEXT + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "return" || a:jump == "finish" + return + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + continue " discards jump that caused the :finally + call assert_report('should not get here') + endtry + call assert_report('should not get here') + elseif loop == 2 + Xloop 'a' + endif + endwhile + endfunc + + call C("continue") + Xpath 'b' + call C("break") + Xpath 'c' + call C("return") + Xpath 'd' + let g:jump = "finish" + ExecAsScript C + unlet g:jump + Xpath 'e' + try + call C("error") + Xpath 'f' + finally + Xpath 'g' + try + call C("interrupt") + Xpath 'h' + finally + Xpath 'i' + call C("throw") + Xpath 'j' + endtry + endtry + Xpath 'k' + [CODE] + let verify =<< trim [CODE] + call assert_equal('a2ba3ca4da5ea6fga7hia8jk', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 35: :finally reason discarded by :break {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by a :break in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_discard_by_break() + let test =<< trim [CODE] + func B(jump) + XloopNEXT + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "return" || a:jump == "finish" + return + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + break " discards jump that caused the :finally + call assert_report('should not get here') + endtry + elseif loop == 2 + call assert_report('should not get here') + endif + endwhile + Xloop 'a' + endfunc + + call B("continue") + Xpath 'b' + call B("break") + Xpath 'c' + call B("return") + Xpath 'd' + let g:jump = "finish" + ExecAsScript B + unlet g:jump + Xpath 'e' + try + call B("error") + Xpath 'f' + finally + Xpath 'g' + try + call B("interrupt") + Xpath 'h' + finally + Xpath 'i' + call B("throw") + Xpath 'j' + endtry + endtry + Xpath 'k' + [CODE] + let verify =<< trim [CODE] + call assert_equal('a2ba3ca4da5ea6fga7hia8jk', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 36: :finally reason discarded by :return {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by a :return in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_discard_by_return() + let test =<< trim [CODE] + func R(jump, retval) abort + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "return" + return + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + return a:retval " discards jump that caused the :finally + call assert_report('should not get here') + endtry + elseif loop == 2 + call assert_report('should not get here') + endif + endwhile + call assert_report('should not get here') + endfunc + + let sum = -R("continue", -8) + Xpath 'a' + let sum = sum - R("break", -16) + Xpath 'b' + let sum = sum - R("return", -32) + Xpath 'c' + try + let sum = sum - R("error", -64) + Xpath 'd' + finally + Xpath 'e' + try + let sum = sum - R("interrupt", -128) + Xpath 'f' + finally + Xpath 'g' + let sum = sum - R("throw", -256) + Xpath 'h' + endtry + endtry + Xpath 'i' + + let expected = 8 + 16 + 32 + 64 + 128 + 256 + call assert_equal(sum, expected) + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefghi', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 37: :finally reason discarded by :finish {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by a :finish in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_discard_by_finish() + let test =<< trim [CODE] + func F(jump) " not executed as function, transformed to a script + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "finish" + finish + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + finish " discards jump that caused the :finally + call assert_report('should not get here') + endtry + elseif loop == 2 + call assert_report('should not get here') + endif + endwhile + call assert_report('should not get here') + endfunc + + let scriptF = MakeScript("F") + delfunction F + + let g:jump = "continue" + exec "source" scriptF + Xpath 'a' + let g:jump = "break" + exec "source" scriptF + Xpath 'b' + let g:jump = "finish" + exec "source" scriptF + Xpath 'c' + try + let g:jump = "error" + exec "source" scriptF + Xpath 'd' + finally + Xpath 'e' + try + let g:jump = "interrupt" + exec "source" scriptF + Xpath 'f' + finally + Xpath 'g' + try + let g:jump = "throw" + exec "source" scriptF + Xpath 'h' + finally + Xpath 'i' + endtry + endtry + endtry + unlet g:jump + call delete(scriptF) + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefghi', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 38: :finally reason discarded by an error {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by an error in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_discard_by_error() + let test =<< trim [CODE] + func E(jump) + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "return" || a:jump == "finish" + return + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + asdf " error; discards jump that caused the :finally + endtry + elseif loop == 2 + call assert_report('should not get here') + endif + endwhile + call assert_report('should not get here') + endfunc + + try + Xpath 'a' + call E("continue") + call assert_report('should not get here') + finally + try + Xpath 'b' + call E("break") + call assert_report('should not get here') + finally + try + Xpath 'c' + call E("return") + call assert_report('should not get here') + finally + try + Xpath 'd' + let g:jump = "finish" + ExecAsScript E + call assert_report('should not get here') + finally + unlet g:jump + try + Xpath 'e' + call E("error") + call assert_report('should not get here') + finally + try + Xpath 'f' + call E("interrupt") + call assert_report('should not get here') + finally + try + Xpath 'g' + call E("throw") + call assert_report('should not get here') + finally + Xpath 'h' + delfunction E + endtry + endtry + endtry + endtry + endtry + endtry + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefgh', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 39: :finally reason discarded by an interrupt {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by an interrupt in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_discarded_by_interrupt() + let test =<< trim [CODE] + func I(jump) + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "return" || a:jump == "finish" + return + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + call interrupt() + let dummy = 0 + endtry + elseif loop == 2 + call assert_report('should not get here') + endif + endwhile + call assert_report('should not get here') + endfunc + + try + try + Xpath 'a' + call I("continue") + call assert_report('should not get here') + finally + try + Xpath 'b' + call I("break") + call assert_report('should not get here') + finally + try + Xpath 'c' + call I("return") + call assert_report('should not get here') + finally + try + Xpath 'd' + let g:jump = "finish" + ExecAsScript I + call assert_report('should not get here') + finally + unlet g:jump + try + Xpath 'e' + call I("error") + call assert_report('should not get here') + finally + try + Xpath 'f' + call I("interrupt") + call assert_report('should not get here') + finally + try + Xpath 'g' + call I("throw") + call assert_report('should not get here') + finally + Xpath 'h' + delfunction I + endtry + endtry + endtry + endtry + endtry + endtry + endtry + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'A' + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefghA', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 40: :finally reason discarded by :throw {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by a :throw in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_discard_by_throw() + let test =<< trim [CODE] + func T(jump) + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "return" || a:jump == "finish" + return + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + throw "xyz" " discards jump that caused the :finally + endtry + elseif loop == 2 + call assert_report('should not get here') + endif + endwhile + call assert_report('should not get here') + endfunc + + try + Xpath 'a' + call T("continue") + call assert_report('should not get here') + finally + try + Xpath 'b' + call T("break") + call assert_report('should not get here') + finally + try + Xpath 'c' + call T("return") + call assert_report('should not get here') + finally + try + Xpath 'd' + let g:jump = "finish" + ExecAsScript T + call assert_report('should not get here') + finally + unlet g:jump + try + Xpath 'e' + call T("error") + call assert_report('should not get here') + finally + try + Xpath 'f' + call T("interrupt") + call assert_report('should not get here') + finally + try + Xpath 'g' + call T("throw") + call assert_report('should not get here') + finally + Xpath 'h' + delfunction T + endtry + endtry + endtry + endtry + endtry + endtry + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefgh', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 49: Throwing exceptions across functions {{{1 +" +" When an exception is thrown but not caught inside a function, the +" caller is checked for a matching :catch clause. +"------------------------------------------------------------------------------- + +func T49_C() + try + Xpath 'a' + throw "arrgh" + call assert_report('should not get here') + catch /arrgh/ + Xpath 'b' + endtry + Xpath 'c' +endfunc + +func T49_T1() + XloopNEXT + try + Xloop 'd' + throw "arrgh" + call assert_report('should not get here') + finally + Xloop 'e' + endtry + Xloop 'f' +endfunc + +func T49_T2() + try + Xpath 'g' + call T49_T1() + call assert_report('should not get here') + finally + Xpath 'h' + endtry + call assert_report('should not get here') +endfunc + +func Test_throw_exception_across_funcs() + XpathINIT + XloopINIT + try + Xpath 'i' + call T49_C() " throw and catch + Xpath 'j' + catch /.*/ + call assert_report('should not get here') + endtry + + try + Xpath 'k' + call T49_T1() " throw, one level + call assert_report('should not get here') + catch /arrgh/ + Xpath 'l' + catch /.*/ + call assert_report('should not get here') + endtry + + try + Xpath 'm' + call T49_T2() " throw, two levels + call assert_report('should not get here') + catch /arrgh/ + Xpath 'n' + catch /.*/ + call assert_report('should not get here') + endtry + Xpath 'o' + + call assert_equal('iabcjkd2e2lmgd3e3hno', g:Xpath) +endfunc + +"------------------------------------------------------------------------------- +" Test 50: Throwing exceptions across script files {{{1 +" +" When an exception is thrown but not caught inside a script file, +" the sourcing script or function is checked for a matching :catch +" clause. +" +" This test executes the bodies of the functions C, T1, and T2 from +" the previous test as script files (:return replaced by :finish). +"------------------------------------------------------------------------------- + +func T50_F() + try + Xpath 'A' + exec "source" g:scriptC + Xpath 'B' + catch /.*/ + call assert_report('should not get here') + endtry + + try + Xpath 'C' + exec "source" g:scriptT1 + call assert_report('should not get here') + catch /arrgh/ + Xpath 'D' + catch /.*/ + call assert_report('should not get here') + endtry +endfunc + +func Test_throw_across_script() + XpathINIT + XloopINIT + let g:scriptC = MakeScript("T49_C") + let g:scriptT1 = MakeScript("T49_T1") + let scriptT2 = MakeScript("T49_T2", g:scriptT1) + + try + Xpath 'E' + call T50_F() + Xpath 'F' + exec "source" scriptT2 + call assert_report('should not get here') + catch /arrgh/ + Xpath 'G' + catch /.*/ + call assert_report('should not get here') + endtry + Xpath 'H' + call assert_equal('EAabcBCd2e2DFgd3e3hGH', g:Xpath) + + call delete(g:scriptC) + call delete(g:scriptT1) + call delete(scriptT2) + unlet g:scriptC g:scriptT1 scriptT2 +endfunc + +"------------------------------------------------------------------------------- +" Test 52: Uncaught exceptions {{{1 +" +" When an exception is thrown but not caught, an error message is +" displayed when the script is terminated. In case of an interrupt +" or error exception, the normal interrupt or error message(s) are +" displayed. +"------------------------------------------------------------------------------- + +func Test_uncaught_exception_1() + CheckEnglish + + let test =<< trim [CODE] + Xpath 'a' + throw "arrgh" + call assert_report('should not get here')` + [CODE] + let verify =<< trim [CODE] + call assert_equal('E605: Exception not caught: arrgh', v:errmsg) + call assert_equal('a', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_uncaught_exception_2() + CheckEnglish + + let test =<< trim [CODE] + try + Xpath 'a' + throw "oops" + call assert_report('should not get here')` + catch /arrgh/ + call assert_report('should not get here')` + endtry + call assert_report('should not get here')` + [CODE] + let verify =<< trim [CODE] + call assert_equal('E605: Exception not caught: oops', v:errmsg) + call assert_equal('a', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_uncaught_exception_3() + CheckEnglish + + let test =<< trim [CODE] + func T() + Xpath 'c' + throw "brrr" + call assert_report('should not get here')` + endfunc + + try + Xpath 'a' + throw "arrgh" + call assert_report('should not get here')` + catch /.*/ + Xpath 'b' + call T() + call assert_report('should not get here')` + endtry + call assert_report('should not get here')` + [CODE] + let verify =<< trim [CODE] + call assert_equal('E605: Exception not caught: brrr', v:errmsg) + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_uncaught_exception_4() + CheckEnglish + + let test =<< trim [CODE] + try + Xpath 'a' + throw "arrgh" + call assert_report('should not get here')` + finally + Xpath 'b' + throw "brrr" + call assert_report('should not get here')` + endtry + call assert_report('should not get here')` + [CODE] + let verify =<< trim [CODE] + call assert_equal('E605: Exception not caught: brrr', v:errmsg) + call assert_equal('ab', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_uncaught_exception_5() + CheckEnglish + + " Need to catch and handle interrupt, otherwise the test will wait for the + " user to press <Enter> to continue + let test =<< trim [CODE] + try + try + Xpath 'a' + call interrupt() + call assert_report('should not get here') + endtry + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'b' + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('ab', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_uncaught_exception_6() + CheckEnglish + + let test =<< trim [CODE] + try + Xpath 'a' + let x = novar " error E121; exception: E121 + catch /E15:/ " should not catch + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('a', g:Xpath) + call assert_equal('E121: Undefined variable: novar', v:errmsg) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_uncaught_exception_7() + CheckEnglish + + let test =<< trim [CODE] + try + Xpath 'a' + " error E108/E488; exception: E488 + unlet novar # + catch /E108:/ " should not catch + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('a', g:Xpath) + call assert_equal('E488: Trailing characters: #', v:errmsg) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 53: Nesting errors: :endif/:else/:elseif {{{1 +" +" For nesting errors of :if conditionals the correct error messages +" should be given. +"------------------------------------------------------------------------------- + +func Test_nested_if_else_errors() + CheckEnglish + + " :endif without :if + let code =<< trim END + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if') + + " :endif without :if + let code =<< trim END + while 1 + endif + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if') + + " :endif without :if + let code =<< trim END + try + finally + endif + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if') + + " :endif without :if + let code =<< trim END + try + endif + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if') + + " :endif without :if + let code =<< trim END + try + throw "a" + catch /a/ + endif + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if') + + " :else without :if + let code =<< trim END + else + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if') + + " :else without :if + let code =<< trim END + while 1 + else + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if') + + " :else without :if + let code =<< trim END + try + finally + else + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if') + + " :else without :if + let code =<< trim END + try + else + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if') + + " :else without :if + let code =<< trim END + try + throw "a" + catch /a/ + else + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if') + + " :elseif without :if + let code =<< trim END + elseif 1 + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if') + + " :elseif without :if + let code =<< trim END + while 1 + elseif 1 + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if') + + " :elseif without :if + let code =<< trim END + try + finally + elseif 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if') + + " :elseif without :if + let code =<< trim END + try + elseif 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if') + + " :elseif without :if + let code =<< trim END + try + throw "a" + catch /a/ + elseif 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if') + + " multiple :else + let code =<< trim END + if 1 + else + else + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(else):E583: multiple :else') + + " :elseif after :else + let code =<< trim END + if 1 + else + elseif 1 + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(elseif):E584: :elseif after :else') + + call delete('Xtest') +endfunc + +"------------------------------------------------------------------------------- +" Test 54: Nesting errors: :while/:endwhile {{{1 +" +" For nesting errors of :while conditionals the correct error messages +" should be given. +" +" This test reuses the function MESSAGES() from the previous test. +" This functions checks the messages in g:msgfile. +"------------------------------------------------------------------------------- + +func Test_nested_while_error() + CheckEnglish + + " :endwhile without :while + let code =<< trim END + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + " :endwhile without :while + let code =<< trim END + if 1 + endwhile + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + " Missing :endif + let code =<< trim END + while 1 + if 1 + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E171: Missing :endif') + + " :endwhile without :while + let code =<< trim END + try + finally + endwhile + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + " Missing :endtry + let code =<< trim END + while 1 + try + finally + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E600: Missing :endtry') + + " Missing :endtry + let code =<< trim END + while 1 + if 1 + try + finally + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E600: Missing :endtry') + + " Missing :endif + let code =<< trim END + while 1 + try + finally + if 1 + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E171: Missing :endif') + + " :endwhile without :while + let code =<< trim END + try + endwhile + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + " :endwhile without :while + let code =<< trim END + while 1 + try + endwhile + endtry + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + " :endwhile without :while + let code =<< trim END + try + throw "a" + catch /a/ + endwhile + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + " :endwhile without :while + let code =<< trim END + while 1 + try + throw "a" + catch /a/ + endwhile + endtry + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + call delete('Xtest') +endfunc + +"------------------------------------------------------------------------------- +" Test 55: Nesting errors: :continue/:break {{{1 +" +" For nesting errors of :continue and :break commands the correct +" error messages should be given. +" +" This test reuses the function MESSAGES() from the previous test. +" This functions checks the messages in g:msgfile. +"------------------------------------------------------------------------------- + +func Test_nested_cont_break_error() + CheckEnglish + + " :continue without :while + let code =<< trim END + continue + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for') + + " :continue without :while + let code =<< trim END + if 1 + continue + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for') + + " :continue without :while + let code =<< trim END + try + finally + continue + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for') + + " :continue without :while + let code =<< trim END + try + continue + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for') + + " :continue without :while + let code =<< trim END + try + throw "a" + catch /a/ + continue + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for') + + " :break without :while + let code =<< trim END + break + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for') + + " :break without :while + let code =<< trim END + if 1 + break + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for') + + " :break without :while + let code =<< trim END + try + finally + break + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for') + + " :break without :while + let code =<< trim END + try + break + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for') + + " :break without :while + let code =<< trim END + try + throw "a" + catch /a/ + break + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for') + + call delete('Xtest') +endfunc + +"------------------------------------------------------------------------------- +" Test 56: Nesting errors: :endtry {{{1 +" +" For nesting errors of :try conditionals the correct error messages +" should be given. +" +" This test reuses the function MESSAGES() from the previous test. +" This functions checks the messages in g:msgfile. +"------------------------------------------------------------------------------- + +func Test_nested_endtry_error() + CheckEnglish + + " :endtry without :try + let code =<< trim END + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E602: :endtry without :try') + + " :endtry without :try + let code =<< trim END + if 1 + endtry + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E602: :endtry without :try') + + " :endtry without :try + let code =<< trim END + while 1 + endtry + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E602: :endtry without :try') + + " Missing :endif + let code =<< trim END + try + if 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E171: Missing :endif') + + " Missing :endwhile + let code =<< trim END + try + while 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E170: Missing :endwhile') + + " Missing :endif + let code =<< trim END + try + finally + if 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E171: Missing :endif') + + " Missing :endwhile + let code =<< trim END + try + finally + while 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E170: Missing :endwhile') + + " Missing :endif + let code =<< trim END + try + throw "a" + catch /a/ + if 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E171: Missing :endif') + + " Missing :endwhile + let code =<< trim END + try + throw "a" + catch /a/ + while 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E170: Missing :endwhile') + + call delete('Xtest') +endfunc + +"------------------------------------------------------------------------------- +" Test 57: v:exception and v:throwpoint for user exceptions {{{1 +" +" v:exception evaluates to the value of the exception that was caught +" most recently and is not finished. (A caught exception is finished +" when the next ":catch", ":finally", or ":endtry" is reached.) +" v:throwpoint evaluates to the script/function name and line number +" where that exception has been thrown. +"------------------------------------------------------------------------------- + +func Test_user_exception_info() + CheckEnglish + + XpathINIT + XloopINIT + + func FuncException() + let g:exception = v:exception + endfunc + + func FuncThrowpoint() + let g:throwpoint = v:throwpoint + endfunc + + let scriptException = MakeScript("FuncException") + let scriptThrowPoint = MakeScript("FuncThrowpoint") + + command! CmdException let g:exception = v:exception + command! CmdThrowpoint let g:throwpoint = v:throwpoint + + func T(arg, line) + if a:line == 2 + throw a:arg " in line 2 + elseif a:line == 4 + throw a:arg " in line 4 + elseif a:line == 6 + throw a:arg " in line 6 + elseif a:line == 8 + throw a:arg " in line 8 + endif + endfunc + + func G(arg, line) + call T(a:arg, a:line) + endfunc + + func F(arg, line) + call G(a:arg, a:line) + endfunc + + let scriptT = MakeScript("T") + let scriptG = MakeScript("G", scriptT) + let scriptF = MakeScript("F", scriptG) + + try + Xpath 'a' + call F("oops", 2) + catch /.*/ + Xpath 'b' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + + exec "let exception = v:exception" + exec "let throwpoint = v:throwpoint" + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + + CmdException + CmdThrowpoint + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + + call FuncException() + call FuncThrowpoint() + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + + exec "source" scriptException + exec "source" scriptThrowPoint + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + + try + Xpath 'c' + call G("arrgh", 4) + catch /.*/ + Xpath 'd' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("arrgh", v:exception) + call assert_match('\<G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<4\>', v:throwpoint) + + try + Xpath 'e' + let g:arg = "autsch" + let g:line = 6 + exec "source" scriptF + catch /.*/ + Xpath 'f' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("autsch", v:exception) + call assert_match(fnamemodify(scriptT, ':t'), v:throwpoint) + call assert_match('\<6\>', v:throwpoint) + finally + Xpath 'g' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("arrgh", v:exception) + call assert_match('\<G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<4\>', v:throwpoint) + try + Xpath 'h' + let g:arg = "brrrr" + let g:line = 8 + exec "source" scriptG + catch /.*/ + Xpath 'i' + let exception = v:exception + let throwpoint = v:throwpoint + " Resolve scriptT for matching it against v:throwpoint. + call assert_equal("brrrr", v:exception) + call assert_match(fnamemodify(scriptT, ':t'), v:throwpoint) + call assert_match('\<8\>', v:throwpoint) + finally + Xpath 'j' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("arrgh", v:exception) + call assert_match('\<G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<4\>', v:throwpoint) + endtry + Xpath 'k' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("arrgh", v:exception) + call assert_match('\<G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<4\>', v:throwpoint) + endtry + Xpath 'l' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("arrgh", v:exception) + call assert_match('\<G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<4\>', v:throwpoint) + finally + Xpath 'm' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + endtry + Xpath 'n' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + finally + Xpath 'o' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("", v:exception) + call assert_match('^$', v:throwpoint) + call assert_match('^$', v:throwpoint) + endtry + + call assert_equal('abcdefghijklmno', g:Xpath) + + unlet exception throwpoint + delfunction FuncException + delfunction FuncThrowpoint + call delete(scriptException) + call delete(scriptThrowPoint) + unlet scriptException scriptThrowPoint + delcommand CmdException + delcommand CmdThrowpoint + delfunction T + delfunction G + delfunction F + call delete(scriptT) + call delete(scriptG) + call delete(scriptF) + unlet scriptT scriptG scriptF +endfunc + +"------------------------------------------------------------------------------- +" +" Test 58: v:exception and v:throwpoint for error/interrupt exceptions {{{1 +" +" v:exception and v:throwpoint work also for error and interrupt +" exceptions. +"------------------------------------------------------------------------------- + +func Test_execption_info_for_error() + CheckEnglish + + let test =<< trim [CODE] + func T(line) + if a:line == 2 + delfunction T " error (function in use) in line 2 + elseif a:line == 4 + call interrupt() + endif + endfunc + + while 1 + try + Xpath 'a' + call T(2) + call assert_report('should not get here') + catch /.*/ + Xpath 'b' + if v:exception !~ 'Vim(delfunction):' + call assert_report('should not get here') + endif + if v:throwpoint !~ '\<T\>' + call assert_report('should not get here') + endif + if v:throwpoint !~ '\<2\>' + call assert_report('should not get here') + endif + finally + Xpath 'c' + if v:exception != "" + call assert_report('should not get here') + endif + if v:throwpoint != "" + call assert_report('should not get here') + endif + break + endtry + endwhile + + Xpath 'd' + if v:exception != "" + call assert_report('should not get here') + endif + if v:throwpoint != "" + call assert_report('should not get here') + endif + + while 1 + try + Xpath 'e' + call T(4) + call assert_report('should not get here') + catch /.*/ + Xpath 'f' + if v:exception != 'Vim:Interrupt' + call assert_report('should not get here') + endif + if v:throwpoint !~ 'function T' + call assert_report('should not get here') + endif + if v:throwpoint !~ '\<4\>' + call assert_report('should not get here') + endif + finally + Xpath 'g' + if v:exception != "" + call assert_report('should not get here') + endif + if v:throwpoint != "" + call assert_report('should not get here') + endif + break + endtry + endwhile + + Xpath 'h' + if v:exception != "" + call assert_report('should not get here') + endif + if v:throwpoint != "" + call assert_report('should not get here') + endif + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefgh', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" +" Test 59: v:exception and v:throwpoint when discarding exceptions {{{1 +" +" When a :catch clause is left by a ":break" etc or an error or +" interrupt exception, v:exception and v:throwpoint are reset. They +" are not affected by an exception that is discarded before being +" caught. +"------------------------------------------------------------------------------- +func Test_exception_info_on_discard() + CheckEnglish + + let test =<< trim [CODE] + let sfile = expand("<sfile>") + + while 1 + try + throw "x1" + catch /.*/ + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + while 1 + try + throw "x2" + catch /.*/ + break + finally + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + endtry + break + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + while 1 + try + let errcaught = 0 + try + try + throw "x3" + catch /.*/ + let lnum = expand("<sflnum>") + asdf + endtry + catch /.*/ + let errcaught = 1 + call assert_match('Vim:E492: Not an editor command:', v:exception) + call assert_match('line ' .. (lnum + 1), v:throwpoint) + endtry + finally + call assert_equal(1, errcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'a' + + while 1 + try + let intcaught = 0 + try + try + throw "x4" + catch /.*/ + let lnum = expand("<sflnum>") + call interrupt() + endtry + catch /.*/ + let intcaught = 1 + call assert_match('Vim:Interrupt', v:exception) + call assert_match('line ' .. (lnum + 1), v:throwpoint) + endtry + finally + call assert_equal(1, intcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'b' + + while 1 + try + let errcaught = 0 + try + try + if 1 + let lnum = expand("<sflnum>") + throw "x5" + " missing endif + catch /.*/ + call assert_report('should not get here') + endtry + catch /.*/ + let errcaught = 1 + call assert_match('Vim(catch):E171: Missing :endif:', v:exception) + call assert_match('line ' .. (lnum + 3), v:throwpoint) + endtry + finally + call assert_equal(1, errcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'c' + + try + while 1 + try + throw "x6" + finally + break + endtry + break + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + try + while 1 + try + throw "x7" + finally + break + endtry + break + endwhile + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + endtry + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + while 1 + try + let errcaught = 0 + try + try + throw "x8" + finally + let lnum = expand("<sflnum>") + asdf + endtry + catch /.*/ + let errcaught = 1 + call assert_match('Vim:E492: Not an editor command:', v:exception) + call assert_match('line ' .. (lnum + 1), v:throwpoint) + endtry + finally + call assert_equal(1, errcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'd' + + while 1 + try + let intcaught = 0 + try + try + throw "x9" + finally + let lnum = expand("<sflnum>") + call interrupt() + endtry + catch /.*/ + let intcaught = 1 + call assert_match('Vim:Interrupt', v:exception) + call assert_match('line ' .. (lnum + 1), v:throwpoint) + endtry + finally + call assert_equal(1, intcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'e' + + while 1 + try + let errcaught = 0 + try + try + if 1 + let lnum = expand("<sflnum>") + throw "x10" + " missing endif + finally + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + endtry + catch /.*/ + let errcaught = 1 + call assert_match('Vim(finally):E171: Missing :endif:', v:exception) + call assert_match('line ' .. (lnum + 3), v:throwpoint) + endtry + finally + call assert_equal(1, errcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'f' + + while 1 + try + let errcaught = 0 + try + try + if 1 + let lnum = expand("<sflnum>") + throw "x11" + " missing endif + endtry + catch /.*/ + let errcaught = 1 + call assert_match('Vim(endtry):E171: Missing :endif:', v:exception) + call assert_match('line ' .. (lnum + 3), v:throwpoint) + endtry + finally + call assert_equal(1, errcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'g' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefg', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" +" Test 60: (Re)throwing v:exception; :echoerr. {{{1 +" +" A user exception can be rethrown after catching by throwing +" v:exception. An error or interrupt exception cannot be rethrown +" because Vim exceptions cannot be faked. A Vim exception using the +" value of v:exception can, however, be triggered by the :echoerr +" command. +"------------------------------------------------------------------------------- + +func Test_rethrow_exception_1() + XpathINIT + try + try + Xpath 'a' + throw "oops" + catch /oops/ + Xpath 'b' + throw v:exception " rethrow user exception + catch /.*/ + call assert_report('should not get here') + endtry + catch /^oops$/ " catches rethrown user exception + Xpath 'c' + catch /.*/ + call assert_report('should not get here') + endtry + call assert_equal('abc', g:Xpath) +endfunc + +func Test_rethrow_exception_2() + XpathINIT + try + let caught = 0 + try + Xpath 'a' + write /n/o/n/w/r/i/t/a/b/l/e/_/f/i/l/e + call assert_report('should not get here') + catch /^Vim(write):/ + let caught = 1 + throw v:exception " throw error: cannot fake Vim exception + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'b' + call assert_equal(1, caught) + endtry + catch /^Vim(throw):/ " catches throw error + let caught = caught + 1 + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + call assert_equal(2, caught) + endtry + call assert_equal('abc', g:Xpath) +endfunc + +func Test_rethrow_exception_3() + XpathINIT + try + let caught = 0 + try + Xpath 'a' + asdf + catch /^Vim/ " catch error exception + let caught = 1 + " Trigger Vim error exception with value specified after :echoerr + let value = substitute(v:exception, '^Vim\((.*)\)\=:', '', "") + echoerr value + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'b' + call assert_equal(1, caught) + endtry + catch /^Vim(echoerr):/ + let caught = caught + 1 + call assert_match(value, v:exception) + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + call assert_equal(2, caught) + endtry + call assert_equal('abc', g:Xpath) +endfunc + +func Test_rethrow_exception_3() + XpathINIT + try + let errcaught = 0 + try + Xpath 'a' + let intcaught = 0 + call interrupt() + catch /^Vim:/ " catch interrupt exception + let intcaught = 1 + " Trigger Vim error exception with value specified after :echoerr + echoerr substitute(v:exception, '^Vim\((.*)\)\=:', '', "") + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'b' + call assert_equal(1, intcaught) + endtry + catch /^Vim(echoerr):/ + let errcaught = 1 + call assert_match('Interrupt', v:exception) + finally + Xpath 'c' + call assert_equal(1, errcaught) + endtry + call assert_equal('abc', g:Xpath) +endfunc + +"------------------------------------------------------------------------------- +" Test 61: Catching interrupt exceptions {{{1 +" +" When an interrupt occurs inside a :try/:endtry region, an +" interrupt exception is thrown and can be caught. Its value is +" "Vim:Interrupt". If the interrupt occurs after an error or a :throw +" but before a matching :catch is reached, all following :catches of +" that try block are ignored, but the interrupt exception can be +" caught by the next surrounding try conditional. An interrupt is +" ignored when there is a previous interrupt that has not been caught +" or causes a :finally clause to be executed. "------------------------------------------------------------------------------- + +func Test_catch_intr_exception() + let test =<< trim [CODE] + while 1 + try + try + Xpath 'a' + call interrupt() + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'b' + finally + Xpath 'c' + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'd' + break + endtry + endwhile + + while 1 + try + try + try + Xpath 'e' + asdf + call assert_report('should not get here') + catch /do_not_catch/ + call assert_report('should not get here') + catch /.*/ + Xpath 'f' + call interrupt() + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'g' + call interrupt() + call assert_report('should not get here') + endtry + catch /^Vim:Interrupt$/ + Xpath 'h' + finally + Xpath 'i' + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'j' + break + endtry + endwhile + + while 1 + try + try + try + Xpath 'k' + throw "x" + call assert_report('should not get here') + catch /do_not_catch/ + call assert_report('should not get here') + catch /x/ + Xpath 'l' + call interrupt() + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + endtry + catch /^Vim:Interrupt$/ + Xpath 'm' + finally + Xpath 'n' + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'o' + break + endtry + endwhile + + while 1 + try + try + Xpath 'p' + call interrupt() + call assert_report('should not get here') + catch /do_not_catch/ + call interrupt() + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'q' + finally + Xpath 'r' + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 's' + break + endtry + endwhile + + Xpath 't' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefghijklmnopqrst', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 62: Catching error exceptions {{{1 +" +" An error inside a :try/:endtry region is converted to an exception +" and can be caught. The error exception has a "Vim(cmdname):" prefix +" where cmdname is the name of the failing command, or a "Vim:" prefix +" if no command name is known. The "Vim" prefixes cannot be faked. +"------------------------------------------------------------------------------- + +func Test_catch_err_exception_1() + XpathINIT + while 1 + try + try + let caught = 0 + unlet novar + catch /^Vim(unlet):/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim(unlet):', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match('E108: No such variable: "novar"', v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abc', g:Xpath) +endfunc + +func Test_catch_err_exception_2() + XpathINIT + while 1 + try + try + let caught = 0 + throw novar " error in :throw + catch /^Vim(throw):/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim(throw):', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match('E121: Undefined variable: novar', v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abc', g:Xpath) +endfunc + +func Test_catch_err_exception_3() + XpathINIT + while 1 + try + try + let caught = 0 + throw "Vim:faked" " error: cannot fake Vim exception + catch /^Vim(throw):/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim(throw):', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match("E608: Cannot :throw exceptions with 'Vim' prefix", + \ v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abc', g:Xpath) +endfunc + +func Test_catch_err_exception_4() + XpathINIT + func F() + while 1 + " Missing :endwhile + endfunc + + while 1 + try + try + let caught = 0 + call F() + catch /^Vim(endfunction):/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim(endfunction):', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match("E170: Missing :endwhile", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abc', g:Xpath) + delfunc F +endfunc + +func Test_catch_err_exception_5() + XpathINIT + func F() + while 1 + " Missing :endwhile + endfunc + + while 1 + try + try + let caught = 0 + ExecAsScript F + catch /^Vim:/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim:', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match("E170: Missing :endwhile", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abc', g:Xpath) + delfunc F +endfunc + +func Test_catch_err_exception_6() + XpathINIT + func G() + call G() + endfunc + + while 1 + try + let mfd_save = &mfd + set mfd=3 + try + let caught = 0 + call G() + catch /^Vim(call):/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim(call):', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match("E132: Function call depth is higher than 'maxfuncdepth'", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + let &mfd = mfd_save + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abc', g:Xpath) + delfunc G +endfunc + +func Test_catch_err_exception_7() + XpathINIT + func H() + return H() + endfunc + + while 1 + try + let mfd_save = &mfd + set mfd=3 + try + let caught = 0 + call H() + catch /^Vim(return):/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim(return):', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match("E132: Function call depth is higher than 'maxfuncdepth'", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + let &mfd = mfd_save + break " discard error for $VIMNOERRTHROW + endtry + call assert_report('should not get here') + endwhile + + call assert_equal('abc', g:Xpath) + delfunc H +endfunc + +"------------------------------------------------------------------------------- +" Test 63: Suppressing error exceptions by :silent!. {{{1 +" +" A :silent! command inside a :try/:endtry region suppresses the +" conversion of errors to an exception and the immediate abortion on +" error. When the commands executed by the :silent! themselves open +" a new :try/:endtry region, conversion of errors to exception and +" immediate abortion is switched on again - until the next :silent! +" etc. The :silent! has the effect of setting v:errmsg to the error +" message text (without displaying it) and continuing with the next +" script line. +" +" When a command triggering autocommands is executed by :silent! +" inside a :try/:endtry, the autocommand execution is not suppressed +" on error. +" +" This test reuses the function MSG() from the previous test. +"------------------------------------------------------------------------------- + +func Test_silent_exception() + XpathINIT + XloopINIT + let g:taken = "" + + func S(n) abort + XloopNEXT + let g:taken = g:taken . "E" . a:n + let v:errmsg = "" + exec "asdf" . a:n + + " Check that ":silent!" continues: + Xloop 'a' + + " Check that ":silent!" sets "v:errmsg": + call assert_match("E492: Not an editor command", v:errmsg) + endfunc + + func Foo() + while 1 + try + try + let caught = 0 + " This is not silent: + call S(3) + catch /^Vim:/ + Xpath 'b' + let caught = 1 + let errmsg3 = substitute(v:exception, '^Vim:', '', "") + silent! call S(4) + finally + call assert_equal(1, caught) + Xpath 'c' + call assert_match("E492: Not an editor command", errmsg3) + silent! call S(5) + " Break out of try conditionals that cover ":silent!". This also + " discards the aborting error when $VIMNOERRTHROW is non-zero. + break + endtry + catch /.*/ + call assert_report('should not get here') + endtry + endwhile + " This is a double ":silent!" (see caller). + silent! call S(6) + endfunc + + func Bar() + try + silent! call S(2) + silent! execute "call Foo() | call S(7)" + silent! call S(8) + endtry " normal end of try cond that covers ":silent!" + " This has a ":silent!" from the caller: + call S(9) + endfunc + + silent! call S(1) + silent! call Bar() + silent! call S(10) + + call assert_equal("E1E2E3E4E5E6E7E8E9E10", g:taken) + + augroup TMP + au! + autocmd BufWritePost * Xpath 'd' + augroup END + + Xpath 'e' + silent! write /i/m/p/o/s/s/i/b/l/e + Xpath 'f' + + call assert_equal('a2a3ba5ca6a7a8a9a10a11edf', g:Xpath) + + augroup TMP + au! + augroup END + augroup! TMP + delfunction S + delfunction Foo + delfunction Bar +endfunc + +"------------------------------------------------------------------------------- +" Test 64: Error exceptions after error, interrupt or :throw {{{1 +" +" When an error occurs after an interrupt or a :throw but before +" a matching :catch is reached, all following :catches of that try +" block are ignored, but the error exception can be caught by the next +" surrounding try conditional. Any previous error exception is +" discarded. An error is ignored when there is a previous error that +" has not been caught. +"------------------------------------------------------------------------------- + +func Test_exception_after_error_1() + XpathINIT + while 1 + try + try + Xpath 'a' + let caught = 0 + while 1 + if 1 + " Missing :endif + endwhile " throw error exception + catch /^Vim(/ + Xpath 'b' + let caught = 1 + finally + Xpath 'c' + call assert_equal(1, caught) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'd' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abcd', g:Xpath) +endfunc + +func Test_exception_after_error_2() + XpathINIT + while 1 + try + try + Xpath 'a' + let caught = 0 + try + if 1 + " Missing :endif + catch /.*/ " throw error exception + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + endtry + catch /^Vim(/ + Xpath 'b' + let caught = 1 + finally + Xpath 'c' + call assert_equal(1, caught) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'd' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abcd', g:Xpath) +endfunc + +func Test_exception_after_error_3() + XpathINIT + while 1 + try + try + let caught = 0 + try + Xpath 'a' + call interrupt() + catch /do_not_catch/ + call assert_report('should not get here') + if 1 + " Missing :endif + catch /.*/ " throw error exception + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + endtry + catch /^Vim(/ + Xpath 'b' + let caught = 1 + finally + Xpath 'c' + call assert_equal(1, caught) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'd' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abcd', g:Xpath) +endfunc + +func Test_exception_after_error_4() + XpathINIT + while 1 + try + try + let caught = 0 + try + Xpath 'a' + throw "x" + catch /do_not_catch/ + call assert_report('should not get here') + if 1 + " Missing :endif + catch /x/ " throw error exception + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + endtry + catch /^Vim(/ + Xpath 'b' + let caught = 1 + finally + Xpath 'c' + call assert_equal(1, caught) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'd' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abcd', g:Xpath) +endfunc + +func Test_exception_after_error_5() + XpathINIT + while 1 + try + try + let caught = 0 + Xpath 'a' + endif " :endif without :if; throw error exception + if 1 + " Missing :endif + catch /do_not_catch/ " ignore new error + call assert_report('should not get here') + catch /^Vim(endif):/ + Xpath 'b' + let caught = 1 + catch /^Vim(/ + call assert_report('should not get here') + finally + Xpath 'c' + call assert_equal(1, caught) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'd' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abcd', g:Xpath) +endfunc + +"------------------------------------------------------------------------------- +" Test 65: Errors in the /pattern/ argument of a :catch {{{1 +" +" On an error in the /pattern/ argument of a :catch, the :catch does +" not match. Any following :catches of the same :try/:endtry don't +" match either. Finally clauses are executed. +"------------------------------------------------------------------------------- + +func Test_catch_pattern_error() + CheckEnglish + XpathINIT + + try + try + Xpath 'a' + throw "oops" + catch /^oops$/ + Xpath 'b' + catch /\)/ " not checked; exception has already been caught + call assert_report('should not get here') + endtry + Xpath 'c' + catch /.*/ + call assert_report('should not get here') + endtry + call assert_equal('abc', g:Xpath) + + XpathINIT + func F() + try + try + try + Xpath 'a' + throw "ab" + catch /abc/ " does not catch + call assert_report('should not get here') + catch /\)/ " error; discards exception + call assert_report('should not get here') + catch /.*/ " not checked + call assert_report('should not get here') + finally + Xpath 'b' + endtry + call assert_report('should not get here') + catch /^ab$/ " checked, but original exception is discarded + call assert_report('should not get here') + catch /^Vim(catch):/ + Xpath 'c' + call assert_match('Vim(catch):E475: Invalid argument:', v:exception) + finally + Xpath 'd' + endtry + Xpath 'e' + catch /.*/ + call assert_report('should not get here') + endtry + Xpath 'f' + endfunc + + call F() + call assert_equal('abcdef', g:Xpath) + + delfunc F +endfunc + +"------------------------------------------------------------------------------- +" Test 66: Stop range :call on error, interrupt, or :throw {{{1 +" +" When a function which is multiply called for a range since it +" doesn't handle the range itself has an error in a command +" dynamically enclosed by :try/:endtry or gets an interrupt or +" executes a :throw, no more calls for the remaining lines in the +" range are made. On an error in a command not dynamically enclosed +" by :try/:endtry, the function is executed again for the remaining +" lines in the range. +"------------------------------------------------------------------------------- + +func Test_stop_range_on_error() + let test =<< trim [CODE] + let file = tempname() + exec "edit" file + call setline(1, ['line 1', 'line 2', 'line 3']) + let taken = "" + let expected = "G1EF1E(1)F1E(2)F1E(3)G2EF2E(1)G3IF3I(1)G4TF4T(1)G5AF5A(1)" + + func F(reason, n) abort + let g:taken = g:taken .. "F" .. a:n .. + \ substitute(a:reason, '\(\l\).*', '\u\1', "") .. + \ "(" .. line(".") .. ")" + + if a:reason == "error" + asdf + elseif a:reason == "interrupt" + call interrupt() + elseif a:reason == "throw" + throw "xyz" + elseif a:reason == "aborting error" + XloopNEXT + call assert_equal(g:taken, g:expected) + try + bwipeout! + call delete(g:file) + asdf + endtry + endif + endfunc + + func G(reason, n) + let g:taken = g:taken .. "G" .. a:n .. + \ substitute(a:reason, '\(\l\).*', '\u\1', "") + 1,3call F(a:reason, a:n) + endfunc + + Xpath 'a' + call G("error", 1) + try + Xpath 'b' + try + call G("error", 2) + call assert_report('should not get here') + finally + Xpath 'c' + try + call G("interrupt", 3) + call assert_report('should not get here') + finally + Xpath 'd' + try + call G("throw", 4) + call assert_report('should not get here') + endtry + endtry + endtry + catch /xyz/ + Xpath 'e' + catch /.*/ + call assert_report('should not get here') + endtry + Xpath 'f' + call G("aborting error", 5) + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdef', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 67: :throw across :call command {{{1 +" +" On a call command, an exception might be thrown when evaluating the +" function name, during evaluation of the arguments, or when the +" function is being executed. The exception can be caught by the +" caller. +"------------------------------------------------------------------------------- + +func THROW(x, n) + if a:n == 1 + Xpath 'A' + elseif a:n == 2 + Xpath 'B' + elseif a:n == 3 + Xpath 'C' + endif + throw a:x +endfunc + +func NAME(x, n) + if a:n == 1 + call assert_report('should not get here') + elseif a:n == 2 + Xpath 'D' + elseif a:n == 3 + Xpath 'E' + elseif a:n == 4 + Xpath 'F' + endif + return a:x +endfunc + +func ARG(x, n) + if a:n == 1 + call assert_report('should not get here') + elseif a:n == 2 + call assert_report('should not get here') + elseif a:n == 3 + Xpath 'G' + elseif a:n == 4 + Xpath 'I' + endif + return a:x +endfunc + +func Test_throw_across_call_cmd() + XpathINIT + + func F(x, n) + if a:n == 2 + call assert_report('should not get here') + elseif a:n == 4 + Xpath 'a' + endif + endfunc + + while 1 + try + let v:errmsg = "" + + while 1 + try + Xpath 'b' + call {NAME(THROW("name", 1), 1)}(ARG(4711, 1), 1) + call assert_report('should not get here') + catch /^name$/ + Xpath 'c' + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + + while 1 + try + Xpath 'd' + call {NAME("F", 2)}(ARG(THROW("arg", 2), 2), 2) + call assert_report('should not get here') + catch /^arg$/ + Xpath 'e' + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + + while 1 + try + Xpath 'f' + call {NAME("THROW", 3)}(ARG("call", 3), 3) + call assert_report('should not get here') + catch /^call$/ + Xpath 'g' + catch /^0$/ " default return value + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + + while 1 + try + Xpath 'h' + call {NAME("F", 4)}(ARG(4711, 4), 4) + Xpath 'i' + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + + catch /^0$/ " default return value + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + + call assert_equal('bAcdDBefEGCghFIai', g:Xpath) + delfunction F +endfunc + +"------------------------------------------------------------------------------- +" Test 68: :throw across function calls in expressions {{{1 +" +" On a function call within an expression, an exception might be +" thrown when evaluating the function name, during evaluation of the +" arguments, or when the function is being executed. The exception +" can be caught by the caller. +" +" This test reuses the functions THROW(), NAME(), and ARG() from the +" previous test. +"------------------------------------------------------------------------------- + +func Test_throw_across_call_expr() + XpathINIT + + func F(x, n) + if a:n == 2 + call assert_report('should not get here') + elseif a:n == 4 + Xpath 'a' + endif + return a:x + endfunction + + while 1 + try + let error = 0 + let v:errmsg = "" + + while 1 + try + Xpath 'b' + let var1 = {NAME(THROW("name", 1), 1)}(ARG(4711, 1), 1) + call assert_report('should not get here') + catch /^name$/ + Xpath 'c' + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + call assert_true(!exists('var1')) + + while 1 + try + Xpath 'd' + let var2 = {NAME("F", 2)}(ARG(THROW("arg", 2), 2), 2) + call assert_report('should not get here') + catch /^arg$/ + Xpath 'e' + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + call assert_true(!exists('var2')) + + while 1 + try + Xpath 'f' + let var3 = {NAME("THROW", 3)}(ARG("call", 3), 3) + call assert_report('should not get here') + catch /^call$/ + Xpath 'g' + catch /^0$/ " default return value + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + call assert_true(!exists('var3')) + + while 1 + try + Xpath 'h' + let var4 = {NAME("F", 4)}(ARG(4711, 4), 4) + Xpath 'i' + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + call assert_true(exists('var4') && var4 == 4711) + + catch /^0$/ " default return value + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + break + endtry + endwhile + + call assert_equal('bAcdDBefEGCghFIai', g:Xpath) + delfunc F +endfunc + +"------------------------------------------------------------------------------- +" Test 76: Errors, interrupts, :throw during expression evaluation {{{1 +" +" When a function call made during expression evaluation is aborted +" due to an error inside a :try/:endtry region or due to an interrupt +" or a :throw, the expression evaluation is aborted as well. No +" message is displayed for the cancelled expression evaluation. On an +" error not inside :try/:endtry, the expression evaluation continues. +"------------------------------------------------------------------------------- + +func Test_expr_eval_error() + let test =<< trim [CODE] + let taken = "" + + func ERR(n) + let g:taken = g:taken .. "E" .. a:n + asdf + endfunc + + func ERRabort(n) abort + let g:taken = g:taken .. "A" .. a:n + asdf + endfunc " returns -1; may cause follow-up msg for illegal var/func name + + func WRAP(n, arg) + let g:taken = g:taken .. "W" .. a:n + let g:saved_errmsg = v:errmsg + return arg + endfunc + + func INT(n) + let g:taken = g:taken .. "I" .. a:n + call interrupt() + endfunc + + func THR(n) + let g:taken = g:taken .. "T" .. a:n + throw "should not be caught" + endfunc + + func CONT(n) + let g:taken = g:taken .. "C" .. a:n + endfunc + + func MSG(n) + let g:taken = g:taken .. "M" .. a:n + let errmsg = (a:n >= 37 && a:n <= 44) ? g:saved_errmsg : v:errmsg + let msgptn = (a:n >= 10 && a:n <= 27) ? "^$" : "asdf" + call assert_match(msgptn, errmsg) + let v:errmsg = "" + let g:saved_errmsg = "" + endfunc + + let v:errmsg = "" + + try + let t = 1 + while t <= 9 + Xloop 'a' + try + if t == 1 + let v{ERR(t) + CONT(t)} = 0 + elseif t == 2 + let v{ERR(t) + CONT(t)} + elseif t == 3 + let var = exists('v{ERR(t) + CONT(t)}') + elseif t == 4 + unlet v{ERR(t) + CONT(t)} + elseif t == 5 + function F{ERR(t) + CONT(t)}() + endfunction + elseif t == 6 + function F{ERR(t) + CONT(t)} + elseif t == 7 + let var = exists('*F{ERR(t) + CONT(t)}') + elseif t == 8 + delfunction F{ERR(t) + CONT(t)} + elseif t == 9 + let var = ERR(t) + CONT(t) + endif + catch /asdf/ + " v:errmsg is not set when the error message is converted to an + " exception. Set it to the original error message. + let v:errmsg = substitute(v:exception, '^Vim:', '', "") + catch /^Vim\((\a\+)\)\=:/ + " An error exception has been thrown after the original error. + let v:errmsg = "" + finally + call MSG(t) + let t = t + 1 + XloopNEXT + continue " discard an aborting error + endtry + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + + try + let t = 10 + while t <= 18 + Xloop 'b' + try + if t == 10 + let v{INT(t) + CONT(t)} = 0 + elseif t == 11 + let v{INT(t) + CONT(t)} + elseif t == 12 + let var = exists('v{INT(t) + CONT(t)}') + elseif t == 13 + unlet v{INT(t) + CONT(t)} + elseif t == 14 + function F{INT(t) + CONT(t)}() + endfunction + elseif t == 15 + function F{INT(t) + CONT(t)} + elseif t == 16 + let var = exists('*F{INT(t) + CONT(t)}') + elseif t == 17 + delfunction F{INT(t) + CONT(t)} + elseif t == 18 + let var = INT(t) + CONT(t) + endif + catch /^Vim\((\a\+)\)\=:\(Interrupt\)\@!/ + " An error exception has been triggered after the interrupt. + let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") + finally + call MSG(t) + let t = t + 1 + XloopNEXT + continue " discard interrupt + endtry + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + + try + let t = 19 + while t <= 27 + Xloop 'c' + try + if t == 19 + let v{THR(t) + CONT(t)} = 0 + elseif t == 20 + let v{THR(t) + CONT(t)} + elseif t == 21 + let var = exists('v{THR(t) + CONT(t)}') + elseif t == 22 + unlet v{THR(t) + CONT(t)} + elseif t == 23 + function F{THR(t) + CONT(t)}() + endfunction + elseif t == 24 + function F{THR(t) + CONT(t)} + elseif t == 25 + let var = exists('*F{THR(t) + CONT(t)}') + elseif t == 26 + delfunction F{THR(t) + CONT(t)} + elseif t == 27 + let var = THR(t) + CONT(t) + endif + catch /^Vim\((\a\+)\)\=:/ + " An error exception has been triggered after the :throw. + let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") + finally + call MSG(t) + let t = t + 1 + XloopNEXT + continue " discard exception + endtry + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + + let v{ERR(28) + CONT(28)} = 0 + call MSG(28) + let v{ERR(29) + CONT(29)} + call MSG(29) + let var = exists('v{ERR(30) + CONT(30)}') + call MSG(30) + unlet v{ERR(31) + CONT(31)} + call MSG(31) + function F{ERR(32) + CONT(32)}() + endfunction + call MSG(32) + function F{ERR(33) + CONT(33)} + call MSG(33) + let var = exists('*F{ERR(34) + CONT(34)}') + call MSG(34) + delfunction F{ERR(35) + CONT(35)} + call MSG(35) + let var = ERR(36) + CONT(36) + call MSG(36) + + let saved_errmsg = "" + + let v{WRAP(37, ERRabort(37)) + CONT(37)} = 0 + call MSG(37) + let v{WRAP(38, ERRabort(38)) + CONT(38)} + call MSG(38) + let var = exists('v{WRAP(39, ERRabort(39)) + CONT(39)}') + call MSG(39) + unlet v{WRAP(40, ERRabort(40)) + CONT(40)} + call MSG(40) + function F{WRAP(41, ERRabort(41)) + CONT(41)}() + endfunction + call MSG(41) + function F{WRAP(42, ERRabort(42)) + CONT(42)} + call MSG(42) + let var = exists('*F{WRAP(43, ERRabort(43)) + CONT(43)}') + call MSG(43) + delfunction F{WRAP(44, ERRabort(44)) + CONT(44)} + call MSG(44) + let var = ERRabort(45) + CONT(45) + call MSG(45) + Xpath 'd' + + let expected = "" + \ .. "E1M1E2M2E3M3E4M4E5M5E6M6E7M7E8M8E9M9" + \ .. "I10M10I11M11I12M12I13M13I14M14I15M15I16M16I17M17I18M18" + \ .. "T19M19T20M20T21M21T22M22T23M23T24M24T25M25T26M26T27M27" + \ .. "E28C28M28E29C29M29E30C30M30E31C31M31E32C32M32E33C33M33" + \ .. "E34C34M34E35C35M35E36C36M36" + \ .. "A37W37C37M37A38W38C38M38A39W39C39M39A40W40C40M40A41W41C41M41" + \ .. "A42W42C42M42A43W43C43M43A44W44C44M44A45C45M45" + call assert_equal(expected, taken) + [CODE] + let verify =<< trim [CODE] + let expected = "a1a2a3a4a5a6a7a8a9" + \ .. "b10b11b12b13b14b15b16b17b18" + \ .. "c19c20c21c22c23c24c25c26c27d" + call assert_equal(expected, g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 77: Errors, interrupts, :throw in name{brace-expression} {{{1 +" +" When a function call made during evaluation of an expression in +" braces as part of a function name after ":function" is aborted due +" to an error inside a :try/:endtry region or due to an interrupt or +" a :throw, the expression evaluation is aborted as well, and the +" function definition is ignored, skipping all commands to the +" ":endfunction". On an error not inside :try/:endtry, the expression +" evaluation continues and the function gets defined, and can be +" called and deleted. +"------------------------------------------------------------------------------- +func Test_brace_expr_error() + let test =<< trim [CODE] + func ERR() abort + Xloop 'a' + asdf + endfunc " returns -1 + + func OK() + Xloop 'b' + let v:errmsg = "" + return 0 + endfunc + + let v:errmsg = "" + + Xpath 'c' + func F{1 + ERR() + OK()}(arg) + " F0 should be defined. + if exists("a:arg") && a:arg == "calling" + Xpath 'd' + else + call assert_report('should not get here') + endif + endfunction + call assert_equal("", v:errmsg) + XloopNEXT + + Xpath 'e' + call F{1 + ERR() + OK()}("calling") + call assert_equal("", v:errmsg) + XloopNEXT + + Xpath 'f' + delfunction F{1 + ERR() + OK()} + call assert_equal("", v:errmsg) + XloopNEXT + + try + while 1 + try + Xpath 'g' + func G{1 + ERR() + OK()}(arg) + " G0 should not be defined, and the function body should be + " skipped. + call assert_report('should not get here') + " Use an unmatched ":finally" to check whether the body is + " skipped when an error occurs in ERR(). This works whether or + " not the exception is converted to an exception. + finally + call assert_report('should not get here') + endtry + try + call assert_report('should not get here') + endfunction + + call assert_report('should not get here') + catch /asdf/ + " Jumped to when the function is not defined and the body is + " skipped. + Xpath 'h' + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'i' + break + endtry " jumped to when the body is not skipped + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('ca1b1ea2b2dfa3b3ga4hi', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 78: Messages on parsing errors in expression evaluation {{{1 +" +" When an expression evaluation detects a parsing error, an error +" message is given and converted to an exception, and the expression +" evaluation is aborted. +"------------------------------------------------------------------------------- +func Test_expr_eval_error_msg() + CheckEnglish + + let test =<< trim [CODE] + let taken = "" + + func F(n) + let g:taken = g:taken . "F" . a:n + endfunc + + func MSG(n, enr, emsg) + let g:taken = g:taken . "M" . a:n + call assert_match('^' .. a:enr .. ':', v:errmsg) + call assert_match(a:emsg, v:errmsg) + endfunc + + func CONT(n) + let g:taken = g:taken . "C" . a:n + endfunc + + let v:errmsg = "" + try + let t = 1 + while t <= 14 + let g:taken = g:taken . "T" . t + let v:errmsg = "" + try + if t == 1 + let v{novar + CONT(t)} = 0 + elseif t == 2 + let v{novar + CONT(t)} + elseif t == 3 + let var = exists('v{novar + CONT(t)}') + elseif t == 4 + unlet v{novar + CONT(t)} + elseif t == 5 + function F{novar + CONT(t)}() + endfunction + elseif t == 6 + function F{novar + CONT(t)} + elseif t == 7 + let var = exists('*F{novar + CONT(t)}') + elseif t == 8 + delfunction F{novar + CONT(t)} + elseif t == 9 + echo novar + CONT(t) + elseif t == 10 + echo v{novar + CONT(t)} + elseif t == 11 + echo F{novar + CONT(t)} + elseif t == 12 + let var = novar + CONT(t) + elseif t == 13 + let var = v{novar + CONT(t)} + elseif t == 14 + let var = F{novar + CONT(t)}() + endif + catch /^Vim\((\a\+)\)\=:/ + Xloop 'a' + " v:errmsg is not set when the error message is converted to an + " exception. Set it to the original error message. + let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") + finally + Xloop 'b' + if t <= 8 && t != 3 && t != 7 + call MSG(t, 'E475', 'Invalid argument\>') + else + call MSG(t, 'E121', "Undefined variable") + endif + let t = t + 1 + XloopNEXT + continue " discard an aborting error + endtry + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + + func T(n, expr, enr, emsg) + try + let g:taken = g:taken . "T" . a:n + let v:errmsg = "" + try + execute "let var = " . a:expr + catch /^Vim\((\a\+)\)\=:/ + Xloop 'c' + " v:errmsg is not set when the error message is converted to an + " exception. Set it to the original error message. + let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") + finally + Xloop 'd' + call MSG(a:n, a:enr, a:emsg) + XloopNEXT + " Discard an aborting error: + return + endtry + catch /.*/ + call assert_report('should not get here') + endtry + endfunc + + call T(15, 'Nofunc() + CONT(15)', 'E117', "Unknown function") + call T(16, 'F(1 2 + CONT(16))', 'E116', "Invalid arguments") + call T(17, 'F(1, 2) + CONT(17)', 'E118', "Too many arguments") + call T(18, 'F() + CONT(18)', 'E119', "Not enough arguments") + call T(19, '{(1} + CONT(19)', 'E110', "Missing ')'") + call T(20, '("abc"[1) + CONT(20)', 'E111', "Missing ']'") + call T(21, '(1 +) + CONT(21)', 'E15', "Invalid expression") + call T(22, '1 2 + CONT(22)', 'E488', "Trailing characters: 2 +") + call T(23, '(1 ? 2) + CONT(23)', 'E109', "Missing ':' after '?'") + call T(24, '("abc) + CONT(24)', 'E114', "Missing quote") + call T(25, "('abc) + CONT(25)", 'E115', "Missing quote") + call T(26, '& + CONT(26)', 'E112', "Option name missing") + call T(27, '&asdf + CONT(27)', 'E113', "Unknown option") + + let expected = "" + \ .. "T1M1T2M2T3M3T4M4T5M5T6M6T7M7T8M8T9M9T10M10T11M11T12M12T13M13T14M14" + \ .. "T15M15T16M16T17M17T18M18T19M19T20M20T21M21T22M22T23M23T24M24T25M25" + \ .. "T26M26T27M27" + + call assert_equal(expected, taken) + [CODE] + let verify =<< trim [CODE] + let expected = "a1b1a2b2a3b3a4b4a5b5a6b6a7b7a8b8a9b9a10b10a11b11a12b12" + \ .. "a13b13a14b14c15d15c16d16c17d17c18d18c19d19c20d20" + \ .. "c21d21c22d22c23d23c24d24c25d25c26d26c27d27" + call assert_equal(expected, g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 79: Throwing one of several errors for the same command {{{1 +" +" When several errors appear in a row (for instance during expression +" evaluation), the first as the most specific one is used when +" throwing an error exception. If, however, a syntax error is +" detected afterwards, this one is used for the error exception. +" On a syntax error, the next command is not executed, on a normal +" error, however, it is (relevant only in a function without the +" "abort" flag). v:errmsg is not set. +" +" If throwing error exceptions is configured off, v:errmsg is always +" set to the latest error message, that is, to the more general +" message or the syntax error, respectively. +"------------------------------------------------------------------------------- +func Test_throw_multi_error() + CheckEnglish + + let test =<< trim [CODE] + func NEXT(cmd) + exec a:cmd . " | Xloop 'a'" + endfun + + call NEXT('echo novar') " (checks nextcmd) + XloopNEXT + call NEXT('let novar #') " (skips nextcmd) + XloopNEXT + call NEXT('unlet novar #') " (skips nextcmd) + XloopNEXT + call NEXT('let {novar}') " (skips nextcmd) + XloopNEXT + call NEXT('unlet{ novar}') " (skips nextcmd) + + call assert_equal('a1', g:Xpath) + XpathINIT + XloopINIT + + func EXEC(cmd) + exec a:cmd + endfunc + + try + while 1 " dummy loop + try + let v:errmsg = "" + call EXEC('echo novar') " normal error + catch /^Vim\((\a\+)\)\=:/ + Xpath 'b' + call assert_match('E121: Undefined variable: novar', v:exception) + finally + Xpath 'c' + call assert_equal("", v:errmsg) + break + endtry + endwhile + + Xpath 'd' + let cmd = "let" + while cmd != "" + try + let v:errmsg = "" + call EXEC(cmd . ' novar #') " normal plus syntax error + catch /^Vim\((\a\+)\)\=:/ + Xloop 'e' + call assert_match('E488: Trailing characters', v:exception) + finally + Xloop 'f' + call assert_equal("", v:errmsg) + if cmd == "let" + let cmd = "unlet" + else + let cmd = "" + endif + XloopNEXT + continue + endtry + endwhile + + Xpath 'g' + let cmd = "let" + while cmd != "" + try + let v:errmsg = "" + call EXEC(cmd . ' {novar}') " normal plus syntax error + catch /^Vim\((\a\+)\)\=:/ + Xloop 'h' + call assert_match('E475: Invalid argument: {novar}', v:exception) + finally + Xloop 'i' + call assert_equal("", v:errmsg) + if cmd == "let" + let cmd = "unlet" + else + let cmd = "" + endif + XloopNEXT + continue + endtry + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + Xpath 'j' + [CODE] + let verify =<< trim [CODE] + call assert_equal('bcde1f1e2f2gh3i3h4i4j', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 80: Syntax error in expression for illegal :elseif {{{1 +" +" If there is a syntax error in the expression after an illegal +" :elseif, an error message is given (or an error exception thrown) +" for the illegal :elseif rather than the expression error. +"------------------------------------------------------------------------------- +func Test_if_syntax_error() + CheckEnglish + + let test =<< trim [CODE] + let v:errmsg = "" + if 0 + else + elseif 1 ||| 2 + endif + Xpath 'a' + call assert_match('E584: :elseif after :else', v:errmsg) + + let v:errmsg = "" + if 1 + else + elseif 1 ||| 2 + endif + Xpath 'b' + call assert_match('E584: :elseif after :else', v:errmsg) + + let v:errmsg = "" + elseif 1 ||| 2 + Xpath 'c' + call assert_match('E582: :elseif without :if', v:errmsg) + + let v:errmsg = "" + while 1 + elseif 1 ||| 2 + endwhile + Xpath 'd' + call assert_match('E582: :elseif without :if', v:errmsg) + + while 1 + try + try + let v:errmsg = "" + if 0 + else + elseif 1 ||| 2 + endif + catch /^Vim\((\a\+)\)\=:/ + Xpath 'e' + call assert_match('E584: :elseif after :else', v:exception) + finally + Xpath 'f' + call assert_equal("", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'g' + break + endtry + endwhile + + while 1 + try + try + let v:errmsg = "" + if 1 + else + elseif 1 ||| 2 + endif + catch /^Vim\((\a\+)\)\=:/ + Xpath 'h' + call assert_match('E584: :elseif after :else', v:exception) + finally + Xpath 'i' + call assert_equal("", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'j' + break + endtry + endwhile + + while 1 + try + try + let v:errmsg = "" + elseif 1 ||| 2 + catch /^Vim\((\a\+)\)\=:/ + Xpath 'k' + call assert_match('E582: :elseif without :if', v:exception) + finally + Xpath 'l' + call assert_equal("", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'm' + break + endtry + endwhile + + while 1 + try + try + let v:errmsg = "" + while 1 + elseif 1 ||| 2 + endwhile + catch /^Vim\((\a\+)\)\=:/ + Xpath 'n' + call assert_match('E582: :elseif without :if', v:exception) + finally + Xpath 'o' + call assert_equal("", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'p' + break + endtry + endwhile + Xpath 'q' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefghijklmnopq', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 81: Discarding exceptions after an error or interrupt {{{1 +" +" When an exception is thrown from inside a :try conditional without +" :catch and :finally clauses and an error or interrupt occurs before +" the :endtry is reached, the exception is discarded. +"------------------------------------------------------------------------------- + +func Test_discard_exception_after_error_1() + let test =<< trim [CODE] + try + Xpath 'a' + try + Xpath 'b' + throw "arrgh" + call assert_report('should not get here') + if 1 + call assert_report('should not get here') + " error after :throw: missing :endif + endtry + call assert_report('should not get here') + catch /arrgh/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('ab', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +" interrupt the code before the endtry is invoked +func Test_discard_exception_after_error_2() + XpathINIT + let lines =<< trim [CODE] + try + Xpath 'a' + try + Xpath 'b' + throw "arrgh" + call assert_report('should not get here') + endtry " interrupt here + call assert_report('should not get here') + catch /arrgh/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + call writefile(lines, 'Xscript') + + breakadd file 7 Xscript + try + let caught_intr = 0 + debuggreedy + call feedkeys(":source Xscript\<CR>quit\<CR>", "xt") + catch /^Vim:Interrupt$/ + call assert_match('Xscript, line 7', v:throwpoint) + let caught_intr = 1 + endtry + 0debuggreedy + call assert_equal(1, caught_intr) + call assert_equal('ab', g:Xpath) + breakdel * + call delete('Xscript') +endfunc + +"------------------------------------------------------------------------------- +" Test 82: Ignoring :catch clauses after an error or interrupt {{{1 +" +" When an exception is thrown and an error or interrupt occurs before +" the matching :catch clause is reached, the exception is discarded +" and the :catch clause is ignored (also for the error or interrupt +" exception being thrown then). +"------------------------------------------------------------------------------- + +func Test_ignore_catch_after_error_1() + let test =<< trim [CODE] + try + try + Xpath 'a' + throw "arrgh" + call assert_report('should not get here') + if 1 + call assert_report('should not get here') + " error after :throw: missing :endif + catch /.*/ + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + catch /arrgh/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('a', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_ignore_catch_after_error_2() + let test =<< trim [CODE] + func E() + try + try + Xpath 'a' + throw "arrgh" + call assert_report('should not get here') + if 1 + call assert_report('should not get here') + " error after :throw: missing :endif + catch /.*/ + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + catch /arrgh/ + call assert_report('should not get here') + endtry + endfunc + + call E() + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('a', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +" interrupt right before a catch is invoked in a script +func Test_ignore_catch_after_intr_1() + XpathINIT + let lines =<< trim [CODE] + try + try + Xpath 'a' + throw "arrgh" + call assert_report('should not get here') + catch /.*/ " interrupt here + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + catch /arrgh/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + call writefile(lines, 'Xscript') + + breakadd file 6 Xscript + try + let caught_intr = 0 + debuggreedy + call feedkeys(":source Xscript\<CR>quit\<CR>", "xt") + catch /^Vim:Interrupt$/ + call assert_match('Xscript, line 6', v:throwpoint) + let caught_intr = 1 + endtry + 0debuggreedy + call assert_equal(1, caught_intr) + call assert_equal('a', g:Xpath) + breakdel * + call delete('Xscript') +endfunc + +" interrupt right before a catch is invoked inside a function. +func Test_ignore_catch_after_intr_2() + XpathINIT + func F() + try + try + Xpath 'a' + throw "arrgh" + call assert_report('should not get here') + catch /.*/ " interrupt here + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + catch /arrgh/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endfunc + + breakadd func 6 F + try + let caught_intr = 0 + debuggreedy + call feedkeys(":call F()\<CR>quit\<CR>", "xt") + catch /^Vim:Interrupt$/ + call assert_match('\.F, line 6', v:throwpoint) + let caught_intr = 1 + endtry + 0debuggreedy + call assert_equal(1, caught_intr) + call assert_equal('a', g:Xpath) + breakdel * + delfunc F +endfunc + +"------------------------------------------------------------------------------- +" Test 83: Executing :finally clauses after an error or interrupt {{{1 +" +" When an exception is thrown and an error or interrupt occurs before +" the :finally of the innermost :try is reached, the exception is +" discarded and the :finally clause is executed. +"------------------------------------------------------------------------------- + +func Test_finally_after_error() + let test =<< trim [CODE] + try + Xpath 'a' + try + Xpath 'b' + throw "arrgh" + call assert_report('should not get here') + if 1 + call assert_report('should not get here') + " error after :throw: missing :endif + finally + Xpath 'c' + endtry + call assert_report('should not get here') + catch /arrgh/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +" interrupt the code right before the finally is invoked +func Test_finally_after_intr() + XpathINIT + let lines =<< trim [CODE] + try + Xpath 'a' + try + Xpath 'b' + throw "arrgh" + call assert_report('should not get here') + finally " interrupt here + Xpath 'c' + endtry + call assert_report('should not get here') + catch /arrgh/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + call writefile(lines, 'Xscript') + + breakadd file 7 Xscript + try + let caught_intr = 0 + debuggreedy + call feedkeys(":source Xscript\<CR>quit\<CR>", "xt") + catch /^Vim:Interrupt$/ + call assert_match('Xscript, line 7', v:throwpoint) + let caught_intr = 1 + endtry + 0debuggreedy + call assert_equal(1, caught_intr) + call assert_equal('abc', g:Xpath) + breakdel * + call delete('Xscript') +endfunc + +"------------------------------------------------------------------------------- +" Test 84: Exceptions in autocommand sequences. {{{1 +" +" When an exception occurs in a sequence of autocommands for +" a specific event, the rest of the sequence is not executed. The +" command that triggered the autocommand execution aborts, and the +" exception is propagated to the caller. +" +" For the FuncUndefined event under a function call expression or +" :call command, the function is not executed, even when it has +" been defined by the autocommands before the exception occurred. +"------------------------------------------------------------------------------- + +func Test_autocmd_exception() + let test =<< trim [CODE] + func INT() + call interrupt() + endfunc + + aug TMP + autocmd! + + autocmd User x1 Xpath 'a' + autocmd User x1 throw "x1" + autocmd User x1 call assert_report('should not get here') + + autocmd User x2 Xpath 'b' + autocmd User x2 asdf + autocmd User x2 call assert_report('should not get here') + + autocmd User x3 Xpath 'c' + autocmd User x3 call INT() + autocmd User x3 call assert_report('should not get here') + + autocmd FuncUndefined U1 func U1() + autocmd FuncUndefined U1 call assert_report('should not get here') + autocmd FuncUndefined U1 endfunc + autocmd FuncUndefined U1 Xpath 'd' + autocmd FuncUndefined U1 throw "U1" + autocmd FuncUndefined U1 call assert_report('should not get here') + + autocmd FuncUndefined U2 func U2() + autocmd FuncUndefined U2 call assert_report('should not get here') + autocmd FuncUndefined U2 endfunc + autocmd FuncUndefined U2 Xpath 'e' + autocmd FuncUndefined U2 ASDF + autocmd FuncUndefined U2 call assert_report('should not get here') + + autocmd FuncUndefined U3 func U3() + autocmd FuncUndefined U3 call assert_report('should not get here') + autocmd FuncUndefined U3 endfunc + autocmd FuncUndefined U3 Xpath 'f' + autocmd FuncUndefined U3 call INT() + autocmd FuncUndefined U3 call assert_report('should not get here') + aug END + + try + try + Xpath 'g' + doautocmd User x1 + catch /x1/ + Xpath 'h' + endtry + + while 1 + try + Xpath 'i' + doautocmd User x2 + catch /asdf/ + Xpath 'j' + finally + Xpath 'k' + break + endtry + endwhile + + while 1 + try + Xpath 'l' + doautocmd User x3 + catch /Vim:Interrupt/ + Xpath 'm' + finally + Xpath 'n' + " ... but break loop for caught interrupt exception, + " or discard interrupt and break loop if $VIMNOINTTHROW + break + endtry + endwhile + + if exists("*U1") | delfunction U1 | endif + if exists("*U2") | delfunction U2 | endif + if exists("*U3") | delfunction U3 | endif + + try + Xpath 'o' + call U1() + catch /U1/ + Xpath 'p' + endtry + + while 1 + try + Xpath 'q' + call U2() + catch /ASDF/ + Xpath 'r' + finally + Xpath 's' + " ... but break loop for caught error exception, + " or discard error and break loop if $VIMNOERRTHROW + break + endtry + endwhile + + while 1 + try + Xpath 't' + call U3() + catch /Vim:Interrupt/ + Xpath 'u' + finally + Xpath 'v' + " ... but break loop for caught interrupt exception, + " or discard interrupt and break loop if $VIMNOINTTHROW + break + endtry + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + Xpath 'w' + [CODE] + let verify =<< trim [CODE] + call assert_equal('gahibjklcmnodpqerstfuvw', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 85: Error exceptions in autocommands for I/O command events {{{1 +" +" When an I/O command is inside :try/:endtry, autocommands to be +" executed after it should be skipped on an error (exception) in the +" command itself or in autocommands to be executed before the command. +" In the latter case, the I/O command should not be executed either. +" Example 1: BufWritePre, :write, BufWritePost +" Example 2: FileReadPre, :read, FileReadPost. +"------------------------------------------------------------------------------- + +func Test_autocmd_error_io_exception() + let test =<< trim [CODE] + " Remove the autocommands for the events specified as arguments in all used + " autogroups. + func Delete_autocommands(...) + let augfile = tempname() + while 1 + try + exec "redir >" . augfile + aug + redir END + exec "edit" augfile + g/^$/d + norm G$ + let wrap = "w" + while search('\%( \|^\)\@<=.\{-}\%( \)\@=', wrap) > 0 + let wrap = "W" + exec "norm y/ \n" + let argno = 1 + while argno <= a:0 + exec "au!" escape(@", " ") a:{argno} + let argno = argno + 1 + endwhile + endwhile + catch /.*/ + finally + bwipeout! + call delete(augfile) + break + endtry + endwhile + endfunc + + call Delete_autocommands("BufWritePre", "BufWritePost") + + while 1 + try + try + let post = 0 + aug TMP + au! BufWritePost * let post = 1 + aug END + write /n/o/n/e/x/i/s/t/e/n/t + catch /^Vim(write):/ + Xpath 'a' + call assert_match("E212: Can't open file for writing", v:exception) + finally + Xpath 'b' + call assert_equal(0, post) + au! TMP + aug! TMP + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + break + endtry + endwhile + + while 1 + try + try + let post = 0 + aug TMP + au! BufWritePre * asdf + au! BufWritePost * let post = 1 + aug END + let tmpfile = tempname() + exec "write" tmpfile + catch /^Vim\((write)\)\=:/ + Xpath 'd' + call assert_match('E492: Not an editor command', v:exception) + finally + Xpath 'e' + if filereadable(tmpfile) + call assert_report('should not get here') + endif + call assert_equal(0, post) + au! TMP + aug! TMP + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'f' + break + endtry + endwhile + + call delete(tmpfile) + + call Delete_autocommands("BufWritePre", "BufWritePost", + \ "BufReadPre", "BufReadPost", "FileReadPre", "FileReadPost") + + while 1 + try + try + let post = 0 + aug TMP + au! FileReadPost * let post = 1 + aug END + let caught = 0 + read /n/o/n/e/x/i/s/t/e/n/t + catch /^Vim(read):/ + Xpath 'g' + call assert_match("E484: Can't open file", v:exception) + finally + Xpath 'h' + call assert_equal(0, post) + au! TMP + aug! TMP + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'i' + break + endtry + endwhile + + while 1 + try + let infile = tempname() + let tmpfile = tempname() + call writefile(["XYZ"], infile) + exec "edit" tmpfile + try + Xpath 'j' + try + let post = 0 + aug TMP + au! FileReadPre * asdf + au! FileReadPost * let post = 1 + aug END + exec "0read" infile + catch /^Vim\((read)\)\=:/ + Xpath 'k' + call assert_match('E492: Not an editor command', v:exception) + finally + Xpath 'l' + if getline("1") == "XYZ" + call assert_report('should not get here') + endif + call assert_equal(0, post) + au! TMP + aug! TMP + endtry + finally + Xpath 'm' + bwipeout! + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'n' + break + endtry + endwhile + + call delete(infile) + call delete(tmpfile) + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefghijklmn', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + "------------------------------------------------------------------------------- " Test 87 using (expr) ? funcref : funcref {{{1 " @@ -1443,7 +6796,7 @@ endfunc " Undefined behavior was detected by ubsan with line continuation " after an empty line. "------------------------------------------------------------------------------- -func Test_script_emty_line_continuation() +func Test_script_empty_line_continuation() \ endfunc @@ -1495,55 +6848,6 @@ func Test_bitwise_functions() call assert_fails("call invert({})", 'E728:') endfunc -" Test trailing text after :endfunction {{{1 -func Test_endfunction_trailing() - call assert_false(exists('*Xtest')) - - exe "func Xtest()\necho 'hello'\nendfunc\nlet done = 'yes'" - call assert_true(exists('*Xtest')) - call assert_equal('yes', done) - delfunc Xtest - unlet done - - exe "func Xtest()\necho 'hello'\nendfunc|let done = 'yes'" - call assert_true(exists('*Xtest')) - call assert_equal('yes', done) - delfunc Xtest - unlet done - - " trailing line break - exe "func Xtest()\necho 'hello'\nendfunc\n" - call assert_true(exists('*Xtest')) - delfunc Xtest - - set verbose=1 - exe "func Xtest()\necho 'hello'\nendfunc \" garbage" - call assert_notmatch('W22:', split(execute('1messages'), "\n")[0]) - call assert_true(exists('*Xtest')) - delfunc Xtest - - exe "func Xtest()\necho 'hello'\nendfunc garbage" - call assert_match('W22:', split(execute('1messages'), "\n")[0]) - call assert_true(exists('*Xtest')) - delfunc Xtest - set verbose=0 - - function Foo() - echo 'hello' - endfunction | echo 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' - delfunc Foo -endfunc - -func Test_delfunction_force() - delfunc! Xtest - delfunc! Xtest - func Xtest() - echo 'nothing' - endfunc - delfunc! Xtest - delfunc! Xtest -endfunc - " Test using bang after user command {{{1 func Test_user_command_with_bang() command -bang Nieuw let nieuw = 1 @@ -1553,26 +6857,6 @@ func Test_user_command_with_bang() delcommand Nieuw endfunc -" Test for script-local function -func <SID>DoLast() - call append(line('$'), "last line") -endfunc - -func s:DoNothing() - call append(line('$'), "nothing line") -endfunc - -func Test_script_local_func() - set nocp nomore viminfo+=nviminfo - new - nnoremap <buffer> _x :call <SID>DoNothing()<bar>call <SID>DoLast()<bar>delfunc <SID>DoNothing<bar>delfunc <SID>DoLast<cr> - - normal _x - call assert_equal('nothing line', getline(2)) - call assert_equal('last line', getline(3)) - enew! | close -endfunc - func Test_script_expand_sfile() let lines =<< trim END func s:snr() @@ -1653,6 +6937,22 @@ func Test_compound_assignment_operators() call assert_equal(4.2, x) call assert_fails('let x %= 0.5', 'E734') call assert_fails('let x .= "f"', 'E734') + let x = !3.14 + call assert_equal(0.0, x) + + " integer and float operations + let x = 1 + let x *= 2.1 + call assert_equal(2.1, x) + let x = 1 + let x /= 0.25 + call assert_equal(4.0, x) + let x = 1 + call assert_fails('let x %= 0.25', 'E734:') + let x = 1 + call assert_fails('let x .= 0.25', 'E734:') + let x = 1.0 + call assert_fails('let x += [1.1]', 'E734:') endif " Test for environment variable @@ -1716,87 +7016,6 @@ func Test_unlet_env() call assert_equal('', $TESTVAR) endfunc -func Test_funccall_garbage_collect() - func Func(x, ...) - call add(a:x, a:000) - endfunc - call Func([], []) - " Must not crash cause by invalid freeing - call test_garbagecollect_now() - call assert_true(v:true) - delfunc Func -endfunc - -func Test_function_defined_line() - if has('gui_running') - " Can't catch the output of gvim. - return - endif - - let lines =<< trim [CODE] - " F1 - func F1() - " F2 - func F2() - " - " - " - return - endfunc - " F3 - execute "func F3()\n\n\n\nreturn\nendfunc" - " F4 - execute "func F4()\n - \\n - \\n - \\n - \return\n - \endfunc" - endfunc - " F5 - execute "func F5()\n\n\n\nreturn\nendfunc" - " F6 - execute "func F6()\n - \\n - \\n - \\n - \return\n - \endfunc" - call F1() - verbose func F1 - verbose func F2 - verbose func F3 - verbose func F4 - verbose func F5 - verbose func F6 - qall! - [CODE] - - call writefile(lines, 'Xtest.vim') - let res = system(GetVimCommandClean() .. ' -es -X -S Xtest.vim') - call assert_equal(0, v:shell_error) - - let m = matchstr(res, 'function F1()[^[:print:]]*[[:print:]]*') - call assert_match(' line 2$', m) - - let m = matchstr(res, 'function F2()[^[:print:]]*[[:print:]]*') - call assert_match(' line 4$', m) - - let m = matchstr(res, 'function F3()[^[:print:]]*[[:print:]]*') - call assert_match(' line 11$', m) - - let m = matchstr(res, 'function F4()[^[:print:]]*[[:print:]]*') - call assert_match(' line 13$', m) - - let m = matchstr(res, 'function F5()[^[:print:]]*[[:print:]]*') - call assert_match(' line 21$', m) - - let m = matchstr(res, 'function F6()[^[:print:]]*[[:print:]]*') - call assert_match(' line 23$', m) - - call delete('Xtest.vim') -endfunc - " Test for missing :endif, :endfor, :endwhile and :endtry {{{1 func Test_missing_end() call writefile(['if 2 > 1', 'echo ">"'], 'Xscript') @@ -1834,13 +7053,14 @@ func Test_missing_end() " Missing 'in' in a :for statement call assert_fails('for i range(1) | endfor', 'E690:') + + " Incorrect number of variables in for + call assert_fails('for [i,] in range(3) | endfor', 'E475:') endfunc " Test for deep nesting of if/for/while/try statements {{{1 func Test_deep_nest() - if !CanRunVimInTerminal() - throw 'Skipped: cannot run vim in terminal' - endif + CheckRunVimInTerminal let lines =<< trim [SCRIPT] " Deep nesting of if ... endif @@ -1929,14 +7149,113 @@ func Test_deep_nest() call delete('Xscript') endfunc -" Test for <sfile>, <slnum> in a function {{{1 -func Test_sfile_in_function() - func Xfunc() - call assert_match('..Test_sfile_in_function\[5]..Xfunc', expand('<sfile>')) - call assert_equal('2', expand('<slnum>')) - endfunc - call Xfunc() - delfunc Xfunc +" Test for errors in converting to float from various types {{{1 +func Test_float_conversion_errors() + if has('float') + call assert_fails('let x = 4.0 % 2.0', 'E804') + call assert_fails('echo 1.1[0]', 'E806') + call assert_fails('echo sort([function("min"), 1], "f")', 'E891:') + call assert_fails('echo 3.2 == "vim"', 'E892:') + call assert_fails('echo sort([[], 1], "f")', 'E893:') + call assert_fails('echo sort([{}, 1], "f")', 'E894:') + call assert_fails('echo 3.2 == v:true', 'E362:') + " call assert_fails('echo 3.2 == v:none', 'E907:') + endif +endfunc + +" invalid function names {{{1 +func Test_invalid_function_names() + " function name not starting with capital + let caught_e128 = 0 + try + func! g:test() + echo "test" + endfunc + catch /E128:/ + let caught_e128 = 1 + endtry + call assert_equal(1, caught_e128) + + " function name includes a colon + let caught_e884 = 0 + try + func! b:test() + echo "test" + endfunc + catch /E884:/ + let caught_e884 = 1 + endtry + call assert_equal(1, caught_e884) + + " function name followed by # + let caught_e128 = 0 + try + func! test2() "# + echo "test2" + endfunc + catch /E128:/ + let caught_e128 = 1 + endtry + call assert_equal(1, caught_e128) + + " function name starting with/without "g:", buffer-local funcref. + function! g:Foo(n) + return 'called Foo(' . a:n . ')' + endfunction + let b:my_func = function('Foo') + call assert_equal('called Foo(1)', b:my_func(1)) + call assert_equal('called Foo(2)', g:Foo(2)) + call assert_equal('called Foo(3)', Foo(3)) + delfunc g:Foo + + " script-local function used in Funcref must exist. + let lines =<< trim END + func s:Testje() + return "foo" + endfunc + let Bar = function('s:Testje') + call assert_equal(0, exists('s:Testje')) + call assert_equal(1, exists('*s:Testje')) + call assert_equal(1, exists('Bar')) + call assert_equal(1, exists('*Bar')) + END + call writefile(lines, 'Xscript') + source Xscript + call delete('Xscript') +endfunc + +" substring and variable name {{{1 +func Test_substring_var() + let str = 'abcdef' + let n = 3 + call assert_equal('def', str[n:]) + call assert_equal('abcd', str[:n]) + call assert_equal('d', str[n:n]) + unlet n + let nn = 3 + call assert_equal('def', str[nn:]) + call assert_equal('abcd', str[:nn]) + call assert_equal('d', str[nn:nn]) + unlet nn + let b:nn = 4 + call assert_equal('ef', str[b:nn:]) + call assert_equal('abcde', str[:b:nn]) + call assert_equal('e', str[b:nn:b:nn]) + unlet b:nn +endfunc + +" Test using s: with a typed command {{{1 +func Test_typed_script_var() + CheckRunVimInTerminal + + let buf = RunVimInTerminal('', {'rows': 6}) + + " Deep nesting of if ... endif + call term_sendkeys(buf, ":echo get(s:, 'foo', 'x')\n") + call TermWait(buf) + call WaitForAssert({-> assert_match('^E116:', term_getline(buf, 5))}) + + call StopVimInTerminal(buf) endfunc func Test_for_over_string() diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim index e712896562..2bf8c3fc77 100644 --- a/src/nvim/testdir/test_virtualedit.vim +++ b/src/nvim/testdir/test_virtualedit.vim @@ -80,6 +80,10 @@ func Test_edit_change() call setline(1, "\t⒌") normal Cx call assert_equal('x', getline(1)) + " Do a visual block change + call setline(1, ['a', 'b', 'c']) + exe "normal gg3l\<C-V>2jcx" + call assert_equal(['a x', 'b x', 'c x'], getline(1, '$')) bwipe! set virtualedit= endfunc @@ -289,6 +293,16 @@ func Test_replace_after_eol() call append(0, '"r"') normal gg$5lrxa call assert_equal('"r" x', getline(1)) + " visual block replace + %d _ + call setline(1, ['a', '', 'b']) + exe "normal 2l\<C-V>2jrx" + call assert_equal(['a x', ' x', 'b x'], getline(1, '$')) + " visual characterwise selection replace after eol + %d _ + call setline(1, 'a') + normal 4lv2lrx + call assert_equal('a xxx', getline(1)) bwipe! set virtualedit= endfunc @@ -346,6 +360,48 @@ func Test_yank_paste_small_del_reg() set virtualedit= endfunc +" Test for delete that breaks a tab into spaces +func Test_delete_break_tab() + new + call setline(1, "one\ttwo") + set virtualedit=all + normal v3ld + call assert_equal(' two', getline(1)) + set virtualedit& + close! +endfunc + +" Test for using <BS>, <C-W> and <C-U> in virtual edit mode +" to erase character, word and line. +func Test_ve_backspace() + new + call setline(1, 'sample') + set virtualedit=all + set backspace=indent,eol,start + exe "normal 15|i\<BS>\<BS>" + call assert_equal([0, 1, 7, 5], getpos('.')) + exe "normal 15|i\<C-W>" + call assert_equal([0, 1, 6, 0], getpos('.')) + exe "normal 15|i\<C-U>" + call assert_equal([0, 1, 1, 0], getpos('.')) + set backspace& + set virtualedit& + close! +endfunc + +" Test for delete (x) on EOL character and after EOL +func Test_delete_past_eol() + new + call setline(1, "ab") + set virtualedit=all + exe "normal 2lx" + call assert_equal('ab', getline(1)) + exe "normal 10lx" + call assert_equal('ab', getline(1)) + set virtualedit& + bw! +endfunc + " After calling s:TryVirtualeditReplace(), line 1 will contain one of these " two strings, depending on whether virtual editing is on or off. let s:result_ve_on = 'a x' diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 65665d36c0..14d62089cf 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -225,6 +225,15 @@ func Test_virtual_replace() exe "normal iabcdefghijklmnopqrst\<Esc>0gRAB\tIJKLMNO\tQR" call assert_equal(['AB......CDEFGHI.Jkl', \ 'AB IJKLMNO QRst'], getline(12, 13)) + + " Test inserting Tab with 'noexpandtab' and 'softabstop' set to 4 + %d + call setline(1, 'aaaaaaaaaaaaa') + set softtabstop=4 + exe "normal gggR\<Tab>\<Tab>x" + call assert_equal("\txaaaa", getline(1)) + set softtabstop& + enew! set noai bs&vim if exists('save_t_kD') @@ -474,6 +483,18 @@ func Test_visual_block_put() bw! endfunc +func Test_visual_block_put_invalid() + enew! + behave mswin + norm yy + norm v)Ps/^/ + " this was causing the column to become negative + silent norm ggv)P + + bwipe! + behave xterm +endfunc + " Visual modes (v V CTRL-V) followed by an operator; count; repeating func Test_visual_mode_op() new @@ -1269,7 +1290,7 @@ func Test_visual_block_with_virtualedit() endfunc func Test_visual_block_ctrl_w_f() - " Emtpy block selected in new buffer should not result in an error. + " Empty block selected in new buffer should not result in an error. au! BufNew foo sil norm f edit foo @@ -1317,6 +1338,18 @@ func Test_visual_reselect_with_count() call delete('XvisualReselect') endfunc +func Test_visual_reselect_exclusive() + new + call setline(1, ['abcde', 'abcde']) + set selection=exclusive + normal 1G0viwd + normal 2G01vd + call assert_equal(['', ''], getline(1, 2)) + + set selection& + bwipe! +endfunc + func Test_visual_block_insert_round_off() new " The number of characters are tuned to fill a 4096 byte allocated block, diff --git a/src/nvim/testdir/test_winbuf_close.vim b/src/nvim/testdir/test_winbuf_close.vim index f4878c2397..26b4ba8778 100644 --- a/src/nvim/testdir/test_winbuf_close.vim +++ b/src/nvim/testdir/test_winbuf_close.vim @@ -115,7 +115,7 @@ func Test_winbuf_close() call assert_equal('Xtest2', bufname('%')) quit! call assert_equal('Xtest3', bufname('%')) - call assert_fails('silent! quit!', 'E162') + call assert_fails('silent! quit!', 'E37') call assert_equal('Xtest1', bufname('%')) call delete('Xtest1') @@ -192,7 +192,23 @@ func Test_tabwin_close() call win_execute(l:wid, 'close') " Should not crash. call assert_true(v:true) - %bwipe! + + " This tests closing a window in another tab, while leaving the tab open + " i.e. two windows in another tab. + tabedit + let w:this_win = 42 + new + let othertab_wid = win_getid() + tabprevious + call win_execute(othertab_wid, 'q') + " drawing the tabline helps check that the other tab's windows and buffers + " are still valid + redrawtabline + " but to be certain, ensure we can focus the other tab too + tabnext + call assert_equal(42, w:this_win) + + bwipe! endfunc " Test when closing a split window (above/below) restores space to the window diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index c4ce4d638c..c25b1f1157 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -501,10 +501,13 @@ func Test_win_screenpos() call assert_equal([1, 32], win_screenpos(2)) call assert_equal([12, 1], win_screenpos(3)) call assert_equal([0, 0], win_screenpos(4)) + call assert_fails('let l = win_screenpos([])', 'E745:') only endfunc func Test_window_jump_tag() + CheckFeature quickfix + help /iccf call assert_match('^|iccf|', getline('.')) @@ -542,6 +545,7 @@ func Test_window_newtab() call assert_equal(2, tabpagenr('$')) call assert_equal(['Xb', 'Xa'], map(tabpagebuflist(1), 'bufname(v:val)')) call assert_equal(['Xc' ], map(2->tabpagebuflist(), 'bufname(v:val)')) + call assert_equal(['Xc' ], map(tabpagebuflist(), 'bufname(v:val)')) %bw! endfunc @@ -592,6 +596,11 @@ func Test_window_contents() call assert_equal(59, line("w0")) call assert_equal('59 ', s3) + %d + call setline(1, ['one', 'two', 'three']) + call assert_equal(1, line('w0')) + call assert_equal(3, line('w$')) + bwipeout! call test_garbagecollect_now() endfunc @@ -728,6 +737,7 @@ func Test_relative_cursor_position_in_one_line_window() only! bwipe! + call assert_fails('call winrestview(v:_null_dict)', 'E474:') endfunc func Test_relative_cursor_position_after_move_and_resize() @@ -904,6 +914,10 @@ func Test_winnr() call assert_fails("echo winnr('ll')", 'E15:') call assert_fails("echo winnr('5')", 'E15:') call assert_equal(4, winnr('0h')) + call assert_fails("let w = winnr([])", 'E730:') + call assert_equal('unknown', win_gettype(-1)) + call assert_equal(-1, winheight(-1)) + call assert_equal(-1, winwidth(-1)) tabnew call assert_equal(8, tabpagewinnr(1, 'j')) @@ -924,9 +938,12 @@ func Test_winrestview() call assert_equal(view, winsaveview()) bwipe! + call assert_fails('call winrestview(v:_null_dict)', 'E474:') endfunc func Test_win_splitmove() + CheckFeature quickfix + edit a leftabove split b leftabove vsplit c @@ -952,6 +969,7 @@ func Test_win_splitmove() call assert_equal(bufname(winbufnr(2)), 'b') call assert_equal(bufname(winbufnr(3)), 'a') call assert_equal(bufname(winbufnr(4)), 'd') + call assert_fails('call win_splitmove(winnr(), winnr("k"), v:_null_dict)', 'E474:') only | bd call assert_fails('call win_splitmove(winnr(), 123)', 'E957:') @@ -1124,6 +1142,18 @@ func Test_split_cmds_with_no_room() call Run_noroom_for_newwindow_test('v') endfunc +" Test for various wincmd failures +func Test_wincmd_fails() + only! + call assert_beeps("normal \<C-W>w") + call assert_beeps("normal \<C-W>p") + call assert_beeps("normal \<C-W>gk") + call assert_beeps("normal \<C-W>r") + call assert_beeps("normal \<C-W>K") + call assert_beeps("normal \<C-W>H") + call assert_beeps("normal \<C-W>2gt") +endfunc + func Test_window_resize() " Vertical :resize (absolute, relative, min and max size). vsplit @@ -1209,7 +1239,7 @@ endfunc " Test for jumping to a vertical/horizontal neighbor window based on the " current cursor position -func Test_window_goto_neightbor() +func Test_window_goto_neighbor() %bw! " Vertical window movement @@ -1388,17 +1418,20 @@ func Test_win_move_separator() call assert_equal(w0, winwidth(0)) call assert_true(win_move_separator(0, -1)) call assert_equal(w0, winwidth(0)) + " check that win_move_separator doesn't error with offsets beyond moving " possibility call assert_true(win_move_separator(id, 5000)) call assert_true(winwidth(id) > w) call assert_true(win_move_separator(id, -5000)) call assert_true(winwidth(id) < w) + " check that win_move_separator returns false for an invalid window wincmd = let w = winwidth(0) call assert_false(win_move_separator(-1, 1)) call assert_equal(w, winwidth(0)) + " check that win_move_separator returns false for a floating window let id = nvim_open_win( \ 0, 0, #{relative: 'editor', row: 2, col: 2, width: 5, height: 3}) @@ -1406,6 +1439,13 @@ func Test_win_move_separator() call assert_false(win_move_separator(id, 1)) call assert_equal(w, winwidth(id)) call nvim_win_close(id, 1) + + " check that using another tabpage fails without crash + let id = win_getid() + tabnew + call assert_fails('call win_move_separator(id, -1)', 'E1308:') + tabclose + %bwipe! endfunc @@ -1454,17 +1494,20 @@ func Test_win_move_statusline() call assert_true(id->win_move_statusline(-offset)) call assert_equal(h, winheight(id)) endfor + " check that win_move_statusline doesn't error with offsets beyond moving " possibility call assert_true(win_move_statusline(id, 5000)) call assert_true(winheight(id) > h) call assert_true(win_move_statusline(id, -5000)) call assert_true(winheight(id) < h) + " check that win_move_statusline returns false for an invalid window wincmd = let h = winheight(0) call assert_false(win_move_statusline(-1, 1)) call assert_equal(h, winheight(0)) + " check that win_move_statusline returns false for a floating window let id = nvim_open_win( \ 0, 0, #{relative: 'editor', row: 2, col: 2, width: 5, height: 3}) @@ -1472,6 +1515,13 @@ func Test_win_move_statusline() call assert_false(win_move_statusline(id, 1)) call assert_equal(h, winheight(id)) call nvim_win_close(id, 1) + + " check that using another tabpage fails without crash + let id = win_getid() + tabnew + call assert_fails('call win_move_statusline(id, -1)', 'E1308:') + tabclose + %bwipe! endfunc @@ -1711,6 +1761,8 @@ function Test_splitkeep_callback() call term_sendkeys(buf, ":quit\<CR>Gt") call VerifyScreenDump(buf, 'Test_splitkeep_callback_4', {}) + + call StopVimInTerminal(buf) endfunc function Test_splitkeep_fold() @@ -1741,6 +1793,41 @@ function Test_splitkeep_fold() call term_sendkeys(buf, ":wincmd k\<CR>:quit\<CR>") call VerifyScreenDump(buf, 'Test_splitkeep_fold_4', {}) + + call StopVimInTerminal(buf) +endfunction + +function Test_splitkeep_status() + CheckScreendump + + let lines =<< trim END + call setline(1, ['a', 'b', 'c']) + set nomodified + set splitkeep=screen + let win = winnr() + wincmd s + wincmd j + END + call writefile(lines, 'XTestSplitkeepStatus', 'D') + let buf = RunVimInTerminal('-S XTestSplitkeepStatus', #{rows: 10}) + + call term_sendkeys(buf, ":call win_move_statusline(win, 1)\<CR>") + call VerifyScreenDump(buf, 'Test_splitkeep_status_1', {}) + + call StopVimInTerminal(buf) endfunction +function Test_new_help_window_on_error() + help change.txt + execute "normal! /CTRL-@\<CR>" + silent! execute "normal! \<C-W>]" + + let wincount = winnr('$') + help 'mod' + + call assert_equal(wincount, winnr('$')) + call assert_equal(expand("<cword>"), "'mod'") +endfunction + + " 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 8bf4ede350..396a49b55f 100644 --- a/src/nvim/testdir/test_window_id.vim +++ b/src/nvim/testdir/test_window_id.vim @@ -1,5 +1,7 @@ " Test using the window ID. +source check.vim + func Test_win_getid() edit one let id1 = win_getid() @@ -90,10 +92,16 @@ func Test_win_getid() split call assert_equal(sort([id5, win_getid()]), sort(win_findbuf(bufnr5))) + call assert_fails('let w = win_getid([])', 'E745:') + call assert_equal(0, win_getid(-1)) + call assert_equal(-1, win_getid(1, -1)) + only! endfunc func Test_win_getid_curtab() + CheckFeature quickfix + tabedit X tabfirst copen @@ -127,4 +135,8 @@ func Test_winlayout() let w2 = win_getid() call assert_equal(['leaf', w2], 2->winlayout()) tabclose + + call assert_equal([], winlayout(-1)) endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim index adc05ab979..6019cee193 100644 --- a/src/nvim/testdir/test_writefile.vim +++ b/src/nvim/testdir/test_writefile.vim @@ -57,7 +57,29 @@ func Test_writefile_fails_conversion() call assert_fails('write ++enc=cp932', 'E513:') call assert_equal(contents, readfile('Xfile')) + " With 'backupcopy' set, if there is a conversion error, the backup file is + " still created. + set backupcopy=yes writebackup& backup& + call delete('Xfile' .. &backupext) + call assert_fails('write ++enc=cp932', 'E513:') + call assert_equal(contents, readfile('Xfile')) + call assert_equal(contents, readfile('Xfile' .. &backupext)) + set backupcopy& + %bw! + + " Conversion error during write + new + call setline(1, ["\U10000000"]) + let output = execute('write! ++enc=utf-16 Xfile') + call assert_match('CONVERSION ERROR', output) + let output = execute('write! ++enc=ucs-2 Xfile') + call assert_match('CONVERSION ERROR', output) + call delete('Xfilz~') + call delete('Xfily~') + %bw! + call delete('Xfile') + call delete('Xfile' .. &backupext) bwipe! set backup& writebackup& backupdir&vim backupskip&vim endfunc @@ -136,9 +158,7 @@ func Test_writefile_sync_arg() endfunc func Test_writefile_sync_dev_stdout() - if !has('unix') - return - endif + CheckUnix if filewritable('/dev/stdout') " Just check that this doesn't cause an error. call writefile(['one'], '/dev/stdout', 's') @@ -260,6 +280,225 @@ func Test_write_errors() close call delete('Xfile') + + " Nvim treats NULL list/blob more like empty list/blob + " call writefile(v:_null_list, 'Xfile') + " call assert_false(filereadable('Xfile')) + " call writefile(v:_null_blob, 'Xfile') + " call assert_false(filereadable('Xfile')) + call assert_fails('call writefile([], "")', 'E482:') + + " very long file name + let long_fname = repeat('n', 5000) + call assert_fails('exe "w " .. long_fname', 'E75:') + call assert_fails('call writefile([], long_fname)', 'E482:') + + " Test for writing to a block device on Unix-like systems + if has('unix') && getfperm('/dev/loop0') != '' + \ && getftype('/dev/loop0') == 'bdev' && !IsRoot() + new + edit /dev/loop0 + call assert_fails('write', 'E503: ') + call assert_fails('write!', 'E503: ') + close! + endif +endfunc + +" Test for writing to a file which is modified after Vim read it +func Test_write_file_mtime() + CheckEnglish + CheckRunVimInTerminal + + " First read the file into a buffer + call writefile(["Line1", "Line2"], 'Xfile') + let old_ftime = getftime('Xfile') + let buf = RunVimInTerminal('Xfile', #{rows : 10}) + call term_wait(buf) + call term_sendkeys(buf, ":set noswapfile\<CR>") + call term_wait(buf) + + " Modify the file directly. Make sure the file modification time is + " different. Note that on Linux/Unix, the file is considered modified + " outside, only if the difference is 2 seconds or more + sleep 1 + call writefile(["Line3", "Line4"], 'Xfile') + let new_ftime = getftime('Xfile') + while new_ftime - old_ftime < 2 + sleep 100m + call writefile(["Line3", "Line4"], 'Xfile') + let new_ftime = getftime('Xfile') + endwhile + + " Try to overwrite the file and check for the prompt + call term_sendkeys(buf, ":w\<CR>") + call term_wait(buf) + call WaitForAssert({-> assert_equal("WARNING: The file has been changed since reading it!!!", term_getline(buf, 9))}) + call assert_equal("Do you really want to write to it (y/n)?", + \ term_getline(buf, 10)) + call term_sendkeys(buf, "n\<CR>") + call term_wait(buf) + call assert_equal(new_ftime, getftime('Xfile')) + call term_sendkeys(buf, ":w\<CR>") + call term_wait(buf) + call term_sendkeys(buf, "y\<CR>") + call term_wait(buf) + call WaitForAssert({-> assert_equal('Line2', readfile('Xfile')[1])}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xfile') +endfunc + +" Test for an autocmd unloading a buffer during a write command +func Test_write_autocmd_unloadbuf_lockmark() + augroup WriteTest + autocmd BufWritePre Xfile enew | write + augroup END + e Xfile + call assert_fails('lockmarks write', ['E32', 'E203:']) + augroup WriteTest + au! + augroup END + augroup! WriteTest +endfunc + +" Test for writing a buffer with 'acwrite' but without autocmds +func Test_write_acwrite_error() + new Xfile + call setline(1, ['line1', 'line2', 'line3']) + set buftype=acwrite + call assert_fails('write', 'E676:') + call assert_fails('1,2write!', 'E676:') + call assert_fails('w >>', 'E676:') + close! +endfunc + +" Test for adding and removing lines from an autocmd when writing a buffer +func Test_write_autocmd_add_remove_lines() + new Xfile + call setline(1, ['aaa', 'bbb', 'ccc', 'ddd']) + + " Autocmd deleting lines from the file when writing a partial file + augroup WriteTest2 + au! + autocmd FileWritePre Xfile 1,2d + augroup END + call assert_fails('2,3w!', 'E204:') + + " Autocmd adding lines to a file when writing a partial file + augroup WriteTest2 + au! + autocmd FileWritePre Xfile call append(0, ['xxx', 'yyy']) + augroup END + %d + call setline(1, ['aaa', 'bbb', 'ccc', 'ddd']) + 1,2w! + call assert_equal(['xxx', 'yyy', 'aaa', 'bbb'], readfile('Xfile')) + + " Autocmd deleting lines from the file when writing the whole file + augroup WriteTest2 + au! + autocmd BufWritePre Xfile 1,2d + augroup END + %d + call setline(1, ['aaa', 'bbb', 'ccc', 'ddd']) + w + call assert_equal(['ccc', 'ddd'], readfile('Xfile')) + + augroup WriteTest2 + au! + augroup END + augroup! WriteTest2 + + close! + call delete('Xfile') +endfunc + +" Test for writing to a readonly file +func Test_write_readonly() + call writefile([], 'Xfile') + call setfperm('Xfile', "r--------") + edit Xfile + set noreadonly backupskip= + call assert_fails('write', 'E505:') + let save_cpo = &cpo + set cpo+=W + call assert_fails('write!', 'E504:') + let &cpo = save_cpo + call setline(1, ['line1']) + write! + call assert_equal(['line1'], readfile('Xfile')) + + " Auto-saving a readonly file should fail with 'autowriteall' + %bw! + e Xfile + set noreadonly autowriteall + call setline(1, ['aaaa']) + call assert_fails('n', 'E505:') + set cpo+=W + call assert_fails('n', 'E504:') + set cpo-=W + set autowriteall& + + set backupskip& + call delete('Xfile') + %bw! +endfunc + +" Test for 'patchmode' +func Test_patchmode() + call writefile(['one'], 'Xfile') + set patchmode=.orig nobackup backupskip= writebackup + new Xfile + call setline(1, 'two') + " first write should create the .orig file + write + call assert_equal(['one'], readfile('Xfile.orig')) + call setline(1, 'three') + " subsequent writes should not create/modify the .orig file + write + call assert_equal(['one'], readfile('Xfile.orig')) + + " use 'patchmode' with 'nobackup' and 'nowritebackup' to create an empty + " original file + call delete('Xfile') + call delete('Xfile.orig') + %bw! + set patchmode=.orig nobackup nowritebackup + edit Xfile + call setline(1, ['xxx']) + write + call assert_equal(['xxx'], readfile('Xfile')) + call assert_equal([], readfile('Xfile.orig')) + + set patchmode& backup& backupskip& writebackup& + call delete('Xfile') + call delete('Xfile.orig') +endfunc + +" Test for writing to a file in a readonly directory +" NOTE: if you run tests as root this will fail. Don't run tests as root! +func Test_write_readonly_dir() + " On MS-Windows, modifying files in a read-only directory is allowed. + CheckUnix + " Root can do it too. + CheckNotRoot + + call mkdir('Xdir/') + call writefile(['one'], 'Xdir/Xfile1') + call setfperm('Xdir', 'r-xr--r--') + " try to create a new file in the directory + new Xdir/Xfile2 + call setline(1, 'two') + call assert_fails('write', 'E212:') + " try to create a backup file in the directory + edit! Xdir/Xfile1 + set backupdir=./Xdir backupskip= + set patchmode=.orig + call assert_fails('write', 'E509:') + call setfperm('Xdir', 'rwxr--r--') + call delete('Xdir', 'rf') + set backupdir& backupskip& patchmode& endfunc " Test for writing a file using invalid file encoding @@ -272,15 +511,15 @@ endfunc " Tests for reading and writing files with conversion for Win32. func Test_write_file_encoding() - throw 'skipped: Nvim does not support :w ++enc=cp1251' + throw 'Skipped: Nvim does not support encoding=latin1' CheckMSWindows let save_encoding = &encoding let save_fileencodings = &fileencodings - set encoding& fileencodings& + set encoding=latin1 fileencodings& let text =<< trim END - 1 utf-8 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 - 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 - 3 cp866 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + 1 utf-8 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01 + 2 cp1251 text: Vim version 6.2. : 1970 Jan 01 + 3 cp866 text: Vim version 6.2. : 1970 Jan 01 END call writefile(text, 'Xfile') edit Xfile @@ -295,9 +534,9 @@ func Test_write_file_encoding() .w ++enc=cp866 >> Xtest .w! ++enc=utf-8 Xutf8 let expected =<< trim END - 1 utf-8 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 - 1 utf-8 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 - 1 utf-8 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + 1 utf-8 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01 + 1 utf-8 text: Vim version 6.2. : 1970 Jan 01 + 1 utf-8 text: Vim version 6.2. : 1970 Jan 01 END call assert_equal(expected, readfile('Xtest')) @@ -308,9 +547,9 @@ func Test_write_file_encoding() .w ++enc=cp866 >> Xtest .w! ++enc=cp1251 Xcp1251 let expected =<< trim END - 2 cp1251 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 - 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 - 2 cp1251 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + 2 cp1251 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01 + 2 cp1251 text: Vim version 6.2. : 1970 Jan 01 + 2 cp1251 text: Vim version 6.2. : 1970 Jan 01 END call assert_equal(expected, readfile('Xtest')) @@ -321,9 +560,9 @@ func Test_write_file_encoding() .w ++enc=cp866 >> Xtest .w! ++enc=cp866 Xcp866 let expected =<< trim END - 3 cp866 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 - 3 cp866 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 - 3 cp866 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + 3 cp866 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01 + 3 cp866 text: Vim version 6.2. : 1970 Jan 01 + 3 cp866 text: Vim version 6.2. : 1970 Jan 01 END call assert_equal(expected, readfile('Xtest')) @@ -337,9 +576,9 @@ func Test_write_file_encoding() e Xcp866 .w ++enc=utf-8 >> Xtest let expected =<< trim END - 1 utf-8 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 - 2 cp1251 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 - 3 cp866 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 + 1 utf-8 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01 + 2 cp1251 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01 + 3 cp866 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01 END call assert_equal(expected, readfile('Xtest')) @@ -353,9 +592,9 @@ func Test_write_file_encoding() e Xcp866 .w ++enc=cp1251 >> Xtest let expected =<< trim END - 1 utf-8 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 - 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 - 3 cp866 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 + 1 utf-8 text: Vim version 6.2. : 1970 Jan 01 + 2 cp1251 text: Vim version 6.2. : 1970 Jan 01 + 3 cp866 text: Vim version 6.2. : 1970 Jan 01 END call assert_equal(expected, readfile('Xtest')) @@ -369,9 +608,9 @@ func Test_write_file_encoding() e Xcp866 .w ++enc=cp866 >> Xtest let expected =<< trim END - 1 utf-8 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 - 2 cp1251 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 - 3 cp866 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + 1 utf-8 text: Vim version 6.2. : 1970 Jan 01 + 2 cp1251 text: Vim version 6.2. : 1970 Jan 01 + 3 cp866 text: Vim version 6.2. : 1970 Jan 01 END call assert_equal(expected, readfile('Xtest')) @@ -529,10 +768,177 @@ func Test_read_write_bin() call assert_equal(0z6E6F656F6C0A, readfile('XNoEolSetEol', 'B')) call delete('XNoEolSetEol') - set ff& + set ff& fixeol& bwipe! XNoEolSetEol endfunc +" Test for the 'backupcopy' option when writing files +func Test_backupcopy() + CheckUnix + set backupskip= + " With the default 'backupcopy' setting, saving a symbolic link file + " should not break the link. + set backupcopy& + call writefile(['1111'], 'Xfile1') + silent !ln -s Xfile1 Xfile2 + new Xfile2 + call setline(1, ['2222']) + write + close + call assert_equal(['2222'], readfile('Xfile1')) + call assert_equal('Xfile1', resolve('Xfile2')) + call assert_equal('link', getftype('Xfile2')) + call delete('Xfile1') + call delete('Xfile2') + + " With the 'backupcopy' set to 'breaksymlink', saving a symbolic link file + " should break the link. + set backupcopy=yes,breaksymlink + call writefile(['1111'], 'Xfile1') + silent !ln -s Xfile1 Xfile2 + new Xfile2 + call setline(1, ['2222']) + write + close + call assert_equal(['1111'], readfile('Xfile1')) + call assert_equal(['2222'], readfile('Xfile2')) + call assert_equal('Xfile2', resolve('Xfile2')) + call assert_equal('file', getftype('Xfile2')) + call delete('Xfile1') + call delete('Xfile2') + set backupcopy& + + " With the default 'backupcopy' setting, saving a hard link file + " should not break the link. + set backupcopy& + call writefile(['1111'], 'Xfile1') + silent !ln Xfile1 Xfile2 + new Xfile2 + call setline(1, ['2222']) + write + close + call assert_equal(['2222'], readfile('Xfile1')) + call delete('Xfile1') + call delete('Xfile2') + + " With the 'backupcopy' set to 'breaksymlink', saving a hard link file + " should break the link. + set backupcopy=yes,breakhardlink + call writefile(['1111'], 'Xfile1') + silent !ln Xfile1 Xfile2 + new Xfile2 + call setline(1, ['2222']) + write + call assert_equal(['1111'], readfile('Xfile1')) + call assert_equal(['2222'], readfile('Xfile2')) + call delete('Xfile1') + call delete('Xfile2') + + " If a backup file is already present, then a slightly modified filename + " should be used as the backup file. Try with 'backupcopy' set to 'yes' and + " 'no'. + %bw + call writefile(['aaaa'], 'Xfile') + call writefile(['bbbb'], 'Xfile.bak') + set backupcopy=yes backupext=.bak + new Xfile + call setline(1, ['cccc']) + write + close + call assert_equal(['cccc'], readfile('Xfile')) + call assert_equal(['bbbb'], readfile('Xfile.bak')) + set backupcopy=no backupext=.bak + new Xfile + call setline(1, ['dddd']) + write + close + call assert_equal(['dddd'], readfile('Xfile')) + call assert_equal(['bbbb'], readfile('Xfile.bak')) + call delete('Xfile') + call delete('Xfile.bak') + + " Write to a device file (in Unix-like systems) which cannot be backed up. + if has('unix') + set writebackup backupcopy=yes nobackup + new + call setline(1, ['aaaa']) + let output = execute('write! /dev/null') + call assert_match('"/dev/null" \[Device]', output) + close + set writebackup backupcopy=no nobackup + new + call setline(1, ['aaaa']) + let output = execute('write! /dev/null') + call assert_match('"/dev/null" \[Device]', output) + close + set backup writebackup& backupcopy& + new + call setline(1, ['aaaa']) + let output = execute('write! /dev/null') + call assert_match('"/dev/null" \[Device]', output) + close + endif + + set backupcopy& backupskip& backupext& backup& +endfunc + +" Test for writing a file with 'encoding' set to 'utf-16' +func Test_write_utf16() + new + call setline(1, ["\U00010001"]) + write ++enc=utf-16 Xfile + bw! + call assert_equal(0zD800DC01, readfile('Xfile', 'B')[0:3]) + call delete('Xfile') +endfunc + +" Test for trying to save a backup file when the backup file is a symbolic +" link to the original file. The backup file should not be modified. +func Test_write_backup_symlink() + CheckUnix + call mkdir('Xbackup') + let save_backupdir = &backupdir + set backupdir=.,./Xbackup + call writefile(['1111'], 'Xfile') + silent !ln -s Xfile Xfile.bak + + new Xfile + set backup backupcopy=yes backupext=.bak + write + call assert_equal('link', getftype('Xfile.bak')) + call assert_equal('Xfile', resolve('Xfile.bak')) + " backup file should be created in the 'backup' directory + if !has('bsd') + " This check fails on FreeBSD + call assert_true(filereadable('./Xbackup/Xfile.bak')) + endif + set backup& backupcopy& backupext& + %bw + + call delete('Xfile') + call delete('Xfile.bak') + call delete('Xbackup', 'rf') + let &backupdir = save_backupdir +endfunc + +" Test for ':write ++bin' and ':write ++nobin' +func Test_write_binary_file() + " create a file without an eol/eof character + call writefile(0z616161, 'Xfile1', 'b') + new Xfile1 + write ++bin Xfile2 + write ++nobin Xfile3 + call assert_equal(0z616161, readblob('Xfile2')) + if has('win32') + call assert_equal(0z6161610D.0A, readblob('Xfile3')) + else + call assert_equal(0z6161610A, readblob('Xfile3')) + endif + call delete('Xfile1') + call delete('Xfile2') + call delete('Xfile3') +endfunc + " Check that buffer is written before triggering QuitPre func Test_wq_quitpre_autocommand() edit Xsomefile diff --git a/src/nvim/testdir/vim9.vim b/src/nvim/testdir/vim9.vim new file mode 100644 index 0000000000..3c0ff2b2dd --- /dev/null +++ b/src/nvim/testdir/vim9.vim @@ -0,0 +1,112 @@ + +" Use a different file name for each run. +let s:sequence = 1 + +func CheckScriptFailure(lines, error, lnum = -3) + if get(a:lines, 0, '') ==# 'vim9script' + return + endif + let cwd = getcwd() + let fname = 'XScriptFailure' .. s:sequence + let s:sequence += 1 + call writefile(a:lines, fname) + try + call assert_fails('so ' .. fname, a:error, a:lines, a:lnum) + finally + call chdir(cwd) + call delete(fname) + endtry +endfunc + +func CheckScriptSuccess(lines) + if get(a:lines, 0, '') ==# 'vim9script' + return + endif + let cwd = getcwd() + let fname = 'XScriptSuccess' .. s:sequence + let s:sequence += 1 + call writefile(a:lines, fname) + try + exe 'so ' .. fname + finally + call chdir(cwd) + call delete(fname) + endtry +endfunc + +" Check that "lines" inside a legacy function has no error. +func CheckLegacySuccess(lines) + let cwd = getcwd() + let fname = 'XlegacySuccess' .. s:sequence + let s:sequence += 1 + call writefile(['func Func()'] + a:lines + ['endfunc'], fname) + try + exe 'so ' .. fname + call Func() + finally + delfunc! Func + call chdir(cwd) + call delete(fname) + endtry +endfunc + +" Check that "lines" inside a legacy function results in the expected error +func CheckLegacyFailure(lines, error) + let cwd = getcwd() + let fname = 'XlegacyFails' .. s:sequence + let s:sequence += 1 + call writefile(['func Func()'] + a:lines + ['endfunc', 'call Func()'], fname) + try + call assert_fails('so ' .. fname, a:error) + finally + delfunc! Func + call chdir(cwd) + call delete(fname) + endtry +endfunc + +" Execute "lines" in a legacy function, translated as in +" CheckLegacyAndVim9Success() +func CheckTransLegacySuccess(lines) + let legacylines = a:lines->deepcopy()->map({_, v -> + \ v->substitute('\<VAR\>', 'let', 'g') + \ ->substitute('\<LET\>', 'let', 'g') + \ ->substitute('\<LSTART\>', '{', 'g') + \ ->substitute('\<LMIDDLE\>', '->', 'g') + \ ->substitute('\<LEND\>', '}', 'g') + \ ->substitute('\<TRUE\>', '1', 'g') + \ ->substitute('\<FALSE\>', '0', 'g') + \ ->substitute('#"', ' "', 'g') + \ }) + call CheckLegacySuccess(legacylines) +endfunc + +" Execute "lines" in a legacy function +" Use 'VAR' for a declaration. +" Use 'LET' for an assignment +" Use ' #"' for a comment +" Use LSTART arg LMIDDLE expr LEND for lambda +" Use 'TRUE' for 1 +" Use 'FALSE' for 0 +func CheckLegacyAndVim9Success(lines) + call CheckTransLegacySuccess(a:lines) +endfunc + +" Execute "lines" in a legacy function +" Use 'VAR' for a declaration. +" Use 'LET' for an assignment +" Use ' #"' for a comment +func CheckLegacyAndVim9Failure(lines, error) + if type(a:error) == type('string') + let legacyError = error + else + let legacyError = error[0] + endif + + let legacylines = a:lines->deepcopy()->map({_, v -> + \ v->substitute('\<VAR\>', 'let', 'g') + \ ->substitute('\<LET\>', 'let', 'g') + \ ->substitute('#"', ' "', 'g') + \ }) + call CheckLegacyFailure(legacylines, legacyError) +endfunc diff --git a/src/nvim/testing.c b/src/nvim/testing.c index 348d5c6e29..edf92c78ac 100644 --- a/src/nvim/testing.c +++ b/src/nvim/testing.c @@ -3,17 +3,46 @@ // testing.c: Support for tests +#include <inttypes.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> + +#include "nvim/ascii.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_docmd.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/hashtab.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/os/os.h" #include "nvim/runtime.h" +#include "nvim/strings.h" #include "nvim/testing.h" +#include "nvim/types.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "testing.c.generated.h" #endif +static char e_assert_fails_second_arg[] + = N_("E856: assert_fails() second argument must be a string or a list with one or two strings"); +static char e_assert_fails_fourth_argument[] + = N_("E1115: assert_fails() fourth argument must be a number"); +static char e_assert_fails_fifth_argument[] + = N_("E1116: assert_fails() fifth argument must be a string"); +static char e_calling_test_garbagecollect_now_while_v_testing_is_not_set[] + = N_("E1142: Calling test_garbagecollect_now() while v:testing is not set"); + /// Prepare "gap" for an assert error and add the sourcing position. static void prepare_assert_error(garray_T *gap) { @@ -39,15 +68,15 @@ static void prepare_assert_error(garray_T *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) +static void ga_concat_esc(garray_T *gap, const char *p, int clen) FUNC_ATTR_NONNULL_ALL { - char_u buf[NUMBUFLEN]; + char buf[NUMBUFLEN]; if (clen > 1) { memmove(buf, p, (size_t)clen); buf[clen] = NUL; - ga_concat(gap, (char *)buf); + ga_concat(gap, buf); } else { switch (*p) { case BS: @@ -65,11 +94,11 @@ static void ga_concat_esc(garray_T *gap, const char_u *p, int clen) case '\\': ga_concat(gap, "\\\\"); break; default: - if (*p < ' ') { - vim_snprintf((char *)buf, NUMBUFLEN, "\\x%02x", *p); - ga_concat(gap, (char *)buf); + if ((uint8_t)(*p) < ' ' || *p == 0x7f) { + vim_snprintf(buf, NUMBUFLEN, "\\x%02x", *p); + ga_concat(gap, buf); } else { - ga_append(gap, (char)(*p)); + ga_append(gap, (uint8_t)(*p)); } break; } @@ -78,22 +107,22 @@ static void ga_concat_esc(garray_T *gap, const char_u *p, int clen) /// 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) +static void ga_concat_shorten_esc(garray_T *gap, const char *str) FUNC_ATTR_NONNULL_ARG(1) { - char_u buf[NUMBUFLEN]; + char buf[NUMBUFLEN]; if (str == NULL) { ga_concat(gap, "NULL"); return; } - for (const char_u *p = str; *p != NUL; p++) { + for (const char *p = str; *p != NUL; p++) { int same_len = 1; - const char_u *s = p; + const char *s = p; const int c = mb_ptr2char_adv(&s); const int clen = (int)(s - p); - while (*s != NUL && c == utf_ptr2char((char *)s)) { + while (*s != NUL && c == utf_ptr2char(s)) { same_len++; s += clen; } @@ -101,8 +130,8 @@ static void ga_concat_shorten_esc(garray_T *gap, const char_u *str) 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); + vim_snprintf(buf, NUMBUFLEN, "%d", same_len); + ga_concat(gap, buf); ga_concat(gap, " times]"); p = s - 1; } else { @@ -112,18 +141,21 @@ static void ga_concat_shorten_esc(garray_T *gap, const char_u *str) } /// 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, +static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char *exp_str, typval_T *exp_tv_arg, typval_T *got_tv_arg, assert_type_T atype) { - char_u *tofree; + char *tofree; typval_T *exp_tv = exp_tv_arg; typval_T *got_tv = got_tv_arg; bool did_copy = false; int omitted = 0; - if (opt_msg_tv->v_type != VAR_UNKNOWN) { - tofree = (char_u *)encode_tv2echo(opt_msg_tv, NULL); - ga_concat(gap, (char *)tofree); + if (opt_msg_tv->v_type != VAR_UNKNOWN + && !(opt_msg_tv->v_type == VAR_STRING + && (opt_msg_tv->vval.v_string == NULL + || *opt_msg_tv->vval.v_string == NUL))) { + tofree = encode_tv2echo(opt_msg_tv, NULL); + ga_concat(gap, tofree); xfree(tofree); ga_concat(gap, ": "); } @@ -156,7 +188,7 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_s if (item2 == NULL || !tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &item2->di_tv, false, false)) { // item of exp_d not present in got_d or values differ. - const size_t key_len = STRLEN(hi->hi_key); + const size_t key_len = strlen(hi->hi_key); tv_dict_add_tv(exp_tv->vval.v_dict, (const char *)hi->hi_key, key_len, &TV_DICT_HI2DI(hi)->di_tv); if (item2 != NULL) { @@ -177,7 +209,7 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_s dictitem_T *item2 = tv_dict_find(exp_d, (const char *)hi->hi_key, -1); if (item2 == NULL) { // item of got_d not present in exp_d - const size_t key_len = STRLEN(hi->hi_key); + const size_t key_len = strlen(hi->hi_key); tv_dict_add_tv(got_tv->vval.v_dict, (const char *)hi->hi_key, key_len, &TV_DICT_HI2DI(hi)->di_tv); } @@ -186,7 +218,7 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_s } } - tofree = (char_u *)encode_tv2string(exp_tv, NULL); + tofree = encode_tv2string(exp_tv, NULL); ga_concat_shorten_esc(gap, tofree); xfree(tofree); } else { @@ -201,7 +233,7 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_s } else { ga_concat(gap, " but got "); } - tofree = (char_u *)encode_tv2string(got_tv, NULL); + tofree = encode_tv2string(got_tv, NULL); ga_concat_shorten_esc(gap, tofree); xfree(tofree); @@ -241,13 +273,12 @@ static int assert_match_common(typval_T *argvars, assert_type_T atype) { char buf1[NUMBUFLEN]; char buf2[NUMBUFLEN]; + const int called_emsg_before = called_emsg; const char *const pat = tv_get_string_buf_chk(&argvars[0], buf1); const char *const text = tv_get_string_buf_chk(&argvars[1], buf2); - if (pat == NULL || text == NULL) { - emsg(_(e_invarg)); - } else if (pattern_match((char *)pat, (char *)text, false) - != (atype == ASSERT_MATCH)) { + if (called_emsg == called_emsg_before + && pattern_match(pat, text, false) != (atype == ASSERT_MATCH)) { garray_T ga; prepare_assert_error(&ga); fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1], atype); @@ -275,7 +306,7 @@ static int assert_bool(typval_T *argvars, bool is_true) : kBoolVarFalse)))) { prepare_assert_error(&ga); fill_assert_error(&ga, &argvars[1], - (char_u *)(is_true ? "True" : "False"), + is_true ? "True" : "False", NULL, &argvars[0], ASSERT_OTHER); assert_error(&ga); ga_clear(&ga); @@ -348,11 +379,12 @@ static int assert_equalfile(typval_T *argvars) { char buf1[NUMBUFLEN]; char buf2[NUMBUFLEN]; + const int called_emsg_before = called_emsg; const char *const fname1 = tv_get_string_buf_chk(&argvars[0], buf1); const char *const fname2 = tv_get_string_buf_chk(&argvars[1], buf2); garray_T ga; - if (fname1 == NULL || fname2 == NULL) { + if (called_emsg > called_emsg_before) { return 0; } @@ -362,12 +394,12 @@ static int assert_equalfile(typval_T *argvars) char line2[200]; ptrdiff_t lineidx = 0; if (fd1 == NULL) { - snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname1); + snprintf(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); + snprintf(IObuff, IOSIZE, (char *)e_notread, fname2); } else { int64_t linecount = 1; for (int64_t count = 0;; count++) { @@ -386,7 +418,7 @@ static int assert_equalfile(typval_T *argvars) line2[lineidx] = (char)c2; lineidx++; if (c1 != c2) { - snprintf((char *)IObuff, IOSIZE, + snprintf(IObuff, IOSIZE, "difference at byte %" PRId64 ", line %" PRId64, count, linecount); break; @@ -413,7 +445,7 @@ static int assert_equalfile(typval_T *argvars) xfree(tofree); ga_concat(&ga, ": "); } - ga_concat(&ga, (char *)IObuff); + ga_concat(&ga, IObuff); if (lineidx > 0) { line1[lineidx] = NUL; line2[lineidx] = NUL; @@ -474,11 +506,13 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) garray_T ga; int save_trylevel = trylevel; const int called_emsg_before = called_emsg; + char *wrong_arg_msg = NULL; // trylevel must be zero for a ":throw" command to be considered failed trylevel = 0; suppress_errthrow = true; - emsg_silent = true; + in_assert_fails = true; + no_wait_return++; do_cmdline_cmd(cmd); if (called_emsg == called_emsg_before) { @@ -490,13 +524,75 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_number = 1; } else if (argvars[1].v_type != VAR_UNKNOWN) { char buf[NUMBUFLEN]; - const char *const error = tv_get_string_buf_chk(&argvars[1], buf); + const char *expected; + bool error_found = false; + int error_found_index = 1; + char *actual = emsg_assert_fails_msg == NULL ? "[unknown]" : emsg_assert_fails_msg; + + if (argvars[1].v_type == VAR_STRING) { + expected = tv_get_string_buf_chk(&argvars[1], buf); + error_found = expected == NULL || strstr(actual, expected) == NULL; + } else if (argvars[1].v_type == VAR_LIST) { + const list_T *const list = argvars[1].vval.v_list; + if (list == NULL || tv_list_len(list) < 1 || tv_list_len(list) > 2) { + wrong_arg_msg = e_assert_fails_second_arg; + goto theend; + } + const typval_T *tv = TV_LIST_ITEM_TV(tv_list_first(list)); + expected = tv_get_string_buf_chk(tv, buf); + if (!pattern_match(expected, actual, false)) { + error_found = true; + } else if (tv_list_len(list) == 2) { + tv = TV_LIST_ITEM_TV(tv_list_last(list)); + actual = get_vim_var_str(VV_ERRMSG); + expected = tv_get_string_buf_chk(tv, buf); + if (!pattern_match(expected, actual, false)) { + error_found = true; + } + } + } else { + wrong_arg_msg = e_assert_fails_second_arg; + goto theend; + } + + if (!error_found && argvars[2].v_type != VAR_UNKNOWN + && argvars[3].v_type != VAR_UNKNOWN) { + if (argvars[3].v_type != VAR_NUMBER) { + wrong_arg_msg = e_assert_fails_fourth_argument; + goto theend; + } else if (argvars[3].vval.v_number >= 0 + && argvars[3].vval.v_number != emsg_assert_fails_lnum) { + error_found = true; + error_found_index = 3; + } + if (!error_found && argvars[4].v_type != VAR_UNKNOWN) { + if (argvars[4].v_type != VAR_STRING) { + wrong_arg_msg = e_assert_fails_fifth_argument; + goto theend; + } else if (argvars[4].vval.v_string != NULL + && !pattern_match(argvars[4].vval.v_string, + emsg_assert_fails_context, false)) { + error_found = true; + error_found_index = 4; + } + } + } - if (error == NULL - || strstr(get_vim_var_str(VV_ERRMSG), error) == NULL) { + if (error_found) { + typval_T actual_tv; prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[2], NULL, &argvars[1], - get_vim_var_tv(VV_ERRMSG), ASSERT_OTHER); + if (error_found_index == 3) { + actual_tv.v_type = VAR_NUMBER; + actual_tv.vval.v_number = emsg_assert_fails_lnum; + } else if (error_found_index == 4) { + actual_tv.v_type = VAR_STRING; + actual_tv.vval.v_string = emsg_assert_fails_context; + } else { + actual_tv.v_type = VAR_STRING; + actual_tv.vval.v_string = actual; + } + fill_assert_error(&ga, &argvars[2], NULL, + &argvars[error_found_index], &actual_tv, ASSERT_OTHER); ga_concat(&ga, ": "); assert_append_cmd_or_arg(&ga, argvars, cmd); assert_error(&ga); @@ -505,11 +601,23 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } +theend: trylevel = save_trylevel; suppress_errthrow = false; - emsg_silent = false; + in_assert_fails = false; + did_emsg = false; + got_int = false; + msg_col = 0; + no_wait_return--; + need_wait_return = false; emsg_on_display = false; + msg_reset_scroll(); + lines_left = Rows; + XFREE_CLEAR(emsg_assert_fails_msg); set_vim_var_string(VV_ERRMSG, NULL, 0); + if (wrong_arg_msg != NULL) { + emsg(_(wrong_arg_msg)); + } } // "assert_false(actual[, msg])" function @@ -534,8 +642,8 @@ static int assert_inrange(typval_T *argvars) 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); + char *const tofree = encode_tv2string(&argvars[3], NULL); + ga_concat(&ga, tofree); xfree(tofree); } else { char msg[80]; @@ -563,7 +671,7 @@ static int assert_inrange(typval_T *argvars) vim_snprintf(msg, sizeof(msg), "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",", lower, upper); // -V576 - fill_assert_error(&ga, &argvars[3], (char_u *)msg, NULL, &argvars[2], + fill_assert_error(&ga, &argvars[3], msg, NULL, &argvars[2], ASSERT_INRANGE); assert_error(&ga); ga_clear(&ga); @@ -614,7 +722,11 @@ void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv, EvalFuncData { // This is dangerous, any Lists and Dicts used internally may be freed // while still in use. - garbage_collect(true); + if (!get_vim_var_nr(VV_TESTING)) { + emsg(_(e_calling_test_garbagecollect_now_while_v_testing_is_not_set)); + } else { + garbage_collect(true); + } } /// "test_write_list_log()" function diff --git a/src/nvim/textformat.c b/src/nvim/textformat.c index 25728ef0f1..fbea1ccfb7 100644 --- a/src/nvim/textformat.c +++ b/src/nvim/textformat.c @@ -4,20 +4,29 @@ // textformat.c: text formatting functions #include <stdbool.h> +#include <stdint.h> +#include <string.h> #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/getchar.h" #include "nvim/globals.h" #include "nvim/indent.h" #include "nvim/indent_c.h" +#include "nvim/macros.h" +#include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/ops.h" @@ -28,6 +37,7 @@ #include "nvim/strings.h" #include "nvim/textformat.h" #include "nvim/textobject.h" +#include "nvim/types.h" #include "nvim/undo.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -117,13 +127,13 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on // Don't break until after the comment leader if (do_comments) { - char_u *line = (char_u *)get_cursor_line_ptr(); - leader_len = get_leader_len((char *)line, NULL, false, true); + char *line = get_cursor_line_ptr(); + leader_len = get_leader_len(line, NULL, false, true); if (leader_len == 0 && curbuf->b_p_cin) { // Check for a line comment after code. - int comment_start = check_linecomment((char *)line); + int comment_start = check_linecomment(line); if (comment_start != MAXCOL) { - leader_len = get_leader_len((char *)line + comment_start, NULL, false, true); + leader_len = get_leader_len(line + comment_start, NULL, false, true); if (leader_len != 0) { leader_len += comment_start; } @@ -465,14 +475,14 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on /// ('e' in comment flags), so that this line is skipped, and not joined to the /// previous line. A new paragraph starts after a blank line, or when the /// comment leader changes. -static int fmt_check_par(linenr_T lnum, int *leader_len, char_u **leader_flags, bool do_comments) +static int fmt_check_par(linenr_T lnum, int *leader_len, char **leader_flags, bool do_comments) { - char_u *flags = NULL; // init for GCC - char_u *ptr; + char *flags = NULL; // init for GCC + char *ptr; - ptr = (char_u *)ml_get(lnum); + ptr = ml_get(lnum); if (do_comments) { - *leader_len = get_leader_len((char *)ptr, (char **)leader_flags, false, true); + *leader_len = get_leader_len(ptr, leader_flags, false, true); } else { *leader_len = 0; } @@ -485,7 +495,7 @@ static int fmt_check_par(linenr_T lnum, int *leader_len, char_u **leader_flags, } } - return *skipwhite((char *)ptr + *leader_len) == NUL + return *skipwhite(ptr + *leader_len) == NUL || (*leader_len > 0 && *flags == COM_END) || startPS(lnum, NUL, false); } @@ -493,14 +503,14 @@ static int fmt_check_par(linenr_T lnum, int *leader_len, char_u **leader_flags, /// @return true if line "lnum" ends in a white character. static bool ends_in_white(linenr_T lnum) { - char_u *s = (char_u *)ml_get(lnum); + char *s = ml_get(lnum); size_t l; if (*s == NUL) { return false; } - l = STRLEN(s) - 1; - return ascii_iswhite(s[l]); + l = strlen(s) - 1; + return ascii_iswhite((uint8_t)s[l]); } /// @return true if the two comment leaders given are the same. @@ -534,7 +544,8 @@ static bool same_leader(linenr_T lnum, int leader1_len, char *leader1_flags, int return false; } if (*p == COM_START) { - if (*(ml_get(lnum) + leader1_len) == NUL) { + int line_len = (int)strlen(ml_get(lnum)); + if (line_len <= leader1_len) { return false; } if (leader2_flags == NULL || leader2_len == 0) { @@ -577,16 +588,16 @@ static bool same_leader(linenr_T lnum, int leader1_len, char *leader1_flags, int /// false when the previous line is in the same paragraph. static bool paragraph_start(linenr_T lnum) { - char_u *p; + char *p; int leader_len = 0; // leader len of current line - char_u *leader_flags = NULL; // flags for leader of current line + char *leader_flags = NULL; // flags for leader of current line int next_leader_len = 0; // leader len of next line - char_u *next_leader_flags = NULL; // flags for leader of next line + char *next_leader_flags = NULL; // flags for leader of next line if (lnum <= 1) { return true; // start of the file } - p = (char_u *)ml_get(lnum - 1); + p = ml_get(lnum - 1); if (*p == NUL) { return true; // after empty line } @@ -605,8 +616,8 @@ static bool paragraph_start(linenr_T lnum) if (has_format_option(FO_Q_NUMBER) && (get_number_indent(lnum) > 0)) { return true; // numbered item starts in "lnum". } - if (!same_leader(lnum - 1, leader_len, (char *)leader_flags, - next_leader_len, (char *)next_leader_flags)) { + if (!same_leader(lnum - 1, leader_len, leader_flags, + next_leader_len, next_leader_flags)) { return true; // change of comment leader. } return false; @@ -914,8 +925,8 @@ void format_lines(linenr_T line_count, bool avoid_fex) bool next_is_start_par = false; 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 = NULL; // flags for leader of next line + char *leader_flags = NULL; // flags for leader of current line + char *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; @@ -1018,14 +1029,14 @@ void format_lines(linenr_T line_count, bool avoid_fex) // When the comment leader changes, it's the end of the paragraph. if (curwin->w_cursor.lnum >= curbuf->b_ml.ml_line_count || !same_leader(curwin->w_cursor.lnum, - leader_len, (char *)leader_flags, + leader_len, leader_flags, next_leader_len, - (char *)next_leader_flags)) { + next_leader_flags)) { // 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 + || strncmp(next_leader_flags, "://", 3) != 0 || check_linecomment(get_cursor_line_ptr()) == MAXCOL) { is_end_par = true; } diff --git a/src/nvim/textformat.h b/src/nvim/textformat.h index 3c918a028b..fcc9c2d6f4 100644 --- a/src/nvim/textformat.h +++ b/src/nvim/textformat.h @@ -1,8 +1,8 @@ #ifndef NVIM_TEXTFORMAT_H #define NVIM_TEXTFORMAT_H -#include "nvim/normal.h" // for oparg_T -#include "nvim/pos.h" // for linenr_T +#include "nvim/normal.h" +#include "nvim/pos.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "textformat.h.generated.h" diff --git a/src/nvim/textobject.c b/src/nvim/textobject.c index e6b330cbf1..8e786c271c 100644 --- a/src/nvim/textobject.c +++ b/src/nvim/textobject.c @@ -4,8 +4,12 @@ // textobject.c: functions for text objects #include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" @@ -13,13 +17,17 @@ #include "nvim/fold.h" #include "nvim/globals.h" #include "nvim/indent.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" +#include "nvim/memory.h" #include "nvim/normal.h" +#include "nvim/option_defs.h" #include "nvim/pos.h" +#include "nvim/screen.h" #include "nvim/search.h" -#include "nvim/textformat.h" +#include "nvim/strings.h" #include "nvim/textobject.h" #include "nvim/vim.h" @@ -213,13 +221,13 @@ bool findpar(bool *pincl, int dir, long count, int what, bool both) } curwin->w_cursor.lnum = curr; if (curr == curbuf->b_ml.ml_line_count && what != '}') { - char_u *line = (char_u *)ml_get(curr); + char *line = ml_get(curr); // Put the cursor on the last character in the last line and make the // motion inclusive. - if ((curwin->w_cursor.col = (colnr_T)STRLEN(line)) != 0) { + if ((curwin->w_cursor.col = (colnr_T)strlen(line)) != 0) { curwin->w_cursor.col--; - curwin->w_cursor.col -= utf_head_off((char *)line, (char *)line + curwin->w_cursor.col); + curwin->w_cursor.col -= utf_head_off(line, line + curwin->w_cursor.col); *pincl = true; } } else { @@ -229,9 +237,9 @@ bool findpar(bool *pincl, int dir, long count, int what, bool both) } /// check if the string 's' is a nroff macro that is in option 'opt' -static bool inmacro(char_u *opt, char_u *s) +static bool inmacro(char *opt, const char *s) { - char_u *macro; + char *macro; for (macro = opt; macro[0]; macro++) { // Accept two characters in the option being equal to two characters @@ -258,13 +266,13 @@ static bool inmacro(char_u *opt, char_u *s) /// If 'both' is true also stop at '}' bool startPS(linenr_T lnum, int para, bool both) { - char_u *s; + char *s; - s = (char_u *)ml_get(lnum); - if (*s == para || *s == '\f' || (both && *s == '}')) { + s = ml_get(lnum); + if ((uint8_t)(*s) == para || *s == '\f' || (both && *s == '}')) { return true; } - if (*s == '.' && (inmacro((char_u *)p_sections, s + 1) + if (*s == '.' && (inmacro(p_sections, s + 1) || (!para && inmacro(p_para, s + 1)))) { return true; } @@ -1032,8 +1040,8 @@ int current_block(oparg_T *oap, long count, bool include, int what, int other) /// @return true if the cursor is on a "<aaa>" tag. Ignore "<aaa/>". static bool in_html_tag(bool end_tag) { - char_u *line = (char_u *)get_cursor_line_ptr(); - char_u *p; + char *line = get_cursor_line_ptr(); + char *p; int c; int lc = NUL; pos_T pos; @@ -1070,7 +1078,7 @@ static bool in_html_tag(bool end_tag) if (inc(&pos) < 0) { return false; } - c = *ml_get_pos(&pos); + c = (uint8_t)(*ml_get_pos(&pos)); if (c == '>') { break; } @@ -1089,8 +1097,8 @@ int current_tagblock(oparg_T *oap, long count_arg, bool include) pos_T start_pos; pos_T end_pos; pos_T old_start, old_end; - char_u *p; - char_u *cp; + char *p; + char *cp; int len; bool do_include = include; bool save_p_ws = p_ws; @@ -1157,7 +1165,7 @@ again: // Search for matching "</aaa>". First isolate the "aaa". inc_cursor(); - p = (char_u *)get_cursor_pos_ptr(); + p = get_cursor_pos_ptr(); for (cp = p; *cp != NUL && *cp != '>' && !ascii_iswhite(*cp); MB_PTR_ADV(cp)) {} @@ -1196,7 +1204,7 @@ again: } } } else { - char_u *c = (char_u *)get_cursor_pos_ptr(); + char *c = get_cursor_pos_ptr(); // Exclude the '<' of the end tag. // If the closing tag is on new line, do not decrement cursor, but make // operation exclusive, so that the linefeed will be selected @@ -1433,15 +1441,15 @@ extend: /// @param escape escape characters, can be NULL /// /// @return column number of "quotechar" or -1 when not found. -static int find_next_quote(char_u *line, int col, int quotechar, char_u *escape) +static int find_next_quote(char *line, int col, int quotechar, char *escape) { int c; for (;;) { - c = line[col]; + c = (uint8_t)line[col]; if (c == NUL) { return -1; - } else if (escape != NULL && vim_strchr((char *)escape, c)) { + } else if (escape != NULL && vim_strchr(escape, c)) { col++; if (line[col] == NUL) { return -1; @@ -1449,7 +1457,7 @@ static int find_next_quote(char_u *line, int col, int quotechar, char_u *escape) } else if (c == quotechar) { break; } - col += utfc_ptr2len((char *)line + col); + col += utfc_ptr2len(line + col); } return col; } @@ -1461,23 +1469,23 @@ static int find_next_quote(char_u *line, int col, int quotechar, char_u *escape) /// @param escape escape characters, can be NULL /// /// @return the found column or zero. -static int find_prev_quote(char_u *line, int col_start, int quotechar, char_u *escape) +static int find_prev_quote(char *line, int col_start, int quotechar, char *escape) { int n; while (col_start > 0) { col_start--; - col_start -= utf_head_off((char *)line, (char *)line + col_start); + col_start -= utf_head_off(line, line + col_start); n = 0; if (escape != NULL) { - while (col_start - n > 0 && vim_strchr((char *)escape, - line[col_start - n - 1]) != NULL) { + while (col_start - n > 0 && vim_strchr(escape, + (uint8_t)line[col_start - n - 1]) != NULL) { n++; } } if (n & 1) { col_start -= n; // uneven number of escape chars, skip it - } else if (line[col_start] == quotechar) { + } else if ((uint8_t)line[col_start] == quotechar) { break; } } @@ -1493,7 +1501,7 @@ static int find_prev_quote(char_u *line, int col_start, int quotechar, char_u *e bool current_quote(oparg_T *oap, long count, bool include, int quotechar) FUNC_ATTR_NONNULL_ALL { - char_u *line = (char_u *)get_cursor_line_ptr(); + char *line = get_cursor_line_ptr(); int col_end; int col_start = curwin->w_cursor.col; bool inclusive = false; @@ -1542,16 +1550,16 @@ bool current_quote(oparg_T *oap, long count, bool include, int quotechar) // quotes. if (vis_bef_curs) { inside_quotes = VIsual.col > 0 - && line[VIsual.col - 1] == quotechar + && (uint8_t)line[VIsual.col - 1] == quotechar && line[curwin->w_cursor.col] != NUL - && line[curwin->w_cursor.col + 1] == quotechar; + && (uint8_t)line[curwin->w_cursor.col + 1] == quotechar; i = VIsual.col; col_end = curwin->w_cursor.col; } else { inside_quotes = curwin->w_cursor.col > 0 - && line[curwin->w_cursor.col - 1] == quotechar + && (uint8_t)line[curwin->w_cursor.col - 1] == quotechar && line[VIsual.col] != NUL - && line[VIsual.col + 1] == quotechar; + && (uint8_t)line[VIsual.col + 1] == quotechar; i = curwin->w_cursor.col; col_end = VIsual.col; } @@ -1563,14 +1571,14 @@ bool current_quote(oparg_T *oap, long count, bool include, int quotechar) if (line[i] == NUL) { break; } - if (line[i++] == quotechar) { + if ((uint8_t)line[i++] == quotechar) { selected_quote = true; break; } } } - if (!vis_empty && line[col_start] == quotechar) { + if (!vis_empty && (uint8_t)line[col_start] == quotechar) { // Already selecting something and on a quote character. Find the // next quoted string. if (vis_bef_curs) { @@ -1580,7 +1588,7 @@ bool current_quote(oparg_T *oap, long count, bool include, int quotechar) if (col_start < 0) { goto abort_search; } - col_end = find_next_quote(line, col_start + 1, quotechar, (char_u *)curbuf->b_p_qe); + col_end = find_next_quote(line, col_start + 1, quotechar, curbuf->b_p_qe); if (col_end < 0) { // We were on a starting quote perhaps? col_end = col_start; @@ -1588,17 +1596,17 @@ bool current_quote(oparg_T *oap, long count, bool include, int quotechar) } } else { col_end = find_prev_quote(line, col_start, quotechar, NULL); - if (line[col_end] != quotechar) { + if ((uint8_t)line[col_end] != quotechar) { goto abort_search; } - col_start = find_prev_quote(line, col_end, quotechar, (char_u *)curbuf->b_p_qe); - if (line[col_start] != quotechar) { + col_start = find_prev_quote(line, col_end, quotechar, curbuf->b_p_qe); + if ((uint8_t)line[col_start] != quotechar) { // We were on an ending quote perhaps? col_start = col_end; col_end = curwin->w_cursor.col; } } - } else if (line[col_start] == quotechar || !vis_empty) { + } else if ((uint8_t)line[col_start] == quotechar || !vis_empty) { int first_col = col_start; if (!vis_empty) { @@ -1620,7 +1628,7 @@ bool current_quote(oparg_T *oap, long count, bool include, int quotechar) goto abort_search; } // Find close quote character. - col_end = find_next_quote(line, col_start + 1, quotechar, (char_u *)curbuf->b_p_qe); + col_end = find_next_quote(line, col_start + 1, quotechar, curbuf->b_p_qe); if (col_end < 0) { goto abort_search; } @@ -1633,8 +1641,8 @@ bool current_quote(oparg_T *oap, long count, bool include, int quotechar) } } else { // Search backward for a starting quote. - col_start = find_prev_quote(line, col_start, quotechar, (char_u *)curbuf->b_p_qe); - if (line[col_start] != quotechar) { + col_start = find_prev_quote(line, col_start, quotechar, curbuf->b_p_qe); + if ((uint8_t)line[col_start] != quotechar) { // No quote before the cursor, look after the cursor. col_start = find_next_quote(line, col_start, quotechar, NULL); if (col_start < 0) { @@ -1643,7 +1651,7 @@ bool current_quote(oparg_T *oap, long count, bool include, int quotechar) } // Find close quote character. - col_end = find_next_quote(line, col_start + 1, quotechar, (char_u *)curbuf->b_p_qe); + col_end = find_next_quote(line, col_start + 1, quotechar, curbuf->b_p_qe); if (col_end < 0) { goto abort_search; } @@ -1677,9 +1685,9 @@ bool current_quote(oparg_T *oap, long count, bool include, int quotechar) || (vis_bef_curs && !selected_quote && (inside_quotes - || (line[VIsual.col] != quotechar + || ((uint8_t)line[VIsual.col] != quotechar && (VIsual.col == 0 - || line[VIsual.col - 1] != quotechar))))) { + || (uint8_t)line[VIsual.col - 1] != quotechar))))) { VIsual = curwin->w_cursor; redraw_curbuf_later(UPD_INVERTED); } @@ -1707,9 +1715,9 @@ bool current_quote(oparg_T *oap, long count, bool include, int quotechar) // quote. if (inside_quotes || (!selected_quote - && line[VIsual.col] != quotechar + && (uint8_t)line[VIsual.col] != quotechar && (line[VIsual.col] == NUL - || line[VIsual.col + 1] != quotechar))) { + || (uint8_t)line[VIsual.col + 1] != quotechar))) { dec_cursor(); VIsual = curwin->w_cursor; } diff --git a/src/nvim/textobject.h b/src/nvim/textobject.h index 26f88613fd..e31557f297 100644 --- a/src/nvim/textobject.h +++ b/src/nvim/textobject.h @@ -1,9 +1,9 @@ #ifndef NVIM_TEXTOBJECT_H #define NVIM_TEXTOBJECT_H -#include "nvim/normal.h" // for oparg_T -#include "nvim/pos.h" // for linenr_T -#include "nvim/vim.h" // for Direction +#include "nvim/normal.h" +#include "nvim/pos.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "textobject.h.generated.h" diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index fbeca26274..2cb39ab26b 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -1,20 +1,29 @@ // 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 <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" #include "nvim/ascii.h" -#include "nvim/autocmd.h" #include "nvim/charset.h" -#include "nvim/ex_docmd.h" +#include "nvim/event/defs.h" +#include "nvim/log.h" #include "nvim/macros.h" #include "nvim/main.h" +#include "nvim/map.h" +#include "nvim/memory.h" #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/tui/input.h" +#include "nvim/tui/input_defs.h" #include "nvim/tui/tui.h" -#include "nvim/vim.h" +#include "nvim/types.h" +#include "nvim/ui_client.h" #ifdef MSWIN # include "nvim/os/os_win_console.h" #endif @@ -140,19 +149,7 @@ void tinput_init(TermInput *input, Loop *loop) kitty_key_map_entry[i].name); } - // If stdin is not a pty, switch to stderr. For cases like: - // echo q | nvim -es - // ls *.md | xargs nvim -#ifdef MSWIN - if (!os_isatty(input->in_fd)) { - input->in_fd = os_get_conin_fd(); - } -#else - if (!os_isatty(input->in_fd) && os_isatty(STDERR_FILENO)) { - input->in_fd = STDERR_FILENO; - } -#endif - input_global_fd_init(input->in_fd); + input->in_fd = STDIN_FILENO; const char *term = os_getenv("TERM"); if (!term) { @@ -161,7 +158,7 @@ void tinput_init(TermInput *input, Loop *loop) input->tk = termkey_new_abstract(term, TERMKEY_FLAG_UTF8 | TERMKEY_FLAG_NOSTART); - termkey_hook_terminfo_getstr(input->tk, input->tk_ti_hook_fn, NULL); + termkey_hook_terminfo_getstr(input->tk, input->tk_ti_hook_fn, input); termkey_start(input->tk); int curflags = termkey_get_canonflags(input->tk); @@ -207,16 +204,12 @@ static void tinput_wait_enqueue(void **argv) 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, - keys.data, keys.size, (intptr_t)input->paste); - } + MAXSIZE_TEMP_ARRAY(args, 3); + ADD_C(args, STRING_OBJ(keys)); // 'data' + ADD_C(args, BOOLEAN_OBJ(true)); // 'crlf' + ADD_C(args, INTEGER_OBJ(input->paste)); // 'phase' + rpc_send_event(ui_client_channel_id, "nvim_paste", args); + api_free_string(keys); if (input->paste == 1) { // Paste phase: "continue" input->paste = 2; @@ -225,60 +218,22 @@ static void tinput_wait_enqueue(void **argv) } 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, NULL))); - // TODO(bfredl): could be non-blocking now with paste? - ArenaMem res_mem = NULL; - Object result = rpc_send_call(ui_client_channel_id, "nvim_input", args, &res_mem, &err); - consumed = result.type == kObjectTypeInteger ? (size_t)result.data.integer : 0; - arena_mem_free(res_mem); - } else { - consumed = input_enqueue(keys); - } - if (consumed) { - rbuffer_consumed(input->key_buffer, consumed); - } + MAXSIZE_TEMP_ARRAY(args, 1); + ADD_C(args, STRING_OBJ(keys)); + // NOTE: This is non-blocking and won't check partially processed input, + // but should be fine as all big sends are handled with nvim_paste, not nvim_input + rpc_send_event(ui_client_channel_id, "nvim_input", args); + rbuffer_consumed(input->key_buffer, len); rbuffer_reset(input->key_buffer); - if (consumed < len) { - break; - } } } - uv_mutex_lock(&input->key_buffer_mutex); - input->waiting = false; - uv_cond_signal(&input->key_buffer_cond); - uv_mutex_unlock(&input->key_buffer_mutex); -} - -static void tinput_paste_event(void **argv) -{ - String keys = { .data = argv[0], .size = (size_t)argv[1] }; - intptr_t phase = (intptr_t)argv[2]; - - Error err = ERROR_INIT; - nvim_paste(keys, true, phase, &err); - if (ERROR_SET(&err)) { - semsg("paste: %s", err.msg); - api_clear_error(&err); - } - - api_free_string(keys); } static void tinput_flush(TermInput *input, bool wait_until_empty) { size_t drain_boundary = wait_until_empty ? 0 : 0xff; do { - uv_mutex_lock(&input->key_buffer_mutex); - loop_schedule_fast(&main_loop, event_create(tinput_wait_enqueue, 1, input)); - input->waiting = true; - while (input->waiting) { - uv_cond_wait(&input->key_buffer_cond, &input->key_buffer_mutex); - } - uv_mutex_unlock(&input->key_buffer_mutex); + tinput_wait_enqueue((void **)&input); } while (rbuffer_size(input->key_buffer) > drain_boundary); } @@ -324,15 +279,14 @@ static void forward_simple_utf8(TermInput *input, TermKeyKey *key) && map_has(KittyKey, cstr_t)(&kitty_key_map, (KittyKey)key->code.codepoint)) { handle_kitty_key_protocol(input, key); return; - } else { - while (*ptr) { - if (*ptr == '<') { - len += (size_t)snprintf(buf + len, sizeof(buf) - len, "<lt>"); - } else { - buf[len++] = *ptr; - } - ptr++; + } + while (*ptr) { + if (*ptr == '<') { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "<lt>"); + } else { + buf[len++] = *ptr; } + ptr++; } tinput_enqueue(input, buf, len); @@ -355,21 +309,20 @@ static void forward_modified_utf8(TermInput *input, TermKeyKey *key) (KittyKey)key->code.codepoint)) { handle_kitty_key_protocol(input, key); return; - } else { - // Termkey doesn't include the S- modifier for ASCII characters (e.g., - // ctrl-shift-l is <C-L> instead of <C-S-L>. Vim, on the other hand, - // treats <C-L> and <C-l> the same, requiring the S- modifier. - len = termkey_strfkey(input->tk, buf, sizeof(buf), key, TERMKEY_FORMAT_VIM); - if ((key->modifiers & TERMKEY_KEYMOD_CTRL) - && !(key->modifiers & TERMKEY_KEYMOD_SHIFT) - && ASCII_ISUPPER(key->code.codepoint)) { - assert(len <= 62); - // Make room for the S- - memmove(buf + 3, buf + 1, len - 1); - buf[1] = 'S'; - buf[2] = '-'; - len += 2; - } + } + // Termkey doesn't include the S- modifier for ASCII characters (e.g., + // ctrl-shift-l is <C-L> instead of <C-S-L>. Vim, on the other hand, + // treats <C-L> and <C-l> the same, requiring the S- modifier. + len = termkey_strfkey(input->tk, buf, sizeof(buf), key, TERMKEY_FORMAT_VIM); + if ((key->modifiers & TERMKEY_KEYMOD_CTRL) + && !(key->modifiers & TERMKEY_KEYMOD_SHIFT) + && ASCII_ISUPPER(key->code.codepoint)) { + assert(len <= 62); + // Make room for the S- + memmove(buf + 3, buf + 1, len - 1); + buf[1] = 'S'; + buf[2] = '-'; + len += 2; } } @@ -560,7 +513,10 @@ 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); - autocmd_schedule_focusgained(focus_gained); + + MAXSIZE_TEMP_ARRAY(args, 1); + ADD_C(args, BOOLEAN_OBJ(focus_gained)); + rpc_send_event(ui_client_channel_id, "nvim_ui_set_focus", args); return true; } return false; @@ -607,10 +563,14 @@ static HandleState handle_bracketed_paste(TermInput *input) return kNotApplicable; } -static void set_bg_deferred(void **argv) +static void set_bg(char *bgvalue) { - char *bgvalue = argv[0]; - set_tty_background(bgvalue); + if (ui_client_attached) { + MAXSIZE_TEMP_ARRAY(args, 2); + ADD_C(args, STRING_OBJ(cstr_as_string("term_background"))); + ADD_C(args, STRING_OBJ(cstr_as_string(bgvalue))); + rpc_send_event(ui_client_channel_id, "nvim_ui_set_option", args); + } } // During startup, tui.c requests the background color (see `ext.get_bg`). @@ -692,10 +652,11 @@ static HandleState handle_background_color(TermInput *input) double g = (double)rgb[1] / (double)rgb_max[1]; double b = (double)rgb[2] / (double)rgb_max[2]; double luminance = (0.299 * r) + (0.587 * g) + (0.114 * b); // CCIR 601 - char *bgvalue = luminance < 0.5 ? "dark" : "light"; + bool is_dark = luminance < 0.5; + char *bgvalue = is_dark ? "dark" : "light"; DLOG("bg response: %s", bgvalue); - loop_schedule_deferred(&main_loop, - event_create(set_bg_deferred, 1, bgvalue)); + ui_client_bg_response = is_dark ? kTrue : kFalse; + set_bg(bgvalue); input->waiting_for_bg_response = 0; } else if (!done && !bad) { // An incomplete sequence was found, waiting for the next input. diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h index 0b60394850..5df108b107 100644 --- a/src/nvim/tui/input.h +++ b/src/nvim/tui/input.h @@ -2,10 +2,14 @@ #define NVIM_TUI_INPUT_H #include <stdbool.h> +#include <stdint.h> #include <termkey.h> +#include <uv.h> +#include "nvim/event/loop.h" #include "nvim/event/stream.h" #include "nvim/event/time.h" +#include "nvim/rbuffer.h" #include "nvim/tui/input_defs.h" #include "nvim/tui/tui.h" diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c index 229e340dc3..d630a34ce2 100644 --- a/src/nvim/tui/terminfo.c +++ b/src/nvim/tui/terminfo.c @@ -7,15 +7,20 @@ #include <string.h> #include <unibilium.h> -#include "nvim/globals.h" -#include "nvim/log.h" +#include "klib/kvec.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" +#include "nvim/ascii.h" +#include "nvim/charset.h" #include "nvim/memory.h" -#include "nvim/message.h" -#include "nvim/option.h" -#include "nvim/os/os.h" +#include "nvim/strings.h" #include "nvim/tui/terminfo.h" #include "nvim/tui/terminfo_defs.h" +#ifdef __FreeBSD__ +# include "nvim/os/os.h" +#endif + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tui/terminfo.c.generated.h" #endif @@ -145,82 +150,80 @@ unibi_term *terminfo_from_builtin(const char *term, char **termname) /// Serves a similar purpose as Vim `:set termcap` (removed in Nvim). /// /// @note adapted from unibilium unibi-dump.c -void terminfo_info_msg(const unibi_term *const ut) +/// @return allocated string +String terminfo_info_msg(const unibi_term *ut, const char *termname) { - if (exiting) { - return; - } - msg_puts_title("\n\n--- Terminal info --- {{{\n"); + StringBuilder data = KV_INITIAL_VALUE; - char *term; - get_tty_option("term", &term); - msg_printf_attr(0, "&term: %s\n", term); - msg_printf_attr(0, "Description: %s\n", unibi_get_name(ut)); + kv_printf(data, "&term: %s\n", termname); + kv_printf(data, "Description: %s\n", unibi_get_name(ut)); const char **a = unibi_get_aliases(ut); if (*a) { - msg_puts("Aliases: "); + kv_printf(data, "Aliases: "); do { - msg_printf_attr(0, "%s%s\n", *a, a[1] ? " | " : ""); + kv_printf(data, "%s%s\n", *a, a[1] ? " | " : ""); a++; } while (*a); } - msg_puts("Boolean capabilities:\n"); + kv_printf(data, "Boolean capabilities:\n"); for (enum unibi_boolean i = unibi_boolean_begin_ + 1; i < unibi_boolean_end_; i++) { - msg_printf_attr(0, " %-25s %-10s = %s\n", unibi_name_bool(i), - unibi_short_name_bool(i), - unibi_get_bool(ut, i) ? "true" : "false"); + kv_printf(data, " %-25s %-10s = %s\n", unibi_name_bool(i), + unibi_short_name_bool(i), + unibi_get_bool(ut, i) ? "true" : "false"); } - msg_puts("Numeric capabilities:\n"); + kv_printf(data, "Numeric capabilities:\n"); for (enum unibi_numeric i = unibi_numeric_begin_ + 1; i < unibi_numeric_end_; i++) { int n = unibi_get_num(ut, i); // -1 means "empty" - msg_printf_attr(0, " %-25s %-10s = %d\n", unibi_name_num(i), - unibi_short_name_num(i), n); + kv_printf(data, " %-25s %-10s = %d\n", unibi_name_num(i), + unibi_short_name_num(i), n); } - msg_puts("String capabilities:\n"); + kv_printf(data, "String capabilities:\n"); for (enum unibi_string i = unibi_string_begin_ + 1; i < unibi_string_end_; i++) { const char *s = unibi_get_str(ut, i); if (s) { - msg_printf_attr(0, " %-25s %-10s = ", unibi_name_str(i), - unibi_short_name_str(i)); + kv_printf(data, " %-25s %-10s = ", unibi_name_str(i), + unibi_short_name_str(i)); // Most of these strings will contain escape sequences. - msg_outtrans_special(s, false, 0); - msg_putchar('\n'); + kv_transstr(&data, s, false); + kv_push(data, '\n'); } } if (unibi_count_ext_bool(ut)) { - msg_puts("Extended boolean capabilities:\n"); + kv_printf(data, "Extended boolean capabilities:\n"); for (size_t i = 0; i < unibi_count_ext_bool(ut); i++) { - msg_printf_attr(0, " %-25s = %s\n", - unibi_get_ext_bool_name(ut, i), - unibi_get_ext_bool(ut, i) ? "true" : "false"); + kv_printf(data, " %-25s = %s\n", + unibi_get_ext_bool_name(ut, i), + unibi_get_ext_bool(ut, i) ? "true" : "false"); } } if (unibi_count_ext_num(ut)) { - msg_puts("Extended numeric capabilities:\n"); + kv_printf(data, "Extended numeric capabilities:\n"); for (size_t i = 0; i < unibi_count_ext_num(ut); i++) { - msg_printf_attr(0, " %-25s = %d\n", - unibi_get_ext_num_name(ut, i), - unibi_get_ext_num(ut, i)); + kv_printf(data, " %-25s = %d\n", + unibi_get_ext_num_name(ut, i), + unibi_get_ext_num(ut, i)); } } if (unibi_count_ext_str(ut)) { - msg_puts("Extended string capabilities:\n"); + kv_printf(data, "Extended string capabilities:\n"); for (size_t i = 0; i < unibi_count_ext_str(ut); i++) { - msg_printf_attr(0, " %-25s = ", unibi_get_ext_str_name(ut, i)); - msg_outtrans_special(unibi_get_ext_str(ut, i), false, 0); - msg_putchar('\n'); + kv_printf(data, " %-25s = ", unibi_get_ext_str_name(ut, i)); + // NOTE: unibi_get_ext_str(ut, i) might be NULL, as termcap + // might include junk data on mac os. kv_transstr will handle this. + kv_transstr(&data, unibi_get_ext_str(ut, i), false); + kv_push(data, '\n'); } } + kv_push(data, NUL); - msg_puts("}}}\n"); - xfree(term); + return cbuf_as_string(data.items, data.size - 1); } diff --git a/src/nvim/tui/terminfo.h b/src/nvim/tui/terminfo.h index 099df8967f..178d384457 100644 --- a/src/nvim/tui/terminfo.h +++ b/src/nvim/tui/terminfo.h @@ -3,6 +3,8 @@ #include <unibilium.h> +#include "nvim/api/private/defs.h" + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tui/terminfo.h.generated.h" #endif diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 1cb1c34ad3..a50e44f7a3 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1,48 +1,48 @@ // 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 -// Terminal UI functions. Invoked (by ui_bridge.c) on the TUI thread. +// Terminal UI functions. Invoked (by ui_client.c) on the UI process. #include <assert.h> -#include <limits.h> +#include <signal.h> #include <stdbool.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> #include <unibilium.h> #include <uv.h> -#if defined(HAVE_TERMIOS_H) -# include <termios.h> -#endif +#include "auto/config.h" #include "klib/kvec.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" #include "nvim/ascii.h" +#include "nvim/cursor_shape.h" +#include "nvim/event/defs.h" #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" #include "nvim/event/signal.h" -#include "nvim/highlight.h" +#include "nvim/event/stream.h" +#include "nvim/globals.h" +#include "nvim/grid_defs.h" +#include "nvim/highlight_defs.h" #include "nvim/log.h" +#include "nvim/macros.h" #include "nvim/main.h" -#include "nvim/map.h" +#include "nvim/mbyte.h" #include "nvim/memory.h" -#include "nvim/option.h" +#include "nvim/msgpack_rpc/channel.h" #include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os/signal.h" -#include "nvim/os/tty.h" -#include "nvim/ui.h" -#include "nvim/vim.h" +#include "nvim/ui_client.h" #ifdef MSWIN # include "nvim/os/os_win_console.h" #endif -#include "nvim/cursor_shape.h" -#include "nvim/macros.h" -#include "nvim/strings.h" -#include "nvim/syntax.h" #include "nvim/tui/input.h" #include "nvim/tui/terminfo.h" #include "nvim/tui/tui.h" #include "nvim/ugrid.h" -#include "nvim/ui_bridge.h" +#include "nvim/ui.h" // Space reserved in two output buffers to make the cursor normal or invisible // when flushing. No existing terminal will require 32 bytes to do that. @@ -63,8 +63,21 @@ do { \ (var) = unibi_var_from_num((num)); \ } while (0) +# define UNIBI_SET_STR_VAR(var, str) \ + do { \ + (var) = unibi_var_from_str((str)); \ + } while (0) #else -# define UNIBI_SET_NUM_VAR(var, num) (var).i = (num); +# define UNIBI_SET_NUM_VAR(var, num) \ + do { \ + (var).p = NULL; \ + (var).i = (num); \ + } while (0) +# define UNIBI_SET_STR_VAR(var, str) \ + do { \ + (var).i = INT_MIN; \ + (var).p = str; \ + } while (0) #endif typedef struct { @@ -72,7 +85,6 @@ typedef struct { } Rect; struct TUIData { - UIBridgeData *bridge; Loop *loop; unibi_var_t params[9]; char buf[OUTBUF_SIZE]; @@ -83,13 +95,14 @@ struct TUIData { TermInput input; uv_loop_t write_loop; unibi_term *ut; + char *term; // value of $TERM union { uv_tty_t tty; uv_pipe_t pipe; } output_handle; bool out_isatty; - SignalWatcher winch_handle, cont_handle; - bool cont_received; + SignalWatcher winch_handle; + uv_timer_t startup_delay_timer; UGrid grid; kvec_t(Rect) invalid_regions; int row, col; @@ -106,6 +119,7 @@ struct TUIData { bool mouse_move_enabled; bool busy, is_invisible, want_invisible; bool cork, overflow; + bool set_cursor_color_as_str; bool cursor_color_changed; bool is_starting; FILE *screenshot; @@ -116,12 +130,14 @@ struct TUIData { bool default_attr; bool can_clear_attr; ModeShape showing_mode; + Integer verbose; struct { int enable_mouse, disable_mouse; int enable_mouse_move, disable_mouse_move; int enable_bracketed_paste, disable_bracketed_paste; int enable_lr_margin, disable_lr_margin; int enter_strikethrough_mode; + int enter_altfont_mode; int set_rgb_foreground, set_rgb_background; int set_cursor_color; int reset_cursor_color; @@ -137,155 +153,143 @@ struct TUIData { int get_extkeys; } unibi_ext; char *space_buf; + bool stopped; + int width; + int height; + bool rgb; }; static int got_winch = 0; static bool cursor_style_enabled = false; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tui/tui.c.generated.h" #endif -UI *tui_start(void) -{ - UI *ui = xcalloc(1, sizeof(UI)); // Freed by ui_bridge_stop(). - ui->stop = tui_stop; - ui->grid_resize = tui_grid_resize; - ui->grid_clear = tui_grid_clear; - ui->grid_cursor_goto = tui_grid_cursor_goto; - ui->mode_info_set = tui_mode_info_set; - ui->update_menu = tui_update_menu; - ui->busy_start = tui_busy_start; - ui->busy_stop = tui_busy_stop; - ui->mouse_on = tui_mouse_on; - ui->mouse_off = tui_mouse_off; - ui->mode_change = tui_mode_change; - ui->grid_scroll = tui_grid_scroll; - ui->hl_attr_define = tui_hl_attr_define; - ui->bell = tui_bell; - ui->visual_bell = tui_visual_bell; - ui->default_colors_set = tui_default_colors_set; - ui->flush = tui_flush; - ui->suspend = tui_suspend; - ui->set_title = tui_set_title; - ui->set_icon = tui_set_icon; - ui->screenshot = tui_screenshot; - ui->option_set = tui_option_set; - ui->raw_line = tui_raw_line; - - CLEAR_FIELD(ui->ui_ext); - ui->ui_ext[kUILinegrid] = true; - ui->ui_ext[kUITermColors] = true; - - return ui_bridge_attach(ui, tui_main, tui_scheduler); -} - -void tui_enable_extkeys(TUIData *data) -{ - TermInput input = data->input; - unibi_term *ut = data->ut; - UI *ui = data->bridge->ui; +TUIData *tui_start(int *width, int *height, char **term) +{ + TUIData *tui = xcalloc(1, sizeof(TUIData)); + tui->is_starting = true; + tui->screenshot = NULL; + tui->stopped = false; + tui->loop = &main_loop; + kv_init(tui->invalid_regions); + signal_watcher_init(tui->loop, &tui->winch_handle, tui); + + // TODO(bfredl): zero hl is empty, send this explicitly? + kv_push(tui->attrs, HLATTRS_INIT); + + tui->input.tk_ti_hook_fn = tui_tk_ti_getstr; + tinput_init(&tui->input, &main_loop); + ugrid_init(&tui->grid); + tui_terminal_start(tui); + + uv_timer_init(&tui->loop->uv, &tui->startup_delay_timer); + tui->startup_delay_timer.data = tui; + uv_timer_start(&tui->startup_delay_timer, after_startup_cb, + 100, 0); + + loop_poll_events(&main_loop, 1); + *width = tui->width; + *height = tui->height; + *term = tui->term; + return tui; +} + +void tui_enable_extkeys(TUIData *tui) +{ + TermInput input = tui->input; + unibi_term *ut = tui->ut; switch (input.extkeys_type) { case kExtkeysCSIu: - data->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys", - "\x1b[>1u"); - data->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys", - "\x1b[<1u"); + tui->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys", + "\x1b[>1u"); + tui->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys", + "\x1b[<1u"); break; case kExtkeysXterm: - 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"); + tui->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys", + "\x1b[>4;2m"); + tui->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys", + "\x1b[>4;0m"); break; default: break; } - unibi_out_ext(ui, data->unibi_ext.enable_extended_keys); + unibi_out_ext(tui, tui->unibi_ext.enable_extended_keys); } -static size_t unibi_pre_fmt_str(TUIData *data, unsigned int unibi_index, char *buf, size_t len) +static size_t unibi_pre_fmt_str(TUIData *tui, unsigned int unibi_index, char *buf, size_t len) { - const char *str = unibi_get_str(data->ut, unibi_index); + const char *str = unibi_get_str(tui->ut, unibi_index); if (!str) { return 0U; } - return unibi_run(str, data->params, buf, len); -} - -static void termname_set_event(void **argv) -{ - char *termname = argv[0]; - set_tty_option("term", termname); - // Do not free termname, it is freed by set_tty_option. -} - -static void terminfo_start(UI *ui) -{ - TUIData *data = ui->data; - data->scroll_region_is_full_screen = true; - data->bufpos = 0; - data->default_attr = false; - data->can_clear_attr = false; - data->is_invisible = true; - data->want_invisible = false; - data->busy = false; - data->cork = false; - data->overflow = false; - data->cursor_color_changed = false; - data->showing_mode = SHAPE_IDX_N; - data->unibi_ext.enable_mouse = -1; - data->unibi_ext.disable_mouse = -1; - data->unibi_ext.enable_mouse_move = -1; - data->unibi_ext.disable_mouse_move = -1; - data->unibi_ext.set_cursor_color = -1; - data->unibi_ext.reset_cursor_color = -1; - data->unibi_ext.enable_bracketed_paste = -1; - data->unibi_ext.disable_bracketed_paste = -1; - data->unibi_ext.enter_strikethrough_mode = -1; - data->unibi_ext.enable_lr_margin = -1; - data->unibi_ext.disable_lr_margin = -1; - data->unibi_ext.enable_focus_reporting = -1; - data->unibi_ext.disable_focus_reporting = -1; - data->unibi_ext.resize_screen = -1; - data->unibi_ext.reset_scroll_region = -1; - data->unibi_ext.set_cursor_style = -1; - 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->unibi_ext.get_extkeys = -1; - data->out_fd = STDOUT_FILENO; - data->out_isatty = os_isatty(data->out_fd); - data->input.tui_data = data; + return unibi_run(str, tui->params, buf, len); +} + +static void terminfo_start(TUIData *tui) +{ + tui->scroll_region_is_full_screen = true; + tui->bufpos = 0; + tui->default_attr = false; + tui->can_clear_attr = false; + tui->is_invisible = true; + tui->want_invisible = false; + tui->busy = false; + tui->cork = false; + tui->overflow = false; + tui->set_cursor_color_as_str = false; + tui->cursor_color_changed = false; + tui->showing_mode = SHAPE_IDX_N; + tui->unibi_ext.enable_mouse = -1; + tui->unibi_ext.disable_mouse = -1; + tui->unibi_ext.enable_mouse_move = -1; + tui->unibi_ext.disable_mouse_move = -1; + tui->unibi_ext.set_cursor_color = -1; + tui->unibi_ext.reset_cursor_color = -1; + tui->unibi_ext.enable_bracketed_paste = -1; + tui->unibi_ext.disable_bracketed_paste = -1; + tui->unibi_ext.enter_strikethrough_mode = -1; + tui->unibi_ext.enter_altfont_mode = -1; + tui->unibi_ext.enable_lr_margin = -1; + tui->unibi_ext.disable_lr_margin = -1; + tui->unibi_ext.enable_focus_reporting = -1; + tui->unibi_ext.disable_focus_reporting = -1; + tui->unibi_ext.resize_screen = -1; + tui->unibi_ext.reset_scroll_region = -1; + tui->unibi_ext.set_cursor_style = -1; + tui->unibi_ext.reset_cursor_style = -1; + tui->unibi_ext.get_bg = -1; + tui->unibi_ext.set_underline_color = -1; + tui->unibi_ext.enable_extended_keys = -1; + tui->unibi_ext.disable_extended_keys = -1; + tui->unibi_ext.get_extkeys = -1; + tui->out_fd = STDOUT_FILENO; + tui->out_isatty = os_isatty(tui->out_fd); + tui->input.tui_data = tui; const char *term = os_getenv("TERM"); #ifdef MSWIN - os_tty_guess_term(&term, data->out_fd); + os_tty_guess_term(&term, tui->out_fd); os_setenv("TERM", term, 1); // Old os_getenv() pointer is invalid after os_setenv(), fetch it again. term = os_getenv("TERM"); #endif // Set up unibilium/terminfo. - char *termname = NULL; if (term) { - os_env_var_lock(); - data->ut = unibi_from_term(term); - os_env_var_unlock(); - if (data->ut) { - termname = xstrdup(term); + tui->ut = unibi_from_term(term); + if (tui->ut) { + if (!tui->term) { + tui->term = xstrdup(term); + } } } - if (!data->ut) { - data->ut = terminfo_from_builtin(term, &termname); + if (!tui->ut) { + tui->ut = terminfo_from_builtin(term, &tui->term); } - // Update 'term' option. - loop_schedule_deferred(&main_loop, - event_create(termname_set_event, 1, termname)); // None of the following work over SSH; see :help TERM . const char *colorterm = os_getenv("COLORTERM"); @@ -302,59 +306,59 @@ static void terminfo_start(UI *ui) long konsolev = konsolev_env ? strtol(konsolev_env, NULL, 10) : (konsole ? 1 : 0); - patch_terminfo_bugs(data, term, colorterm, vtev, konsolev, iterm_env, nsterm); - augment_terminfo(data, term, vtev, konsolev, iterm_env, nsterm); - data->can_change_scroll_region = - !!unibi_get_str(data->ut, unibi_change_scroll_region); - data->can_set_lr_margin = - !!unibi_get_str(data->ut, unibi_set_lr_margin); - data->can_set_left_right_margin = - !!unibi_get_str(data->ut, unibi_set_left_margin_parm) - && !!unibi_get_str(data->ut, unibi_set_right_margin_parm); - data->can_scroll = - !!unibi_get_str(data->ut, unibi_delete_line) - && !!unibi_get_str(data->ut, unibi_parm_delete_line) - && !!unibi_get_str(data->ut, unibi_insert_line) - && !!unibi_get_str(data->ut, unibi_parm_insert_line); - data->can_erase_chars = !!unibi_get_str(data->ut, unibi_erase_chars); - data->immediate_wrap_after_last_column = + patch_terminfo_bugs(tui, term, colorterm, vtev, konsolev, iterm_env, nsterm); + augment_terminfo(tui, term, vtev, konsolev, iterm_env, nsterm); + tui->can_change_scroll_region = + !!unibi_get_str(tui->ut, unibi_change_scroll_region); + tui->can_set_lr_margin = + !!unibi_get_str(tui->ut, unibi_set_lr_margin); + tui->can_set_left_right_margin = + !!unibi_get_str(tui->ut, unibi_set_left_margin_parm) + && !!unibi_get_str(tui->ut, unibi_set_right_margin_parm); + tui->can_scroll = + !!unibi_get_str(tui->ut, unibi_delete_line) + && !!unibi_get_str(tui->ut, unibi_parm_delete_line) + && !!unibi_get_str(tui->ut, unibi_insert_line) + && !!unibi_get_str(tui->ut, unibi_parm_insert_line); + tui->can_erase_chars = !!unibi_get_str(tui->ut, unibi_erase_chars); + tui->immediate_wrap_after_last_column = terminfo_is_term_family(term, "conemu") || terminfo_is_term_family(term, "cygwin") || terminfo_is_term_family(term, "win32con") || terminfo_is_term_family(term, "interix"); - data->bce = unibi_get_bool(data->ut, unibi_back_color_erase); - data->normlen = unibi_pre_fmt_str(data, unibi_cursor_normal, - data->norm, sizeof data->norm); - data->invislen = unibi_pre_fmt_str(data, unibi_cursor_invisible, - data->invis, sizeof data->invis); + tui->bce = unibi_get_bool(tui->ut, unibi_back_color_erase); + tui->normlen = unibi_pre_fmt_str(tui, unibi_cursor_normal, + tui->norm, sizeof tui->norm); + tui->invislen = unibi_pre_fmt_str(tui, unibi_cursor_invisible, + tui->invis, sizeof tui->invis); // Set 't_Co' from the result of unibilium & fix_terminfo. - t_colors = unibi_get_num(data->ut, unibi_max_colors); + t_colors = unibi_get_num(tui->ut, unibi_max_colors); // Enter alternate screen, save title, and clear. // NOTE: Do this *before* changing terminal settings. #6433 - unibi_out(ui, unibi_enter_ca_mode); + unibi_out(tui, unibi_enter_ca_mode); // Save title/icon to the "stack". #4063 - unibi_out_ext(ui, data->unibi_ext.save_title); - unibi_out(ui, unibi_keypad_xmit); - unibi_out(ui, unibi_clear_screen); + unibi_out_ext(tui, tui->unibi_ext.save_title); + unibi_out(tui, unibi_keypad_xmit); + unibi_out(tui, unibi_clear_screen); // Ask the terminal to send us the background color. - data->input.waiting_for_bg_response = 5; - unibi_out_ext(ui, data->unibi_ext.get_bg); + tui->input.waiting_for_bg_response = 5; + unibi_out_ext(tui, tui->unibi_ext.get_bg); // Enable bracketed paste - unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); + unibi_out_ext(tui, tui->unibi_ext.enable_bracketed_paste); // Query the terminal to see if it supports CSI u - data->input.waiting_for_csiu_response = 5; - unibi_out_ext(ui, data->unibi_ext.get_extkeys); + tui->input.waiting_for_csiu_response = 5; + unibi_out_ext(tui, tui->unibi_ext.get_extkeys); int ret; - uv_loop_init(&data->write_loop); - if (data->out_isatty) { - ret = uv_tty_init(&data->write_loop, &data->output_handle.tty, data->out_fd, 0); + uv_loop_init(&tui->write_loop); + if (tui->out_isatty) { + ret = uv_tty_init(&tui->write_loop, &tui->output_handle.tty, tui->out_fd, 0); if (ret) { ELOG("uv_tty_init failed: %s", uv_strerror(ret)); } #ifdef MSWIN - ret = uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_RAW); + ret = uv_tty_set_mode(&tui->output_handle.tty, UV_TTY_MODE_RAW); if (ret) { ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret)); } @@ -362,7 +366,7 @@ static void terminfo_start(UI *ui) int retry_count = 10; // A signal may cause uv_tty_set_mode() to fail (e.g., SIGCONT). Retry a // few times. #12322 - while ((ret = uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO)) == UV_EINTR + while ((ret = uv_tty_set_mode(&tui->output_handle.tty, UV_TTY_MODE_IO)) == UV_EINTR && retry_count > 0) { retry_count--; } @@ -371,191 +375,135 @@ static void terminfo_start(UI *ui) } #endif } else { - ret = uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0); + ret = uv_pipe_init(&tui->write_loop, &tui->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); + ret = uv_pipe_open(&tui->output_handle.pipe, tui->out_fd); if (ret) { ELOG("uv_pipe_open failed: %s", uv_strerror(ret)); } } - flush_buf(ui); + flush_buf(tui); } -static void terminfo_stop(UI *ui) +static void terminfo_stop(TUIData *tui) { - TUIData *data = ui->data; // Destroy output stuff - tui_mode_change(ui, (String)STRING_INIT, SHAPE_IDX_N); - tui_mouse_off(ui); - unibi_out(ui, unibi_exit_attribute_mode); + tui_mode_change(tui, (String)STRING_INIT, SHAPE_IDX_N); + tui_mouse_off(tui); + unibi_out(tui, unibi_exit_attribute_mode); // Reset cursor to normal before exiting alternate screen. - unibi_out(ui, unibi_cursor_normal); - unibi_out(ui, unibi_keypad_local); + unibi_out(tui, unibi_cursor_normal); + unibi_out(tui, unibi_keypad_local); // Disable extended keys before exiting alternate screen. - unibi_out_ext(ui, data->unibi_ext.disable_extended_keys); - unibi_out(ui, unibi_exit_ca_mode); + unibi_out_ext(tui, tui->unibi_ext.disable_extended_keys); + unibi_out(tui, unibi_exit_ca_mode); // Restore title/icon from the "stack". #4063 - unibi_out_ext(ui, data->unibi_ext.restore_title); - if (data->cursor_color_changed) { - unibi_out_ext(ui, data->unibi_ext.reset_cursor_color); + unibi_out_ext(tui, tui->unibi_ext.restore_title); + if (tui->cursor_color_changed) { + unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color); } // Disable bracketed paste - unibi_out_ext(ui, data->unibi_ext.disable_bracketed_paste); + unibi_out_ext(tui, tui->unibi_ext.disable_bracketed_paste); // Disable focus reporting - unibi_out_ext(ui, data->unibi_ext.disable_focus_reporting); - flush_buf(ui); + unibi_out_ext(tui, tui->unibi_ext.disable_focus_reporting); + flush_buf(tui); uv_tty_reset_mode(); - uv_close((uv_handle_t *)&data->output_handle, NULL); - uv_run(&data->write_loop, UV_RUN_DEFAULT); - if (uv_loop_close(&data->write_loop)) { + uv_close((uv_handle_t *)&tui->output_handle, NULL); + uv_run(&tui->write_loop, UV_RUN_DEFAULT); + if (uv_loop_close(&tui->write_loop)) { abort(); } - unibi_destroy(data->ut); + unibi_destroy(tui->ut); } -static void tui_terminal_start(UI *ui) +static void tui_terminal_start(TUIData *tui) { - TUIData *data = ui->data; - data->print_attr_id = -1; - ugrid_init(&data->grid); - terminfo_start(ui); - tui_guess_size(ui); - signal_watcher_start(&data->winch_handle, sigwinch_cb, SIGWINCH); - tinput_start(&data->input); + tui->print_attr_id = -1; + terminfo_start(tui); + tui_guess_size(tui); + signal_watcher_start(&tui->winch_handle, sigwinch_cb, SIGWINCH); + tinput_start(&tui->input); } -static void tui_terminal_after_startup(UI *ui) - FUNC_ATTR_NONNULL_ALL +static void after_startup_cb(uv_timer_t *handle) { - TUIData *data = ui->data; + TUIData *tui = handle->data; + tui_terminal_after_startup(tui); +} +static void tui_terminal_after_startup(TUIData *tui) + FUNC_ATTR_NONNULL_ALL +{ // Emit this after Nvim startup, not during. This works around a tmux // 2.3 bug(?) which caused slow drawing during startup. #7649 - unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting); - flush_buf(ui); + unibi_out_ext(tui, tui->unibi_ext.enable_focus_reporting); + flush_buf(tui); } -static void tui_terminal_stop(UI *ui) +/// stop the terminal but allow it to restart later (like after suspend) +static void tui_terminal_stop(TUIData *tui) { - TUIData *data = ui->data; - if (uv_is_closing(STRUCT_CAST(uv_handle_t, &data->output_handle))) { + if (uv_is_closing(STRUCT_CAST(uv_handle_t, &tui->output_handle))) { // Race between SIGCONT (tui.c) and SIGHUP (os/signal.c)? #8075 ELOG("TUI already stopped (race?)"); - ui->data = NULL; // Flag UI as "stopped". + tui->stopped = true; return; } - tinput_stop(&data->input); - signal_watcher_stop(&data->winch_handle); - terminfo_stop(ui); - ugrid_free(&data->grid); + tinput_stop(&tui->input); + signal_watcher_stop(&tui->winch_handle); + terminfo_stop(tui); } -static void tui_stop(UI *ui) +void tui_stop(TUIData *tui) { - tui_terminal_stop(ui); - ui->data = NULL; // Flag UI as "stopped". + tui_terminal_stop(tui); + stream_set_blocking(tui->input.in_fd, true); // normalize stream (#2598) + tinput_destroy(&tui->input); + tui->stopped = true; + signal_watcher_close(&tui->winch_handle, NULL); + uv_close((uv_handle_t *)&tui->startup_delay_timer, NULL); } /// Returns true if UI `ui` is stopped. -static bool tui_is_stopped(UI *ui) -{ - return ui->data == NULL; -} - -/// Main function of the TUI thread. -static void tui_main(UIBridgeData *bridge, UI *ui) -{ - Loop tui_loop; - loop_init(&tui_loop, NULL); - TUIData *data = xcalloc(1, sizeof(TUIData)); - ui->data = data; - data->bridge = bridge; - data->loop = &tui_loop; - data->is_starting = true; - data->screenshot = NULL; - kv_init(data->invalid_regions); - signal_watcher_init(data->loop, &data->winch_handle, ui); - signal_watcher_init(data->loop, &data->cont_handle, data); -#ifdef UNIX - signal_watcher_start(&data->cont_handle, sigcont_cb, SIGCONT); -#endif - - // TODO(bfredl): zero hl is empty, send this explicitly? - kv_push(data->attrs, HLATTRS_INIT); - - data->input.tk_ti_hook_fn = tui_tk_ti_getstr; - tinput_init(&data->input, &tui_loop); - tui_terminal_start(ui); - - // Allow main thread to continue, we are ready to handle UI callbacks. - CONTINUE(bridge); - - loop_schedule_deferred(&main_loop, - event_create(show_termcap_event, 1, data->ut)); - - // "Active" loop: first ~100 ms of startup. - for (size_t ms = 0; ms < 100 && !tui_is_stopped(ui);) { - ms += (loop_poll_events(&tui_loop, 20) ? 20 : 1); - } - if (!tui_is_stopped(ui)) { - tui_terminal_after_startup(ui); - } - // "Passive" (I/O-driven) loop: TUI thread "main loop". - while (!tui_is_stopped(ui)) { - loop_poll_events(&tui_loop, -1); // tui_loop.events is never processed - } - - ui_bridge_stopped(bridge); - tinput_destroy(&data->input); - signal_watcher_stop(&data->cont_handle); - signal_watcher_close(&data->cont_handle, NULL); - signal_watcher_close(&data->winch_handle, NULL); - loop_close(&tui_loop, false); - kv_destroy(data->invalid_regions); - kv_destroy(data->attrs); - xfree(data->space_buf); - xfree(data); -} - -/// Handoff point between the main (ui_bridge) thread and the TUI thread. -static void tui_scheduler(Event event, void *d) +static bool tui_is_stopped(TUIData *tui) { - UI *ui = d; - TUIData *data = ui->data; - loop_schedule_fast(data->loop, event); // `tui_loop` local to tui_main(). + return tui->stopped; } -#ifdef UNIX -static void sigcont_cb(SignalWatcher *watcher, int signum, void *data) +#ifdef EXITFREE +void tui_free_all_mem(TUIData *tui) { - ((TUIData *)data)->cont_received = true; + ugrid_free(&tui->grid); + kv_destroy(tui->invalid_regions); + kv_destroy(tui->attrs); + xfree(tui->space_buf); + xfree(tui->term); + xfree(tui); } #endif -static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data) +static void sigwinch_cb(SignalWatcher *watcher, int signum, void *cbdata) { got_winch++; - UI *ui = data; - if (tui_is_stopped(ui)) { + TUIData *tui = cbdata; + if (tui_is_stopped(tui)) { return; } - tui_guess_size(ui); - ui_schedule_refresh(); + tui_guess_size(tui); } -static bool attrs_differ(UI *ui, int id1, int id2, bool rgb) +static bool attrs_differ(TUIData *tui, int id1, int id2, bool rgb) { - TUIData *data = ui->data; if (id1 == id2) { return false; } else if (id1 < 0 || id2 < 0) { return true; } - HlAttrs a1 = kv_A(data->attrs, (size_t)id1); - HlAttrs a2 = kv_A(data->attrs, (size_t)id2); + HlAttrs a1 = kv_A(tui->attrs, (size_t)id1); + HlAttrs a2 = kv_A(tui->attrs, (size_t)id2); if (rgb) { return a1.rgb_fg_color != a2.rgb_fg_color @@ -566,42 +514,42 @@ 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_ANY_UNDERLINE + || (a1.cterm_ae_attr & HL_UNDERLINE_MASK && a1.rgb_sp_color != a2.rgb_sp_color); } } -static void update_attrs(UI *ui, int attr_id) +static void update_attrs(TUIData *tui, int attr_id) { - TUIData *data = ui->data; - - if (!attrs_differ(ui, attr_id, data->print_attr_id, ui->rgb)) { - data->print_attr_id = attr_id; + if (!attrs_differ(tui, attr_id, tui->print_attr_id, tui->rgb)) { + tui->print_attr_id = attr_id; return; } - data->print_attr_id = attr_id; - HlAttrs attrs = kv_A(data->attrs, (size_t)attr_id); - int attr = ui->rgb ? attrs.rgb_ae_attr : attrs.cterm_ae_attr; + tui->print_attr_id = attr_id; + HlAttrs attrs = kv_A(tui->attrs, (size_t)attr_id); + int attr = tui->rgb ? attrs.rgb_ae_attr : attrs.cterm_ae_attr; bool bold = attr & HL_BOLD; bool italic = attr & HL_ITALIC; bool reverse = attr & HL_INVERSE; bool standout = attr & HL_STANDOUT; bool strikethrough = attr & HL_STRIKETHROUGH; + bool altfont = attr & HL_ALTFONT; bool underline; bool undercurl; bool underdouble; bool underdotted; bool underdashed; - if (data->unibi_ext.set_underline_style != -1) { - underline = attr & HL_UNDERLINE; - undercurl = attr & HL_UNDERCURL; - underdouble = attr & HL_UNDERDOUBLE; - underdashed = attr & HL_UNDERDASHED; - underdotted = attr & HL_UNDERDOTTED; + if (tui->unibi_ext.set_underline_style != -1) { + int ul = attr & HL_UNDERLINE_MASK; + underline = ul == HL_UNDERLINE; + undercurl = ul == HL_UNDERCURL; + underdouble = ul == HL_UNDERDOUBLE; + underdashed = ul == HL_UNDERDASHED; + underdotted = ul == HL_UNDERDOTTED; } else { - underline = attr & HL_ANY_UNDERLINE; + underline = attr & HL_UNDERLINE_MASK; undercurl = false; underdouble = false; underdotted = false; @@ -611,126 +559,128 @@ static void update_attrs(UI *ui, int attr_id) bool has_any_underline = undercurl || underline || underdouble || underdotted || underdashed; - if (unibi_get_str(data->ut, unibi_set_attributes)) { + if (unibi_get_str(tui->ut, unibi_set_attributes)) { if (bold || reverse || underline || standout) { - UNIBI_SET_NUM_VAR(data->params[0], standout); - UNIBI_SET_NUM_VAR(data->params[1], underline); - UNIBI_SET_NUM_VAR(data->params[2], reverse); - UNIBI_SET_NUM_VAR(data->params[3], 0); // blink - UNIBI_SET_NUM_VAR(data->params[4], 0); // dim - UNIBI_SET_NUM_VAR(data->params[5], bold); - UNIBI_SET_NUM_VAR(data->params[6], 0); // blank - UNIBI_SET_NUM_VAR(data->params[7], 0); // protect - UNIBI_SET_NUM_VAR(data->params[8], 0); // alternate character set - unibi_out(ui, unibi_set_attributes); - } else if (!data->default_attr) { - unibi_out(ui, unibi_exit_attribute_mode); + UNIBI_SET_NUM_VAR(tui->params[0], standout); + UNIBI_SET_NUM_VAR(tui->params[1], underline); + UNIBI_SET_NUM_VAR(tui->params[2], reverse); + UNIBI_SET_NUM_VAR(tui->params[3], 0); // blink + UNIBI_SET_NUM_VAR(tui->params[4], 0); // dim + UNIBI_SET_NUM_VAR(tui->params[5], bold); + UNIBI_SET_NUM_VAR(tui->params[6], 0); // blank + UNIBI_SET_NUM_VAR(tui->params[7], 0); // protect + UNIBI_SET_NUM_VAR(tui->params[8], 0); // alternate character set + unibi_out(tui, unibi_set_attributes); + } else if (!tui->default_attr) { + unibi_out(tui, unibi_exit_attribute_mode); } } else { - if (!data->default_attr) { - unibi_out(ui, unibi_exit_attribute_mode); + if (!tui->default_attr) { + unibi_out(tui, unibi_exit_attribute_mode); } if (bold) { - unibi_out(ui, unibi_enter_bold_mode); + unibi_out(tui, unibi_enter_bold_mode); } if (underline) { - unibi_out(ui, unibi_enter_underline_mode); + unibi_out(tui, unibi_enter_underline_mode); } if (standout) { - unibi_out(ui, unibi_enter_standout_mode); + unibi_out(tui, unibi_enter_standout_mode); } if (reverse) { - unibi_out(ui, unibi_enter_reverse_mode); + unibi_out(tui, unibi_enter_reverse_mode); } } if (italic) { - unibi_out(ui, unibi_enter_italics_mode); + unibi_out(tui, unibi_enter_italics_mode); } - if (strikethrough && data->unibi_ext.enter_strikethrough_mode != -1) { - unibi_out_ext(ui, data->unibi_ext.enter_strikethrough_mode); + if (altfont && tui->unibi_ext.enter_altfont_mode != -1) { + unibi_out_ext(tui, tui->unibi_ext.enter_altfont_mode); } - 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 (strikethrough && tui->unibi_ext.enter_strikethrough_mode != -1) { + unibi_out_ext(tui, tui->unibi_ext.enter_strikethrough_mode); } - if (underdouble && 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 && tui->unibi_ext.set_underline_style != -1) { + UNIBI_SET_NUM_VAR(tui->params[0], 3); + unibi_out_ext(tui, tui->unibi_ext.set_underline_style); } - if (underdotted && 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 (underdouble && tui->unibi_ext.set_underline_style != -1) { + UNIBI_SET_NUM_VAR(tui->params[0], 2); + unibi_out_ext(tui, tui->unibi_ext.set_underline_style); } - if (underdashed && 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 (underdotted && tui->unibi_ext.set_underline_style != -1) { + UNIBI_SET_NUM_VAR(tui->params[0], 4); + unibi_out_ext(tui, tui->unibi_ext.set_underline_style); + } + if (underdashed && tui->unibi_ext.set_underline_style != -1) { + UNIBI_SET_NUM_VAR(tui->params[0], 5); + unibi_out_ext(tui, tui->unibi_ext.set_underline_style); } - if (has_any_underline && data->unibi_ext.set_underline_color != -1) { + if (has_any_underline && tui->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 - UNIBI_SET_NUM_VAR(data->params[1], (color >> 8) & 0xff); // green - UNIBI_SET_NUM_VAR(data->params[2], color & 0xff); // blue - unibi_out_ext(ui, data->unibi_ext.set_underline_color); + UNIBI_SET_NUM_VAR(tui->params[0], (color >> 16) & 0xff); // red + UNIBI_SET_NUM_VAR(tui->params[1], (color >> 8) & 0xff); // green + UNIBI_SET_NUM_VAR(tui->params[2], color & 0xff); // blue + unibi_out_ext(tui, tui->unibi_ext.set_underline_color); } } int fg, bg; - if (ui->rgb && !(attr & HL_FG_INDEXED)) { + if (tui->rgb && !(attr & HL_FG_INDEXED)) { fg = ((attrs.rgb_fg_color != -1) - ? attrs.rgb_fg_color : data->clear_attrs.rgb_fg_color); + ? attrs.rgb_fg_color : tui->clear_attrs.rgb_fg_color); if (fg != -1) { - UNIBI_SET_NUM_VAR(data->params[0], (fg >> 16) & 0xff); // red - UNIBI_SET_NUM_VAR(data->params[1], (fg >> 8) & 0xff); // green - UNIBI_SET_NUM_VAR(data->params[2], fg & 0xff); // blue - unibi_out_ext(ui, data->unibi_ext.set_rgb_foreground); + UNIBI_SET_NUM_VAR(tui->params[0], (fg >> 16) & 0xff); // red + UNIBI_SET_NUM_VAR(tui->params[1], (fg >> 8) & 0xff); // green + UNIBI_SET_NUM_VAR(tui->params[2], fg & 0xff); // blue + unibi_out_ext(tui, tui->unibi_ext.set_rgb_foreground); } } else { fg = (attrs.cterm_fg_color - ? attrs.cterm_fg_color - 1 : (data->clear_attrs.cterm_fg_color - 1)); + ? attrs.cterm_fg_color - 1 : (tui->clear_attrs.cterm_fg_color - 1)); if (fg != -1) { - UNIBI_SET_NUM_VAR(data->params[0], fg); - unibi_out(ui, unibi_set_a_foreground); + UNIBI_SET_NUM_VAR(tui->params[0], fg); + unibi_out(tui, unibi_set_a_foreground); } } - if (ui->rgb && !(attr & HL_BG_INDEXED)) { + if (tui->rgb && !(attr & HL_BG_INDEXED)) { bg = ((attrs.rgb_bg_color != -1) - ? attrs.rgb_bg_color : data->clear_attrs.rgb_bg_color); + ? attrs.rgb_bg_color : tui->clear_attrs.rgb_bg_color); if (bg != -1) { - UNIBI_SET_NUM_VAR(data->params[0], (bg >> 16) & 0xff); // red - UNIBI_SET_NUM_VAR(data->params[1], (bg >> 8) & 0xff); // green - UNIBI_SET_NUM_VAR(data->params[2], bg & 0xff); // blue - unibi_out_ext(ui, data->unibi_ext.set_rgb_background); + UNIBI_SET_NUM_VAR(tui->params[0], (bg >> 16) & 0xff); // red + UNIBI_SET_NUM_VAR(tui->params[1], (bg >> 8) & 0xff); // green + UNIBI_SET_NUM_VAR(tui->params[2], bg & 0xff); // blue + unibi_out_ext(tui, tui->unibi_ext.set_rgb_background); } } else { bg = (attrs.cterm_bg_color - ? attrs.cterm_bg_color - 1 : (data->clear_attrs.cterm_bg_color - 1)); + ? attrs.cterm_bg_color - 1 : (tui->clear_attrs.cterm_bg_color - 1)); if (bg != -1) { - UNIBI_SET_NUM_VAR(data->params[0], bg); - unibi_out(ui, unibi_set_a_background); + UNIBI_SET_NUM_VAR(tui->params[0], bg); + unibi_out(tui, unibi_set_a_background); } } - data->default_attr = fg == -1 && bg == -1 - && !bold && !italic && !has_any_underline && !reverse && !standout - && !strikethrough; + tui->default_attr = fg == -1 && bg == -1 + && !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 && !has_any_underline - && !strikethrough && (data->bce || bg == -1); + tui->can_clear_attr = !reverse && !standout && !has_any_underline + && !strikethrough && (tui->bce || bg == -1); } -static void final_column_wrap(UI *ui) +static void final_column_wrap(TUIData *tui) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; - if (grid->row != -1 && grid->col == ui->width) { + UGrid *grid = &tui->grid; + if (grid->row != -1 && grid->col == tui->width) { grid->col = 0; - if (grid->row < MIN(ui->height, grid->height - 1)) { + if (grid->row < MIN(tui->height, grid->height - 1)) { grid->row++; } } @@ -739,33 +689,31 @@ static void final_column_wrap(UI *ui) /// It is undocumented, but in the majority of terminals and terminal emulators /// printing at the right margin does not cause an automatic wrap until the /// next character is printed, holding the cursor in place until then. -static void print_cell(UI *ui, UCell *ptr) +static void print_cell(TUIData *tui, UCell *ptr) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; - if (!data->immediate_wrap_after_last_column) { + UGrid *grid = &tui->grid; + if (!tui->immediate_wrap_after_last_column) { // Printing the next character finally advances the cursor. - final_column_wrap(ui); + final_column_wrap(tui); } - update_attrs(ui, ptr->attr); - out(ui, ptr->data, strlen(ptr->data)); + update_attrs(tui, ptr->attr); + out(tui, ptr->data, strlen(ptr->data)); grid->col++; - if (data->immediate_wrap_after_last_column) { + if (tui->immediate_wrap_after_last_column) { // Printing at the right margin immediately advances the cursor. - final_column_wrap(ui); + final_column_wrap(tui); } } -static bool cheap_to_print(UI *ui, int row, int col, int next) +static bool cheap_to_print(TUIData *tui, int row, int col, int next) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; UCell *cell = grid->cells[row] + col; while (next) { next--; - if (attrs_differ(ui, cell->attr, - data->print_attr_id, ui->rgb)) { - if (data->default_attr) { + if (attrs_differ(tui, cell->attr, + tui->print_attr_id, tui->rgb)) { + if (tui->default_attr) { return false; } } @@ -786,15 +734,14 @@ static bool cheap_to_print(UI *ui, int row, int col, int next) /// (ASCII 0/12) means the same thing and does not mean home. VT, CVT, and /// TAB also stop at software-defined tabulation stops, not at a fixed set /// of row/column positions. -static void cursor_goto(UI *ui, int row, int col) +static void cursor_goto(TUIData *tui, int row, int col) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; if (row == grid->row && col == grid->col) { return; } if (0 == row && 0 == col) { - unibi_out(ui, unibi_cursor_home); + unibi_out(tui, unibi_cursor_home); ugrid_goto(grid, row, col); return; } @@ -803,12 +750,12 @@ static void cursor_goto(UI *ui, int row, int col) } if (0 == col ? col != grid->col : row != grid->row ? false : - 1 == col ? 2 < grid->col && cheap_to_print(ui, grid->row, 0, col) : - 2 == col ? 5 < grid->col && cheap_to_print(ui, grid->row, 0, col) : + 1 == col ? 2 < grid->col && cheap_to_print(tui, grid->row, 0, col) : + 2 == col ? 5 < grid->col && cheap_to_print(tui, grid->row, 0, col) : false) { // Motion to left margin from anywhere else, or CR + printing chars is // even less expensive than using BSes or CUB. - unibi_out(ui, unibi_carriage_return); + unibi_out(tui, unibi_carriage_return); ugrid_goto(grid, grid->row, 0); } if (row == grid->row) { @@ -818,15 +765,15 @@ static void cursor_goto(UI *ui, int row, int col) // motion calculations have OBOEs that cannot be compensated for, // because two terminals that claim to be the same will implement // different cursor positioning rules. - && (data->immediate_wrap_after_last_column || grid->col < ui->width)) { + && (tui->immediate_wrap_after_last_column || grid->col < tui->width)) { int n = grid->col - col; if (n <= 4) { // This might be just BS, so it is considered really cheap. while (n--) { - unibi_out(ui, unibi_cursor_left); + unibi_out(tui, unibi_cursor_left); } } else { - UNIBI_SET_NUM_VAR(data->params[0], n); - unibi_out(ui, unibi_parm_left_cursor); + UNIBI_SET_NUM_VAR(tui->params[0], n); + unibi_out(tui, unibi_parm_left_cursor); } ugrid_goto(grid, row, col); return; @@ -834,11 +781,11 @@ static void cursor_goto(UI *ui, int row, int col) int n = col - grid->col; if (n <= 2) { while (n--) { - unibi_out(ui, unibi_cursor_right); + unibi_out(tui, unibi_cursor_right); } } else { - UNIBI_SET_NUM_VAR(data->params[0], n); - unibi_out(ui, unibi_parm_right_cursor); + UNIBI_SET_NUM_VAR(tui->params[0], n); + unibi_out(tui, unibi_parm_right_cursor); } ugrid_goto(grid, row, col); return; @@ -849,11 +796,11 @@ static void cursor_goto(UI *ui, int row, int col) int n = row - grid->row; if (n <= 4) { // This might be just LF, so it is considered really cheap. while (n--) { - unibi_out(ui, unibi_cursor_down); + unibi_out(tui, unibi_cursor_down); } } else { - UNIBI_SET_NUM_VAR(data->params[0], n); - unibi_out(ui, unibi_parm_down_cursor); + UNIBI_SET_NUM_VAR(tui->params[0], n); + unibi_out(tui, unibi_parm_down_cursor); } ugrid_goto(grid, row, col); return; @@ -861,11 +808,11 @@ static void cursor_goto(UI *ui, int row, int col) int n = grid->row - row; if (n <= 2) { while (n--) { - unibi_out(ui, unibi_cursor_up); + unibi_out(tui, unibi_cursor_up); } } else { - UNIBI_SET_NUM_VAR(data->params[0], n); - unibi_out(ui, unibi_parm_up_cursor); + UNIBI_SET_NUM_VAR(tui->params[0], n); + unibi_out(tui, unibi_parm_up_cursor); } ugrid_goto(grid, row, col); return; @@ -873,20 +820,19 @@ static void cursor_goto(UI *ui, int row, int col) } safe_move: - unibi_goto(ui, row, col); + unibi_goto(tui, row, col); ugrid_goto(grid, row, col); } -static void print_spaces(UI *ui, int width) +static void print_spaces(TUIData *tui, int width) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; - out(ui, data->space_buf, (size_t)width); + out(tui, tui->space_buf, (size_t)width); grid->col += width; - if (data->immediate_wrap_after_last_column) { + if (tui->immediate_wrap_after_last_column) { // Printing at the right margin immediately advances the cursor. - final_column_wrap(ui); + final_column_wrap(tui); } } @@ -895,28 +841,27 @@ static void print_spaces(UI *ui, int width) /// /// @param is_doublewidth whether the character is double-width on the grid. /// If true and the character is ambiguous-width, clear two cells. -static void print_cell_at_pos(UI *ui, int row, int col, UCell *cell, bool is_doublewidth) +static void print_cell_at_pos(TUIData *tui, int row, int col, UCell *cell, bool is_doublewidth) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; if (grid->row == -1 && cell->data[0] == NUL) { // If cursor needs to repositioned and there is nothing to print, don't move cursor. return; } - cursor_goto(ui, row, col); + cursor_goto(tui, row, col); bool is_ambiwidth = utf_ambiguous_width(utf_ptr2char(cell->data)); if (is_ambiwidth && is_doublewidth) { // Clear the two screen cells. // If the character is single-width in the host terminal it won't change the second cell. - update_attrs(ui, cell->attr); - print_spaces(ui, 2); - cursor_goto(ui, row, col); + update_attrs(tui, cell->attr); + print_spaces(tui, 2); + cursor_goto(tui, row, col); } - print_cell(ui, cell); + print_cell(tui, cell); if (is_ambiwidth) { // Force repositioning cursor after printing an ambiguous-width character. @@ -924,120 +869,116 @@ static void print_cell_at_pos(UI *ui, int row, int col, UCell *cell, bool is_dou } } -static void clear_region(UI *ui, int top, int bot, int left, int right, int attr_id) +static void clear_region(TUIData *tui, int top, int bot, int left, int right, int attr_id) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; - update_attrs(ui, attr_id); + update_attrs(tui, attr_id); // Background is set to the default color and the right edge matches the // screen end, try to use terminal codes for clearing the requested area. - if (data->can_clear_attr - && left == 0 && right == ui->width && bot == ui->height) { + if (tui->can_clear_attr + && left == 0 && right == tui->width && bot == tui->height) { if (top == 0) { - unibi_out(ui, unibi_clear_screen); + unibi_out(tui, unibi_clear_screen); ugrid_goto(grid, top, left); } else { - cursor_goto(ui, top, 0); - unibi_out(ui, unibi_clr_eos); + cursor_goto(tui, top, 0); + unibi_out(tui, unibi_clr_eos); } } else { int width = right - left; // iterate through each line and clear for (int row = top; row < bot; row++) { - cursor_goto(ui, row, left); - if (data->can_clear_attr && right == ui->width) { - unibi_out(ui, unibi_clr_eol); - } else if (data->can_erase_chars && data->can_clear_attr && width >= 5) { - UNIBI_SET_NUM_VAR(data->params[0], width); - unibi_out(ui, unibi_erase_chars); + cursor_goto(tui, row, left); + if (tui->can_clear_attr && right == tui->width) { + unibi_out(tui, unibi_clr_eol); + } else if (tui->can_erase_chars && tui->can_clear_attr && width >= 5) { + UNIBI_SET_NUM_VAR(tui->params[0], width); + unibi_out(tui, unibi_erase_chars); } else { - print_spaces(ui, width); + print_spaces(tui, width); } } } } -static void set_scroll_region(UI *ui, int top, int bot, int left, int right) +static void set_scroll_region(TUIData *tui, int top, int bot, int left, int right) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; - UNIBI_SET_NUM_VAR(data->params[0], top); - UNIBI_SET_NUM_VAR(data->params[1], bot); - unibi_out(ui, unibi_change_scroll_region); - if (left != 0 || right != ui->width - 1) { - unibi_out_ext(ui, data->unibi_ext.enable_lr_margin); - if (data->can_set_lr_margin) { - UNIBI_SET_NUM_VAR(data->params[0], left); - UNIBI_SET_NUM_VAR(data->params[1], right); - unibi_out(ui, unibi_set_lr_margin); + UNIBI_SET_NUM_VAR(tui->params[0], top); + UNIBI_SET_NUM_VAR(tui->params[1], bot); + unibi_out(tui, unibi_change_scroll_region); + if (left != 0 || right != tui->width - 1) { + unibi_out_ext(tui, tui->unibi_ext.enable_lr_margin); + if (tui->can_set_lr_margin) { + UNIBI_SET_NUM_VAR(tui->params[0], left); + UNIBI_SET_NUM_VAR(tui->params[1], right); + unibi_out(tui, unibi_set_lr_margin); } else { - UNIBI_SET_NUM_VAR(data->params[0], left); - unibi_out(ui, unibi_set_left_margin_parm); - UNIBI_SET_NUM_VAR(data->params[0], right); - unibi_out(ui, unibi_set_right_margin_parm); + UNIBI_SET_NUM_VAR(tui->params[0], left); + unibi_out(tui, unibi_set_left_margin_parm); + UNIBI_SET_NUM_VAR(tui->params[0], right); + unibi_out(tui, unibi_set_right_margin_parm); } } grid->row = -1; } -static void reset_scroll_region(UI *ui, bool fullwidth) +static void reset_scroll_region(TUIData *tui, bool fullwidth) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; - if (0 <= data->unibi_ext.reset_scroll_region) { - unibi_out_ext(ui, data->unibi_ext.reset_scroll_region); + if (0 <= tui->unibi_ext.reset_scroll_region) { + unibi_out_ext(tui, tui->unibi_ext.reset_scroll_region); } else { - UNIBI_SET_NUM_VAR(data->params[0], 0); - UNIBI_SET_NUM_VAR(data->params[1], ui->height - 1); - unibi_out(ui, unibi_change_scroll_region); + UNIBI_SET_NUM_VAR(tui->params[0], 0); + UNIBI_SET_NUM_VAR(tui->params[1], tui->height - 1); + unibi_out(tui, unibi_change_scroll_region); } if (!fullwidth) { - if (data->can_set_lr_margin) { - UNIBI_SET_NUM_VAR(data->params[0], 0); - UNIBI_SET_NUM_VAR(data->params[1], ui->width - 1); - unibi_out(ui, unibi_set_lr_margin); + if (tui->can_set_lr_margin) { + UNIBI_SET_NUM_VAR(tui->params[0], 0); + UNIBI_SET_NUM_VAR(tui->params[1], tui->width - 1); + unibi_out(tui, unibi_set_lr_margin); } else { - UNIBI_SET_NUM_VAR(data->params[0], 0); - unibi_out(ui, unibi_set_left_margin_parm); - UNIBI_SET_NUM_VAR(data->params[0], ui->width - 1); - unibi_out(ui, unibi_set_right_margin_parm); + UNIBI_SET_NUM_VAR(tui->params[0], 0); + unibi_out(tui, unibi_set_left_margin_parm); + UNIBI_SET_NUM_VAR(tui->params[0], tui->width - 1); + unibi_out(tui, unibi_set_right_margin_parm); } - unibi_out_ext(ui, data->unibi_ext.disable_lr_margin); + unibi_out_ext(tui, tui->unibi_ext.disable_lr_margin); } grid->row = -1; } -static void tui_grid_resize(UI *ui, Integer g, Integer width, Integer height) +void tui_grid_resize(TUIData *tui, Integer g, Integer width, Integer height) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; ugrid_resize(grid, (int)width, (int)height); - xfree(data->space_buf); - data->space_buf = xmalloc((size_t)width * sizeof(*data->space_buf)); - memset(data->space_buf, ' ', (size_t)width); + xfree(tui->space_buf); + tui->space_buf = xmalloc((size_t)width * sizeof(*tui->space_buf)); + memset(tui->space_buf, ' ', (size_t)width); // resize might not always be followed by a clear before flush // so clip the invalid region - for (size_t i = 0; i < kv_size(data->invalid_regions); i++) { - Rect *r = &kv_A(data->invalid_regions, i); + for (size_t i = 0; i < kv_size(tui->invalid_regions); i++) { + Rect *r = &kv_A(tui->invalid_regions, i); r->bot = MIN(r->bot, grid->height); r->right = MIN(r->right, grid->width); } - if (!got_winch && !data->is_starting) { + if (!got_winch && !tui->is_starting) { // Resize the _host_ terminal. - UNIBI_SET_NUM_VAR(data->params[0], (int)height); - UNIBI_SET_NUM_VAR(data->params[1], (int)width); - unibi_out_ext(ui, data->unibi_ext.resize_screen); + UNIBI_SET_NUM_VAR(tui->params[0], (int)height); + UNIBI_SET_NUM_VAR(tui->params[1], (int)width); + unibi_out_ext(tui, tui->unibi_ext.resize_screen); // DECSLPP does not reset the scroll region. - if (data->scroll_region_is_full_screen) { - reset_scroll_region(ui, ui->width == grid->width); + if (tui->scroll_region_is_full_screen) { + reset_scroll_region(tui, tui->width == grid->width); } } else { // Already handled the SIGWINCH signal; avoid double-resize. got_winch = got_winch > 0 ? got_winch - 1 : 0; @@ -1045,22 +986,19 @@ static void tui_grid_resize(UI *ui, Integer g, Integer width, Integer height) } } -static void tui_grid_clear(UI *ui, Integer g) +void tui_grid_clear(TUIData *tui, Integer g) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; ugrid_clear(grid); - kv_size(data->invalid_regions) = 0; - clear_region(ui, 0, grid->height, 0, grid->width, 0); + kv_size(tui->invalid_regions) = 0; + clear_region(tui, 0, grid->height, 0, grid->width, 0); } -static void tui_grid_cursor_goto(UI *ui, Integer grid, Integer row, Integer col) +void tui_grid_cursor_goto(TUIData *tui, Integer grid, Integer row, Integer col) { - TUIData *data = ui->data; - // cursor position is validated in tui_flush - data->row = (int)row; - data->col = (int)col; + tui->row = (int)row; + tui->col = (int)col; } CursorShape tui_cursor_decode_shape(const char *shape_str) @@ -1100,13 +1038,12 @@ static cursorentry_T decode_cursor_entry(Dictionary args) return r; } -static void tui_mode_info_set(UI *ui, bool guicursor_enabled, Array args) +void tui_mode_info_set(TUIData *tui, bool guicursor_enabled, Array args) { cursor_style_enabled = guicursor_enabled; if (!guicursor_enabled) { return; // Do not send cursor style control codes. } - TUIData *data = ui->data; assert(args.size); @@ -1114,78 +1051,81 @@ static void tui_mode_info_set(UI *ui, bool guicursor_enabled, Array args) for (size_t i = 0; i < args.size; i++) { assert(args.items[i].type == kObjectTypeDictionary); cursorentry_T r = decode_cursor_entry(args.items[i].data.dictionary); - data->cursor_shapes[i] = r; + tui->cursor_shapes[i] = r; } - tui_set_mode(ui, data->showing_mode); + tui_set_mode(tui, tui->showing_mode); } -static void tui_update_menu(UI *ui) +void tui_update_menu(TUIData *tui) { // Do nothing; menus are for GUI only } -static void tui_busy_start(UI *ui) +void tui_busy_start(TUIData *tui) { - ((TUIData *)ui->data)->busy = true; + tui->busy = true; } -static void tui_busy_stop(UI *ui) +void tui_busy_stop(TUIData *tui) { - ((TUIData *)ui->data)->busy = false; + tui->busy = false; } -static void tui_mouse_on(UI *ui) +void tui_mouse_on(TUIData *tui) { - TUIData *data = ui->data; - if (!data->mouse_enabled) { - unibi_out_ext(ui, data->unibi_ext.enable_mouse); - if (data->mouse_move_enabled) { - unibi_out_ext(ui, data->unibi_ext.enable_mouse_move); + if (!tui->mouse_enabled) { + unibi_out_ext(tui, tui->unibi_ext.enable_mouse); + if (tui->mouse_move_enabled) { + unibi_out_ext(tui, tui->unibi_ext.enable_mouse_move); } - data->mouse_enabled = true; + tui->mouse_enabled = true; } } -static void tui_mouse_off(UI *ui) +void tui_mouse_off(TUIData *tui) { - TUIData *data = ui->data; - if (data->mouse_enabled) { - if (data->mouse_move_enabled) { - unibi_out_ext(ui, data->unibi_ext.disable_mouse_move); + if (tui->mouse_enabled) { + if (tui->mouse_move_enabled) { + unibi_out_ext(tui, tui->unibi_ext.disable_mouse_move); } - unibi_out_ext(ui, data->unibi_ext.disable_mouse); - data->mouse_enabled = false; + unibi_out_ext(tui, tui->unibi_ext.disable_mouse); + tui->mouse_enabled = false; } } -static void tui_set_mode(UI *ui, ModeShape mode) +void tui_set_mode(TUIData *tui, ModeShape mode) { if (!cursor_style_enabled) { return; } - TUIData *data = ui->data; - cursorentry_T c = data->cursor_shapes[mode]; + cursorentry_T c = tui->cursor_shapes[mode]; - if (c.id != 0 && c.id < (int)kv_size(data->attrs) && ui->rgb) { - HlAttrs aep = kv_A(data->attrs, c.id); + if (c.id != 0 && c.id < (int)kv_size(tui->attrs) && tui->rgb) { + HlAttrs aep = kv_A(tui->attrs, c.id); - data->want_invisible = aep.hl_blend == 100; - if (data->want_invisible) { - unibi_out(ui, unibi_cursor_invisible); + tui->want_invisible = aep.hl_blend == 100; + if (tui->want_invisible) { + unibi_out(tui, unibi_cursor_invisible); } else if (aep.rgb_ae_attr & HL_INVERSE) { // We interpret "inverse" as "default" (no termcode for "inverse"...). // Hopefully the user's default cursor color is inverse. - unibi_out_ext(ui, data->unibi_ext.reset_cursor_color); + unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color); } else { - UNIBI_SET_NUM_VAR(data->params[0], aep.rgb_bg_color); - unibi_out_ext(ui, data->unibi_ext.set_cursor_color); - data->cursor_color_changed = true; + if (tui->set_cursor_color_as_str) { + char hexbuf[8]; + snprintf(hexbuf, 7 + 1, "#%06x", aep.rgb_bg_color); + UNIBI_SET_STR_VAR(tui->params[0], hexbuf); + } else { + UNIBI_SET_NUM_VAR(tui->params[0], aep.rgb_bg_color); + } + unibi_out_ext(tui, tui->unibi_ext.set_cursor_color); + tui->cursor_color_changed = true; } } else if (c.id == 0) { // No cursor color for this mode; reset to default. - data->want_invisible = false; - unibi_out_ext(ui, data->unibi_ext.reset_cursor_color); + tui->want_invisible = false; + unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color); } int shape; @@ -1199,83 +1139,86 @@ static void tui_set_mode(UI *ui, ModeShape mode) case SHAPE_VER: shape = 5; break; } - UNIBI_SET_NUM_VAR(data->params[0], shape + (int)(c.blinkon == 0)); - unibi_out_ext(ui, data->unibi_ext.set_cursor_style); + UNIBI_SET_NUM_VAR(tui->params[0], shape + (int)(c.blinkon == 0)); + unibi_out_ext(tui, tui->unibi_ext.set_cursor_style); } /// @param mode editor mode -static void tui_mode_change(UI *ui, String mode, Integer mode_idx) +void tui_mode_change(TUIData *tui, String mode, Integer mode_idx) { - TUIData *data = ui->data; #ifdef UNIX // If stdin is not a TTY, the LHS of pipe may change the state of the TTY // 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) { - int ret = uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_NORMAL); + if (tui->is_starting && tui->input.in_fd == STDERR_FILENO) { + int ret = uv_tty_set_mode(&tui->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); + ret = uv_tty_set_mode(&tui->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); - data->is_starting = false; // mode entered, no longer starting - data->showing_mode = (ModeShape)mode_idx; + tui_set_mode(tui, (ModeShape)mode_idx); + if (tui->is_starting) { + if (tui->verbose >= 3) { + show_verbose_terminfo(tui); + } + } + tui->is_starting = false; // mode entered, no longer starting + tui->showing_mode = (ModeShape)mode_idx; } -static void tui_grid_scroll(UI *ui, Integer g, Integer startrow, // -V751 - Integer endrow, Integer startcol, Integer endcol, Integer rows, - Integer cols FUNC_ATTR_UNUSED) +void tui_grid_scroll(TUIData *tui, Integer g, Integer startrow, // -V751 + Integer endrow, Integer startcol, Integer endcol, Integer rows, + Integer cols FUNC_ATTR_UNUSED) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; int top = (int)startrow, bot = (int)endrow - 1; int left = (int)startcol, right = (int)endcol - 1; - bool fullwidth = left == 0 && right == ui->width - 1; - data->scroll_region_is_full_screen = fullwidth - && top == 0 && bot == ui->height - 1; + bool fullwidth = left == 0 && right == tui->width - 1; + tui->scroll_region_is_full_screen = fullwidth + && top == 0 && bot == tui->height - 1; ugrid_scroll(grid, top, bot, left, right, (int)rows); - bool can_scroll = data->can_scroll - && (data->scroll_region_is_full_screen - || (data->can_change_scroll_region - && ((left == 0 && right == ui->width - 1) - || data->can_set_lr_margin - || data->can_set_left_right_margin))); + bool can_scroll = tui->can_scroll + && (tui->scroll_region_is_full_screen + || (tui->can_change_scroll_region + && ((left == 0 && right == tui->width - 1) + || tui->can_set_lr_margin + || tui->can_set_left_right_margin))); if (can_scroll) { // Change terminal scroll region and move cursor to the top - if (!data->scroll_region_is_full_screen) { - set_scroll_region(ui, top, bot, left, right); + if (!tui->scroll_region_is_full_screen) { + set_scroll_region(tui, top, bot, left, right); } - cursor_goto(ui, top, left); - update_attrs(ui, 0); + cursor_goto(tui, top, left); + update_attrs(tui, 0); if (rows > 0) { if (rows == 1) { - unibi_out(ui, unibi_delete_line); + unibi_out(tui, unibi_delete_line); } else { - UNIBI_SET_NUM_VAR(data->params[0], (int)rows); - unibi_out(ui, unibi_parm_delete_line); + UNIBI_SET_NUM_VAR(tui->params[0], (int)rows); + unibi_out(tui, unibi_parm_delete_line); } } else { if (rows == -1) { - unibi_out(ui, unibi_insert_line); + unibi_out(tui, unibi_insert_line); } else { - UNIBI_SET_NUM_VAR(data->params[0], -(int)rows); - unibi_out(ui, unibi_parm_insert_line); + UNIBI_SET_NUM_VAR(tui->params[0], -(int)rows); + unibi_out(tui, unibi_parm_insert_line); } } // Restore terminal scroll region and cursor - if (!data->scroll_region_is_full_screen) { - reset_scroll_region(ui, fullwidth); + if (!tui->scroll_region_is_full_screen) { + reset_scroll_region(tui, fullwidth); } } else { // Mark the moved region as invalid for redrawing later @@ -1284,47 +1227,46 @@ static void tui_grid_scroll(UI *ui, Integer g, Integer startrow, // -V751 } else { startrow = startrow - rows; } - invalidate(ui, (int)startrow, (int)endrow, (int)startcol, (int)endcol); + invalidate(tui, (int)startrow, (int)endrow, (int)startcol, (int)endcol); } } -static void tui_hl_attr_define(UI *ui, Integer id, HlAttrs attrs, HlAttrs cterm_attrs, Array info) +void tui_hl_attr_define(TUIData *tui, Integer id, HlAttrs attrs, HlAttrs cterm_attrs, Array info) { - TUIData *data = ui->data; - kv_a(data->attrs, (size_t)id) = attrs; + attrs.cterm_ae_attr = cterm_attrs.cterm_ae_attr; + attrs.cterm_fg_color = cterm_attrs.cterm_fg_color; + attrs.cterm_bg_color = cterm_attrs.cterm_bg_color; + kv_a(tui->attrs, (size_t)id) = attrs; } -static void tui_bell(UI *ui) +void tui_bell(TUIData *tui) { - unibi_out(ui, unibi_bell); + unibi_out(tui, unibi_bell); } -static void tui_visual_bell(UI *ui) +void tui_visual_bell(TUIData *tui) { - unibi_out(ui, unibi_flash_screen); + unibi_out(tui, unibi_flash_screen); } -static void tui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, - Integer cterm_fg, Integer cterm_bg) +void tui_default_colors_set(TUIData *tui, Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, + Integer cterm_fg, Integer cterm_bg) { - TUIData *data = ui->data; - - data->clear_attrs.rgb_fg_color = (int)rgb_fg; - data->clear_attrs.rgb_bg_color = (int)rgb_bg; - data->clear_attrs.rgb_sp_color = (int)rgb_sp; - data->clear_attrs.cterm_fg_color = (int)cterm_fg; - data->clear_attrs.cterm_bg_color = (int)cterm_bg; + tui->clear_attrs.rgb_fg_color = (int)rgb_fg; + tui->clear_attrs.rgb_bg_color = (int)rgb_bg; + tui->clear_attrs.rgb_sp_color = (int)rgb_sp; + tui->clear_attrs.cterm_fg_color = (int)cterm_fg; + tui->clear_attrs.cterm_bg_color = (int)cterm_bg; - data->print_attr_id = -1; - invalidate(ui, 0, data->grid.height, 0, data->grid.width); + tui->print_attr_id = -1; + invalidate(tui, 0, tui->grid.height, 0, tui->grid.width); } -static void tui_flush(UI *ui) +void tui_flush(TUIData *tui) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; - size_t nrevents = loop_size(data->loop); + size_t nrevents = loop_size(tui->loop); if (nrevents > TOO_MANY_EVENTS) { WLOG("TUI event-queue flooded (thread_events=%zu); purging", nrevents); // Back-pressure: UI events may accumulate much faster than the terminal @@ -1332,12 +1274,12 @@ static void tui_flush(UI *ui) // wait for the TUI event-queue to drain, and if there are ~millions of // events in the queue, it could take hours. Clearing the queue allows the // UI to recover. #1234 #5396 - loop_purge(data->loop); - tui_busy_stop(ui); // avoid hidden cursor + loop_purge(tui->loop); + tui_busy_stop(tui); // avoid hidden cursor } - while (kv_size(data->invalid_regions)) { - Rect r = kv_pop(data->invalid_regions); + while (kv_size(tui->invalid_regions)) { + Rect r = kv_pop(tui->invalid_regions); assert(r.bot <= grid->height && r.right <= grid->width); for (int row = r.top; row < r.bot; row++) { @@ -1352,167 +1294,175 @@ static void tui_flush(UI *ui) } UGRID_FOREACH_CELL(grid, row, r.left, clear_col, { - print_cell_at_pos(ui, row, curcol, cell, + print_cell_at_pos(tui, row, curcol, cell, curcol < clear_col - 1 && (cell + 1)->data[0] == NUL); }); if (clear_col < r.right) { - clear_region(ui, row, row + 1, clear_col, r.right, clear_attr); + clear_region(tui, row, row + 1, clear_col, r.right, clear_attr); } } } - cursor_goto(ui, data->row, data->col); + cursor_goto(tui, tui->row, tui->col); - flush_buf(ui); + flush_buf(tui); } /// Dumps termcap info to the messages area, if 'verbose' >= 3. -static void show_termcap_event(void **argv) +static void show_verbose_terminfo(TUIData *tui) { - if (p_verbose < 3) { - return; - } - const unibi_term *const ut = argv[0]; + const unibi_term *const ut = tui->ut; if (!ut) { abort(); } - verbose_enter(); - // XXX: (future) if unibi_term is modified (e.g. after a terminal - // query-response) this is a race condition. - terminfo_info_msg(ut); - verbose_leave(); - verbose_stop(); // flush now + + Array chunks = ARRAY_DICT_INIT; + Array title = ARRAY_DICT_INIT; + ADD(title, STRING_OBJ(cstr_to_string("\n\n--- Terminal info --- {{{\n"))); + ADD(title, STRING_OBJ(cstr_to_string("Title"))); + ADD(chunks, ARRAY_OBJ(title)); + Array info = ARRAY_DICT_INIT; + String str = terminfo_info_msg(ut, tui->term); + ADD(info, STRING_OBJ(str)); + ADD(chunks, ARRAY_OBJ(info)); + Array end_fold = ARRAY_DICT_INIT; + ADD(end_fold, STRING_OBJ(cstr_to_string("}}}\n"))); + ADD(end_fold, STRING_OBJ(cstr_to_string("Title"))); + ADD(chunks, ARRAY_OBJ(end_fold)); + + Array args = ARRAY_DICT_INIT; + ADD(args, ARRAY_OBJ(chunks)); + ADD(args, BOOLEAN_OBJ(true)); // history + Dictionary opts = ARRAY_DICT_INIT; + PUT(opts, "verbose", BOOLEAN_OBJ(true)); + ADD(args, DICTIONARY_OBJ(opts)); + rpc_send_event(ui_client_channel_id, "nvim_echo", args); + api_free_array(args); } #ifdef UNIX static void suspend_event(void **argv) { - UI *ui = argv[0]; - TUIData *data = ui->data; - bool enable_mouse = data->mouse_enabled; - tui_terminal_stop(ui); - data->cont_received = false; - stream_set_blocking(input_global_fd(), true); // normalize stream (#2598) - signal_stop(); + TUIData *tui = argv[0]; + bool enable_mouse = tui->mouse_enabled; + tui_terminal_stop(tui); + stream_set_blocking(tui->input.in_fd, true); // normalize stream (#2598) + kill(0, SIGTSTP); - signal_start(); - while (!data->cont_received) { - // poll the event loop until SIGCONT is received - loop_poll_events(data->loop, -1); - } - tui_terminal_start(ui); - tui_terminal_after_startup(ui); + + tui_terminal_start(tui); + tui_terminal_after_startup(tui); if (enable_mouse) { - tui_mouse_on(ui); + tui_mouse_on(tui); } - stream_set_blocking(input_global_fd(), false); // libuv expects this - // resume the main thread - CONTINUE(data->bridge); + stream_set_blocking(tui->input.in_fd, false); // libuv expects this } #endif -static void tui_suspend(UI *ui) +void tui_suspend(TUIData *tui) { - TUIData *data = ui->data; +// on a non-UNIX system, this is a no-op #ifdef UNIX // kill(0, SIGTSTP) won't stop the UI thread, so we must poll for SIGCONT // before continuing. This is done in another callback to avoid // loop_poll_events recursion - multiqueue_put_event(data->loop->fast_events, - event_create(suspend_event, 1, ui)); -#else - // Resume the main thread as suspending isn't implemented. - CONTINUE(data->bridge); + multiqueue_put_event(resize_events, + event_create(suspend_event, 1, tui)); #endif } -static void tui_set_title(UI *ui, String title) +void tui_set_title(TUIData *tui, String title) { - TUIData *data = ui->data; - if (!(title.data && unibi_get_str(data->ut, unibi_to_status_line) - && unibi_get_str(data->ut, unibi_from_status_line))) { + if (!(title.data && unibi_get_str(tui->ut, unibi_to_status_line) + && unibi_get_str(tui->ut, unibi_from_status_line))) { return; } - unibi_out(ui, unibi_to_status_line); - out(ui, title.data, title.size); - unibi_out(ui, unibi_from_status_line); + unibi_out(tui, unibi_to_status_line); + out(tui, title.data, title.size); + unibi_out(tui, unibi_from_status_line); } -static void tui_set_icon(UI *ui, String icon) +void tui_set_icon(TUIData *tui, String icon) {} -static void tui_screenshot(UI *ui, String path) +void tui_screenshot(TUIData *tui, String path) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; - flush_buf(ui); + UGrid *grid = &tui->grid; + flush_buf(tui); grid->row = 0; grid->col = 0; FILE *f = fopen(path.data, "w"); - data->screenshot = f; + tui->screenshot = f; fprintf(f, "%d,%d\n", grid->height, grid->width); - unibi_out(ui, unibi_clear_screen); + unibi_out(tui, unibi_clear_screen); for (int i = 0; i < grid->height; i++) { - cursor_goto(ui, i, 0); + cursor_goto(tui, i, 0); for (int j = 0; j < grid->width; j++) { - print_cell(ui, &grid->cells[i][j]); + print_cell(tui, &grid->cells[i][j]); } } - flush_buf(ui); - data->screenshot = NULL; + flush_buf(tui); + tui->screenshot = NULL; fclose(f); } -static void tui_option_set(UI *ui, String name, Object value) +void tui_option_set(TUIData *tui, String name, Object value) { - TUIData *data = ui->data; if (strequal(name.data, "mousemoveevent")) { - if (data->mouse_move_enabled != value.data.boolean) { - if (data->mouse_enabled) { - tui_mouse_off(ui); - data->mouse_move_enabled = value.data.boolean; - tui_mouse_on(ui); + if (tui->mouse_move_enabled != value.data.boolean) { + if (tui->mouse_enabled) { + tui_mouse_off(tui); + tui->mouse_move_enabled = value.data.boolean; + tui_mouse_on(tui); } else { - data->mouse_move_enabled = value.data.boolean; + tui->mouse_move_enabled = value.data.boolean; } } } else if (strequal(name.data, "termguicolors")) { - ui->rgb = value.data.boolean; - data->print_attr_id = -1; - invalidate(ui, 0, data->grid.height, 0, data->grid.width); + tui->rgb = value.data.boolean; + tui->print_attr_id = -1; + invalidate(tui, 0, tui->grid.height, 0, tui->grid.width); + + if (ui_client_channel_id) { + MAXSIZE_TEMP_ARRAY(args, 2); + ADD_C(args, STRING_OBJ(cstr_as_string("rgb"))); + ADD_C(args, BOOLEAN_OBJ(value.data.boolean)); + rpc_send_event(ui_client_channel_id, "nvim_ui_set_option", args); + } } else if (strequal(name.data, "ttimeout")) { - data->input.ttimeout = value.data.boolean; + tui->input.ttimeout = value.data.boolean; } else if (strequal(name.data, "ttimeoutlen")) { - data->input.ttimeoutlen = (long)value.data.integer; + tui->input.ttimeoutlen = (long)value.data.integer; + } else if (strequal(name.data, "verbose")) { + tui->verbose = value.data.integer; } } -static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol, Integer endcol, - Integer clearcol, Integer clearattr, LineFlags flags, const schar_T *chunk, - const sattr_T *attrs) +void tui_raw_line(TUIData *tui, Integer g, Integer linerow, Integer startcol, Integer endcol, + Integer clearcol, Integer clearattr, LineFlags flags, const schar_T *chunk, + const sattr_T *attrs) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; for (Integer c = startcol; c < endcol; c++) { memcpy(grid->cells[linerow][c].data, chunk[c - startcol], sizeof(schar_T)); - assert((size_t)attrs[c - startcol] < kv_size(data->attrs)); + assert((size_t)attrs[c - startcol] < kv_size(tui->attrs)); grid->cells[linerow][c].attr = attrs[c - startcol]; } UGRID_FOREACH_CELL(grid, (int)linerow, (int)startcol, (int)endcol, { - print_cell_at_pos(ui, (int)linerow, curcol, cell, + print_cell_at_pos(tui, (int)linerow, curcol, cell, curcol < endcol - 1 && (cell + 1)->data[0] == NUL); }); if (clearcol > endcol) { ugrid_clear_chunk(grid, (int)linerow, (int)endcol, (int)clearcol, (sattr_T)clearattr); - clear_region(ui, (int)linerow, (int)linerow + 1, (int)endcol, (int)clearcol, + clear_region(tui, (int)linerow, (int)linerow + 1, (int)endcol, (int)clearcol, (int)clearattr); } - if (flags & kLineFlagWrap && ui->width == grid->width + if (flags & kLineFlagWrap && tui->width == grid->width && linerow + 1 < grid->height) { // Only do line wrapping if the grid width is equal to the terminal // width and the line continuation is within the grid. @@ -1520,23 +1470,22 @@ static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol, I if (endcol != grid->width) { // Print the last char of the row, if we haven't already done so. int size = grid->cells[linerow][grid->width - 1].data[0] == NUL ? 2 : 1; - print_cell_at_pos(ui, (int)linerow, grid->width - size, + print_cell_at_pos(tui, (int)linerow, grid->width - size, &grid->cells[linerow][grid->width - size], size == 2); } // Wrap the cursor over to the next line. The next line will be // printed immediately without an intervening newline. - final_column_wrap(ui); + final_column_wrap(tui); } } -static void invalidate(UI *ui, int top, int bot, int left, int right) +static void invalidate(TUIData *tui, int top, int bot, int left, int right) { - TUIData *data = ui->data; Rect *intersects = NULL; - for (size_t i = 0; i < kv_size(data->invalid_regions); i++) { - Rect *r = &kv_A(data->invalid_regions, i); + for (size_t i = 0; i < kv_size(tui->invalid_regions); i++) { + Rect *r = &kv_A(tui->invalid_regions, i); // adjacent regions are treated as overlapping if (!(top > r->bot || bot < r->top) && !(left > r->right || right < r->left)) { @@ -1554,20 +1503,19 @@ static void invalidate(UI *ui, int top, int bot, int left, int right) intersects->right = MAX(right, intersects->right); } else { // Else just add a new entry; - kv_push(data->invalid_regions, ((Rect) { top, bot, left, right })); + kv_push(tui->invalid_regions, ((Rect) { top, bot, left, right })); } } /// Tries to get the user's wanted dimensions (columns and rows) for the entire /// application (i.e., the host terminal). -static void tui_guess_size(UI *ui) +void tui_guess_size(TUIData *tui) { - TUIData *data = ui->data; int width = 0, height = 0; // 1 - try from a system call(ioctl/TIOCGWINSZ on unix) - if (data->out_isatty - && !uv_tty_get_winsize(&data->output_handle.tty, &width, &height)) { + if (tui->out_isatty + && !uv_tty_get_winsize(&tui->output_handle.tty, &width, &height)) { goto end; } @@ -1582,8 +1530,8 @@ static void tui_guess_size(UI *ui) } // 3 - read from terminfo if available - height = unibi_get_num(data->ut, unibi_lines); - width = unibi_get_num(data->ut, unibi_columns); + height = unibi_get_num(tui->ut, unibi_lines); + width = unibi_get_num(tui->ut, unibi_columns); end: if (width <= 0 || height <= 0) { @@ -1592,45 +1540,49 @@ static void tui_guess_size(UI *ui) height = DFLT_ROWS; } - data->bridge->bridge.width = ui->width = width; - data->bridge->bridge.height = ui->height = height; + if (tui->width != width || tui->height != height) { + tui->width = width; + tui->height = height; + + ui_client_set_size(width, height); + } } -static void unibi_goto(UI *ui, int row, int col) +static void unibi_goto(TUIData *tui, int row, int col) { - TUIData *data = ui->data; - UNIBI_SET_NUM_VAR(data->params[0], row); - UNIBI_SET_NUM_VAR(data->params[1], col); - unibi_out(ui, unibi_cursor_address); + UNIBI_SET_NUM_VAR(tui->params[0], row); + UNIBI_SET_NUM_VAR(tui->params[1], col); + unibi_out(tui, unibi_cursor_address); } #define UNIBI_OUT(fn) \ do { \ - TUIData *data = ui->data; \ const char *str = NULL; \ if (unibi_index >= 0) { \ - str = fn(data->ut, (unsigned)unibi_index); \ + str = fn(tui->ut, (unsigned)unibi_index); \ } \ if (str) { \ unibi_var_t vars[26 + 26]; \ - size_t orig_pos = data->bufpos; \ + unibi_var_t params[9]; \ + size_t orig_pos = tui->bufpos; \ memset(&vars, 0, sizeof(vars)); \ - data->cork = true; \ + tui->cork = true; \ retry: \ - unibi_format(vars, vars + 26, str, data->params, out, ui, pad, ui); \ - if (data->overflow) { \ - data->bufpos = orig_pos; \ - flush_buf(ui); \ + memcpy(params, tui->params, sizeof(params)); \ + unibi_format(vars, vars + 26, str, params, out, tui, pad, tui); \ + if (tui->overflow) { \ + tui->bufpos = orig_pos; \ + flush_buf(tui); \ goto retry; \ } \ - data->cork = false; \ + tui->cork = false; \ } \ } while (0) -static void unibi_out(UI *ui, int unibi_index) +static void unibi_out(TUIData *tui, int unibi_index) { UNIBI_OUT(unibi_get_str); } -static void unibi_out_ext(UI *ui, int unibi_index) +static void unibi_out_ext(TUIData *tui, int unibi_index) { UNIBI_OUT(unibi_get_ext_str); } @@ -1638,26 +1590,24 @@ static void unibi_out_ext(UI *ui, int unibi_index) static void out(void *ctx, const char *str, size_t len) { - UI *ui = ctx; - TUIData *data = ui->data; - size_t available = sizeof(data->buf) - data->bufpos; + TUIData *tui = ctx; + size_t available = sizeof(tui->buf) - tui->bufpos; - if (data->cork && data->overflow) { + if (tui->cork && tui->overflow) { return; } if (len > available) { - if (data->cork) { + if (tui->cork) { // Called by unibi_format(): avoid flush_buf() halfway an escape sequence. - data->overflow = true; + tui->overflow = true; return; - } else { - flush_buf(ui); } + flush_buf(tui); } - memcpy(data->buf + data->bufpos, str, len); - data->bufpos += len; + memcpy(tui->buf + tui->bufpos, str, len); + tui->bufpos += len; } /// Called by unibi_format() for padding instructions. @@ -1673,15 +1623,14 @@ static void pad(void *ctx, size_t delay, int scale FUNC_ATTR_UNUSED, int force) return; } - UI *ui = ctx; - TUIData *data = ui->data; + TUIData *tui = ctx; - if (data->overflow) { + if (tui->overflow) { return; } - flush_buf(ui); - loop_uv_run(data->loop, (int)(delay / 10), false); + flush_buf(tui); + uv_sleep((unsigned int)(delay/10)); } static void unibi_set_if_empty(unibi_term *ut, enum unibi_string str, const char *val) @@ -1718,10 +1667,10 @@ static int unibi_find_ext_bool(unibi_term *ut, const char *name) /// Patches the terminfo records after loading from system or built-in db. /// Several entries in terminfo are known to be deficient or outright wrong; /// and several terminal emulators falsely announce incorrect terminal types. -static void patch_terminfo_bugs(TUIData *data, const char *term, const char *colorterm, +static void patch_terminfo_bugs(TUIData *tui, const char *term, const char *colorterm, long vte_version, long konsolev, bool iterm_env, bool nsterm) { - unibi_term *ut = data->ut; + unibi_term *ut = tui->ut; const char *xterm_version = os_getenv("XTERM_VERSION"); #if 0 // We don't need to identify this specifically, for now. bool roxterm = !!os_getenv("ROXTERM_ID"); @@ -1918,14 +1867,14 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, const char *col #define XTERM_SETAB_16 \ "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m" - data->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg", - "\x1b]11;?\x07"); + tui->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg", + "\x1b]11;?\x07"); // Query the terminal to see if it supports CSI u key encoding by writing CSI // ? u followed by a request for the primary device attributes (CSI c) // See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#detection-of-support-for-this-protocol - data->unibi_ext.get_extkeys = (int)unibi_add_ext_str(ut, "ext.get_extkeys", - "\x1b[?u\x1b[c"); + tui->unibi_ext.get_extkeys = (int)unibi_add_ext_str(ut, "ext.get_extkeys", + "\x1b[?u\x1b[c"); // Terminals with 256-colour SGR support despite what terminfo says. if (unibi_get_num(ut, unibi_max_colors) < 256) { @@ -1956,14 +1905,14 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, const char *col // Blacklist of terminals that cannot be trusted to report DECSCUSR support. if (!(st || (vte_version != 0 && vte_version < 3900) || konsolev)) { - data->unibi_ext.reset_cursor_style = unibi_find_ext_str(ut, "Se"); - data->unibi_ext.set_cursor_style = unibi_find_ext_str(ut, "Ss"); + tui->unibi_ext.reset_cursor_style = unibi_find_ext_str(ut, "Se"); + tui->unibi_ext.set_cursor_style = unibi_find_ext_str(ut, "Ss"); } // Dickey ncurses terminfo includes Ss/Se capabilities since 2011-07-14. So // adding them to terminal types, that have such control sequences but lack // the correct terminfo entries, is a fixup, not an augmentation. - if (-1 == data->unibi_ext.set_cursor_style) { + if (-1 == tui->unibi_ext.set_cursor_style) { // DECSCUSR (cursor shape) is widely supported. // https://github.com/gnachman/iTerm2/pull/92 if ((!bsdvt && (!konsolev || konsolev >= 180770)) @@ -1989,59 +1938,59 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, const char *col // Example: console-terminal-emulator from the nosh toolset. || (linuxvt && (xterm_version || (vte_version > 0) || colorterm)))) { - data->unibi_ext.set_cursor_style = + tui->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", "\x1b[%p1%d q"); - if (-1 == data->unibi_ext.reset_cursor_style) { - data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", - ""); + if (-1 == tui->unibi_ext.reset_cursor_style) { + tui->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", + ""); } - unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style, + unibi_set_ext_str(ut, (size_t)tui->unibi_ext.reset_cursor_style, "\x1b[ q"); } else if (linuxvt) { // Linux uses an idiosyncratic escape code to set the cursor shape and // does not support DECSCUSR. // See http://linuxgazette.net/137/anonymous.html for more info - data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", - "\x1b[?" - "%?" - // The parameter passed to Ss is the DECSCUSR parameter, so the - // terminal capability has to translate into the Linux idiosyncratic - // parameter. - // - // linuxvt only supports block and underline. It is also only - // possible to have a steady block (no steady underline) - "%p1%{2}%<" "%t%{8}" // blink block - "%e%p1%{2}%=" "%t%{112}" // steady block - "%e%p1%{3}%=" "%t%{4}" // blink underline (set to half block) - "%e%p1%{4}%=" "%t%{4}" // steady underline - "%e%p1%{5}%=" "%t%{2}" // blink bar (set to underline) - "%e%p1%{6}%=" "%t%{2}" // steady bar - "%e%{0}" // anything else - "%;" "%dc"); - if (-1 == data->unibi_ext.reset_cursor_style) { - data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", - ""); + tui->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", + "\x1b[?" + "%?" + // The parameter passed to Ss is the DECSCUSR parameter, so the + // terminal capability has to translate into the Linux idiosyncratic + // parameter. + // + // linuxvt only supports block and underline. It is also only + // possible to have a steady block (no steady underline) + "%p1%{2}%<" "%t%{8}" // blink block + "%e%p1%{2}%=" "%t%{112}" // steady block + "%e%p1%{3}%=" "%t%{4}" // blink underline (set to half block) + "%e%p1%{4}%=" "%t%{4}" // steady underline + "%e%p1%{5}%=" "%t%{2}" // blink bar (set to underline) + "%e%p1%{6}%=" "%t%{2}" // steady bar + "%e%{0}" // anything else + "%;" "%dc"); + if (-1 == tui->unibi_ext.reset_cursor_style) { + tui->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", + ""); } - unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style, + unibi_set_ext_str(ut, (size_t)tui->unibi_ext.reset_cursor_style, "\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 - data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", - TMUX_WRAP(tmux, - "\x1b]50;CursorShape=%?" - "%p1%{3}%<" "%t%{0}" // block - "%e%p1%{5}%<" "%t%{2}" // underline - "%e%{1}" // everything else is bar - "%;%d;BlinkingCursorEnabled=%?" - "%p1%{1}%<" "%t%{1}" // Fortunately if we exclude zero as special, - "%e%p1%{1}%&" // in all other cases we can treat bit #0 as a flag. - "%;%d\x07")); - if (-1 == data->unibi_ext.reset_cursor_style) { - data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", - ""); + tui->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", + TMUX_WRAP(tmux, + "\x1b]50;CursorShape=%?" + "%p1%{3}%<" "%t%{0}" // block + "%e%p1%{5}%<" "%t%{2}" // underline + "%e%{1}" // everything else is bar + "%;%d;BlinkingCursorEnabled=%?" + "%p1%{1}%<" "%t%{1}" // Fortunately if we exclude zero as special, + "%e%p1%{1}%&" // in all other cases we can treat bit #0 as a flag. + "%;%d\x07")); + if (-1 == tui->unibi_ext.reset_cursor_style) { + tui->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", + ""); } - unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style, + unibi_set_ext_str(ut, (size_t)tui->unibi_ext.reset_cursor_style, "\x1b]50;\x07"); } } @@ -2049,10 +1998,10 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, const char *col /// This adds stuff that is not in standard terminfo as extended unibilium /// capabilities. -static void augment_terminfo(TUIData *data, const char *term, long vte_version, long konsolev, +static void augment_terminfo(TUIData *tui, const char *term, long vte_version, long konsolev, bool iterm_env, bool nsterm) { - unibi_term *ut = data->ut; + unibi_term *ut = tui->ut; bool xterm = terminfo_is_term_family(term, "xterm") // Treat Terminal.app as generic xterm-like, for now. || nsterm; @@ -2082,24 +2031,29 @@ static void augment_terminfo(TUIData *data, const char *term, long vte_version, || konsolev // per commentary in VT102Emulation.cpp || teraterm // per TeraTerm "Supported Control Functions" doco || rxvt) { // per command.C - data->unibi_ext.resize_screen = (int)unibi_add_ext_str(ut, - "ext.resize_screen", - "\x1b[8;%p1%d;%p2%dt"); + tui->unibi_ext.resize_screen = (int)unibi_add_ext_str(ut, + "ext.resize_screen", + "\x1b[8;%p1%d;%p2%dt"); } if (putty || xterm || hterm || rxvt) { - data->unibi_ext.reset_scroll_region = (int)unibi_add_ext_str(ut, - "ext.reset_scroll_region", - "\x1b[r"); + tui->unibi_ext.reset_scroll_region = (int)unibi_add_ext_str(ut, + "ext.reset_scroll_region", + "\x1b[r"); } // terminfo describes strikethrough modes as rmxx/smxx with respect // to the ECMA-48 strikeout/crossed-out attributes. - data->unibi_ext.enter_strikethrough_mode = unibi_find_ext_str(ut, "smxx"); + tui->unibi_ext.enter_strikethrough_mode = unibi_find_ext_str(ut, "smxx"); + + // It should be pretty safe to always enable this, as terminals will ignore + // unrecognised SGR numbers. + tui->unibi_ext.enter_altfont_mode = (int)unibi_add_ext_str(ut, "ext.enter_altfont_mode", + "\x1b[11m"); // Dickey ncurses terminfo does not include the setrgbf and setrgbb // capabilities, proposed by Rüdiger Sonderfeld on 2013-10-15. Adding // them here when terminfo lacks them is an augmentation, not a fixup. - // https://gist.github.com/XVilka/8346728 + // https://github.com/termstandard/colors // At this time (2017-07-12) it seems like all terminals that support rgb // color codes can use semicolons in the terminal code and be fine. @@ -2114,110 +2068,119 @@ static void augment_terminfo(TUIData *data, const char *term, long vte_version, // per http://invisible-island.net/xterm/xterm.log.html#xterm_282 || true_xterm); - data->unibi_ext.set_rgb_foreground = unibi_find_ext_str(ut, "setrgbf"); - if (-1 == data->unibi_ext.set_rgb_foreground) { + tui->unibi_ext.set_rgb_foreground = unibi_find_ext_str(ut, "setrgbf"); + if (-1 == tui->unibi_ext.set_rgb_foreground) { if (has_colon_rgb) { - data->unibi_ext.set_rgb_foreground = (int)unibi_add_ext_str(ut, "setrgbf", - "\x1b[38:2:%p1%d:%p2%d:%p3%dm"); + tui->unibi_ext.set_rgb_foreground = (int)unibi_add_ext_str(ut, "setrgbf", + "\x1b[38:2:%p1%d:%p2%d:%p3%dm"); } else { - data->unibi_ext.set_rgb_foreground = (int)unibi_add_ext_str(ut, "setrgbf", - "\x1b[38;2;%p1%d;%p2%d;%p3%dm"); + tui->unibi_ext.set_rgb_foreground = (int)unibi_add_ext_str(ut, "setrgbf", + "\x1b[38;2;%p1%d;%p2%d;%p3%dm"); } } - data->unibi_ext.set_rgb_background = unibi_find_ext_str(ut, "setrgbb"); - if (-1 == data->unibi_ext.set_rgb_background) { + tui->unibi_ext.set_rgb_background = unibi_find_ext_str(ut, "setrgbb"); + if (-1 == tui->unibi_ext.set_rgb_background) { if (has_colon_rgb) { - data->unibi_ext.set_rgb_background = (int)unibi_add_ext_str(ut, "setrgbb", - "\x1b[48:2:%p1%d:%p2%d:%p3%dm"); + tui->unibi_ext.set_rgb_background = (int)unibi_add_ext_str(ut, "setrgbb", + "\x1b[48:2:%p1%d:%p2%d:%p3%dm"); } else { - data->unibi_ext.set_rgb_background = (int)unibi_add_ext_str(ut, "setrgbb", - "\x1b[48;2;%p1%d;%p2%d;%p3%dm"); + tui->unibi_ext.set_rgb_background = (int)unibi_add_ext_str(ut, "setrgbb", + "\x1b[48;2;%p1%d;%p2%d;%p3%dm"); } } - data->unibi_ext.set_cursor_color = unibi_find_ext_str(ut, "Cs"); - if (-1 == data->unibi_ext.set_cursor_color) { + tui->unibi_ext.set_cursor_color = unibi_find_ext_str(ut, "Cs"); + if (-1 == tui->unibi_ext.set_cursor_color) { if (iterm || iterm_pretending_xterm) { // FIXME: Bypassing tmux like this affects the cursor colour globally, in // all panes, which is not particularly desirable. A better approach // would use a tmux control sequence and an extra if(screen) test. - data->unibi_ext.set_cursor_color = + tui->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(ut, NULL, TMUX_WRAP(tmux, "\033]Pl%p1%06x\033\\")); } else if ((xterm || hterm || rxvt || tmux || alacritty) && (vte_version == 0 || vte_version >= 3900)) { // Supported in urxvt, newer VTE. - data->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(ut, "ext.set_cursor_color", - "\033]12;#%p1%06x\007"); + tui->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(ut, "ext.set_cursor_color", + "\033]12;%p1%s\007"); } } - if (-1 != data->unibi_ext.set_cursor_color) { - data->unibi_ext.reset_cursor_color = unibi_find_ext_str(ut, "Cr"); - if (-1 == data->unibi_ext.reset_cursor_color) { - data->unibi_ext.reset_cursor_color = (int)unibi_add_ext_str(ut, "ext.reset_cursor_color", - "\x1b]112\x07"); + if (-1 != tui->unibi_ext.set_cursor_color) { + // Some terminals supporting cursor color changing specify their Cs + // capability to take a string parameter. Others take a numeric parameter. + // If and only if the format string contains `%s` we assume a string + // parameter. #20628 + const char *set_cursor_color = + unibi_get_ext_str(ut, (unsigned)tui->unibi_ext.set_cursor_color); + if (set_cursor_color) { + tui->set_cursor_color_as_str = strstr(set_cursor_color, "%s") != NULL; + } + + tui->unibi_ext.reset_cursor_color = unibi_find_ext_str(ut, "Cr"); + if (-1 == tui->unibi_ext.reset_cursor_color) { + tui->unibi_ext.reset_cursor_color = (int)unibi_add_ext_str(ut, "ext.reset_cursor_color", + "\x1b]112\x07"); } } - data->unibi_ext.save_title = (int)unibi_add_ext_str(ut, "ext.save_title", "\x1b[22;0t"); - data->unibi_ext.restore_title = (int)unibi_add_ext_str(ut, "ext.restore_title", "\x1b[23;0t"); + tui->unibi_ext.save_title = (int)unibi_add_ext_str(ut, "ext.save_title", "\x1b[22;0t"); + tui->unibi_ext.restore_title = (int)unibi_add_ext_str(ut, "ext.restore_title", "\x1b[23;0t"); /// Terminals usually ignore unrecognized private modes, and there is no /// known ambiguity with these. So we just set them unconditionally. - data->unibi_ext.enable_lr_margin = + tui->unibi_ext.enable_lr_margin = (int)unibi_add_ext_str(ut, "ext.enable_lr_margin", "\x1b[?69h"); - data->unibi_ext.disable_lr_margin = (int)unibi_add_ext_str(ut, "ext.disable_lr_margin", - "\x1b[?69l"); - data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str(ut, "ext.enable_bpaste", - "\x1b[?2004h"); - data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(ut, "ext.disable_bpaste", - "\x1b[?2004l"); + tui->unibi_ext.disable_lr_margin = (int)unibi_add_ext_str(ut, "ext.disable_lr_margin", + "\x1b[?69l"); + tui->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str(ut, "ext.enable_bpaste", + "\x1b[?2004h"); + tui->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(ut, "ext.disable_bpaste", + "\x1b[?2004l"); // For urxvt send BOTH xterm and old urxvt sequences. #8695 - data->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(ut, "ext.enable_focus", - rxvt ? "\x1b[?1004h\x1b]777;focus;on\x7" : "\x1b[?1004h"); - data->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(ut, "ext.disable_focus", - rxvt ? "\x1b[?1004l\x1b]777;focus;off\x7" : "\x1b[?1004l"); - data->unibi_ext.enable_mouse = (int)unibi_add_ext_str(ut, "ext.enable_mouse", - "\x1b[?1002h\x1b[?1006h"); - data->unibi_ext.disable_mouse = (int)unibi_add_ext_str(ut, "ext.disable_mouse", - "\x1b[?1002l\x1b[?1006l"); - data->unibi_ext.enable_mouse_move = (int)unibi_add_ext_str(ut, "ext.enable_mouse_move", - "\x1b[?1003h"); - data->unibi_ext.disable_mouse_move = (int)unibi_add_ext_str(ut, "ext.disable_mouse_move", - "\x1b[?1003l"); + tui->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(ut, "ext.enable_focus", + rxvt ? "\x1b[?1004h\x1b]777;focus;on\x7" : "\x1b[?1004h"); + tui->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(ut, "ext.disable_focus", + rxvt ? "\x1b[?1004l\x1b]777;focus;off\x7" : "\x1b[?1004l"); + tui->unibi_ext.enable_mouse = (int)unibi_add_ext_str(ut, "ext.enable_mouse", + "\x1b[?1002h\x1b[?1006h"); + tui->unibi_ext.disable_mouse = (int)unibi_add_ext_str(ut, "ext.disable_mouse", + "\x1b[?1002l\x1b[?1006l"); + tui->unibi_ext.enable_mouse_move = (int)unibi_add_ext_str(ut, "ext.enable_mouse_move", + "\x1b[?1003h"); + tui->unibi_ext.disable_mouse_move = (int)unibi_add_ext_str(ut, "ext.disable_mouse_move", + "\x1b[?1003l"); // Extended underline. // terminfo will have Smulx for this (but no support for colors yet). - data->unibi_ext.set_underline_style = unibi_find_ext_str(ut, "Smulx"); - if (data->unibi_ext.set_underline_style == -1) { + tui->unibi_ext.set_underline_style = unibi_find_ext_str(ut, "Smulx"); + if (tui->unibi_ext.set_underline_style == -1) { int ext_bool_Su = unibi_find_ext_bool(ut, "Su"); // used by kitty if (vte_version >= 5102 || konsolev >= 221170 || (ext_bool_Su != -1 && unibi_get_ext_bool(ut, (size_t)ext_bool_Su))) { - data->unibi_ext.set_underline_style = (int)unibi_add_ext_str(ut, "ext.set_underline_style", - "\x1b[4:%p1%dm"); + tui->unibi_ext.set_underline_style = (int)unibi_add_ext_str(ut, "ext.set_underline_style", + "\x1b[4:%p1%dm"); } } - if (data->unibi_ext.set_underline_style != -1) { + if (tui->unibi_ext.set_underline_style != -1) { // Only support colon syntax. #9270 - 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"); + tui->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 && (vte_version == 0 || vte_version >= 5400)) { // Fallback to Xterm's modifyOtherKeys if terminal does not support CSI u - data->input.extkeys_type = kExtkeysXterm; + tui->input.extkeys_type = kExtkeysXterm; } } -static void flush_buf(UI *ui) +static void flush_buf(TUIData *tui) { uv_write_t req; uv_buf_t bufs[3]; uv_buf_t *bufp = &bufs[0]; - TUIData *data = ui->data; // The content of the output for each condition is shown in the following - // table. Therefore, if data->bufpos == 0 and N/A or invis + norm, there is + // table. Therefore, if tui->bufpos == 0 and N/A or invis + norm, there is // no need to output it. // // | is_invisible | !is_invisible @@ -2229,54 +2192,54 @@ static void flush_buf(UI *ui) // | !want_invisible | norm | invis + norm // ------+-----------------+--------------+--------------- // - if (data->bufpos <= 0 - && ((data->is_invisible && data->busy) - || (data->is_invisible && !data->busy && data->want_invisible) - || (!data->is_invisible && !data->busy && !data->want_invisible))) { + if (tui->bufpos <= 0 + && ((tui->is_invisible && tui->busy) + || (tui->is_invisible && !tui->busy && tui->want_invisible) + || (!tui->is_invisible && !tui->busy && !tui->want_invisible))) { return; } - if (!data->is_invisible) { + if (!tui->is_invisible) { // cursor is visible. Write a "cursor invisible" command before writing the // buffer. - bufp->base = data->invis; - bufp->len = UV_BUF_LEN(data->invislen); + bufp->base = tui->invis; + bufp->len = UV_BUF_LEN(tui->invislen); bufp++; - data->is_invisible = true; + tui->is_invisible = true; } - if (data->bufpos > 0) { - bufp->base = data->buf; - bufp->len = UV_BUF_LEN(data->bufpos); + if (tui->bufpos > 0) { + bufp->base = tui->buf; + bufp->len = UV_BUF_LEN(tui->bufpos); bufp++; } - if (!data->busy) { - assert(data->is_invisible); + if (!tui->busy) { + assert(tui->is_invisible); // not busy and the cursor is invisible. Write a "cursor normal" command // after writing the buffer. - if (!data->want_invisible) { - bufp->base = data->norm; - bufp->len = UV_BUF_LEN(data->normlen); + if (!tui->want_invisible) { + bufp->base = tui->norm; + bufp->len = UV_BUF_LEN(tui->normlen); bufp++; - data->is_invisible = false; + tui->is_invisible = false; } } - if (data->screenshot) { + if (tui->screenshot) { for (size_t i = 0; i < (size_t)(bufp - bufs); i++) { - fwrite(bufs[i].base, bufs[i].len, 1, data->screenshot); + fwrite(bufs[i].base, bufs[i].len, 1, tui->screenshot); } } else { - int ret = uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle), + int ret = uv_write(&req, STRUCT_CAST(uv_stream_t, &tui->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); + uv_run(&tui->write_loop, UV_RUN_DEFAULT); } - data->bufpos = 0; - data->overflow = false; + tui->bufpos = 0; + tui->overflow = false; } /// Try to get "kbs" code from stty because "the terminfo kbs entry is extremely @@ -2284,12 +2247,12 @@ static void flush_buf(UI *ui) /// /// @see tmux/tty-keys.c fe4e9470bb504357d073320f5d305b22663ee3fd /// @see https://bugzilla.redhat.com/show_bug.cgi?id=142659 -static const char *tui_get_stty_erase(void) +static const char *tui_get_stty_erase(int fd) { static char stty_erase[2] = { 0 }; #if defined(HAVE_TERMIOS_H) struct termios t; - if (tcgetattr(input_global_fd(), &t) != -1) { + if (tcgetattr(fd, &t) != -1) { stty_erase[0] = (char)t.c_cc[VERASE]; stty_erase[1] = '\0'; DLOG("stty/termios:erase=%s", stty_erase); @@ -2302,9 +2265,10 @@ static const char *tui_get_stty_erase(void) /// @see TermInput.tk_ti_hook_fn static const char *tui_tk_ti_getstr(const char *name, const char *value, void *data) { + TermInput *input = data; static const char *stty_erase = NULL; if (stty_erase == NULL) { - stty_erase = tui_get_stty_erase(); + stty_erase = tui_get_stty_erase(input->in_fd); } if (strequal(name, "key_backspace")) { diff --git a/src/nvim/types.h b/src/nvim/types.h index fb10bf21d9..d90c623955 100644 --- a/src/nvim/types.h +++ b/src/nvim/types.h @@ -45,6 +45,9 @@ typedef enum { kTrue = 1, } TriState; +#define TRISTATE_TO_BOOL(val, \ + default) ((val) == kTrue ? true : ((val) == kFalse ? false : (default))) + typedef struct Decoration Decoration; #endif // NVIM_TYPES_H diff --git a/src/nvim/ugrid.c b/src/nvim/ugrid.c index d96da3f2bb..be1746983c 100644 --- a/src/nvim/ugrid.c +++ b/src/nvim/ugrid.c @@ -2,14 +2,10 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> -#include <limits.h> -#include <stdbool.h> -#include <stdio.h> +#include <string.h> -#include "nvim/assert.h" +#include "nvim/memory.h" #include "nvim/ugrid.h" -#include "nvim/ui.h" -#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ugrid.c.generated.h" diff --git a/src/nvim/ugrid.h b/src/nvim/ugrid.h index ae11153c61..a85a6fb4a8 100644 --- a/src/nvim/ugrid.h +++ b/src/nvim/ugrid.h @@ -2,8 +2,12 @@ #define NVIM_UGRID_H #include "nvim/globals.h" +#include "nvim/grid_defs.h" #include "nvim/ui.h" +struct ucell; +struct ugrid; + typedef struct ucell UCell; typedef struct ugrid UGrid; diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 2c9510bf34..9f1cb87eb0 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -2,47 +2,40 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> -#include <inttypes.h> #include <limits.h> #include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> #include <string.h> +#include "klib/kvec.h" +#include "nvim/api/private/helpers.h" +#include "nvim/api/ui.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" -#include "nvim/charset.h" -#include "nvim/cursor.h" +#include "nvim/buffer_defs.h" #include "nvim/cursor_shape.h" -#include "nvim/diff.h" #include "nvim/drawscreen.h" -#include "nvim/event/loop.h" #include "nvim/ex_getln.h" -#include "nvim/fold.h" -#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" +#include "nvim/highlight_defs.h" #include "nvim/log.h" -#include "nvim/main.h" -#include "nvim/mbyte.h" +#include "nvim/lua/executor.h" +#include "nvim/map.h" #include "nvim/memory.h" -#include "nvim/move.h" -#include "nvim/msgpack_rpc/channel.h" -#include "nvim/normal.h" +#include "nvim/message.h" #include "nvim/option.h" -#include "nvim/os/input.h" -#include "nvim/os/signal.h" #include "nvim/os/time.h" -#include "nvim/os_unix.h" -#include "nvim/popupmenu.h" +#include "nvim/strings.h" #include "nvim/ui.h" +#include "nvim/ui_client.h" #include "nvim/ui_compositor.h" #include "nvim/vim.h" #include "nvim/window.h" -#ifdef FEAT_TUI -# include "nvim/tui/tui.h" -#else -# include "nvim/msgpack_rpc/server.h" -#endif -#include "nvim/api/private/helpers.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui.c.generated.h" @@ -61,6 +54,9 @@ static bool pending_mode_info_update = false; static bool pending_mode_update = false; static handle_T cursor_grid_handle = DEFAULT_GRID_HANDLE; +static PMap(uint32_t) ui_event_cbs = MAP_INIT; +bool ui_cb_ext[kUIExtCount]; ///< Internalized UI capabilities. + static bool has_mouse = false; static int pending_has_mouse = -1; @@ -102,8 +98,8 @@ static char uilog_last_event[1024] = { 0 }; bool any_call = false; \ for (size_t i = 0; i < ui_count; i++) { \ UI *ui = uis[i]; \ - if (ui->funname && (cond)) { \ - ui->funname(__VA_ARGS__); \ + if ((cond)) { \ + remote_ui_##funname(__VA_ARGS__); \ any_call = true; \ } \ } \ @@ -116,10 +112,6 @@ static char uilog_last_event[1024] = { 0 }; # include "ui_events_call.generated.h" #endif -#ifndef EXITFREE -# undef entered_free_all_mem -#endif - void ui_init(void) { default_grid.handle = 1; @@ -128,36 +120,25 @@ void ui_init(void) kv_ensure_space(call_buf, 16); } +#ifdef EXITFREE void ui_free_all_mem(void) { kv_destroy(call_buf); -} -void ui_builtin_start(void) -{ -#ifdef FEAT_TUI - tui_start(); -#else - fprintf(stderr, "Nvim headless-mode started.\n"); - size_t len; - char **addrs = server_address_list(&len); - if (addrs != NULL) { - fprintf(stderr, "Listening on:\n"); - for (size_t i = 0; i < len; i++) { - fprintf(stderr, "\t%s\n", addrs[i]); - } - xfree(addrs); - } - fprintf(stderr, "Press CTRL+C to exit.\n"); -#endif + UIEventCallback *event_cb; + map_foreach_value(&ui_event_cbs, event_cb, { + free_ui_event_callback(event_cb); + }) + pmap_destroy(uint32_t)(&ui_event_cbs); } +#endif bool ui_rgb_attached(void) { if (!headless_mode && p_tgc) { return true; } - for (size_t i = 1; i < ui_count; i++) { + for (size_t i = 0; i < ui_count; i++) { if (uis[i]->rgb) { return true; } @@ -168,7 +149,7 @@ bool ui_rgb_attached(void) /// Returns true if any UI requested `override=true`. bool ui_override(void) { - for (size_t i = 1; i < ui_count; i++) { + for (size_t i = 0; i < ui_count; i++) { if (uis[i]->override) { return true; } @@ -178,17 +159,21 @@ bool ui_override(void) bool ui_active(void) { - return ui_count > 1; + return ui_count > 0; } void ui_refresh(void) { + if (ui_client_channel_id) { + abort(); + } + if (!ui_active()) { return; } if (updating_screen) { - deferred_refresh_event(NULL); + ui_schedule_refresh(); return; } @@ -198,16 +183,13 @@ void ui_refresh(void) ext_widgets[i] = true; } - UI *compositor = uis[0]; - bool inclusive = ui_override(); - for (size_t i = 1; i < ui_count; i++) { + for (size_t i = 0; i < ui_count; i++) { UI *ui = uis[i]; width = MIN(ui->width, width); height = MIN(ui->height, height); for (UIExtension j = 0; (int)j < kUIExtCount; j++) { - bool in_compositor = ui->composed && compositor->ui_ext[j]; - ext_widgets[j] &= (ui->ui_ext[j] || in_compositor || inclusive); + ext_widgets[j] &= (ui->ui_ext[j] || inclusive); } } @@ -215,6 +197,9 @@ void ui_refresh(void) pending_cursor_update = true; for (UIExtension i = 0; (int)i < kUIExtCount; i++) { + if (i < kUIGlobalCount) { + ext_widgets[i] |= ui_cb_ext[i]; + } ui_ext[i] = ext_widgets[i]; if (i < kUIGlobalCount) { ui_call_option_set(cstr_as_string((char *)ui_ext_names[i]), @@ -224,20 +209,13 @@ void ui_refresh(void) ui_default_colors_set(); - 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; - ADD(args, INTEGER_OBJ((int)width)); - ADD(args, INTEGER_OBJ((int)height)); - rpc_send_event(ui_client_channel_id, "nvim_ui_try_resize", args); - } + int save_p_lz = p_lz; + p_lz = false; // convince redrawing() to return true ... + screen_resize(width, height); + p_lz = save_p_lz; if (ext_widgets[kUIMessages]) { - p_ch = 0; + set_option_value("cmdheight", 0L, NULL, 0); command_height(); } ui_mode_info_set(); @@ -249,7 +227,7 @@ void ui_refresh(void) int ui_pum_get_height(void) { int pum_height = 0; - for (size_t i = 1; i < ui_count; i++) { + for (size_t i = 0; i < ui_count; i++) { int ui_pum_height = uis[i]->pum_nlines; if (ui_pum_height) { pum_height = @@ -261,7 +239,7 @@ int ui_pum_get_height(void) bool ui_pum_get_pos(double *pwidth, double *pheight, double *prow, double *pcol) { - for (size_t i = 1; i < ui_count; i++) { + for (size_t i = 0; i < ui_count; i++) { if (!uis[i]->pum_pos) { continue; } @@ -281,10 +259,6 @@ static void ui_refresh_event(void **argv) void ui_schedule_refresh(void) { - loop_schedule_fast(&main_loop, event_create(deferred_refresh_event, 0)); -} -static void deferred_refresh_event(void **argv) -{ multiqueue_put(resize_events, ui_refresh_event, 0); } @@ -315,34 +289,36 @@ 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; + if (emsg_silent != 0 || in_assert_fails) { + return; + } - // 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(); - } + 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)); - } + // 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)); } } @@ -372,10 +348,7 @@ void ui_attach_impl(UI *ui, uint64_t chanid) } ui_refresh(); - bool is_compositor = (ui == uis[0]); - if (!is_compositor) { - do_autocmd_uienter(chanid, true); - } + do_autocmd_uienter(chanid, true); } void ui_detach_impl(UI *ui, uint64_t chanid) @@ -420,9 +393,9 @@ void ui_set_ext_option(UI *ui, UIExtension ext, bool active) ui_refresh(); return; } - if (ui->option_set && (ui_ext_names[ext][0] != '_' || active)) { - ui->option_set(ui, cstr_as_string((char *)ui_ext_names[ext]), - BOOLEAN_OBJ(active)); + if (ui_ext_names[ext][0] != '_' || active) { + remote_ui_option_set(ui, cstr_as_string((char *)ui_ext_names[ext]), + BOOLEAN_OBJ(active)); } if (ext == kUITermColors) { ui_default_colors_set(); @@ -452,7 +425,7 @@ void ui_line(ScreenGrid *grid, int row, int startcol, int endcol, int clearcol, MIN(clearcol, (int)grid->cols - 1)); ui_call_flush(); uint64_t wd = (uint64_t)labs(p_wd); - os_microdelay(wd * 1000u, true); + os_microdelay(wd * 1000U, true); pending_cursor_update = true; // restore the cursor later } } @@ -506,6 +479,7 @@ handle_T ui_cursor_grid(void) void ui_flush(void) { + assert(!ui_client_channel_id); if (!ui_active()) { return; } @@ -617,7 +591,7 @@ bool ui_has(UIExtension ext) Array ui_array(void) { Array all_uis = ARRAY_DICT_INIT; - for (size_t i = 1; i < ui_count; i++) { + for (size_t i = 0; i < ui_count; i++) { UI *ui = uis[i]; Dictionary info = ARRAY_DICT_INIT; PUT(info, "width", INTEGER_OBJ(ui->width)); @@ -629,7 +603,7 @@ Array ui_array(void) PUT(info, ui_ext_names[j], BOOLEAN_OBJ(ui->ui_ext[j])); } } - ui->inspect(ui, &info); + remote_ui_inspect(ui, &info); ADD(all_uis, DICTIONARY_OBJ(info)); } return all_uis; @@ -662,3 +636,75 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error) win_set_inner_size(wp, true); } } + +void ui_call_event(char *name, Array args) +{ + UIEventCallback *event_cb; + bool handled = false; + map_foreach_value(&ui_event_cbs, event_cb, { + Error err = ERROR_INIT; + Object res = nlua_call_ref(event_cb->cb, name, args, false, &err); + if (res.type == kObjectTypeBoolean && res.data.boolean == true) { + handled = true; + } + if (ERROR_SET(&err)) { + ELOG("Error while executing ui_comp_event callback: %s", err.msg); + } + api_clear_error(&err); + }) + + if (!handled) { + UI_CALL(true, event, ui, name, args); + } +} + +void ui_cb_update_ext(void) +{ + memset(ui_cb_ext, 0, ARRAY_SIZE(ui_cb_ext)); + + for (size_t i = 0; i < kUIGlobalCount; i++) { + UIEventCallback *event_cb; + + map_foreach_value(&ui_event_cbs, event_cb, { + if (event_cb->ext_widgets[i]) { + ui_cb_ext[i] = true; + break; + } + }) + } +} + +void free_ui_event_callback(UIEventCallback *event_cb) +{ + api_free_luaref(event_cb->cb); + xfree(event_cb); +} + +void ui_add_cb(uint32_t ns_id, LuaRef cb, bool *ext_widgets) +{ + UIEventCallback *event_cb = xcalloc(1, sizeof(UIEventCallback)); + event_cb->cb = cb; + memcpy(event_cb->ext_widgets, ext_widgets, ARRAY_SIZE(event_cb->ext_widgets)); + if (event_cb->ext_widgets[kUIMessages]) { + event_cb->ext_widgets[kUICmdline] = true; + } + + UIEventCallback **item = (UIEventCallback **)pmap_ref(uint32_t)(&ui_event_cbs, ns_id, true); + if (*item) { + free_ui_event_callback(*item); + } + *item = event_cb; + + ui_cb_update_ext(); + ui_refresh(); +} + +void ui_remove_cb(uint32_t ns_id) +{ + if (pmap_has(uint32_t)(&ui_event_cbs, ns_id)) { + free_ui_event_callback(pmap_get(uint32_t)(&ui_event_cbs, ns_id)); + pmap_del(uint32_t)(&ui_event_cbs, ns_id); + } + ui_cb_update_ext(); + ui_refresh(); +} diff --git a/src/nvim/ui.h b/src/nvim/ui.h index 9034e7b764..9140a9f1f3 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -6,9 +6,14 @@ #include <stdint.h> #include "nvim/api/private/defs.h" +#include "nvim/event/multiqueue.h" #include "nvim/globals.h" #include "nvim/highlight_defs.h" +#include "nvim/macros.h" #include "nvim/memory.h" +#include "nvim/types.h" + +struct ui_t; typedef enum { kUICmdline = 0, @@ -47,6 +52,41 @@ enum { typedef int LineFlags; +typedef struct { + uint64_t channel_id; + +#define UI_BUF_SIZE 4096 ///< total buffer size for pending msgpack data. + /// guaranteed size available for each new event (so packing of simple events + /// and the header of grid_line will never fail) +#define EVENT_BUF_SIZE 256 + char buf[UI_BUF_SIZE]; ///< buffer of packed but not yet sent msgpack data + char *buf_wptr; ///< write head of buffer + const char *cur_event; ///< name of current event (might get multiple arglists) + Array call_buf; ///< buffer for constructing a single arg list (max 16 elements!) + + // state for write_cb, while packing a single arglist to msgpack. This + // might fail due to buffer overflow. + size_t pack_totlen; + bool buf_overflow; + char *temp_buf; + + // We start packing the two outermost msgpack arrays before knowing the total + // number of elements. Thus track the location where array size will need + // to be written in the msgpack buffer, once the specific array is finished. + char *nevents_pos; + char *ncalls_pos; + uint32_t nevents; ///< number of distinct events (top-level args to "redraw" + uint32_t ncalls; ///< number of calls made to the current event (plus one for the name!) + bool flushed_events; ///< events where sent to client without "flush" event + + int hl_id; // Current highlight for legacy put event. + Integer cursor_row, cursor_col; // Intended visible cursor position. + + // Position of legacy cursor, used both for drawing and visible user cursor. + Integer client_row, client_col; + bool wildmenu_active; +} UIData; + struct ui_t { bool rgb; bool override; ///< Force highest-requested UI capabilities. @@ -60,13 +100,9 @@ struct ui_t { double pum_col; double pum_height; double pum_width; - void *data; -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "ui_events.generated.h" -#endif - - void (*inspect)(UI *ui, Dictionary *info); + // TODO(bfredl): integrate into struct! + UIData data[1]; }; typedef struct ui_event_callback { @@ -74,11 +110,12 @@ typedef struct ui_event_callback { bool ext_widgets[kUIGlobalCount]; } UIEventCallback; +// uncrustify:off #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui.h.generated.h" - # include "ui_events_call.h.generated.h" #endif +// uncrustify:on EXTERN MultiQueue *resize_events; #endif // NVIM_UI_H diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c deleted file mode 100644 index 809d278029..0000000000 --- a/src/nvim/ui_bridge.c +++ /dev/null @@ -1,223 +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 - -// UI wrapper that sends requests to the UI thread. -// Used by the built-in TUI and libnvim-based UIs. - -#include <assert.h> -#include <limits.h> -#include <stdbool.h> -#include <stdio.h> - -#include "nvim/api/private/helpers.h" -#include "nvim/log.h" -#include "nvim/main.h" -#include "nvim/memory.h" -#include "nvim/ugrid.h" -#include "nvim/ui.h" -#include "nvim/ui_bridge.h" -#include "nvim/vim.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "ui_bridge.c.generated.h" -#endif - -#define UI(b) (((UIBridgeData *)b)->ui) - -// Schedule a function call on the UI bridge thread. -#define UI_BRIDGE_CALL(ui, name, argc, ...) \ - ((UIBridgeData *)ui)->scheduler(event_create(ui_bridge_##name##_event, argc, __VA_ARGS__), UI(ui)) - -#define INT2PTR(i) ((void *)(intptr_t)i) -#define PTR2INT(p) ((Integer)(intptr_t)p) - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "ui_events_bridge.generated.h" -#endif - -UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) -{ - UIBridgeData *rv = xcalloc(1, sizeof(UIBridgeData)); - rv->ui = ui; - rv->bridge.rgb = ui->rgb; - rv->bridge.width = ui->width; - rv->bridge.height = ui->height; - rv->bridge.stop = ui_bridge_stop; - rv->bridge.grid_resize = ui_bridge_grid_resize; - rv->bridge.grid_clear = ui_bridge_grid_clear; - rv->bridge.grid_cursor_goto = ui_bridge_grid_cursor_goto; - rv->bridge.mode_info_set = ui_bridge_mode_info_set; - rv->bridge.update_menu = ui_bridge_update_menu; - rv->bridge.busy_start = ui_bridge_busy_start; - rv->bridge.busy_stop = ui_bridge_busy_stop; - rv->bridge.mouse_on = ui_bridge_mouse_on; - rv->bridge.mouse_off = ui_bridge_mouse_off; - rv->bridge.mode_change = ui_bridge_mode_change; - rv->bridge.grid_scroll = ui_bridge_grid_scroll; - rv->bridge.hl_attr_define = ui_bridge_hl_attr_define; - rv->bridge.bell = ui_bridge_bell; - rv->bridge.visual_bell = ui_bridge_visual_bell; - rv->bridge.default_colors_set = ui_bridge_default_colors_set; - rv->bridge.flush = ui_bridge_flush; - rv->bridge.suspend = ui_bridge_suspend; - rv->bridge.set_title = ui_bridge_set_title; - rv->bridge.set_icon = ui_bridge_set_icon; - rv->bridge.screenshot = ui_bridge_screenshot; - rv->bridge.option_set = ui_bridge_option_set; - rv->bridge.raw_line = ui_bridge_raw_line; - rv->bridge.inspect = ui_bridge_inspect; - rv->scheduler = scheduler; - - for (UIExtension i = 0; (int)i < kUIExtCount; i++) { - rv->bridge.ui_ext[i] = ui->ui_ext[i]; - } - - rv->ui_main = ui_main; - uv_mutex_init(&rv->mutex); - uv_cond_init(&rv->cond); - uv_mutex_lock(&rv->mutex); - rv->ready = false; - - if (uv_thread_create(&rv->ui_thread, ui_thread_run, rv)) { - abort(); - } - - // Suspend the main thread until CONTINUE is called by the UI thread. - while (!rv->ready) { - uv_cond_wait(&rv->cond, &rv->mutex); - } - uv_mutex_unlock(&rv->mutex); - - ui_attach_impl(&rv->bridge, 0); - - return &rv->bridge; -} - -void ui_bridge_stopped(UIBridgeData *bridge) -{ - uv_mutex_lock(&bridge->mutex); - bridge->stopped = true; - uv_mutex_unlock(&bridge->mutex); -} - -static void ui_thread_run(void *data) -{ - UIBridgeData *bridge = data; - bridge->ui_main(bridge, bridge->ui); -} - -static void ui_bridge_stop(UI *b) -{ - // Detach bridge first, so that "stop" is the last event the TUI loop - // receives from the main thread. #8041 - ui_detach_impl(b, 0); - - UIBridgeData *bridge = (UIBridgeData *)b; - bool stopped = bridge->stopped = false; - UI_BRIDGE_CALL(b, stop, 1, b); - for (;;) { - uv_mutex_lock(&bridge->mutex); - stopped = bridge->stopped; - uv_mutex_unlock(&bridge->mutex); - if (stopped) { // -V547 - break; - } - // TODO(justinmk): Remove this. Use a cond-wait above. #9274 - loop_poll_events(&main_loop, 10); // Process one event. - } - uv_thread_join(&bridge->ui_thread); - uv_mutex_destroy(&bridge->mutex); - uv_cond_destroy(&bridge->cond); - xfree(bridge->ui); // Threads joined, now safe to free UI container. #7922 - xfree(b); -} -static void ui_bridge_stop_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->stop(ui); -} - -static void ui_bridge_hl_attr_define(UI *ui, Integer id, HlAttrs attrs, HlAttrs cterm_attrs, - Array info) -{ - HlAttrs *a = xmalloc(sizeof(HlAttrs)); - *a = attrs; - UI_BRIDGE_CALL(ui, hl_attr_define, 3, ui, INT2PTR(id), a); -} -static void ui_bridge_hl_attr_define_event(void **argv) -{ - UI *ui = UI(argv[0]); - Array info = ARRAY_DICT_INIT; - ui->hl_attr_define(ui, PTR2INT(argv[1]), *((HlAttrs *)argv[2]), - *((HlAttrs *)argv[2]), info); - xfree(argv[2]); -} - -static void ui_bridge_raw_line_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->raw_line(ui, PTR2INT(argv[1]), PTR2INT(argv[2]), PTR2INT(argv[3]), - PTR2INT(argv[4]), PTR2INT(argv[5]), PTR2INT(argv[6]), - (LineFlags)PTR2INT(argv[7]), argv[8], argv[9]); - xfree(argv[8]); - xfree(argv[9]); -} -static void ui_bridge_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol, - Integer clearcol, Integer clearattr, LineFlags flags, - const schar_T *chunk, const sattr_T *attrs) -{ - size_t ncol = (size_t)(endcol - startcol); - schar_T *c = xmemdup(chunk, ncol * sizeof(schar_T)); - sattr_T *hl = xmemdup(attrs, ncol * sizeof(sattr_T)); - UI_BRIDGE_CALL(ui, raw_line, 10, ui, INT2PTR(grid), INT2PTR(row), - INT2PTR(startcol), INT2PTR(endcol), INT2PTR(clearcol), - INT2PTR(clearattr), INT2PTR(flags), c, hl); -} - -static void ui_bridge_suspend(UI *b) -{ - UIBridgeData *data = (UIBridgeData *)b; - uv_mutex_lock(&data->mutex); - UI_BRIDGE_CALL(b, suspend, 1, b); - data->ready = false; - // Suspend the main thread until CONTINUE is called by the UI thread. - while (!data->ready) { - uv_cond_wait(&data->cond, &data->mutex); - } - uv_mutex_unlock(&data->mutex); -} -static void ui_bridge_suspend_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->suspend(ui); -} - -static void ui_bridge_option_set(UI *ui, String name, Object value) -{ - String copy_name = copy_string(name, NULL); - Object *copy_value = xmalloc(sizeof(Object)); - *copy_value = copy_object(value, NULL); - UI_BRIDGE_CALL(ui, option_set, 4, ui, copy_name.data, - INT2PTR(copy_name.size), copy_value); - // TODO(bfredl): when/if TUI/bridge teardown is refactored to use events, the - // commit that introduced this special case can be reverted. - // For now this is needed for nvim_list_uis(). - if (strequal(name.data, "termguicolors")) { - ui->rgb = value.data.boolean; - } -} -static void ui_bridge_option_set_event(void **argv) -{ - UI *ui = UI(argv[0]); - String name = (String){ .data = argv[1], .size = (size_t)argv[2] }; - Object value = *(Object *)argv[3]; - ui->option_set(ui, name, value); - api_free_string(name); - api_free_object(value); - xfree(argv[3]); -} - -static void ui_bridge_inspect(UI *ui, Dictionary *info) -{ - PUT(*info, "chan", INTEGER_OBJ(0)); -} diff --git a/src/nvim/ui_bridge.h b/src/nvim/ui_bridge.h deleted file mode 100644 index c18600a857..0000000000 --- a/src/nvim/ui_bridge.h +++ /dev/null @@ -1,44 +0,0 @@ -// Bridge for communication between a UI thread and nvim core. -// Used by the built-in TUI and libnvim-based UIs. -#ifndef NVIM_UI_BRIDGE_H -#define NVIM_UI_BRIDGE_H - -#include <uv.h> - -#include "nvim/event/defs.h" -#include "nvim/ui.h" - -typedef struct ui_bridge_data UIBridgeData; -typedef void (*ui_main_fn)(UIBridgeData *bridge, UI *ui); -struct ui_bridge_data { - UI bridge; // actual UI passed to ui_attach - UI *ui; // UI pointer that will have its callback called in - // another thread - event_scheduler scheduler; - uv_thread_t ui_thread; - ui_main_fn ui_main; - uv_mutex_t mutex; - uv_cond_t cond; - // When the UI thread is called, the main thread will suspend until - // the call returns. This flag is used as a condition for the main - // thread to continue. - bool ready; - // When a stop request is sent from the main thread, it must wait until the UI - // thread finishes handling all events. This flag is set by the UI thread as a - // signal that it will no longer send messages to the main thread. - bool stopped; -}; - -#define CONTINUE(b) \ - do { \ - UIBridgeData *d = (UIBridgeData *)b; \ - uv_mutex_lock(&d->mutex); \ - d->ready = true; \ - uv_cond_signal(&d->cond); \ - uv_mutex_unlock(&d->mutex); \ - } while (0) - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "ui_bridge.h.generated.h" -#endif -#endif // NVIM_UI_BRIDGE_H diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c index 265c54f72d..378c0e4831 100644 --- a/src/nvim/ui_client.c +++ b/src/nvim/ui_client.c @@ -1,44 +1,126 @@ // 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 <assert.h> #include <stdbool.h> #include <stdint.h> +#include <stdlib.h> -#include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" +#include "nvim/channel.h" +#include "nvim/eval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/event/loop.h" +#include "nvim/globals.h" #include "nvim/highlight.h" #include "nvim/log.h" -#include "nvim/map.h" +#include "nvim/main.h" +#include "nvim/memory.h" #include "nvim/msgpack_rpc/channel.h" -#include "nvim/screen.h" +#include "nvim/msgpack_rpc/channel_defs.h" +#include "nvim/os/os_defs.h" +#include "nvim/tui/tui.h" #include "nvim/ui.h" #include "nvim/ui_client.h" -#include "nvim/vim.h" +static TUIData *tui = NULL; + +// uncrustify:off #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui_client.c.generated.h" - # include "ui_events_client.generated.h" #endif +// uncrustify:on +// -void ui_client_init(uint64_t chan) +uint64_t ui_client_start_server(int argc, char **argv) { - Array args = ARRAY_DICT_INIT; - int width = Columns; - int height = Rows; - Dictionary opts = ARRAY_DICT_INIT; + varnumber_T exit_status; + char **args = xmalloc(((size_t)(2 + argc)) * sizeof(char *)); + int args_idx = 0; + args[args_idx++] = xstrdup((const char *)get_vim_var_str(VV_PROGPATH)); + args[args_idx++] = xstrdup("--embed"); + for (int i = 1; i < argc; i++) { + args[args_idx++] = xstrdup(argv[i]); + } + args[args_idx++] = NULL; + + CallbackReader on_err = CALLBACK_READER_INIT; + on_err.fwd_err = true; + + Channel *channel = channel_job_start(args, CALLBACK_READER_INIT, + on_err, CALLBACK_NONE, + false, true, true, false, kChannelStdinPipe, + NULL, 0, 0, NULL, &exit_status); + + // If stdin is not a pty, it is forwarded to the client. + // Replace stdin in the TUI process with the tty fd. + if (ui_client_forward_stdin) { + close(0); +#ifdef MSWIN + os_open_conin_fd(); +#else + dup(stderr_isatty ? STDERR_FILENO : STDOUT_FILENO); +#endif + } - PUT(opts, "rgb", BOOLEAN_OBJ(true)); - PUT(opts, "ext_linegrid", BOOLEAN_OBJ(true)); - PUT(opts, "ext_termcolors", BOOLEAN_OBJ(true)); + return channel->id; +} - ADD(args, INTEGER_OBJ((int)width)); - ADD(args, INTEGER_OBJ((int)height)); - ADD(args, DICTIONARY_OBJ(opts)); +void ui_client_run(bool remote_ui) + FUNC_ATTR_NORETURN +{ + int width, height; + char *term; + tui = tui_start(&width, &height, &term); + + MAXSIZE_TEMP_ARRAY(args, 3); + ADD_C(args, INTEGER_OBJ(width)); + ADD_C(args, INTEGER_OBJ(height)); + + MAXSIZE_TEMP_DICT(opts, 9); + PUT_C(opts, "rgb", BOOLEAN_OBJ(true)); + PUT_C(opts, "ext_linegrid", BOOLEAN_OBJ(true)); + PUT_C(opts, "ext_termcolors", BOOLEAN_OBJ(true)); + if (term) { + PUT(opts, "term_name", STRING_OBJ(cstr_to_string(term))); + } + if (ui_client_bg_response != kNone) { + bool is_dark = (ui_client_bg_response == kTrue); + PUT_C(opts, "term_background", STRING_OBJ(cstr_as_string(is_dark ? "dark" : "light"))); + } + PUT_C(opts, "term_colors", INTEGER_OBJ(t_colors)); + if (!remote_ui) { + PUT_C(opts, "stdin_tty", BOOLEAN_OBJ(stdin_isatty)); + PUT_C(opts, "stdout_tty", BOOLEAN_OBJ(stdout_isatty)); + if (ui_client_forward_stdin) { + PUT_C(opts, "stdin_fd", INTEGER_OBJ(UI_CLIENT_STDIN_FD)); + } + } + ADD_C(args, DICTIONARY_OBJ(opts)); + + rpc_send_event(ui_client_channel_id, "nvim_ui_attach", args); + ui_client_attached = true; - rpc_send_event(chan, "nvim_ui_attach", args); - ui_client_channel_id = chan; + // os_exit() will be invoked when the client channel detaches + while (true) { + LOOP_PROCESS_EVENTS(&main_loop, resize_events, -1); + } +} + +void ui_client_stop(void) +{ + tui_stop(tui); +} + +void ui_client_set_size(int width, int height) +{ + // The currently known size will be sent when attaching + if (ui_client_attached) { + MAXSIZE_TEMP_ARRAY(args, 2); + ADD_C(args, INTEGER_OBJ((int)width)); + ADD_C(args, INTEGER_OBJ((int)height)); + rpc_send_event(ui_client_channel_id, "nvim_ui_try_resize", args); + } } UIClientHandler ui_client_get_redraw_handler(const char *name, size_t name_len, Error *error) @@ -61,20 +143,6 @@ Object handle_ui_client_redraw(uint64_t channel_id, Array args, Arena *arena, Er 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; @@ -83,7 +151,7 @@ static HlAttrs ui_client_dict2hlattrs(Dictionary d, bool rgb) // TODO(bfredl): log "err" return HLATTRS_INIT; } - return dict2hlattrs(&dict, true, NULL, &err); + return dict2hlattrs(&dict, rgb, NULL, &err); } void ui_client_event_grid_resize(Array args) @@ -99,7 +167,7 @@ void ui_client_event_grid_resize(Array args) 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); + tui_grid_resize(tui, grid, width, height); if (grid_line_buf_size < (size_t)width) { xfree(grid_line_buf_char); @@ -125,6 +193,6 @@ void ui_client_event_raw_line(GridLineEvent *g) // TODO(hlpr98): Accommodate other LineFlags when included in grid_line LineFlags lineflags = 0; - ui_call_raw_line(grid, row, startcol, endcol, clearcol, g->cur_attr, lineflags, - (const schar_T *)grid_line_buf_char, grid_line_buf_attr); + tui_raw_line(tui, grid, row, startcol, endcol, clearcol, g->cur_attr, lineflags, + (const schar_T *)grid_line_buf_char, grid_line_buf_attr); } diff --git a/src/nvim/ui_client.h b/src/nvim/ui_client.h index 311dafaa0b..7e5f847039 100644 --- a/src/nvim/ui_client.h +++ b/src/nvim/ui_client.h @@ -1,8 +1,14 @@ #ifndef NVIM_UI_CLIENT_H #define NVIM_UI_CLIENT_H +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + #include "nvim/api/private/defs.h" #include "nvim/grid_defs.h" +#include "nvim/macros.h" +#include "nvim/types.h" typedef struct { const char *name; @@ -14,10 +20,29 @@ EXTERN size_t grid_line_buf_size INIT(= 0); EXTERN schar_T *grid_line_buf_char INIT(= NULL); EXTERN sattr_T *grid_line_buf_attr INIT(= NULL); +// ID of the ui client channel. If zero, the client is not running. +EXTERN uint64_t ui_client_channel_id INIT(= 0); + +// TODO(bfredl): the current structure for how tui and ui_client.c communicate is a bit awkward. +// This will be restructured as part of The UI Devirtualization Project. + +/// Whether ui client has sent nvim_ui_attach yet +EXTERN bool ui_client_attached INIT(= false); + +/// Whether ui client has gotten a response about the bg color of the terminal, +/// kTrue=dark, kFalse=light, kNone=no response yet +EXTERN TriState ui_client_bg_response INIT(= kNone); + +/// The ui client should forward its stdin to the nvim process +/// by convention, this uses fd=3 (next free number after stdio) +EXTERN bool ui_client_forward_stdin INIT(= false); + +#define UI_CLIENT_STDIN_FD 3 +// uncrustify:off #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui_client.h.generated.h" - # include "ui_events_client.h.generated.h" #endif +// uncrustify:on #endif // NVIM_UI_CLIENT_H diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index 47c83b8ed1..9ff9eabff8 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -7,25 +7,28 @@ // Layer-based compositing: https://en.wikipedia.org/wiki/Digital_compositing #include <assert.h> +#include <inttypes.h> #include <limits.h> #include <stdbool.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> #include "klib/kvec.h" -#include "nvim/api/private/helpers.h" +#include "nvim/api/private/defs.h" #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/log.h" -#include "nvim/lua/executor.h" -#include "nvim/main.h" -#include "nvim/map.h" +#include "nvim/macros.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/os/os.h" -#include "nvim/popupmenu.h" -#include "nvim/ugrid.h" +#include "nvim/option_defs.h" +#include "nvim/os/time.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" #include "nvim/vim.h" @@ -34,7 +37,6 @@ # include "ui_compositor.c.generated.h" #endif -static UI *compositor = NULL; static int composed_uis = 0; kvec_t(ScreenGrid *) layers = KV_INITIAL_VALUE; @@ -55,52 +57,12 @@ static bool msg_was_scrolled = false; static int msg_sep_row = -1; static schar_T msg_sep_char = { ' ', NUL }; -static PMap(uint32_t) ui_event_cbs = MAP_INIT; - static int dbghl_normal, dbghl_clear, dbghl_composed, dbghl_recompose; void ui_comp_init(void) { - if (compositor != NULL) { - return; - } - compositor = xcalloc(1, sizeof(UI)); - - compositor->rgb = true; - compositor->grid_resize = ui_comp_grid_resize; - compositor->grid_scroll = ui_comp_grid_scroll; - compositor->grid_cursor_goto = ui_comp_grid_cursor_goto; - compositor->raw_line = ui_comp_raw_line; - compositor->msg_set_pos = ui_comp_msg_set_pos; - compositor->event = ui_comp_event; - - // Be unopinionated: will be attached together with a "real" ui anyway - compositor->width = INT_MAX; - compositor->height = INT_MAX; - for (UIExtension i = kUIGlobalCount; (int)i < kUIExtCount; i++) { - compositor->ui_ext[i] = true; - } - - // TODO(bfredl): one day. in the future. - compositor->ui_ext[kUIMultigrid] = false; - - // TODO(bfredl): this will be more complicated if we implement - // hlstate per UI (i e reduce hl ids for non-hlstate UIs) - compositor->ui_ext[kUIHlState] = false; - kv_push(layers, &default_grid); curgrid = &default_grid; - - ui_attach_impl(compositor, 0); -} - -void ui_comp_free_all_mem(void) -{ - UIEventCallback *event_cb; - map_foreach_value(&ui_event_cbs, event_cb, { - free_ui_event_callback(event_cb); - }) - pmap_destroy(uint32_t)(&ui_event_cbs); } void ui_comp_syn_init(void) @@ -253,7 +215,7 @@ bool ui_comp_set_grid(handle_T handle) return false; } -static void ui_comp_raise_grid(ScreenGrid *grid, size_t new_index) +void ui_comp_raise_grid(ScreenGrid *grid, size_t new_index) { size_t old_index = grid->comp_index; for (size_t i = old_index; i < new_index; i++) { @@ -273,7 +235,7 @@ static void ui_comp_raise_grid(ScreenGrid *grid, size_t new_index) } } -static void ui_comp_grid_cursor_goto(UI *ui, Integer grid_handle, Integer r, Integer c) +void ui_comp_grid_cursor_goto(Integer grid_handle, Integer r, Integer c) { if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid_handle)) { return; @@ -505,7 +467,7 @@ static void debug_delay(Integer lines) ui_call_flush(); uint64_t wd = (uint64_t)labs(p_wd); uint64_t factor = (uint64_t)MAX(MIN(lines, 5), 1); - os_microdelay(factor * wd * 1000u, true); + os_microdelay(factor * wd * 1000U, true); } static void compose_area(Integer startrow, Integer endrow, Integer startcol, Integer endcol) @@ -533,9 +495,9 @@ void ui_comp_compose_grid(ScreenGrid *grid) } } -static void ui_comp_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol, - Integer clearcol, Integer clearattr, LineFlags flags, - const schar_T *chunk, const sattr_T *attrs) +void ui_comp_raw_line(Integer grid, Integer row, Integer startcol, Integer endcol, Integer clearcol, + Integer clearattr, LineFlags flags, const schar_T *chunk, + const sattr_T *attrs) { if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid)) { return; @@ -600,14 +562,13 @@ bool ui_comp_set_screen_valid(bool valid) return old_val; } -static void ui_comp_msg_set_pos(UI *ui, Integer grid, Integer row, Boolean scrolled, - String sep_char) +void ui_comp_msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char) { msg_grid.comp_row = (int)row; if (scrolled && row > 0) { msg_sep_row = (int)row - 1; if (sep_char.data) { - STRLCPY(msg_sep_char, sep_char.data, sizeof(msg_sep_char)); + xstrlcpy(msg_sep_char, sep_char.data, sizeof(msg_sep_char)); } } else { msg_sep_row = -1; @@ -645,8 +606,8 @@ static bool curgrid_covered_above(int row) return kv_size(layers) - (above_msg?1:0) > curgrid->comp_index + 1; } -static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot, Integer left, - Integer right, Integer rows, Integer cols) +void ui_comp_grid_scroll(Integer grid, Integer top, Integer bot, Integer left, Integer right, + Integer rows, Integer cols) { if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid)) { return; @@ -680,7 +641,7 @@ static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot, } } -static void ui_comp_grid_resize(UI *ui, Integer grid, Integer width, Integer height) +void ui_comp_grid_resize(Integer grid, Integer width, Integer height) { if (grid == 1) { ui_composed_call_grid_resize(1, width, height); @@ -698,72 +659,3 @@ static void ui_comp_grid_resize(UI *ui, Integer grid, Integer width, Integer hei } } } - -static void ui_comp_event(UI *ui, char *name, Array args) -{ - Error err = ERROR_INIT; - UIEventCallback *event_cb; - bool handled = false; - - map_foreach_value(&ui_event_cbs, event_cb, { - Object res = nlua_call_ref(event_cb->cb, name, args, false, &err); - if (res.type == kObjectTypeBoolean && res.data.boolean == true) { - handled = true; - } - }) - - if (!handled) { - ui_composed_call_event(name, args); - } -} - -static void ui_comp_update_ext(void) -{ - memset(compositor->ui_ext, 0, ARRAY_SIZE(compositor->ui_ext)); - - for (size_t i = 0; i < kUIGlobalCount; i++) { - UIEventCallback *event_cb; - - map_foreach_value(&ui_event_cbs, event_cb, { - if (event_cb->ext_widgets[i]) { - compositor->ui_ext[i] = true; - break; - } - }) - } -} - -void free_ui_event_callback(UIEventCallback *event_cb) -{ - api_free_luaref(event_cb->cb); - xfree(event_cb); -} - -void ui_comp_add_cb(uint32_t ns_id, LuaRef cb, bool *ext_widgets) -{ - UIEventCallback *event_cb = xcalloc(1, sizeof(UIEventCallback)); - event_cb->cb = cb; - memcpy(event_cb->ext_widgets, ext_widgets, ARRAY_SIZE(event_cb->ext_widgets)); - if (event_cb->ext_widgets[kUIMessages]) { - event_cb->ext_widgets[kUICmdline] = true; - } - - UIEventCallback **item = (UIEventCallback **)pmap_ref(uint32_t)(&ui_event_cbs, ns_id, true); - if (*item) { - free_ui_event_callback(*item); - } - *item = event_cb; - - ui_comp_update_ext(); - ui_refresh(); -} - -void ui_comp_remove_cb(uint32_t ns_id) -{ - if (pmap_has(uint32_t)(&ui_event_cbs, ns_id)) { - free_ui_event_callback(pmap_get(uint32_t)(&ui_event_cbs, ns_id)); - pmap_del(uint32_t)(&ui_event_cbs, ns_id); - } - ui_comp_update_ext(); - ui_refresh(); -} diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 1a9066d7f1..2b0bb1d243 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -76,41 +76,56 @@ #include <assert.h> #include <fcntl.h> #include <inttypes.h> -#include <limits.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> +#include <time.h> +#include <uv.h> #include "auto/config.h" #include "klib/kvec.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/option.h" +#include "nvim/os/fs_defs.h" #include "nvim/os/input.h" #include "nvim/os/os.h" +#include "nvim/os/os_defs.h" #include "nvim/os/time.h" -#include "nvim/os_unix.h" #include "nvim/path.h" -#include "nvim/pos.h" // MAXLNUM +#include "nvim/pos.h" +#include "nvim/screen.h" #include "nvim/sha256.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/types.h" #include "nvim/undo.h" +#include "nvim/undo_defs.h" +#include "nvim/vim.h" /// Structure passed around between undofile functions. typedef struct { @@ -558,7 +573,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re } if (size > 0) { - uep->ue_array = xmalloc(sizeof(char_u *) * (size_t)size); + uep->ue_array = xmalloc(sizeof(char *) * (size_t)size); linenr_T lnum; long i; for (i = 0, lnum = top + 1; i < size; i++) { @@ -609,20 +624,20 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re // extra fields for uhp #define UHP_SAVE_NR 1 -static char_u e_not_open[] = N_("E828: Cannot open undo file for writing: %s"); +static char e_not_open[] = N_("E828: Cannot open undo file for writing: %s"); /// Compute the hash for a buffer text into hash[UNDO_HASH_SIZE]. /// /// @param[in] buf The buffer used to compute the hash /// @param[in] hash Array of size UNDO_HASH_SIZE in which to store the value of /// the hash -void u_compute_hash(buf_T *buf, char_u *hash) +void u_compute_hash(buf_T *buf, uint8_t *hash) { context_sha256_T ctx; sha256_start(&ctx); for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { - char_u *p = (char_u *)ml_get_buf(buf, lnum, false); - sha256_update(&ctx, p, (uint32_t)(STRLEN(p) + 1)); + char *p = ml_get_buf(buf, lnum, false); + sha256_update(&ctx, (uint8_t *)p, strlen(p) + 1); } sha256_finish(&ctx, hash); } @@ -662,7 +677,7 @@ char *u_get_undo_file_name(const char *const buf_ffname, const bool reading) // Loop over 'undodir'. When reading find the first file that exists. // When not reading use the first directory that exists or ".". - char *dirp = (char *)p_udir; + char *dirp = p_udir; while (*dirp != NUL) { size_t dir_len = copy_option_part(&dirp, dir_name, MAXPATHL, ","); if (dir_len == 1 && dir_name[0] == '.') { @@ -750,7 +765,7 @@ static void u_free_uhp(u_header_T *uhp) /// @param hash The hash of the buffer contents // /// @returns false in case of an error. -static bool serialize_header(bufinfo_T *bi, char_u *hash) +static bool serialize_header(bufinfo_T *bi, uint8_t *hash) FUNC_ATTR_NONNULL_ALL { buf_T *buf = bi->bi_buf; @@ -773,7 +788,7 @@ static bool serialize_header(bufinfo_T *bi, char_u *hash) undo_write_bytes(bi, (uintmax_t)buf->b_ml.ml_line_count, 4); size_t len = buf->b_u_line_ptr ? strlen(buf->b_u_line_ptr) : 0; undo_write_bytes(bi, len, 4); - if (len > 0 && !undo_write(bi, (char_u *)buf->b_u_line_ptr, len)) { + if (len > 0 && !undo_write(bi, (uint8_t *)buf->b_u_line_ptr, len)) { return false; } undo_write_bytes(bi, (uintmax_t)buf->b_u_line_lnum, 4); @@ -1038,7 +1053,7 @@ static bool serialize_uep(bufinfo_T *bi, u_entry_T *uep) if (!undo_write_bytes(bi, len, 4)) { return false; } - if (len > 0 && !undo_write(bi, (char_u *)uep->ue_array[i], len)) { + if (len > 0 && !undo_write(bi, (uint8_t *)uep->ue_array[i], len)) { return false; } } @@ -1057,18 +1072,18 @@ static u_entry_T *unserialize_uep(bufinfo_T *bi, bool *error, const char *file_n uep->ue_lcount = undo_read_4c(bi); uep->ue_size = undo_read_4c(bi); - char_u **array = NULL; + char **array = NULL; if (uep->ue_size > 0) { - if ((size_t)uep->ue_size < SIZE_MAX / sizeof(char_u *)) { // -V547 - array = xmalloc(sizeof(char_u *) * (size_t)uep->ue_size); - memset(array, 0, sizeof(char_u *) * (size_t)uep->ue_size); + if ((size_t)uep->ue_size < SIZE_MAX / sizeof(char *)) { // -V547 + array = xmalloc(sizeof(char *) * (size_t)uep->ue_size); + memset(array, 0, sizeof(char *) * (size_t)uep->ue_size); } } - uep->ue_array = (char **)array; + uep->ue_array = array; for (size_t i = 0; i < (size_t)uep->ue_size; i++) { int line_len = undo_read_4c(bi); - char_u *line; + char *line; if (line_len >= 0) { line = undo_read_string(bi, (size_t)line_len); } else { @@ -1135,7 +1150,7 @@ static void unserialize_visualinfo(bufinfo_T *bi, visualinfo_T *info) /// @param[in] buf Buffer for which undo file is written. /// @param[in] hash Hash value of the buffer text. Must have #UNDO_HASH_SIZE /// size. -void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, char_u *const hash) +void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, uint8_t *const hash) FUNC_ATTR_NONNULL_ARG(3, 4) { char *file_name; @@ -1194,7 +1209,7 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, } goto theend; } else { - char_u mbuf[UF_START_MAGIC_LEN]; + char mbuf[UF_START_MAGIC_LEN]; ssize_t len = read_eintr(fd, mbuf, UF_START_MAGIC_LEN); close(fd); if (len < UF_START_MAGIC_LEN @@ -1327,9 +1342,9 @@ write_error: #ifdef HAVE_ACL if (buf->b_ffname != NULL) { // For systems that support ACL: get the ACL from the original file. - vim_acl_T acl = mch_get_acl((char_u *)buf->b_ffname); - mch_set_acl((char_u *)file_name, acl); - mch_free_acl(acl); + vim_acl_T acl = os_get_acl(buf->b_ffname); + os_set_acl(file_name, acl); + os_free_acl(acl); } #endif @@ -1344,11 +1359,11 @@ theend: /// a bit more verbose. /// Otherwise use curbuf->b_ffname to generate the undo file name. /// "hash[UNDO_HASH_SIZE]" must be the hash value of the buffer text. -void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_ATTR_UNUSED) +void u_read_undo(char *name, const uint8_t *hash, const char *orig_name FUNC_ATTR_UNUSED) FUNC_ATTR_NONNULL_ARG(2) { u_header_T **uhp_table = NULL; - char_u *line_ptr = NULL; + char *line_ptr = NULL; char *file_name; if (name == NULL) { @@ -1362,7 +1377,7 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT // owner of the text file or equal to the current user. FileInfo file_info_orig; FileInfo file_info_undo; - if (os_fileinfo((const char *)orig_name, &file_info_orig) + if (os_fileinfo(orig_name, &file_info_orig) && os_fileinfo(file_name, &file_info_undo) && file_info_orig.stat.st_uid != file_info_undo.stat.st_uid && file_info_undo.stat.st_uid != getuid()) { @@ -1399,7 +1414,7 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT }; // Read the undo file header. - char_u magic_buf[UF_START_MAGIC_LEN]; + char magic_buf[UF_START_MAGIC_LEN]; if (fread(magic_buf, UF_START_MAGIC_LEN, 1, fp) != 1 || memcmp(magic_buf, UF_START_MAGIC, UF_START_MAGIC_LEN) != 0) { semsg(_("E823: Not an undo file: %s"), file_name); @@ -1411,7 +1426,7 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT goto error; } - char_u read_hash[UNDO_HASH_SIZE]; + uint8_t read_hash[UNDO_HASH_SIZE]; if (!undo_read(&bi, read_hash, UNDO_HASH_SIZE)) { corruption_error("hash", file_name); goto error; @@ -1593,7 +1608,7 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT curbuf->b_u_oldhead = old_idx < 0 ? NULL : uhp_table[old_idx]; curbuf->b_u_newhead = new_idx < 0 ? NULL : uhp_table[new_idx]; curbuf->b_u_curhead = cur_idx < 0 ? NULL : uhp_table[cur_idx]; - curbuf->b_u_line_ptr = (char *)line_ptr; + curbuf->b_u_line_ptr = line_ptr; curbuf->b_u_line_lnum = line_lnum; curbuf->b_u_line_colnr = line_colnr; curbuf->b_u_numhead = num_head; @@ -1728,10 +1743,10 @@ static bool undo_read(bufinfo_T *bi, uint8_t *buffer, size_t size) /// @param len can be zero to allocate an empty line. /// /// @returns a pointer to allocated memory or NULL in case of an error. -static uint8_t *undo_read_string(bufinfo_T *bi, size_t len) +static char *undo_read_string(bufinfo_T *bi, size_t len) { - uint8_t *ptr = xmallocz(len); - if (len > 0 && !undo_read(bi, ptr, len)) { + char *ptr = xmallocz(len); + if (len > 0 && !undo_read(bi, (uint8_t *)ptr, len)) { xfree(ptr); return NULL; } @@ -2223,7 +2238,7 @@ target_zero: /// @param do_buf_event If `true`, send buffer updates. static void u_undoredo(int undo, bool do_buf_event) { - char_u **newarray = NULL; + char **newarray = NULL; linenr_T newlnum = MAXLNUM; u_entry_T *nuep; u_entry_T *newlist = NULL; @@ -2301,7 +2316,7 @@ static void u_undoredo(int undo, bool do_buf_event) // delete the lines between top and bot and save them in newarray if (oldsize > 0) { - newarray = xmalloc(sizeof(char_u *) * (size_t)oldsize); + newarray = xmalloc(sizeof(char *) * (size_t)oldsize); // delete backwards, it goes faster in most cases long i; linenr_T lnum; @@ -2333,7 +2348,7 @@ static void u_undoredo(int undo, bool do_buf_event) } xfree(uep->ue_array[i]); } - xfree((char_u *)uep->ue_array); + xfree(uep->ue_array); } // Adjust marks @@ -2363,7 +2378,7 @@ static void u_undoredo(int undo, bool do_buf_event) u_newcount += newsize; u_oldcount += oldsize; uep->ue_size = oldsize; - uep->ue_array = (char **)newarray; + uep->ue_array = newarray; uep->ue_bot = top + newsize + 1; // insert this entry in front of the new entry list @@ -2550,7 +2565,7 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) uhp = curbuf->b_u_newhead; } - char_u msgbuf[80]; + char msgbuf[80]; if (uhp == NULL) { *msgbuf = NUL; } else { @@ -2579,21 +2594,25 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) } /// 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) +void undo_fmt_time(char *buf, size_t buflen, time_t tt) { if (time(NULL) - tt >= 100) { struct tm curtime; os_localtime_r(&tt, &curtime); + size_t n; if (time(NULL) - tt < (60L * 60L * 12L)) { // within 12 hours - (void)strftime((char *)buf, buflen, "%H:%M:%S", &curtime); + n = strftime(buf, buflen, "%H:%M:%S", &curtime); } else { // longer ago - (void)strftime((char *)buf, buflen, "%Y/%m/%d %H:%M:%S", &curtime); + n = strftime(buf, buflen, "%Y/%m/%d %H:%M:%S", &curtime); + } + if (n == 0) { + buf[0] = NUL; } } else { int64_t seconds = time(NULL) - tt; - vim_snprintf((char *)buf, buflen, + vim_snprintf(buf, buflen, NGETTEXT("%" PRId64 " second ago", "%" PRId64 " seconds ago", (uint32_t)seconds), seconds); @@ -2635,15 +2654,15 @@ 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); - undo_fmt_time((char_u *)IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff), uhp->uh_time); + vim_snprintf(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(IObuff, IOSIZE, " %3ld", uhp->uh_save_nr); } - GA_APPEND(char *, &ga, xstrdup((char *)IObuff)); + GA_APPEND(char *, &ga, xstrdup(IObuff)); } uhp->uh_walk = mark; @@ -2713,9 +2732,8 @@ void ex_undojoin(exarg_T *eap) } if (get_undolevel(curbuf) < 0) { return; // no entries, nothing to do - } else { - curbuf->b_u_synced = false; // Append next change to last entry } + curbuf->b_u_synced = false; // Append next change to last entry } /// Called after writing or reloading the file and setting b_changed to false. @@ -2917,7 +2935,7 @@ static void u_freeentries(buf_T *buf, u_header_T *uhp, u_header_T **uhpp) #ifdef U_DEBUG uhp->uh_magic = 0; #endif - xfree((char_u *)uhp); + xfree(uhp); buf->b_u_numhead--; } @@ -2927,11 +2945,11 @@ static void u_freeentry(u_entry_T *uep, long n) while (n > 0) { xfree(uep->ue_array[--n]); } - xfree((char_u *)uep->ue_array); + xfree(uep->ue_array); #ifdef U_DEBUG uep->ue_magic = 0; #endif - xfree((char_u *)uep); + xfree(uep); } /// invalidate the undo buffer; called when storage has already been released @@ -2960,7 +2978,7 @@ void u_saveline(linenr_T lnum) } else { curbuf->b_u_line_colnr = 0; } - curbuf->b_u_line_ptr = (char *)u_save_line(lnum); + curbuf->b_u_line_ptr = u_save_line(lnum); } /// clear the line saved for the "U" command @@ -2991,13 +3009,13 @@ void u_undoline(void) return; } - char_u *oldp = u_save_line(curbuf->b_u_line_lnum); + char *oldp = u_save_line(curbuf->b_u_line_lnum); ml_replace(curbuf->b_u_line_lnum, curbuf->b_u_line_ptr, true); changed_bytes(curbuf->b_u_line_lnum, 0); - extmark_splice_cols(curbuf, (int)curbuf->b_u_line_lnum - 1, 0, (colnr_T)STRLEN(oldp), + extmark_splice_cols(curbuf, (int)curbuf->b_u_line_lnum - 1, 0, (colnr_T)strlen(oldp), (colnr_T)strlen(curbuf->b_u_line_ptr), kExtmarkUndo); xfree(curbuf->b_u_line_ptr); - curbuf->b_u_line_ptr = (char *)oldp; + curbuf->b_u_line_ptr = oldp; colnr_T t = curbuf->b_u_line_colnr; if (curwin->w_cursor.lnum == curbuf->b_u_line_lnum) { @@ -3025,9 +3043,9 @@ void u_blockfree(buf_T *buf) /// Allocate memory and copy curbuf line into it. /// /// @param lnum the line to copy -static char_u *u_save_line(linenr_T lnum) +static char *u_save_line(linenr_T lnum) { - return (char_u *)u_save_line_buf(curbuf, lnum); + return u_save_line_buf(curbuf, lnum); } /// Allocate memory and copy line into it diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c index c3cf0b6df8..31cb1e8936 100644 --- a/src/nvim/usercmd.c +++ b/src/nvim/usercmd.c @@ -6,19 +6,36 @@ #include <assert.h> #include <inttypes.h> #include <stdbool.h> -#include <stdlib.h> +#include <stdio.h> #include <string.h> +#include "auto/config.h" +#include "lauxlib.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/eval.h" #include "nvim/ex_docmd.h" #include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" +#include "nvim/keycodes.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" +#include "nvim/mapping.h" +#include "nvim/mbyte.h" +#include "nvim/memory.h" +#include "nvim/menu.h" +#include "nvim/message.h" +#include "nvim/option_defs.h" #include "nvim/os/input.h" #include "nvim/runtime.h" +#include "nvim/strings.h" #include "nvim/usercmd.h" +#include "nvim/vim.h" #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -36,8 +53,7 @@ static char e_no_such_user_defined_command_in_current_buffer_str[] /// List of names for completion for ":command" with the EXPAND_ flag. /// Must be alphabetical for completion. -static const char *command_complete[] = -{ +static const char *command_complete[] = { [EXPAND_ARGLIST] = "arglist", [EXPAND_AUGROUP] = "augroup", [EXPAND_BEHAVE] = "behave", @@ -46,7 +62,6 @@ static const char *command_complete[] = [EXPAND_COLORS] = "color", [EXPAND_COMMANDS] = "command", [EXPAND_COMPILER] = "compiler", - [EXPAND_CSCOPE] = "cscope", [EXPAND_USER_DEFINED] = "custom", [EXPAND_USER_LIST] = "customlist", [EXPAND_USER_LUA] = "<Lua function>", @@ -80,6 +95,8 @@ static const char *command_complete[] = [EXPAND_TAGS_LISTFILES] = "tag_listfiles", [EXPAND_USER] = "user", [EXPAND_USER_VARS] = "var", + [EXPAND_BREAKPOINT] = "breakpoint", + [EXPAND_SCRIPTNAMES] = "scriptnames", }; /// List of names of address types. Must be alphabetical for completion. @@ -87,8 +104,7 @@ static struct { cmd_addr_T expand; char *name; char *shortname; -} addr_type_complete[] = -{ +} addr_type_complete[] = { { ADDR_ARGUMENTS, "arguments", "arg" }, { ADDR_LINES, "lines", "line" }, { ADDR_LOADED_BUFFERS, "loaded_buffers", "load" }, @@ -208,6 +224,7 @@ char *find_ucmd(exarg_T *eap, char *p, int *full, expand_T *xp, int *complp) return p; } +/// Set completion context for :command const char *set_context_in_user_cmd(expand_T *xp, const char *arg_in) { const char *arg = arg_in; @@ -259,6 +276,47 @@ const char *set_context_in_user_cmd(expand_T *xp, const char *arg_in) return (const char *)skipwhite(p); } +/// Set the completion context for the argument of a user defined command. +const char *set_context_in_user_cmdarg(const char *cmd FUNC_ATTR_UNUSED, const char *arg, + uint32_t argt, int context, expand_T *xp, bool forceit) +{ + if (context == EXPAND_NOTHING) { + return NULL; + } + + if (argt & EX_XFILE) { + // EX_XFILE: file names are handled above. + xp->xp_context = context; + return NULL; + } + + if (context == EXPAND_MENUS) { + return (const char *)set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit); + } + if (context == EXPAND_COMMANDS) { + return arg; + } + if (context == EXPAND_MAPPINGS) { + return (const char *)set_context_in_map_cmd(xp, "map", (char *)arg, forceit, false, false, + CMD_map); + } + // Find start of last argument. + const char *p = arg; + while (*p) { + if (*p == ' ') { + // argument starts after a space + arg = p + 1; + } else if (*p == '\\' && *(p + 1) != NUL) { + p++; // skip over escaped character + } + MB_PTR_ADV(p); + } + xp->xp_pattern = (char *)arg; + xp->xp_context = context; + + return NULL; +} + char *expand_user_command_name(int idx) { return get_user_commands(NULL, idx - CMD_SIZE); @@ -346,9 +404,8 @@ static char *get_command_complete(int arg) { if (arg >= (int)(ARRAY_SIZE(command_complete))) { return NULL; - } else { - return (char *)command_complete[arg]; } + return (char *)command_complete[arg]; } /// Function given to ExpandGeneric() to obtain the list of values for -complete. @@ -360,9 +417,8 @@ char *get_user_cmd_complete(expand_T *xp, int idx) char *cmd_compl = get_command_complete(idx); if (cmd_compl == NULL) { return ""; - } else { - return cmd_compl; } + return cmd_compl; } int cmdcomplete_str_to_type(const char *complete_str) @@ -396,7 +452,7 @@ static void uc_list(char *name, size_t name_len) // Skip commands which don't match the requested prefix and // commands filtered out. - if (STRNCMP(name, cmd->uc_name, name_len) != 0 + if (strncmp(name, cmd->uc_name, name_len) != 0 || message_filtered(cmd->uc_name)) { continue; } @@ -474,14 +530,14 @@ static void uc_list(char *name, size_t name_len) if (a & (EX_RANGE | EX_COUNT)) { if (a & EX_COUNT) { // -count=N - snprintf((char *)IObuff + len, IOSIZE, "%" PRId64 "c", + snprintf(IObuff + len, IOSIZE, "%" PRId64 "c", (int64_t)cmd->uc_def); len += (int)strlen(IObuff + len); } else if (a & EX_DFLALL) { IObuff[len++] = '%'; } else if (cmd->uc_def >= 0) { // -range=N - snprintf((char *)IObuff + len, IOSIZE, "%" PRId64 "", + snprintf(IObuff + len, IOSIZE, "%" PRId64 "", (int64_t)cmd->uc_def); len += (int)strlen(IObuff + len); } else { @@ -519,7 +575,7 @@ static void uc_list(char *name, size_t name_len) } while (len < 25 - over); IObuff[len] = '\0'; - msg_outtrans((char *)IObuff); + msg_outtrans(IObuff); if (cmd->uc_luaref != LUA_NOREF) { char *fn = nlua_funcref_str(cmd->uc_luaref); @@ -560,7 +616,7 @@ int parse_addr_type_arg(char *value, int vallen, cmd_addr_T *addr_type_arg) for (i = 0; addr_type_complete[i].expand != ADDR_NONE; i++) { a = (int)strlen(addr_type_complete[i].name) == vallen; - b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0; + b = strncmp(value, addr_type_complete[i].name, (size_t)vallen) == 0; if (a && b) { *addr_type_arg = addr_type_complete[i].expand; break; @@ -608,7 +664,7 @@ int parse_compl_arg(const char *value, int vallen, int *complp, uint32_t *argt, continue; } if ((int)strlen(command_complete[i]) == valend - && STRNCMP(value, command_complete[i], valend) == 0) { + && strncmp(value, command_complete[i], (size_t)valend) == 0) { *complp = i; if (i == EXPAND_BUFFERS) { *argt |= EX_BUFNAME; @@ -643,7 +699,7 @@ int parse_compl_arg(const char *value, int vallen, int *complp, uint32_t *argt, } static int uc_scan_attr(char *attr, size_t len, uint32_t *argt, long *def, int *flags, int *complp, - char_u **compl_arg, cmd_addr_T *addr_type_arg) + char **compl_arg, cmd_addr_T *addr_type_arg) FUNC_ATTR_NONNULL_ALL { char *p; @@ -754,7 +810,7 @@ invalid_count: return FAIL; } - if (parse_compl_arg(val, (int)vallen, complp, argt, (char **)compl_arg) + if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg) == FAIL) { return FAIL; } @@ -838,7 +894,7 @@ int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt, cmd = USER_CMD_GA(gap, i); len = strlen(cmd->uc_name); - cmp = STRNCMP(name, cmd->uc_name, name_len); + cmp = strncmp(name, cmd->uc_name, name_len); if (cmp == 0) { if (name_len < len) { cmp = -1; @@ -931,9 +987,9 @@ void ex_command(exarg_T *eap) while (*p == '-') { p++; end = skiptowhite(p); - if (uc_scan_attr(p, (size_t)(end - p), &argt, &def, &flags, &compl, (char_u **)&compl_arg, + if (uc_scan_attr(p, (size_t)(end - p), &argt, &def, &flags, &compl, &compl_arg, &addr_type_arg) == FAIL) { - return; + goto theend; } p = skipwhite(end); } @@ -943,7 +999,7 @@ void ex_command(exarg_T *eap) end = uc_validate_name(name); if (!end) { emsg(_("E182: Invalid command name")); - return; + goto theend; } name_len = (size_t)(end - name); @@ -954,14 +1010,19 @@ void ex_command(exarg_T *eap) 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) { + } else if (name_len <= 4 && strncmp(name, "Next", name_len) == 0) { emsg(_("E841: Reserved name, cannot be used for user defined command")); } else if (compl > 0 && (argt & EX_EXTRA) == 0) { emsg(_(e_complete_used_without_allowing_arguments)); } else { uc_add_command(name, name_len, p, argt, def, flags, compl, compl_arg, LUA_NOREF, LUA_NOREF, addr_type_arg, LUA_NOREF, eap->forceit); + + return; // success } + +theend: + xfree(compl_arg); } /// ":comclear" @@ -997,7 +1058,7 @@ void ex_delcommand(exarg_T *eap) const char *arg = eap->arg; bool buffer_only = false; - if (STRNCMP(arg, "-buffer", 7) == 0 && ascii_iswhite(arg[7])) { + if (strncmp(arg, "-buffer", 7) == 0 && ascii_iswhite(arg[7])) { buffer_only = true; arg = skipwhite(arg + 7); } @@ -1078,7 +1139,7 @@ bool uc_split_args_iter(const char *arg, size_t arglen, size_t *end, char *buf, } /// split and quote args for <f-args> -static char *uc_split_args(char *arg, char **args, size_t *arglens, size_t argc, size_t *lenp) +static char *uc_split_args(char *arg, char **args, const size_t *arglens, size_t argc, size_t *lenp) { char *buf; char *p; @@ -1218,7 +1279,7 @@ static size_t add_cmd_modifier(char *buf, char *mod_str, bool *multi_mods) /// was added. /// /// @return the number of bytes added -size_t add_win_cmd_modifers(char *buf, const cmdmod_T *cmod, bool *multi_mods) +size_t add_win_cmd_modifiers(char *buf, const cmdmod_T *cmod, bool *multi_mods) { size_t result = 0; @@ -1237,8 +1298,18 @@ size_t add_win_cmd_modifers(char *buf, const cmdmod_T *cmod, bool *multi_mods) // :tab if (cmod->cmod_tab > 0) { - result += add_cmd_modifier(buf, "tab", multi_mods); + int tabnr = cmod->cmod_tab - 1; + if (tabnr == tabpage_index(curtab)) { + // For compatibility, don't add a tabpage number if it is the same + // as the default number for :tab. + result += add_cmd_modifier(buf, "tab", multi_mods); + } else { + char tab_buf[NUMBUFLEN + 3]; + snprintf(tab_buf, sizeof(tab_buf), "%dtab", tabnr); + result += add_cmd_modifier(buf, tab_buf, multi_mods); + } } + // :topleft if (cmod->cmod_split & WSP_TOP) { result += add_cmd_modifier(buf, "topleft", multi_mods); @@ -1308,12 +1379,12 @@ size_t uc_mods(char *buf, const cmdmod_T *cmod, bool quote) result += add_cmd_modifier(buf, "verbose", &multi_mods); } else { char verbose_buf[NUMBUFLEN]; - snprintf(verbose_buf, NUMBUFLEN, "%dverbose", verbose_value); + snprintf(verbose_buf, sizeof(verbose_buf), "%dverbose", verbose_value); result += add_cmd_modifier(buf, verbose_buf, &multi_mods); } } // flags from cmod->cmod_split - result += add_win_cmd_modifers(buf, cmod, &multi_mods); + result += add_win_cmd_modifiers(buf, cmod, &multi_mods); if (quote && buf != NULL) { buf += result - 2; @@ -1353,7 +1424,7 @@ static size_t uc_check_code(char *code, size_t len, char *buf, ucmd_T *cmd, exar ct_NONE, } type = ct_NONE; - if ((vim_strchr("qQfF", *p) != NULL) && p[1] == '-') { + if ((vim_strchr("qQfF", (uint8_t)(*p)) != NULL) && p[1] == '-') { quote = (*p == 'q' || *p == 'Q') ? 1 : 2; p += 2; l -= 2; @@ -1361,7 +1432,7 @@ static size_t uc_check_code(char *code, size_t len, char *buf, ucmd_T *cmd, exar l++; if (l <= 1) { - type = ct_NONE; + // type = ct_NONE; } else if (STRNICMP(p, "args>", l) == 0) { type = ct_ARGS; } else if (STRNICMP(p, "bang>", l) == 0) { @@ -1583,10 +1654,10 @@ int do_ucmd(exarg_T *eap, bool preview) end = vim_strchr(start + 1, '>'); } if (buf != NULL) { - for (ksp = p; *ksp != NUL && (char_u)(*ksp) != K_SPECIAL; ksp++) {} - if ((char_u)(*ksp) == K_SPECIAL + for (ksp = p; *ksp != NUL && (uint8_t)(*ksp) != K_SPECIAL; ksp++) {} + if ((uint8_t)(*ksp) == K_SPECIAL && (start == NULL || ksp < start || end == NULL) - && ((char_u)ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)) { + && ((uint8_t)ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)) { // 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. diff --git a/src/nvim/usercmd.h b/src/nvim/usercmd.h index 4d2cf0d9de..b6bf6c1e33 100644 --- a/src/nvim/usercmd.h +++ b/src/nvim/usercmd.h @@ -1,7 +1,12 @@ #ifndef NVIM_USERCMD_H #define NVIM_USERCMD_H +#include <stdint.h> + +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" +#include "nvim/garray.h" +#include "nvim/types.h" typedef struct ucmd { char *uc_name; // The command name diff --git a/src/nvim/version.c b/src/nvim/version.c index d4d406482d..417e5116a5 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -7,34 +7,43 @@ /// Vim originated from Stevie version 3.6 (Fish disk 217) by GRWalter (Fred). #include <assert.h> -#include <inttypes.h> #include <limits.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include "auto/config.h" +#include "auto/versiondef.h" // version info generated by the build system +#include "auto/versiondef_git.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/drawscreen.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/grid.h" -#include "nvim/iconv.h" +#include "nvim/highlight_defs.h" #include "nvim/lua/executor.h" -#include "nvim/memline.h" +#include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/option_defs.h" +#include "nvim/os/os_defs.h" #include "nvim/strings.h" #include "nvim/version.h" #include "nvim/vim.h" -// version info generated by the build system -#include "auto/versiondef.h" - // for ":version", ":intro", and "nvim --version" #ifndef NVIM_VERSION_MEDIUM # define NVIM_VERSION_MEDIUM "v" STR(NVIM_VERSION_MAJOR) \ "." STR(NVIM_VERSION_MINOR) "." STR(NVIM_VERSION_PATCH) \ NVIM_VERSION_PRERELEASE #endif -#define NVIM_VERSION_LONG "NVIM " NVIM_VERSION_MEDIUM +#define NVIM_VERSION_LONG "NVIM " NVIM_VERSION_MEDIUM // NOLINT(bugprone-suspicious-missing-comma) char *Version = VIM_VERSION_SHORT; char *longVersion = NVIM_VERSION_LONG; @@ -55,29 +64,593 @@ static char *features[] = { "-acl", #endif -#if defined(HAVE_ICONV) - "+iconv", -#else - "-iconv", -#endif - -#ifdef FEAT_TUI "+tui", -#else - "-tui", -#endif NULL }; // clang-format off static const int included_patches[] = { - 1850, + 2424, + 2423, + 2422, + 2421, + // 2420, + 2419, + // 2418, + 2417, + 2416, + // 2415, + 2414, + 2413, + 2412, + 2411, + 2410, + 2409, + 2408, + 2407, + 2406, + 2405, + 2404, + // 2403, + 2402, + 2401, + 2400, + // 2399, + 2398, + 2397, + 2396, + 2395, + 2394, + 2393, + 2392, + 2391, + 2390, + 2389, + 2388, + 2387, + // 2386, + 2385, + 2384, + 2383, + 2382, + // 2381, + 2380, + 2379, + 2378, + 2377, + 2376, + 2375, + 2374, + // 2373, + 2372, + // 2371, + 2370, + // 2369, + 2368, + 2367, + 2366, + 2365, + 2364, + 2363, + // 2362, + 2361, + 2360, + 2359, + 2358, + 2357, + 2356, + 2355, + 2354, + 2353, + 2352, + // 2351, + 2350, + 2349, + 2348, + 2347, + 2346, + 2345, + 2344, + 2343, + 2342, + 2341, + 2340, + 2339, + 2338, + // 2337, + 2336, + 2335, + // 2334, + 2333, + 2332, + 2331, + 2330, + 2329, + 2328, + 2327, + 2326, + 2325, + // 2324, + 2323, + 2322, + 2321, + 2320, + 2319, + 2318, + 2317, + 2316, + 2315, + 2314, + 2313, + 2312, + 2311, + 2310, + 2309, + // 2308, + // 2307, + 2306, + 2305, + 2304, + 2303, + 2302, + 2301, + // 2300, + // 2299, + // 2298, + 2297, + // 2296, + // 2295, + 2294, + 2293, + // 2292, + 2291, + 2290, + 2289, + // 2288, + // 2287, + // 2286, + 2285, + 2284, + 2283, + 2282, + 2281, + 2280, + // 2279, + 2278, + // 2277, + // 2276, + 2275, + 2274, + // 2273, + 2272, + 2271, + 2270, + 2269, + 2268, + 2267, + // 2266, + // 2265, + 2264, + 2263, + 2262, + 2261, + 2260, + 2259, + 2258, + 2257, + 2256, + 2255, + 2254, + 2253, + 2252, + // 2251, + // 2250, + 2249, + 2248, + 2247, + 2246, + 2245, + 2244, + 2243, + 2242, + // 2241, + // 2240, + 2239, + 2238, + 2237, + 2236, + 2235, + 2234, + 2233, + // 2232, + 2231, + // 2230, + 2229, + 2228, + 2227, + 2226, + 2225, + 2224, + 2223, + 2222, + 2221, + 2220, + // 2219, + 2218, + 2217, + // 2216, + // 2215, + 2214, + // 2213, + 2212, + // 2211, + // 2210, + 2209, + // 2208, + 2207, + 2206, + 2205, + 2204, + 2203, + 2202, + 2201, + // 2200, + 2199, + 2198, + 2197, + 2196, + // 2195, + // 2194, + // 2193, + // 2192, + 2191, + 2190, + // 2189, + 2188, + 2187, + 2186, + 2185, + 2184, + 2183, + 2182, + // 2181, + 2180, + 2179, + 2178, + 2177, + // 2176, + 2175, + 2174, + 2173, + 2172, + 2171, + 2170, + 2169, + 2168, + 2167, + 2166, + 2165, + // 2164, + 2163, + 2162, + 2161, + 2160, + 2159, + // 2158, + 2157, + // 2156, + // 2155, + 2154, + // 2153, + 2152, + 2151, + 2150, + 2149, + // 2148, + 2147, + // 2146, + 2145, + 2144, + 2143, + // 2142, + 2141, + 2140, + // 2139, + 2138, + 2137, + 2136, + 2135, + 2134, + 2133, + 2132, + 2131, + 2130, + 2129, + 2128, + // 2127, + 2126, + 2125, + 2124, + 2123, + 2122, + // 2121, + 2120, + 2119, + 2118, + 2117, + 2116, + 2115, + // 2114, + 2113, + 2112, + 2111, + // 2110, + // 2109, + 2108, + // 2107, + 2106, + 2105, + 2104, + 2103, + 2102, + 2101, + 2100, + // 2099, + 2098, + 2097, + 2096, + 2095, + // 2094, + // 2093, + // 2092, + 2091, + 2090, + 2089, + 2088, + 2087, + 2086, + // 2085, + 2084, + 2083, + 2082, + 2081, + // 2080, + 2079, + 2078, + // 2077, + // 2076, + 2075, + 2074, + 2073, + 2072, + // 2071, + // 2070, + // 2069, + // 2068, + // 2067, + // 2066, + 2065, + 2064, + 2063, + // 2062, + 2061, + 2060, + 2059, + 2058, + 2057, + 2056, + 2055, + 2054, + // 2053, + 2052, + 2051, + 2050, + 2049, + // 2048, + // 2047, + // 2046, + 2045, + // 2044, + 2043, + 2042, + 2041, + // 2040, + // 2039, + 2038, + 2037, + 2036, + 2035, + 2034, + 2033, + // 2032, + 2031, + 2030, + 2029, + 2028, + 2027, + 2026, + 2025, + 2024, + 2023, + // 2022, + // 2021, + 2020, + 2019, + 2018, + 2017, + 2016, + 2015, + 2014, + 2013, + 2012, + 2011, + 2010, + // 2009, + // 2008, + 2007, + 2006, + 2005, + // 2004, + 2003, + 2002, + 2001, + 2000, + // 1999, + // 1998, + // 1997, + // 1996, + 1995, + 1994, + // 1993, + 1992, + 1991, + 1990, + // 1989, + 1988, + // 1987, + // 1986, + // 1985, + 1984, + 1983, + // 1982, + // 1981, + 1980, + // 1979, + // 1978, + 1977, + 1976, + 1975, + 1974, + 1973, + 1972, + 1971, + 1970, + // 1969, + // 1968, + 1967, + 1966, + 1965, + // 1964, + // 1963, + 1962, + 1961, + 1960, + // 1959, + 1958, + // 1957, + 1956, + 1955, + // 1954, + 1953, + 1952, + 1951, + 1950, + 1949, + 1948, + 1947, + 1946, + // 1945, + // 1944, + // 1943, + 1942, + 1941, + // 1940, + // 1939, + 1938, + 1937, + // 1936, + 1935, + // 1934, + 1933, + 1932, + 1931, + 1930, + // 1929, + // 1928, + 1927, + 1926, + 1925, + 1924, + 1923, + 1922, + 1921, + // 1920, + // 1919, + // 1918, + // 1917, + 1916, + 1915, + 1914, + 1913, + 1912, + 1911, + 1910, + 1909, + // 1908, + // 1907, + // 1906, + // 1905, + // 1904, + 1903, + // 1902, + 1901, + 1900, + 1899, + 1898, + 1897, + 1896, + 1895, + 1894, + 1893, + // 1892, + // 1891, + 1890, + 1889, + 1888, + 1887, + 1886, + 1885, + // 1884, + 1883, + // 1882, + 1881, + // 1880, + 1879, + 1878, + 1877, + 1876, + 1875, + // 1874, + 1873, + 1872, + // 1871, + 1870, + 1869, + 1868, + 1867, + // 1866, + 1865, + 1864, + 1863, + 1862, + 1861, + 1860, + 1859, + 1858, + 1857, + 1856, + 1855, + 1854, + // 1853, + 1852, + // 1851, + // 1850, 1849, 1848, 1847, 1846, - 1845, - 1844, + // 1845, + // 1844, 1843, 1842, 1841, @@ -94,7 +667,7 @@ static const int included_patches[] = { 1830, 1829, 1828, - 1827, + // 1827, 1826, 1825, 1824, @@ -102,58 +675,58 @@ static const int included_patches[] = { 1822, 1821, 1820, - 1819, + // 1819, 1818, 1817, 1816, 1815, - 1814, - 1813, + // 1814, + // 1813, 1812, - 1811, - 1810, + // 1811, + // 1810, 1809, 1808, 1807, 1806, 1805, - // 1804, + 1804, 1803, - 1802, + // 1802, 1801, 1800, - 1799, + // 1799, 1798, 1797, 1796, 1795, - // 1794, + 1794, 1793, 1792, 1791, 1790, - 1789, + // 1789, 1788, - 1787, - 1786, + // 1787, + // 1786, 1785, - 1784, + // 1784, 1783, 1782, 1781, 1780, - 1779, - 1778, + // 1779, + // 1778, 1777, 1776, 1775, 1774, - 1773, + // 1773, 1772, 1771, - 1770, + // 1770, 1769, - 1768, + // 1768, 1767, 1766, 1765, @@ -166,34 +739,34 @@ static const int included_patches[] = { 1758, 1757, 1756, - 1755, - 1754, - 1753, - 1752, - 1751, + // 1755, + // 1754, + // 1753, + // 1752, + // 1751, 1750, 1749, 1748, 1747, 1746, 1745, - // 1744, - // 1743, + 1744, + 1743, 1742, 1741, 1740, 1739, 1738, 1737, - 1736, + // 1736, 1735, 1734, - 1733, - // 1732, + // 1733, + 1732, 1731, - 1730, + // 1730, 1729, - 1728, + // 1728, 1727, 1726, 1725, @@ -202,27 +775,27 @@ static const int included_patches[] = { 1722, 1721, 1720, - 1719, - 1718, + // 1719, + // 1718, 1717, 1716, 1715, - 1714, - 1713, + // 1714, + // 1713, 1712, - 1711, - 1710, - 1709, + // 1711, + // 1710, + // 1709, 1708, - 1707, + // 1707, 1706, 1705, 1704, - 1703, + // 1703, 1702, 1701, - 1700, - 1699, + // 1700, + // 1699, 1698, 1697, 1696, @@ -231,7 +804,7 @@ static const int included_patches[] = { 1693, 1692, 1691, - 1690, + // 1690, 1689, 1688, 1687, @@ -240,140 +813,140 @@ static const int included_patches[] = { 1684, 1683, 1682, - 1681, + // 1681, 1680, 1679, - 1678, + // 1678, 1677, - 1676, - 1675, + // 1676, + // 1675, 1674, - 1673, + // 1673, 1672, 1671, 1670, 1669, 1668, 1667, - 1666, - 1665, + // 1666, + // 1665, 1664, - 1663, + // 1663, 1662, 1661, 1660, - 1659, + // 1659, 1658, 1657, - 1656, - 1655, + // 1656, + // 1655, 1654, 1653, 1652, 1651, 1650, - 1649, + // 1649, 1648, 1647, - 1646, + // 1646, 1645, 1644, 1643, 1642, - 1641, + // 1641, 1640, 1639, 1638, 1637, - 1636, + // 1636, 1635, 1634, 1633, 1632, 1631, 1630, - 1629, - 1628, + // 1629, + // 1628, 1627, - 1626, + // 1626, 1625, 1624, 1623, - 1622, + // 1622, 1621, - 1620, + // 1620, 1619, 1618, - 1617, + // 1617, 1616, - 1615, + // 1615, 1614, 1613, - 1612, + // 1612, 1611, 1610, - 1609, + // 1609, 1608, - 1607, + // 1607, 1606, 1605, 1604, - 1603, - 1602, + // 1603, + // 1602, 1601, - 1600, - 1599, + // 1600, + // 1599, 1598, - 1597, - 1596, + // 1597, + // 1596, 1595, 1594, 1593, - // 1592, + 1592, 1591, 1590, - 1589, + // 1589, 1588, 1587, - 1586, + // 1586, 1585, - 1584, - 1583, + // 1584, + // 1583, 1582, 1581, - 1580, + // 1580, 1579, 1578, - 1577, + // 1577, 1576, 1575, - 1574, - 1573, + // 1574, + // 1573, 1572, - 1571, + // 1571, 1570, 1569, 1568, 1567, 1566, - 1565, + // 1565, 1564, 1563, - 1562, - 1561, - 1560, - 1559, - 1558, + // 1562, + // 1561, + // 1560, + // 1559, + // 1558, 1557, 1556, - 1555, + // 1555, 1554, - 1553, + // 1553, 1552, - 1551, - 1550, + // 1551, + // 1550, 1549, - 1548, + // 1548, 1547, 1546, 1545, @@ -383,59 +956,59 @@ static const int included_patches[] = { 1541, 1540, 1539, - 1538, - 1537, + // 1538, + // 1537, 1536, 1535, - 1534, + // 1534, 1533, 1532, 1531, 1530, 1529, 1528, - 1527, - 1526, - 1525, + // 1527, + // 1526, + // 1525, 1524, - 1523, - 1522, - 1521, - 1520, + // 1523, + // 1522, + // 1521, + // 1520, 1519, - 1518, - 1517, + // 1518, + // 1517, 1516, - 1515, + // 1515, 1514, - 1513, + // 1513, 1512, - 1511, + // 1511, 1510, 1509, - 1508, + // 1508, 1507, 1506, 1505, 1504, 1503, - 1502, + // 1502, 1501, 1500, - 1499, + // 1499, 1498, - 1497, - 1496, - 1495, - 1494, - 1493, + // 1497, + // 1496, + // 1495, + // 1494, + // 1493, 1492, 1491, 1490, 1489, 1488, 1487, - 1486, + // 1486, 1485, 1484, 1483, @@ -448,7 +1021,7 @@ static const int included_patches[] = { 1476, 1475, 1474, - 1473, + // 1473, 1472, 1471, 1470, @@ -458,83 +1031,83 @@ static const int included_patches[] = { 1466, 1465, 1464, - 1463, + // 1463, 1462, 1461, - 1460, - 1459, + // 1460, + // 1459, 1458, 1457, 1456, - 1455, + // 1455, 1454, - 1453, - 1452, - 1451, - 1450, - 1449, - 1448, - 1447, - 1446, - 1445, - 1444, - 1443, - 1442, - 1441, + // 1453, + // 1452, + // 1451, + // 1450, + // 1449, + // 1448, + // 1447, + // 1446, + // 1445, + // 1444, + // 1443, + // 1442, + // 1441, 1440, 1439, - 1438, + // 1438, 1437, 1436, 1435, 1434, 1433, - 1432, - 1431, - 1430, - 1429, - 1428, - 1427, - 1426, + // 1432, + // 1431, + // 1430, + // 1429, + // 1428, + // 1427, + // 1426, 1425, 1424, - 1423, - 1422, - 1421, - 1420, - 1419, + // 1423, + // 1422, + // 1421, + // 1420, + // 1419, 1418, - 1417, - 1416, + // 1417, + // 1416, 1415, - 1414, - 1413, + // 1414, + // 1413, 1412, 1411, - 1410, + // 1410, 1409, - 1408, - 1407, - 1406, - 1405, + // 1408, + // 1407, + // 1406, + // 1405, 1404, 1403, - 1402, + // 1402, 1401, - 1400, - 1399, + // 1400, + // 1399, 1398, 1397, - 1396, - 1395, + // 1396, + // 1395, 1394, 1393, 1392, - 1391, + // 1391, 1390, - 1389, - 1388, - 1387, + // 1389, + // 1388, + // 1387, 1386, 1385, 1384, @@ -545,7 +1118,7 @@ static const int included_patches[] = { 1379, 1378, 1377, - 1376, + // 1376, 1375, 1374, 1373, @@ -557,12 +1130,12 @@ static const int included_patches[] = { 1367, 1366, 1365, - 1364, + // 1364, 1363, 1362, 1361, 1360, - 1359, + // 1359, 1358, 1357, 1356, @@ -570,38 +1143,38 @@ static const int included_patches[] = { 1354, 1353, 1352, - 1351, + // 1351, 1350, 1349, 1348, 1347, 1346, 1345, - 1344, - 1343, + // 1344, + // 1343, 1342, - 1341, - 1340, + // 1341, + // 1340, 1339, 1338, - 1337, + // 1337, 1336, - 1335, + // 1335, 1334, - 1333, - 1332, + // 1333, + // 1332, 1331, 1330, - 1329, - 1328, + // 1329, + // 1328, 1327, - 1326, + // 1326, 1325, 1324, 1323, 1322, - 1321, - 1320, + // 1321, + // 1320, 1319, 1318, 1317, @@ -621,7 +1194,7 @@ static const int included_patches[] = { 1303, 1302, 1301, - // 1300, + 1300, 1299, 1298, 1297, @@ -641,11 +1214,11 @@ static const int included_patches[] = { 1283, 1282, 1281, - 1280, + // 1280, 1279, - 1278, + // 1278, 1277, - 1276, + // 1276, 1275, 1274, 1273, @@ -654,15 +1227,15 @@ static const int included_patches[] = { 1270, 1269, 1268, - 1267, + // 1267, 1266, - 1265, + // 1265, 1264, 1263, 1262, 1261, 1260, - 1259, + // 1259, 1258, 1257, 1256, @@ -695,15 +1268,15 @@ static const int included_patches[] = { 1229, 1228, 1227, - 1226, + // 1226, 1225, - 1224, + // 1224, 1223, 1222, 1221, 1220, 1219, - 1218, + // 1218, 1217, 1216, 1215, @@ -785,10 +1358,10 @@ static const int included_patches[] = { 1139, 1138, 1137, - 1136, + // 1136, 1135, 1134, - 1133, + // 1133, 1132, 1131, 1130, @@ -815,10 +1388,10 @@ static const int included_patches[] = { 1109, 1108, 1107, - 1106, + // 1106, 1105, 1104, - 1103, + // 1103, 1102, 1101, 1100, @@ -831,14 +1404,14 @@ static const int included_patches[] = { 1093, 1092, 1091, - 1090, + // 1090, 1089, 1088, 1087, 1086, 1085, 1084, - 1083, + // 1083, 1082, 1081, 1080, @@ -877,8 +1450,8 @@ static const int included_patches[] = { 1047, 1046, 1045, - 1044, - 1043, + // 1044, + // 1043, 1042, 1041, 1040, @@ -886,14 +1459,14 @@ static const int included_patches[] = { 1038, 1037, 1036, - 1035, + // 1035, 1034, 1033, 1032, 1031, 1030, 1029, - 1028, + // 1028, 1027, 1026, 1025, @@ -902,7 +1475,7 @@ static const int included_patches[] = { 1022, 1021, 1020, - 1019, + // 1019, 1018, 1017, 1016, @@ -917,7 +1490,7 @@ static const int included_patches[] = { 1007, 1006, 1005, - 1004, + // 1004, 1003, 1002, 1001, @@ -944,15 +1517,15 @@ static const int included_patches[] = { 980, 979, 978, - 977, + // 977, 976, 975, 974, 973, 972, 971, - 970, - 969, + // 970, + // 969, 968, 967, 966, @@ -968,7 +1541,7 @@ static const int included_patches[] = { 956, 955, 954, - 953, + // 953, 952, 951, 950, @@ -981,7 +1554,7 @@ static const int included_patches[] = { 943, 942, 941, - 940, + // 940, 939, 938, 937, @@ -993,7 +1566,7 @@ static const int included_patches[] = { 931, 930, 929, - 928, + // 928, 927, 926, 925, @@ -1003,19 +1576,19 @@ static const int included_patches[] = { 921, 920, 919, - 918, + // 918, 917, 916, 915, - 914, + // 914, 913, 912, 911, 910, - 909, + // 909, 908, 907, - 906, + // 906, 905, 904, 903, @@ -1026,12 +1599,12 @@ static const int included_patches[] = { 898, 897, 896, - 895, - 894, + // 895, + // 894, 893, 892, 891, - 890, + // 890, 889, 888, 887, @@ -1041,30 +1614,30 @@ static const int included_patches[] = { 883, 882, 881, - 880, - 879, + // 880, + // 879, 878, 877, - 876, + // 876, 875, 874, 873, 872, 871, - 870, + // 870, 869, 868, 867, 866, 865, 864, - 863, + // 863, 862, 861, 860, 859, 858, - 857, + // 857, 856, 855, 854, @@ -1076,7 +1649,7 @@ static const int included_patches[] = { 848, 847, 846, - 845, + // 845, 844, 843, 842, @@ -1097,16 +1670,16 @@ static const int included_patches[] = { 827, 826, 825, - 824, + // 824, 823, 822, 821, - 820, + // 820, 819, 818, 817, 816, - 815, + // 815, 814, 813, 812, @@ -1151,16 +1724,16 @@ static const int included_patches[] = { 773, 772, 771, - 770, + // 770, 769, - 768, + // 768, 767, 766, 765, 764, 763, 762, - 761, + // 761, 760, 759, 758, @@ -1211,14 +1784,14 @@ static const int included_patches[] = { 713, 712, 711, - 710, + // 710, 709, 708, - 707, + // 707, 706, 705, 704, - 703, + // 703, 702, 701, 700, @@ -1227,26 +1800,26 @@ static const int included_patches[] = { 697, 696, 695, - 694, + // 694, 693, 692, - 691, - 690, - 689, - 688, + // 691, + // 690, + // 689, + // 688, 687, 686, 685, - 684, + // 684, 683, - 682, - 681, + // 682, + // 681, 680, 679, 678, 677, - 676, - 675, + // 676, + // 675, 674, 673, 672, @@ -1254,11 +1827,11 @@ static const int included_patches[] = { 670, 669, 668, - 667, + // 667, 666, - 665, + // 665, 664, - 663, + // 663, 662, 661, 660, @@ -1266,8 +1839,8 @@ static const int included_patches[] = { 658, 657, 656, - 655, - 654, + // 655, + // 654, 653, 652, 651, @@ -1278,16 +1851,16 @@ static const int included_patches[] = { 646, 645, 644, - 643, + // 643, 642, 641, 640, - 639, - 638, + // 639, + // 638, 637, - 636, + // 636, 635, - 634, + // 634, 633, 632, 631, @@ -1309,7 +1882,7 @@ static const int included_patches[] = { 615, 614, 613, - 612, + // 612, 611, 610, 609, @@ -1320,7 +1893,7 @@ static const int included_patches[] = { 604, 603, 602, - 601, + // 601, 600, 599, 598, @@ -1339,10 +1912,10 @@ static const int included_patches[] = { 585, 584, 583, - 582, + // 582, 581, 580, - 579, + // 579, 578, 577, 576, @@ -1496,7 +2069,7 @@ static const int included_patches[] = { 428, 427, 426, - 425, + // 425, 424, 423, 422, @@ -1554,7 +2127,7 @@ static const int included_patches[] = { 370, 369, 368, - 367, + // 367, 366, 365, 364, @@ -1593,7 +2166,7 @@ static const int included_patches[] = { 331, 330, 329, - 328, + // 328, 327, 326, 325, @@ -1609,7 +2182,7 @@ static const int included_patches[] = { 315, 314, 313, - 312, + // 312, 311, 310, 309, @@ -1617,7 +2190,7 @@ static const int included_patches[] = { 307, 306, 305, - 304, + // 304, 303, 302, 301, @@ -1763,7 +2336,7 @@ static const int included_patches[] = { 161, 160, 159, - 158, + // 158, 157, 156, 155, @@ -1834,7 +2407,7 @@ static const int included_patches[] = { 90, 89, 88, - 87, + // 87, 86, 85, 84, @@ -1849,21 +2422,21 @@ static const int included_patches[] = { 75, 74, 73, - 72, + // 72, 71, 70, 69, 68, 67, 66, - 65, + // 65, 64, 63, - 62, + // 62, 61, 60, 59, - 58, + // 58, 57, 56, 55, @@ -1871,8 +2444,8 @@ static const int included_patches[] = { 53, 52, 51, - 50, - 49, + // 50, + // 49, 48, 47, 46, @@ -1921,7 +2494,7 @@ static const int included_patches[] = { 3, 2, 1, - 0, + // 0, }; // clang-format on @@ -2217,17 +2790,21 @@ void intro_message(int colon) long blanklines; int sponsor; char *p; + char *mesg; + int mesg_size; static char *(lines[]) = { N_(NVIM_VERSION_LONG), "", N_("Nvim is open source and freely distributable"), - N_("https://neovim.io/#chat"), + "https://neovim.io/#chat", "", N_("type :help nvim<Enter> if you are new! "), N_("type :checkhealth<Enter> to optimize Nvim"), N_("type :q<Enter> to exit "), N_("type :help<Enter> for help "), "", + N_("type :help news<Enter> to see changes in v%s.%s"), + "", N_("Help poor children in Uganda!"), N_("type :help iccf<Enter> for information "), }; @@ -2236,7 +2813,7 @@ void intro_message(int colon) size_t lines_size = ARRAY_SIZE(lines); assert(lines_size <= LONG_MAX); - blanklines = Rows - ((long)lines_size - 1l); + blanklines = Rows - ((long)lines_size - 1L); // Don't overwrite a statusline. Depends on 'cmdheight'. if (p_ls > 1) { @@ -2258,25 +2835,48 @@ void intro_message(int colon) if (((row >= 2) && (Columns >= 50)) || colon) { for (i = 0; i < (int)ARRAY_SIZE(lines); i++) { p = lines[i]; + mesg = NULL; + mesg_size = 0; + + if (strstr(p, "news") != NULL) { + p = _(p); + mesg_size = snprintf(NULL, 0, p, + STR(NVIM_VERSION_MAJOR), STR(NVIM_VERSION_MINOR)); + assert(mesg_size > 0); + mesg = xmallocz((size_t)mesg_size); + snprintf(mesg, (size_t)mesg_size + 1, p, + STR(NVIM_VERSION_MAJOR), STR(NVIM_VERSION_MINOR)); + } if (sponsor != 0) { if (strstr(p, "children") != NULL) { - p = sponsor < 0 - ? N_("Sponsor Vim development!") - : N_("Become a registered Vim user!"); - } else if (strstr(p, "iccf") != NULL) { - p = sponsor < 0 - ? N_("type :help sponsor<Enter> for information ") - : N_("type :help register<Enter> for information "); - } else if (strstr(p, "Orphans") != NULL) { - p = N_("menu Help->Sponsor/Register for information "); + mesg = sponsor < 0 + ? _("Sponsor Vim development!") + : _("Become a registered Vim user!"); + } + if (strstr(p, "iccf") != NULL) { + mesg = sponsor < 0 + ? _("type :help sponsor<Enter> for information ") + : _("type :help register<Enter> for information "); } } - if (*p != NUL) { - do_intro_line(row, _(p), 0); + if (mesg == NULL) { + if (*p != NUL) { + mesg = _(p); + } else { + mesg = ""; + } + } + + if (*mesg != NUL) { + do_intro_line(row, mesg, 0); } row++; + + if (mesg_size > 0) { + XFREE_CLEAR(mesg); + } } } diff --git a/src/nvim/version.h b/src/nvim/version.h index a19e863152..484350edee 100644 --- a/src/nvim/version.h +++ b/src/nvim/version.h @@ -14,7 +14,7 @@ extern char *longVersion; // Values that change for a new release #define VIM_VERSION_MAJOR 8 -#define VIM_VERSION_MINOR 0 +#define VIM_VERSION_MINOR 1 // Values based on the above #define VIM_VERSION_MAJOR_STR STR(VIM_VERSION_MAJOR) diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 5b7ff7ba52..c4baa911f1 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -1,7 +1,7 @@ #ifndef NVIM_VIM_H #define NVIM_VIM_H -#include "nvim/pos.h" // for linenr_T, MAXCOL, etc... +#include "nvim/pos.h" #include "nvim/types.h" // Some defines from the old feature.h @@ -138,7 +138,6 @@ enum { EXPAND_USER_LIST, EXPAND_USER_LUA, EXPAND_SHELLCMD, - EXPAND_CSCOPE, EXPAND_SIGN, EXPAND_PROFILE, EXPAND_BEHAVE, @@ -155,6 +154,8 @@ enum { EXPAND_MAPCLEAR, EXPAND_ARGLIST, EXPAND_DIFF_BUFFERS, + EXPAND_BREAKPOINT, + EXPAND_SCRIPTNAMES, EXPAND_CHECKHEALTH, EXPAND_LUA, }; @@ -196,16 +197,11 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() // defines to avoid typecasts from (char_u *) to (char *) and back // (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)) +#ifndef HAVE_STRNLEN +# define strnlen xstrnlen // Older versions of SunOS may not have strnlen #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)) -#define STRNCMP(d, s, n) strncmp((char *)(d), (char *)(s), (size_t)(n)) + +#define STRCPY(d, s) strcpy((char *)(d), (char *)(s)) // NOLINT(runtime/printf) #ifdef HAVE_STRCASECMP # define STRICMP(d, s) strcasecmp((char *)(d), (char *)(s)) #else @@ -217,7 +213,7 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() #endif // Like strcpy() but allows overlapped source and destination. -#define STRMOVE(d, s) memmove((d), (s), STRLEN(s) + 1) +#define STRMOVE(d, s) memmove((d), (s), strlen(s) + 1) #ifdef HAVE_STRNCASECMP # define STRNICMP(d, s, n) strncasecmp((char *)(d), (char *)(s), (size_t)(n)) @@ -240,8 +236,6 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() // destination and mess up the screen. #define PERROR(msg) (void)semsg("%s: %s", (msg), strerror(errno)) -#define SHOWCMD_COLS 10 // columns needed by shown command - #include "nvim/path.h" // Enums need a typecast to be used as array index. @@ -252,13 +246,11 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() /// plus six following composing characters of three bytes each. #define MB_MAXBYTES 21 -// This has to go after the include of proto.h, as proto/gui.pro declares -// functions of these names. The declarations would break if the defines had -// been seen at that stage. But it must be before globals.h, where error_ga -// is declared. #ifndef MSWIN -# define mch_errmsg(str) fprintf(stderr, "%s", (str)) -# define mch_msg(str) printf("%s", (str)) +/// Headless (no UI) error message handler. +# define os_errmsg(str) fprintf(stderr, "%s", (str)) +/// Headless (no UI) message handler. +# define os_msg(str) printf("%s", (str)) #endif #include "nvim/buffer_defs.h" // buffer and windows diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 4564831824..53224f2ee9 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -53,6 +53,8 @@ #include <assert.h> #include <stdbool.h> #include <stddef.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include "klib/kvec.h" @@ -60,6 +62,10 @@ #include "nvim/assert.h" #include "nvim/charset.h" #include "nvim/eval/typval.h" +#include "nvim/gettext.h" +#include "nvim/keycodes.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/types.h" #include "nvim/vim.h" @@ -628,8 +634,8 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) GET_CCS(ret, pline); ret.data.cmp.inv = (schar == '<'); ret.data.cmp.type = ((ret.data.cmp.inv ^ haseqsign) - ? kExprCmpGreaterOrEqual - : kExprCmpGreater); + ? kExprCmpGreaterOrEqual + : kExprCmpGreater); break; } @@ -1819,8 +1825,8 @@ static void parse_quoted_string(ParserState *const pstate, ExprASTNode *const no if (p[1] != '*') { flags |= FSK_SIMPLIFY; } - const size_t special_len = trans_special((const char_u **)&p, (size_t)(e - p), - (char_u *)v_p, flags, false, NULL); + const size_t special_len = trans_special(&p, (size_t)(e - p), + v_p, flags, false, NULL); if (special_len != 0) { v_p += special_len; } else { @@ -1963,8 +1969,8 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) || ((*kv_Z(ast_stack, 1))->type != kExprNodeConcat && ((*kv_Z(ast_stack, 1))->type != kExprNodeConcatOrSubscript)))) - ? kELFlagAllowFloat - : 0)); + ? kELFlagAllowFloat + : 0)); LexExprToken cur_token = viml_pexpr_next_token(pstate, want_node_to_lexer_flags[want_node] | lexer_additional_flags); @@ -2031,9 +2037,9 @@ viml_pexpr_parse_process_token: const bool node_is_key = ( is_concat_or_subscript && (cur_token.type == kExprLexPlainIdentifier - ? (!cur_token.data.var.autoload - && cur_token.data.var.scope == kExprVarScopeMissing) - : (cur_token.type == kExprLexNumber)) + ? (!cur_token.data.var.autoload + && cur_token.data.var.scope == kExprVarScopeMissing) + : (cur_token.type == kExprLexNumber)) && prev_token.type != kExprLexSpacing); if (is_concat_or_subscript && !node_is_key) { // Note: in Vim "d. a" (this is the reason behind `prev_token.type != @@ -2120,6 +2126,22 @@ viml_pexpr_parse_process_token: assert(kv_size(pt_stack)); const ExprASTParseType cur_pt = kv_last(pt_stack); assert(lambda_node == NULL || cur_pt == kEPTLambdaArguments); +#define SIMPLE_UB_OP(op) \ + case kExprLex##op: { \ + if (want_node == kENodeValue) { \ + /* Value level: assume unary operator. */ \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnary##op); \ + *top_node_p = cur_node; \ + kvi_push(ast_stack, &cur_node->children); \ + HL_CUR_TOKEN(Unary##op); \ + } else { \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinary##op); \ + ADD_OP_NODE(cur_node); \ + HL_CUR_TOKEN(Binary##op); \ + } \ + want_node = kENodeValue; \ + break; \ + } switch (tok_type) { case kExprLexMissing: case kExprLexSpacing: @@ -2141,22 +2163,6 @@ viml_pexpr_parse_process_token: HL_CUR_TOKEN(Register); break; } -#define SIMPLE_UB_OP(op) \ - case kExprLex##op: { \ - if (want_node == kENodeValue) { \ - /* Value level: assume unary operator. */ \ - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnary##op); \ - *top_node_p = cur_node; \ - kvi_push(ast_stack, &cur_node->children); \ - HL_CUR_TOKEN(Unary##op); \ - } else { \ - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinary##op); \ - ADD_OP_NODE(cur_node); \ - HL_CUR_TOKEN(Binary##op); \ - } \ - want_node = kENodeValue; \ - break; \ - } SIMPLE_UB_OP(Plus) SIMPLE_UB_OP(Minus) #undef SIMPLE_UB_OP @@ -2491,7 +2497,6 @@ viml_pexpr_parse_bracket_closing_error: NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeListLiteral); *top_node_p = cur_node; kvi_push(ast_stack, &cur_node->children); - want_node = kENodeValue; if (cur_pt == kEPTAssignment) { // Additional assignment parse type allows to easily forbid nested // lists. @@ -2707,14 +2712,14 @@ viml_pexpr_parse_figure_brace_closing_error: break; case kExprLexPlainIdentifier: { const ExprVarScope scope = (cur_token.type == kExprLexInvalid - ? kExprVarScopeMissing - : cur_token.data.var.scope); + ? kExprVarScopeMissing + : cur_token.data.var.scope); if (want_node == kENodeValue) { want_node = kENodeOperator; NEW_NODE_WITH_CUR_POS(cur_node, (node_is_key - ? kExprNodePlainKey - : kExprNodePlainIdentifier)); + ? kExprNodePlainKey + : kExprNodePlainIdentifier)); cur_node->data.var.scope = scope; const size_t scope_shift = (scope == kExprVarScopeMissing ? 0 : 2); cur_node->data.var.ident = (pline.data + cur_token.start.col @@ -2732,8 +2737,8 @@ viml_pexpr_parse_figure_brace_closing_error: scope_shift), cur_token.len - scope_shift, (node_is_key - ? HL(IdentifierKey) - : HL(IdentifierName))); + ? HL(IdentifierKey) + : HL(IdentifierName))); } else { if (scope == kExprVarScopeMissing) { // uncrustify:off @@ -2902,15 +2907,15 @@ viml_pexpr_parse_no_paren_closing_error: {} // different error numbers: "E114: Missing quote" and // "E115: Missing quote". ERROR_FROM_TOKEN_AND_MSG(cur_token, (is_double - ? _("E114: Missing double quote: %.*s") - : _("E115: Missing single quote: %.*s"))); + ? _("E114: Missing double quote: %.*s") + : _("E115: Missing single quote: %.*s"))); } if (want_node == kENodeOperator) { OP_MISSING; } NEW_NODE_WITH_CUR_POS(cur_node, (is_double - ? kExprNodeDoubleQuotedString - : kExprNodeSingleQuotedString)); + ? kExprNodeDoubleQuotedString + : kExprNodeSingleQuotedString)); *top_node_p = cur_node; parse_quoted_string(pstate, cur_node, cur_token, &ast_stack, is_invalid); want_node = kENodeOperator; diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 9d0bc9d468..6fe6a784a0 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -6,9 +6,12 @@ #include <stdint.h> #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/types.h" #include "nvim/viml/parser/parser.h" +struct expr_ast_node; + // Defines whether to ignore case: // == kCCStrategyUseOption // ==# kCCStrategyMatchCase @@ -357,7 +360,7 @@ typedef struct { int arg_len; } ExprASTError; -/// Structure representing complety AST for one expression +/// Structure representing complete AST for one expression typedef struct { /// When AST is not correct this message will be printed. /// diff --git a/src/nvim/viml/parser/parser.c b/src/nvim/viml/parser/parser.c index a41b750e76..1547feba90 100644 --- a/src/nvim/viml/parser/parser.c +++ b/src/nvim/viml/parser/parser.c @@ -4,7 +4,7 @@ #include "nvim/viml/parser/parser.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "viml/parser/parser.c.generated.h" +# include "viml/parser/parser.c.generated.h" // IWYU pragma: export #endif void parser_simple_get_line(void *cookie, ParserLine *ret_pline) diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h index 404dc5a0d1..f387301c2d 100644 --- a/src/nvim/viml/parser/parser.h +++ b/src/nvim/viml/parser/parser.h @@ -8,6 +8,7 @@ #include "klib/kvec.h" #include "nvim/func_attr.h" #include "nvim/mbyte.h" +#include "nvim/mbyte_defs.h" #include "nvim/memory.h" /// One parsed line @@ -81,8 +82,8 @@ typedef struct { bool can_continuate; } ParserState; -static inline void viml_parser_init(ParserState *const ret_pstate, const ParserLineGetter get_line, - void *const cookie, ParserHighlight *const colors) +static inline void viml_parser_init(ParserState *ret_pstate, ParserLineGetter get_line, + void *cookie, ParserHighlight *colors) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1, 2); /// Initialize a new parser state instance @@ -109,7 +110,7 @@ static inline void viml_parser_init(ParserState *const ret_pstate, const ParserL kvi_init(ret_pstate->stack); } -static inline void viml_parser_destroy(ParserState *const pstate) +static inline void viml_parser_destroy(ParserState *pstate) REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE; /// Free all memory allocated by the parser on heap @@ -127,8 +128,7 @@ static inline void viml_parser_destroy(ParserState *const pstate) kvi_destroy(pstate->stack); } -static inline void viml_preader_get_line(ParserInputReader *const preader, - ParserLine *const ret_pline) +static inline void viml_preader_get_line(ParserInputReader *preader, ParserLine *ret_pline) REAL_FATTR_NONNULL_ALL; /// Get one line from ParserInputReader @@ -152,8 +152,7 @@ static inline void viml_preader_get_line(ParserInputReader *const preader, *ret_pline = pline; } -static inline bool viml_parser_get_remaining_line(ParserState *const pstate, - ParserLine *const ret_pline) +static inline bool viml_parser_get_remaining_line(ParserState *pstate, ParserLine *ret_pline) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; /// Get currently parsed line, shifted to pstate->pos.col @@ -178,8 +177,7 @@ static inline bool viml_parser_get_remaining_line(ParserState *const pstate, return ret_pline->data != NULL; } -static inline void viml_parser_advance(ParserState *const pstate, - const size_t len) +static inline void viml_parser_advance(ParserState *pstate, size_t len) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; /// Advance position by a given number of bytes @@ -200,10 +198,8 @@ static inline void viml_parser_advance(ParserState *const pstate, const size_t l } } -static inline void viml_parser_highlight(ParserState *const pstate, - const ParserPosition start, - const size_t end_col, - const char *const group) +static inline void viml_parser_highlight(ParserState *pstate, ParserPosition start, size_t len, + const char *group) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; /// Record highlighting of some region of text diff --git a/src/nvim/window.c b/src/nvim/window.c index d7ca718c68..05f84b5a91 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2,21 +2,34 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> +#include <ctype.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "klib/kvec.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" #include "nvim/arglist.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/decoration.h" #include "nvim/diff.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/vars.h" +#include "nvim/eval/window.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" @@ -27,15 +40,19 @@ #include "nvim/fold.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/hashtab.h" -#include "nvim/highlight.h" +#include "nvim/keycodes.h" +#include "nvim/macros.h" #include "nvim/main.h" -#include "nvim/mapping.h" +#include "nvim/map.h" +#include "nvim/mapping.h" // IWYU pragma: keep (langmap_adjust_mb) #include "nvim/mark.h" #include "nvim/match.h" -#include "nvim/memline.h" +#include "nvim/mbyte.h" +#include "nvim/memline_defs.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/mouse.h" @@ -44,16 +61,19 @@ #include "nvim/option.h" #include "nvim/optionstr.h" #include "nvim/os/os.h" -#include "nvim/os_unix.h" +#include "nvim/os/os_defs.h" #include "nvim/path.h" #include "nvim/plines.h" +#include "nvim/pos.h" #include "nvim/quickfix.h" -#include "nvim/regexp.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/state.h" +#include "nvim/statusline.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/terminal.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" #include "nvim/undo.h" @@ -125,15 +145,10 @@ win_T *prevwin_curwin(void) /// @param xchar extra char from ":wincmd gx" or NUL void do_window(int nchar, long Prenum, int xchar) { - long Prenum1; - win_T *wp; - char *ptr; - linenr_T lnum = -1; int type = FIND_DEFINE; - size_t len; char cbuf[40]; - Prenum1 = Prenum == 0 ? 1 : Prenum; + long Prenum1 = Prenum == 0 ? 1 : Prenum; #define CHECK_CMDWIN \ do { \ @@ -236,8 +251,8 @@ newwindow: break; // cursor to preview window - case 'P': - wp = NULL; + case 'P': { + win_T *wp = NULL; FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) { if (wp2->w_p_pvw) { wp = wp2; @@ -250,6 +265,7 @@ newwindow: win_goto(wp); } break; + } // close all but current window case Ctrl_O: @@ -269,13 +285,13 @@ newwindow: if (ONE_WINDOW && Prenum != 1) { // just one window beep_flush(); } else { + win_T *wp; if (Prenum) { // go to specified window for (wp = firstwin; --Prenum > 0;) { if (wp->w_next == NULL) { break; - } else { - wp = wp->w_next; } + wp = wp->w_next; } } else { if (nchar == 'W') { // go to previous window @@ -346,7 +362,7 @@ newwindow: // First create a new tab with the window, then go back to // the old tab and close the window there. - wp = curwin; + win_T *wp = curwin; if (win_new_tabpage((int)Prenum, NULL) == OK && valid_tabpage(oldtab)) { newtab = curtab; @@ -481,16 +497,18 @@ newwindow: // Execute the command right here, required when // "wincmd ]" was used in a function. do_nv_ident(Ctrl_RSB, NUL); + postponed_split = 0; break; // edit file name under cursor in a new window case 'f': case 'F': - case Ctrl_F: + case Ctrl_F: { wingotofile: CHECK_CMDWIN; - ptr = (char *)grab_file_name(Prenum1, &lnum); + linenr_T lnum = -1; + char *ptr = grab_file_name(Prenum1, &lnum); if (ptr != NULL) { tabpage_T *oldtab = curtab; win_T *oldwin = curwin; @@ -510,6 +528,7 @@ wingotofile: xfree(ptr); } break; + } // Go to the first occurrence of the identifier under cursor along path in a // new window -- webb @@ -518,8 +537,10 @@ wingotofile: type = FIND_ANY; FALLTHROUGH; case 'd': // Go to definition, using 'define' - case Ctrl_D: + case Ctrl_D: { CHECK_CMDWIN; + size_t len; + char *ptr; if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0) { break; } @@ -527,11 +548,12 @@ wingotofile: // Make a copy, if the line was changed it will be freed. ptr = xstrnsave(ptr, len); - find_pattern_in_path((char_u *)ptr, 0, len, true, Prenum == 0, + find_pattern_in_path(ptr, 0, len, true, Prenum == 0, type, Prenum1, ACTION_SPLIT, 1, MAXLNUM); xfree(ptr); curwin->w_set_curswant = true; break; + } // Quickfix window only: view the result under the cursor in a new split. case K_KENTER: @@ -554,6 +576,7 @@ wingotofile: no_mapping--; allow_keys--; (void)add_to_showcmd(xchar); + switch (xchar) { case '}': xchar = Ctrl_RSB; @@ -575,6 +598,7 @@ wingotofile: // Execute the command right here, required when // "wincmd g}" was used in a function. do_nv_ident('g', xchar); + postponed_split = 0; break; case 'f': // CTRL-W gf: "gf" in a new tab page @@ -582,6 +606,7 @@ wingotofile: cmdmod.cmod_tab = tabpage_index(curtab) + 1; nchar = xchar; goto wingotofile; + case 't': // CTRL-W gt: go to next tab page goto_tabpage((int)Prenum); break; @@ -626,7 +651,7 @@ wingotofile: static void cmd_with_count(char *cmd, char *bufp, size_t bufsize, int64_t Prenum) { - size_t len = STRLCPY(bufp, cmd, bufsize); + size_t len = xstrlcpy(bufp, cmd, bufsize); if (Prenum > 0 && len < bufsize) { vim_snprintf(bufp + len, bufsize - len, "%" PRId64, Prenum); @@ -637,11 +662,13 @@ void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err) { 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); if (!win || !buf) { return; } + + tabpage_T *tab = win_find_tabpage(win); + if (noautocmd) { block_autocmds(); } @@ -729,20 +756,20 @@ void win_set_minimal_style(win_T *wp) // Hide EOB region: use " " fillchar and cleared highlighting if (wp->w_p_fcs_chars.eob != ' ') { - char_u *old = (char_u *)wp->w_p_fcs; + char *old = wp->w_p_fcs; wp->w_p_fcs = ((*old == NUL) ? xstrdup("eob: ") - : concat_str((char *)old, ",eob: ")); - free_string_option((char *)old); + : concat_str(old, ",eob: ")); + free_string_option(old); } // TODO(bfredl): this could use a highlight namespace directly, // and avoid peculiarities around window options - char_u *old = (char_u *)wp->w_p_winhl; + char *old = wp->w_p_winhl; wp->w_p_winhl = ((*old == NUL) ? xstrdup("EndOfBuffer:") - : concat_str((char *)old, ",EndOfBuffer:")); - free_string_option((char *)old); + : concat_str(old, ",EndOfBuffer:")); + free_string_option(old); parse_winhl_opt(wp); // signcolumn: use 'auto' @@ -762,6 +789,12 @@ void win_set_minimal_style(win_T *wp) free_string_option(wp->w_p_cc); wp->w_p_cc = xstrdup(""); } + + // statuscolumn: cleared + if (wp->w_p_stc != NULL && *wp->w_p_stc != NUL) { + free_string_option(wp->w_p_stc); + wp->w_p_stc = xstrdup(""); + } } void win_config_float(win_T *wp, FloatConfig fconfig) @@ -774,13 +807,22 @@ void win_config_float(win_T *wp, FloatConfig fconfig) fconfig.row += curwin->w_wrow; fconfig.col += curwin->w_wcol; fconfig.window = curwin->handle; + } else if (fconfig.relative == kFloatRelativeMouse) { + int row = mouse_row, col = mouse_col, grid = mouse_grid; + win_T *mouse_win = mouse_find_win(&grid, &row, &col); + if (mouse_win != NULL) { + fconfig.relative = kFloatRelativeWindow; + fconfig.row += row; + fconfig.col += col; + fconfig.window = mouse_win->handle; + } } bool change_external = fconfig.external != wp->w_float_config.external; bool change_border = (fconfig.border != wp->w_float_config.border || memcmp(fconfig.border_hl_ids, wp->w_float_config.border_hl_ids, - sizeof fconfig.border_hl_ids)); + sizeof fconfig.border_hl_ids) != 0); wp->w_float_config = fconfig; @@ -821,16 +863,16 @@ void win_config_float(win_T *wp, FloatConfig fconfig) grid_adjust(&grid, &row_off, &col_off); row += row_off; col += col_off; + if (wp->w_float_config.bufpos.lnum >= 0) { + pos_T pos = { wp->w_float_config.bufpos.lnum + 1, + wp->w_float_config.bufpos.col, 0 }; + int trow, tcol, tcolc, tcole; + textpos2screenpos(parent, &pos, &trow, &tcol, &tcolc, &tcole, true); + row += trow - 1; + col += tcol - 1; + } } api_clear_error(&dummy); - if (wp->w_float_config.bufpos.lnum >= 0) { - pos_T pos = { wp->w_float_config.bufpos.lnum + 1, - wp->w_float_config.bufpos.col, 0 }; - int trow, tcol, tcolc, tcole; - textpos2screenpos(wp, &pos, &trow, &tcol, &tcolc, &tcole, true); - row += trow - 1; - col += tcol - 1; - } wp->w_winrow = row; wp->w_wincol = col; } else { @@ -866,9 +908,8 @@ int win_fdccol_count(win_T *wp) const int fdccol = fdc[4] == ':' ? fdc[5] - '0' : 1; int needed_fdccols = getDeepestNesting(wp); return MIN(fdccol, needed_fdccols); - } else { - return fdc[0] - '0'; } + return fdc[0] - '0'; } void ui_ext_win_position(win_T *wp, bool validate) @@ -1024,26 +1065,13 @@ int win_split(int size, int flags) int win_split_ins(int size, int flags, win_T *new_wp, int dir) { win_T *wp = new_wp; - win_T *oldwin; - int new_size = size; - int i; - int need_status = 0; - bool do_equal = false; - int needed; - int available; - int oldwin_height = 0; - int layout; - frame_T *frp, *curfrp, *frp2, *prevfrp; - int before; - int minheight; - int wmh1; - bool did_set_fraction = false; - // aucmd_win should always remain floating - if (new_wp != NULL && new_wp == aucmd_win) { + // aucmd_win[] should always remain floating + if (new_wp != NULL && is_aucmd_win(new_wp)) { return FAIL; } + win_T *oldwin; if (flags & WSP_TOP) { oldwin = firstwin; } else if (flags & WSP_BOT || curwin->w_floating) { @@ -1053,6 +1081,8 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) oldwin = curwin; } + int need_status = 0; + int new_size = size; bool new_in_layout = (new_wp == NULL || new_wp->w_floating); // add a status line when p_ls == 1 and splitting the first window @@ -1064,30 +1094,33 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) need_status = STATUS_HEIGHT; } - if (flags & WSP_VERT) { - int wmw1; - int minwidth; - - layout = FR_ROW; + bool do_equal = false; + int oldwin_height = 0; + const int layout = flags & WSP_VERT ? FR_ROW : FR_COL; + bool did_set_fraction = false; + if (flags & WSP_VERT) { // Check if we are able to split the current window and compute its // width. // Current window requires at least 1 space. - wmw1 = (p_wmw == 0 ? 1 : (int)p_wmw); - needed = wmw1 + 1; + int wmw1 = (p_wmw == 0 ? 1 : (int)p_wmw); + int needed = wmw1 + 1; if (flags & WSP_ROOM) { needed += (int)p_wiw - wmw1; } + int minwidth; + int available; if (flags & (WSP_BOT | WSP_TOP)) { minwidth = frame_minwidth(topframe, NOWIN); available = topframe->fr_width; needed += minwidth; } else if (p_ea) { minwidth = frame_minwidth(oldwin->w_frame, NOWIN); - prevfrp = oldwin->w_frame; - for (frp = oldwin->w_frame->fr_parent; frp != NULL; + frame_T *prevfrp = oldwin->w_frame; + for (frame_T *frp = oldwin->w_frame->fr_parent; frp != NULL; frp = frp->fr_parent) { if (frp->fr_layout == FR_ROW) { + frame_T *frp2; FOR_ALL_FRAMES(frp2, frp->fr_child) { if (frp2 != prevfrp) { minwidth += frame_minwidth(frp2, NOWIN); @@ -1133,7 +1166,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) // is wider than one of the split windows. if (!do_equal && p_ea && size == 0 && *p_ead != 'v' && oldwin->w_frame->fr_parent != NULL) { - frp = oldwin->w_frame->fr_parent->fr_child; + frame_T *frp = oldwin->w_frame->fr_parent->fr_child; while (frp != NULL) { if (frp->fr_win != oldwin && frp->fr_win != NULL && (frp->fr_win->w_width > new_size @@ -1146,27 +1179,28 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) } } } else { - layout = FR_COL; - // Check if we are able to split the current window and compute its height. // Current window requires at least 1 space plus space for the window bar. - wmh1 = MAX((int)p_wmh, 1) + oldwin->w_winbar_height; - needed = wmh1 + STATUS_HEIGHT; + int wmh1 = MAX((int)p_wmh, 1) + oldwin->w_winbar_height; + int needed = wmh1 + STATUS_HEIGHT; if (flags & WSP_ROOM) { needed += (int)p_wh - wmh1 + oldwin->w_winbar_height; } if (p_ch < 1) { needed += 1; // Adjust for cmdheight=0. } + int minheight; + int available; if (flags & (WSP_BOT | WSP_TOP)) { minheight = frame_minheight(topframe, NOWIN) + need_status; available = topframe->fr_height; needed += minheight; } else if (p_ea) { minheight = frame_minheight(oldwin->w_frame, NOWIN) + need_status; - prevfrp = oldwin->w_frame; - for (frp = oldwin->w_frame->fr_parent; frp != NULL; frp = frp->fr_parent) { + frame_T *prevfrp = oldwin->w_frame; + for (frame_T *frp = oldwin->w_frame->fr_parent; frp != NULL; frp = frp->fr_parent) { if (frp->fr_layout == FR_COL) { + frame_T *frp2; FOR_ALL_FRAMES(frp2, frp->fr_child) { if (frp2 != prevfrp) { minheight += frame_minheight(frp2, NOWIN); @@ -1229,7 +1263,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) if (!do_equal && p_ea && size == 0 && *p_ead != 'h' && oldwin->w_frame->fr_parent != NULL) { - frp = oldwin->w_frame->fr_parent->fr_child; + frame_T *frp = oldwin->w_frame->fr_parent->fr_child; while (frp != NULL) { if (frp->fr_win != oldwin && frp->fr_win != NULL && (frp->fr_win->w_height > new_size @@ -1279,6 +1313,9 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) CLEAR_FIELD(wp->w_border_adj); } + int before; + frame_T *curfrp; + // Reorganise the tree of frames to insert the new window. if (flags & (WSP_TOP | WSP_BOT)) { if ((topframe->fr_layout == FR_COL && (flags & WSP_VERT) == 0) @@ -1307,7 +1344,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) } if (curfrp->fr_parent == NULL || curfrp->fr_parent->fr_layout != layout) { // Need to create a new frame in the tree to make a branch. - frp = xcalloc(1, sizeof(frame_T)); + frame_T *frp = xcalloc(1, sizeof(frame_T)); *frp = *curfrp; curfrp->fr_layout = (char)layout; frp->fr_parent = curfrp; @@ -1325,6 +1362,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) } } + frame_T *frp; if (new_wp == NULL) { frp = wp->w_frame; } else { @@ -1487,10 +1525,12 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) // equalize the window sizes. if (do_equal || dir != 0) { win_equal(wp, true, (flags & WSP_VERT) ? (dir == 'v' ? 'b' : 'h') : (dir == 'h' ? 'b' : 'v')); - } else if (*p_spk != 'c' && wp != aucmd_win) { + } else if (*p_spk != 'c' && !is_aucmd_win(wp)) { win_fix_scroll(false); } + int i; + // Don't change the window height/width to 'winheight' / 'winwidth' if a // size was given. if (flags & WSP_VERT) { @@ -1529,8 +1569,6 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) // being copied. static void win_init(win_T *newp, win_T *oldp, int flags) { - int i; - newp->w_buffer = oldp->w_buffer; newp->w_s = &(oldp->w_buffer->b_s); oldp->w_buffer->b_nwindows++; @@ -1567,7 +1605,7 @@ static void win_init(win_T *newp, win_T *oldp, int flags) } // copy tagstack and folds - for (i = 0; i < oldp->w_tagstacklen; i++) { + for (int i = 0; i < oldp->w_tagstacklen; i++) { taggy_T *tag = &newp->w_tagstack[i]; *tag = oldp->w_tagstack[i]; if (tag->tagname != NULL) { @@ -1626,11 +1664,20 @@ bool win_valid_floating(const win_T *win) /// @param win window to check bool win_valid(const win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { + return tabpage_win_valid(curtab, win); +} + +/// Check if "win" is a pointer to an existing window in tabpage "tp". +/// +/// @param win window to check +static bool tabpage_win_valid(const tabpage_T *tp, const win_T *win) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ if (win == NULL) { return false; } - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + FOR_ALL_WINDOWS_IN_TAB(wp, tp) { if (wp == win) { return true; } @@ -1689,7 +1736,6 @@ int win_count(void) int make_windows(int count, bool vertical) { int maxcount; - int todo; if (vertical) { // Each window needs at least 'winminwidth' lines and a separator column. @@ -1719,6 +1765,8 @@ int make_windows(int count, bool vertical) // when putting the buffers in the windows. block_autocmds(); + int todo; + // todo is number of windows left to create for (todo = count - 1; todo > 0; todo--) { if (vertical) { @@ -1744,12 +1792,6 @@ int make_windows(int count, bool vertical) // Exchange current and next window static void win_exchange(long Prenum) { - frame_T *frp; - frame_T *frp2; - win_T *wp; - win_T *wp2; - int temp; - if (curwin->w_floating) { emsg(e_floatexchange); return; @@ -1761,6 +1803,8 @@ static void win_exchange(long Prenum) return; } + frame_T *frp; + // find window to exchange with if (Prenum) { frp = curwin->w_frame->fr_parent->fr_child; @@ -1778,7 +1822,7 @@ static void win_exchange(long Prenum) if (frp == NULL || frp->fr_win == NULL || frp->fr_win == curwin) { return; } - wp = frp->fr_win; + win_T *wp = frp->fr_win; // 1. remove curwin from the list. Remember after which window it was in wp2 // 2. insert curwin before wp in the list @@ -1786,8 +1830,8 @@ static void win_exchange(long Prenum) // 3. remove wp from the list // 4. insert wp after wp2 // 5. exchange the status line height, winbar height, hsep height and vsep width. - wp2 = curwin->w_prev; - frp2 = curwin->w_frame->fr_prev; + win_T *wp2 = curwin->w_prev; + frame_T *frp2 = curwin->w_frame->fr_prev; if (wp->w_prev != curwin) { win_remove(curwin, NULL); frame_remove(curwin->w_frame); @@ -1804,7 +1848,7 @@ static void win_exchange(long Prenum) frame_append(frp2, wp->w_frame); } } - temp = curwin->w_status_height; + int temp = curwin->w_status_height; curwin->w_status_height = wp->w_status_height; wp->w_status_height = temp; temp = curwin->w_vsep_width; @@ -1836,11 +1880,6 @@ static void win_exchange(long Prenum) // if upwards false the first window becomes the second one static void win_rotate(bool upwards, int count) { - win_T *wp1; - win_T *wp2; - frame_T *frp; - int n; - if (curwin->w_floating) { emsg(e_floatexchange); return; @@ -1853,6 +1892,7 @@ static void win_rotate(bool upwards, int count) } // Check if all frames in this row/col have one window. + frame_T *frp; FOR_ALL_FRAMES(frp, curwin->w_frame->fr_parent->fr_child) { if (frp->fr_win == NULL) { emsg(_("E443: Cannot rotate when another window is split")); @@ -1860,6 +1900,9 @@ static void win_rotate(bool upwards, int count) } } + win_T *wp1; + win_T *wp2; + while (count--) { if (upwards) { // first window becomes last window // remove first window/frame from the list @@ -1892,7 +1935,7 @@ static void win_rotate(bool upwards, int count) } // exchange status height, winbar height, hsep height and vsep width of old and new last window - n = wp2->w_status_height; + int n = wp2->w_status_height; wp2->w_status_height = wp1->w_status_height; wp1->w_status_height = n; n = wp2->w_hsep_height; @@ -1926,7 +1969,7 @@ static void win_totop(int size, int flags) beep_flush(); return; } - if (curwin == aucmd_win) { + if (is_aucmd_win(curwin)) { return; } if (check_split_disallowed() == FAIL) { @@ -1964,8 +2007,6 @@ static void win_totop(int size, int flags) // window. Only works within the same frame! void win_move_after(win_T *win1, win_T *win2) { - int height; - // check if the arguments are reasonable if (win1 == win2) { return; @@ -1981,7 +2022,7 @@ void win_move_after(win_T *win1, win_T *win2) // may need to move the status line, window bar, horizontal or vertical separator of the last // window if (win1 == lastwin) { - height = win1->w_prev->w_status_height; + int height = win1->w_prev->w_status_height; win1->w_prev->w_status_height = win1->w_status_height; win1->w_status_height = height; @@ -1998,7 +2039,7 @@ void win_move_after(win_T *win1, win_T *win2) win1->w_frame->fr_width += 1; } } else if (win2 == lastwin) { - height = win1->w_status_height; + int height = win1->w_status_height; win1->w_status_height = win2->w_status_height; win2->w_status_height = height; @@ -2074,7 +2115,7 @@ void win_equal(win_T *next_curwin, bool current, int dir) win_equal_rec(next_curwin == NULL ? curwin : next_curwin, current, topframe, dir, 0, tabline_height(), Columns, topframe->fr_height); - if (*p_spk != 'c' && next_curwin != aucmd_win) { + if (*p_spk != 'c' && !is_aucmd_win(next_curwin)) { win_fix_scroll(true); } } @@ -2095,15 +2136,11 @@ void win_equal(win_T *next_curwin, bool current, int dir) static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int dir, int col, int row, int width, int height) { - int n, m; int extra_sep = 0; - int wincount, totwincount = 0; - frame_T *fr; + int totwincount = 0; int next_curwin_size = 0; int room = 0; - int new_size; int has_next_curwin = 0; - bool hnc; if (topfr->fr_layout == FR_LEAF) { // Set the width/height of this frame. @@ -2124,7 +2161,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int if (dir != 'v') { // equalize frame widths // Compute the maximum number of windows horizontally in this // frame. - n = frame_minwidth(topfr, NOWIN); + int n = frame_minwidth(topfr, NOWIN); // add one for the rightmost window, it doesn't have a separator if (col + width == Columns) { extra_sep = 1; @@ -2137,13 +2174,14 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int // Compute width for "next_curwin" window and room available for // other windows. // "m" is the minimal width when counting p_wiw for "next_curwin". - m = frame_minwidth(topfr, next_curwin); + int m = frame_minwidth(topfr, next_curwin); room = width - m; if (room < 0) { next_curwin_size = (int)p_wiw + room; room = 0; } else { next_curwin_size = -1; + frame_T *fr; FOR_ALL_FRAMES(fr, topfr->fr_child) { if (!frame_fixed_width(fr)) { continue; @@ -2151,7 +2189,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int // If 'winfixwidth' set keep the window width if possible. // Watch out for this window being the next_curwin. n = frame_minwidth(fr, NOWIN); - new_size = fr->fr_width; + int new_size = fr->fr_width; if (frame_has_win(fr, next_curwin)) { room += (int)p_wiw - (int)p_wmw; next_curwin_size = 0; @@ -2192,8 +2230,10 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int } } + frame_T *fr; FOR_ALL_FRAMES(fr, topfr->fr_child) { - wincount = 1; + int wincount = 1; + int new_size; if (fr->fr_next == NULL) { // last frame gets all that remains (avoid roundoff error) new_size = width; @@ -2204,14 +2244,10 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int wincount = 0; // doesn't count as a sizeable window } else { // Compute the maximum number of windows horiz. in "fr". - n = frame_minwidth(fr, NOWIN); + int n = frame_minwidth(fr, NOWIN); wincount = (n + (fr->fr_next == NULL ? extra_sep : 0)) / ((int)p_wmw + 1); - m = frame_minwidth(fr, next_curwin); - if (has_next_curwin) { - hnc = frame_has_win(fr, next_curwin); - } else { - hnc = false; - } + int m = frame_minwidth(fr, next_curwin); + bool hnc = has_next_curwin && frame_has_win(fr, next_curwin); if (hnc) { // don't count next_curwin wincount--; } @@ -2248,7 +2284,7 @@ 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); + int n = frame_minheight(topfr, NOWIN); // 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; @@ -2263,7 +2299,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int // Compute height for "next_curwin" window and room available for // other windows. // "m" is the minimal height when counting p_wh for "next_curwin". - m = frame_minheight(topfr, next_curwin); + int m = frame_minheight(topfr, next_curwin); room = height - m; if (room < 0) { // The room is less than 'winheight', use all space for the @@ -2272,6 +2308,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int room = 0; } else { next_curwin_size = -1; + frame_T *fr; FOR_ALL_FRAMES(fr, topfr->fr_child) { if (!frame_fixed_height(fr)) { continue; @@ -2279,7 +2316,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int // If 'winfixheight' set keep the window height if possible. // Watch out for this window being the next_curwin. n = frame_minheight(fr, NOWIN); - new_size = fr->fr_height; + int new_size = fr->fr_height; if (frame_has_win(fr, next_curwin)) { room += (int)p_wh - (int)p_wmh; next_curwin_size = 0; @@ -2320,8 +2357,10 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int } } + frame_T *fr; FOR_ALL_FRAMES(fr, topfr->fr_child) { - wincount = 1; + int new_size; + int wincount = 1; if (fr->fr_next == NULL) { // last frame gets all that remains (avoid roundoff error) new_size = height; @@ -2332,14 +2371,10 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int wincount = 0; // doesn't count as a sizeable window } else { // Compute the maximum number of windows vert. in "fr". - n = frame_minheight(fr, NOWIN); + int n = frame_minheight(fr, NOWIN); wincount = get_maximum_wincount(fr, (n + (fr->fr_next == NULL ? extra_sep : 0))); - m = frame_minheight(fr, next_curwin); - if (has_next_curwin) { - hnc = frame_has_win(fr, next_curwin); - } else { - hnc = false; - } + int m = frame_minheight(fr, next_curwin); + bool hnc = has_next_curwin && frame_has_win(fr, next_curwin); if (hnc) { // don't count next_curwin wincount--; } @@ -2449,13 +2484,11 @@ void curwin_init(void) /// @param keep_curwin don't close `curwin` void close_windows(buf_T *buf, bool keep_curwin) { - tabpage_T *tp, *nexttp; - RedrawingDisabled++; // 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));) { + for (win_T *wp = lastwin; wp != NULL && (is_aucmd_win(lastwin) || !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, false) == FAIL) { @@ -2470,8 +2503,10 @@ void close_windows(buf_T *buf, bool keep_curwin) } } + tabpage_T *nexttp; + // Also check windows in other tab pages. - for (tp = first_tabpage; tp != NULL; tp = nexttp) { + for (tabpage_T *tp = first_tabpage; tp != NULL; tp = nexttp) { nexttp = tp->tp_next; if (tp != curtab) { // Start from tp_lastwin to close floating windows with the same buffer first. @@ -2492,24 +2527,23 @@ void close_windows(buf_T *buf, bool keep_curwin) RedrawingDisabled--; } -/// Check that the specified window is the last one. -/// @param win counted even if floating -/// -/// @return true if the specified window is the only window that exists, -/// false if there is another, possibly in another tab page. +/// Check if "win" is the last non-floating window that exists. bool last_window(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return one_window(win) && first_tabpage->tp_next == NULL; } -/// 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 +/// Check if "win" is the only non-floating window in the current tabpage. +bool one_window(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { + if (win->w_floating) { + return false; + } + bool seen_one = false; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp != aucmd_win && (!wp->w_floating || wp == counted_float)) { + if (!wp->w_floating) { if (seen_one) { return false; } @@ -2539,7 +2573,7 @@ bool last_nonfloat(win_T *wp) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT /// @return true if all floating windows can be closed static bool can_close_floating_windows(void) { - assert(lastwin != aucmd_win); + assert(!is_aucmd_win(lastwin)); 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); @@ -2582,8 +2616,8 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev goto_tabpage_tp(alt_tabpage(), false, true); // save index for tabclosed event - char_u prev_idx[NUMBUFLEN]; - sprintf((char *)prev_idx, "%i", tabpage_index(prev_curtab)); + char prev_idx[NUMBUFLEN]; + snprintf(prev_idx, NUMBUFLEN, "%i", tabpage_index(prev_curtab)); // Safety check: Autocommands may have closed the window when jumping // to the other tab page. @@ -2644,11 +2678,6 @@ static void win_close_buffer(win_T *win, bool free_buf, bool abort_if_last) // Returns FAIL when the window was not closed. int win_close(win_T *win, bool free_buf, bool force) { - win_T *wp; - bool other_buffer = false; - bool close_curwin = false; - int dir; - bool help_window = false; tabpage_T *prev_curtab = curtab; frame_T *win_frame = win->w_floating ? NULL : win->w_frame->fr_parent; const bool had_diffmode = win->w_p_diff; @@ -2662,12 +2691,12 @@ int win_close(win_T *win, bool free_buf, bool force) || (win->w_buffer != NULL && win->w_buffer->b_locked > 0)) { return FAIL; // window is already being closed } - if (win == aucmd_win) { + if (is_aucmd_win(win)) { emsg(_(e_autocmd_close)); return FAIL; } if (lastwin->w_floating && one_window(win)) { - if (lastwin == aucmd_win) { + if (is_aucmd_win(lastwin)) { emsg(_("E814: Cannot close window, only autocmd window would remain")); return FAIL; } @@ -2693,6 +2722,8 @@ int win_close(win_T *win, bool free_buf, bool force) return FAIL; } + bool help_window = false; + // When closing the help window, try restoring a snapshot after closing // the window. Otherwise clear the snapshot, it's now invalid. if (bt_help(win->w_buffer)) { @@ -2701,6 +2732,9 @@ int win_close(win_T *win, bool free_buf, bool force) clear_snapshot(curtab, SNAP_HELP_IDX); } + win_T *wp; + bool other_buffer = false; + if (win == curwin) { leaving_window(curwin); @@ -2747,27 +2781,6 @@ int win_close(win_T *win, bool free_buf, bool force) } } - bool was_floating = win->w_floating; - if (ui_has(kUIMultigrid)) { - ui_call_win_close(win->w_grid_alloc.handle); - } - - if (win->w_floating) { - ui_comp_remove_grid(&win->w_grid_alloc); - if (win->w_float_config.external) { - for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) { - if (tp == curtab) { - continue; - } - if (tp->tp_curwin == win) { - // NB: an autocmd can still abort the closing of this window, - // bur carring out this change anyway shouldn't be a catastrophe. - tp->tp_curwin = tp->tp_firstwin; - } - } - } - } - // Fire WinClosed just before starting to free window-related resources. do_autocmd_winclosed(win); // autocmd may have freed the window already. @@ -2813,8 +2826,31 @@ int win_close(win_T *win, bool free_buf, bool force) // let terminal buffers know that this window dimensions may be ignored win->w_closing = true; + bool was_floating = win->w_floating; + if (ui_has(kUIMultigrid)) { + ui_call_win_close(win->w_grid_alloc.handle); + } + + if (win->w_floating) { + ui_comp_remove_grid(&win->w_grid_alloc); + assert(first_tabpage != NULL); // suppress clang "Dereference of NULL pointer" + if (win->w_float_config.external) { + for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) { + if (tp == curtab) { + continue; + } + if (tp->tp_curwin == win) { + // NB: an autocmd can still abort the closing of this window, + // bur carring out this change anyway shouldn't be a catastrophe. + tp->tp_curwin = tp->tp_firstwin; + } + } + } + } + // Free the memory used for the window and get the window that received // the screen space. + int dir; wp = win_free_mem(win, &dir, NULL); if (help_window) { @@ -2826,6 +2862,8 @@ int win_close(win_T *win, bool free_buf, bool force) } } + bool close_curwin = false; + // Make sure curwin isn't invalid. It can cause severe trouble when // printing an error message. For win_equal() curbuf needs to be valid // too. @@ -2937,10 +2975,6 @@ static void do_autocmd_winclosed(win_T *win) // updated. void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) { - int dir; - tabpage_T *ptp = NULL; - bool free_tp = false; - // Get here with win->w_buffer == NULL when win_close() detects the tab page // changed. if (win->w_closing @@ -2960,6 +2994,8 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, false, true); } + tabpage_T *ptp = NULL; + // Careful: Autocommands may have closed the tab page or made it the // current tab page. for (ptp = first_tabpage; ptp != NULL && ptp != tp; ptp = ptp->tp_next) {} @@ -2988,6 +3024,8 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) } } + bool free_tp = false; + // When closing the last window in a tab page remove the tab page. if (tp->tp_firstwin == tp->tp_lastwin) { char prev_idx[NUMBUFLEN]; @@ -3022,6 +3060,7 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) } // Free the memory used for the window. + int dir; win_free_mem(win, &dir, tp); if (free_tp) { @@ -3037,32 +3076,36 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) /// @return a pointer to the window that got the freed up space. static win_T *win_free_mem(win_T *win, int *dirp, tabpage_T *tp) { - frame_T *frp; win_T *wp; + tabpage_T *win_tp = tp == NULL ? curtab : tp; if (!win->w_floating) { // Remove the window and its frame from the tree of frames. - frp = win->w_frame; + frame_T *frp = win->w_frame; wp = winframe_remove(win, dirp, tp); xfree(frp); } else { *dirp = 'h'; // Dummy value. - if (win_valid(prevwin) && prevwin != win) { - wp = prevwin; + if (tp == NULL) { + if (win_valid(prevwin) && prevwin != win) { + wp = prevwin; + } else { + wp = firstwin; + } } else { - wp = firstwin; + if (tabpage_win_valid(tp, tp->tp_prevwin) && tp->tp_prevwin != win) { + wp = tp->tp_prevwin; + } else { + wp = tp->tp_firstwin; + } } } win_free(win, tp); - // When deleting the current window of another tab page select a new - // current window. - if (tp != NULL && win == tp->tp_curwin) { - if (win_valid(tp->tp_prevwin) && tp->tp_prevwin != win) { - tp->tp_curwin = tp->tp_prevwin; - } else { - tp->tp_curwin = tp->tp_firstwin; - } + // When deleting the current window in the tab, select a new current + // window. + if (win == win_tp->tp_curwin) { + win_tp->tp_curwin = wp; } return wp; @@ -3071,8 +3114,6 @@ static win_T *win_free_mem(win_T *win, int *dirp, tabpage_T *tp) #if defined(EXITFREE) void win_free_all(void) { - int dummy; - // avoid an error for switching tabpage with the cmdline window open cmdwin_type = 0; @@ -3083,18 +3124,27 @@ void win_free_all(void) while (lastwin != NULL && lastwin->w_floating) { win_T *wp = lastwin; win_remove(lastwin, NULL); + int dummy; (void)win_free_mem(wp, &dummy, NULL); - if (wp == aucmd_win) { - aucmd_win = NULL; + for (int i = 0; i < AUCMD_WIN_COUNT; i++) { + if (aucmd_win[i].auc_win == wp) { + aucmd_win[i].auc_win = NULL; + } } } - if (aucmd_win != NULL) { - (void)win_free_mem(aucmd_win, &dummy, NULL); - aucmd_win = NULL; + for (int i = 0; i < AUCMD_WIN_COUNT; i++) { + if (aucmd_win[i].auc_win != NULL) { + int dummy; + (void)win_free_mem(aucmd_win[i].auc_win, &dummy, NULL); + aucmd_win[i].auc_win = NULL; + } } + kv_destroy(aucmd_win_vec); + while (firstwin != NULL) { + int dummy; (void)win_free_mem(firstwin, &dummy, NULL); } @@ -3113,18 +3163,16 @@ void win_free_all(void) /// @return a pointer to the window that got the freed up space. win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp) { - frame_T *frp, *frp2, *frp3; - frame_T *frp_close = win->w_frame; - win_T *wp; - // If there is only one window there is nothing to remove. if (tp == NULL ? ONE_WINDOW : tp->tp_firstwin == tp->tp_lastwin) { return NULL; } + frame_T *frp_close = win->w_frame; + // Remove the window from its frame. - frp2 = win_altframe(win, tp); - wp = frame2win(frp2); + frame_T *frp2 = win_altframe(win, tp); + win_T *wp = frame2win(frp2); // Remove this frame from the list of frames. frame_remove(frp_close); @@ -3134,8 +3182,8 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp) // (as close to the closed frame as possible) to distribute the height // to. if (frp2->fr_win != NULL && frp2->fr_win->w_p_wfh) { - frp = frp_close->fr_prev; - frp3 = frp_close->fr_next; + frame_T *frp = frp_close->fr_prev; + frame_T *frp3 = frp_close->fr_next; while (frp != NULL || frp3 != NULL) { if (frp != NULL) { if (!frame_fixed_height(frp)) { @@ -3163,8 +3211,8 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp) // (as close to the closed frame as possible) to distribute the width // to. if (frp2->fr_win != NULL && frp2->fr_win->w_p_wfw) { - frp = frp_close->fr_prev; - frp3 = frp_close->fr_next; + frame_T *frp = frp_close->fr_prev; + frame_T *frp3 = frp_close->fr_next; while (frp != NULL || frp3 != NULL) { if (frp != NULL) { if (!frame_fixed_width(frp)) { @@ -3203,6 +3251,7 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp) // and remove it. frp2->fr_parent->fr_layout = frp2->fr_layout; frp2->fr_parent->fr_child = frp2->fr_child; + frame_T *frp; FOR_ALL_FRAMES(frp, frp2->fr_child) { frp->fr_parent = frp2->fr_parent; } @@ -3228,6 +3277,7 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp) if (frp->fr_prev != NULL) { frp->fr_prev->fr_next = frp->fr_child; } + frame_T *frp3; for (frp3 = frp->fr_child;; frp3 = frp3->fr_next) { frp3->fr_parent = frp2; if (frp3->fr_next == NULL) { @@ -3260,13 +3310,11 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp) /// is left over after "win" is closed. static frame_T *win_altframe(win_T *win, tabpage_T *tp) { - frame_T *frp; - if (tp == NULL ? ONE_WINDOW : tp->tp_firstwin == tp->tp_lastwin) { return alt_tabpage()->tp_curwin->w_frame; } - frp = win->w_frame; + frame_T *frp = win->w_frame; if (frp->fr_prev == NULL) { return frp->fr_next; @@ -3312,14 +3360,13 @@ static frame_T *win_altframe(win_T *win, tabpage_T *tp) // Return the tabpage that will be used if the current one is closed. static tabpage_T *alt_tabpage(void) { - tabpage_T *tp; - // Use the next tab page if possible. if (curtab->tp_next != NULL) { return curtab->tp_next; } // Find the last but one tab page. + tabpage_T *tp; for (tp = first_tabpage; tp->tp_next != curtab; tp = tp->tp_next) {} return tp; } @@ -3357,8 +3404,7 @@ static bool frame_has_win(const frame_T *frp, const win_T *wp) 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) { + for (frame_T *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; } @@ -3374,19 +3420,15 @@ static bool is_bottom_win(win_T *wp) 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. - wp = topfrp->fr_win; + win_T *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) { + frame_T *frp; do { // All frames in this row get the same new height. FOR_ALL_FRAMES(frp, topfrp->fr_child) { @@ -3402,7 +3444,7 @@ void frame_new_height(frame_T *topfrp, int height, bool topfirst, bool wfh) // Complicated case: Resize a column of frames. Resize the bottom // frame first, frames above that when needed. - frp = topfrp->fr_child; + frame_T *frp = topfrp->fr_child; if (wfh) { // Advance past frames with one window with 'wfh' set. while (frame_fixed_height(frp)) { @@ -3425,11 +3467,11 @@ void frame_new_height(frame_T *topfrp, int height, bool topfirst, bool wfh) } } - extra_lines = height - topfrp->fr_height; + int extra_lines = height - topfrp->fr_height; if (extra_lines < 0) { // reduce height of contained frames, bottom or top frame first while (frp != NULL) { - h = frame_minheight(frp, NULL); + int h = frame_minheight(frp, NULL); if (frp->fr_height + extra_lines < h) { extra_lines += frp->fr_height - h; frame_new_height(frp, h, topfirst, wfh); @@ -3532,10 +3574,8 @@ static bool frame_fixed_width(frame_T *frp) // Note: Does not check if there is room! static void frame_add_statusline(frame_T *frp) { - win_T *wp; - if (frp->fr_layout == FR_LEAF) { - wp = frp->fr_win; + win_T *wp = frp->fr_win; if (wp->w_status_height == 0) { if (wp->w_height - STATUS_HEIGHT >= 0) { // don't make it negative wp->w_height -= STATUS_HEIGHT; @@ -3563,15 +3603,11 @@ static void frame_add_statusline(frame_T *frp) /// may cause the width not to be set. static void frame_new_width(frame_T *topfrp, int width, bool leftfirst, bool wfw) { - frame_T *frp; - int extra_cols; - int w; - win_T *wp; - if (topfrp->fr_layout == FR_LEAF) { // Simple case: just one window. - wp = topfrp->fr_win; + win_T *wp = topfrp->fr_win; // Find out if there are any windows right of this one. + frame_T *frp; for (frp = topfrp; frp->fr_parent != NULL; frp = frp->fr_parent) { if (frp->fr_parent->fr_layout == FR_ROW && frp->fr_next != NULL) { break; @@ -3582,6 +3618,7 @@ static void frame_new_width(frame_T *topfrp, int width, bool leftfirst, bool wfw } win_new_width(wp, width - wp->w_vsep_width); } else if (topfrp->fr_layout == FR_COL) { + frame_T *frp; do { // All frames in this column get the same new width. FOR_ALL_FRAMES(frp, topfrp->fr_child) { @@ -3597,7 +3634,7 @@ static void frame_new_width(frame_T *topfrp, int width, bool leftfirst, bool wfw // Complicated case: Resize a row of frames. Resize the rightmost // frame first, frames left of it when needed. - frp = topfrp->fr_child; + frame_T *frp = topfrp->fr_child; if (wfw) { // Advance past frames with one window with 'wfw' set. while (frame_fixed_width(frp)) { @@ -3620,11 +3657,11 @@ static void frame_new_width(frame_T *topfrp, int width, bool leftfirst, bool wfw } } - extra_cols = width - topfrp->fr_width; + int extra_cols = width - topfrp->fr_width; if (extra_cols < 0) { // reduce frame width, rightmost frame first while (frp != NULL) { - w = frame_minwidth(frp, NULL); + int w = frame_minwidth(frp, NULL); if (frp->fr_width + extra_cols < w) { extra_cols += frp->fr_width - w; frame_new_width(frp, w, leftfirst, wfw); @@ -3660,10 +3697,8 @@ static void frame_new_width(frame_T *topfrp, int width, bool leftfirst, bool wfw static void frame_add_vsep(const frame_T *frp) FUNC_ATTR_NONNULL_ARG(1) { - win_T *wp; - if (frp->fr_layout == FR_LEAF) { - wp = frp->fr_win; + win_T *wp = frp->fr_win; if (wp->w_vsep_width == 0) { if (wp->w_width > 0) { // don't make it negative wp->w_width--; @@ -3691,10 +3726,8 @@ static void frame_add_vsep(const frame_T *frp) 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; + win_T *wp = frp->fr_win; if (wp->w_hsep_height == 0) { if (wp->w_height > 0) { // don't make it negative wp->w_height++; @@ -3735,9 +3768,7 @@ static void frame_fix_height(win_T *wp) /// When "next_curwin" is NOWIN, don't use at least one line for the current window. static int frame_minheight(frame_T *topfrp, win_T *next_curwin) { - frame_T *frp; int m; - int n; if (topfrp->fr_win != NULL) { // Combined height of window bar and separator column or status line. @@ -3758,8 +3789,9 @@ static int frame_minheight(frame_T *topfrp, win_T *next_curwin) } else if (topfrp->fr_layout == FR_ROW) { // get the minimal height from each frame in this row m = 0; + frame_T *frp; FOR_ALL_FRAMES(frp, topfrp->fr_child) { - n = frame_minheight(frp, next_curwin); + int n = frame_minheight(frp, next_curwin); if (n > m) { m = n; } @@ -3767,6 +3799,7 @@ static int frame_minheight(frame_T *topfrp, win_T *next_curwin) } else { // Add up the minimal heights for all frames in this column. m = 0; + frame_T *frp; FOR_ALL_FRAMES(frp, topfrp->fr_child) { m += frame_minheight(frp, next_curwin); } @@ -3783,8 +3816,7 @@ static int frame_minheight(frame_T *topfrp, win_T *next_curwin) /// @param next_curwin use p_wh and p_wiw for next_curwin static int frame_minwidth(frame_T *topfrp, win_T *next_curwin) { - frame_T *frp; - int m, n; + int m; if (topfrp->fr_win != NULL) { if (topfrp->fr_win == next_curwin) { @@ -3800,8 +3832,9 @@ static int frame_minwidth(frame_T *topfrp, win_T *next_curwin) } else if (topfrp->fr_layout == FR_COL) { // get the minimal width from each frame in this column m = 0; + frame_T *frp; FOR_ALL_FRAMES(frp, topfrp->fr_child) { - n = frame_minwidth(frp, next_curwin); + int n = frame_minwidth(frp, next_curwin); if (n > m) { m = n; } @@ -3809,6 +3842,7 @@ static int frame_minwidth(frame_T *topfrp, win_T *next_curwin) } else { // Add up the minimal widths for all frames in this row. m = 0; + frame_T *frp; FOR_ALL_FRAMES(frp, topfrp->fr_child) { m += frame_minwidth(frp, next_curwin); } @@ -3826,10 +3860,6 @@ static int frame_minwidth(frame_T *topfrp, win_T *next_curwin) /// @param forceit always hide all other windows void close_others(int message, int forceit) { - win_T *wp; - win_T *nextwp; - int r; - if (curwin->w_floating) { if (message && !autocmd_busy) { emsg(e_floatonly); @@ -3846,14 +3876,15 @@ void close_others(int message, int forceit) } // Be very careful here: autocommands may change the window layout. - for (wp = firstwin; win_valid(wp); wp = nextwp) { + win_T *nextwp; + for (win_T *wp = firstwin; win_valid(wp); wp = nextwp) { nextwp = wp->w_next; if (wp == curwin) { // don't close current window continue; } // Check if it's allowed to abandon this window - r = can_abandon(wp->w_buffer, forceit); + int r = can_abandon(wp->w_buffer, forceit); if (!win_valid(wp)) { // autocommands messed wp up nextwp = firstwin; continue; @@ -3878,6 +3909,27 @@ void close_others(int message, int forceit) } } +/// Store the relevant window pointers for tab page "tp". To be used before +/// use_tabpage(). +void unuse_tabpage(tabpage_T *tp) +{ + tp->tp_topframe = topframe; + tp->tp_firstwin = firstwin; + tp->tp_lastwin = lastwin; + tp->tp_curwin = curwin; +} + +/// Set the relevant pointers to use tab page "tp". May want to call +/// unuse_tabpage() first. +void use_tabpage(tabpage_T *tp) +{ + curtab = tp; + topframe = curtab->tp_topframe; + firstwin = curtab->tp_firstwin; + lastwin = curtab->tp_lastwin; + curwin = curtab->tp_curwin; +} + // Allocate the first window and put an empty buffer in it. // Only called from main(). void win_alloc_first(void) @@ -3888,25 +3940,22 @@ void win_alloc_first(void) } first_tabpage = alloc_tabpage(); - first_tabpage->tp_topframe = topframe; curtab = first_tabpage; - curtab->tp_firstwin = firstwin; - curtab->tp_lastwin = lastwin; - curtab->tp_curwin = curwin; + unuse_tabpage(first_tabpage); } -// Init `aucmd_win`. This can only be done after the first window +// Init `aucmd_win[idx]`. This can only be done after the first window // is fully initialized, thus it can't be in win_alloc_first(). -void win_alloc_aucmd_win(void) +void win_alloc_aucmd_win(int idx) { Error err = ERROR_INIT; FloatConfig fconfig = FLOAT_CONFIG_INIT; fconfig.width = Columns; fconfig.height = 5; fconfig.focusable = false; - aucmd_win = win_new_float(NULL, true, fconfig, &err); - aucmd_win->w_buffer->b_nwindows--; - RESET_BINDING(aucmd_win); + aucmd_win[idx].auc_win = win_new_float(NULL, true, fconfig, &err); + aucmd_win[idx].auc_win->w_buffer->b_nwindows--; + RESET_BINDING(aucmd_win[idx].auc_win); } // Allocate the first window or the first window in a new tab page. @@ -3988,11 +4037,9 @@ static tabpage_T *alloc_tabpage(void) void free_tabpage(tabpage_T *tp) { - int idx; - pmap_del(handle_T)(&tabpage_handles, tp->handle); diff_clear(tp); - for (idx = 0; idx < SNAP_COUNT; idx++) { + for (int idx = 0; idx < SNAP_COUNT; idx++) { clear_snapshot(tp, idx); } vars_clear(&tp->tp_vars->dv_hashtab); // free all t: variables @@ -4016,18 +4063,16 @@ void free_tabpage(tabpage_T *tp) /// tabpage in case of 0. /// @param filename Will be passed to apply_autocmds(). /// @return Was the new tabpage created successfully? FAIL or OK. -int win_new_tabpage(int after, char_u *filename) +int win_new_tabpage(int after, char *filename) { tabpage_T *old_curtab = curtab; - tabpage_T *newtp; - int n; if (cmdwin_type != 0) { emsg(_(e_cmdwin)); return FAIL; } - newtp = alloc_tabpage(); + tabpage_T *newtp = alloc_tabpage(); // Remember the current windows in this Tab page. if (leave_tabpage(curbuf, true) == FAIL) { @@ -4052,7 +4097,7 @@ int win_new_tabpage(int after, char_u *filename) if (after > 0) { // Put new tab page before tab page "after". - n = 2; + int n = 2; for (tp = first_tabpage; tp->tp_next != NULL && n < after; tp = tp->tp_next) { n++; @@ -4081,7 +4126,7 @@ int win_new_tabpage(int after, char_u *filename) apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf); apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf); - apply_autocmds(EVENT_TABNEW, (char *)filename, (char *)filename, false, curbuf); + apply_autocmds(EVENT_TABNEW, filename, filename, false, curbuf); apply_autocmds(EVENT_TABENTER, NULL, NULL, false, curbuf); return OK; @@ -4112,7 +4157,6 @@ int may_open_tabpage(void) int make_tabpages(int maxcount) { int count = maxcount; - int todo; // Limit to 'tabpagemax' tabs. if (count > p_tpm) { @@ -4123,6 +4167,7 @@ int make_tabpages(int maxcount) // when putting the buffers in the windows. block_autocmds(); + int todo; for (todo = count - 1; todo > 0; todo--) { if (win_new_tabpage(0, NULL) == FAIL) { break; @@ -4242,12 +4287,16 @@ static int leave_tabpage(buf_T *new_curbuf, bool trigger_leave_autocmds) return FAIL; } } + + reset_dragwin(); tp->tp_curwin = curwin; tp->tp_prevwin = prevwin; tp->tp_firstwin = firstwin; tp->tp_lastwin = lastwin; tp->tp_old_Rows_avail = ROWS_AVAIL; - tp->tp_old_Columns = Columns; + if (tp->tp_old_Columns != -1) { + tp->tp_old_Columns = Columns; + } firstwin = NULL; lastwin = NULL; return OK; @@ -4265,10 +4314,7 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, bool trigger_enter_a win_T *next_prevwin = tp->tp_prevwin; tabpage_T *old_curtab = curtab; - curtab = tp; - firstwin = tp->tp_firstwin; - lastwin = tp->tp_lastwin; - topframe = tp->tp_topframe; + use_tabpage(tp); if (old_curtab != curtab) { tabpage_check_windows(old_curtab); @@ -4304,13 +4350,22 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, bool trigger_enter_a clear_cmdline = true; } + // If there was a click in a window, it won't be usable for a following + // drag. + reset_dragwin(); + // The tabpage line may have appeared or disappeared, may need to resize the frames for that. // When the Vim window was resized or ROWS_AVAIL changed need to update frame sizes too. if (curtab->tp_old_Rows_avail != ROWS_AVAIL || (old_off != firstwin->w_winrow)) { win_new_screen_rows(); } - if (curtab->tp_old_Columns != Columns && starting == 0) { - win_new_screen_cols(); // update window widths + if (curtab->tp_old_Columns != Columns) { + if (starting == 0) { + win_new_screen_cols(); // update window widths + curtab->tp_old_Columns = Columns; + } else { + curtab->tp_old_Columns = -1; // update window widths later + } } lastused_tabpage = old_curtab; @@ -4360,10 +4415,6 @@ static void tabpage_check_windows(tabpage_T *old_curtab) // When "n" is 9999 go to the last tab page. void goto_tabpage(int n) { - tabpage_T *tp = NULL; // shut up compiler - tabpage_T *ttp; - int i; - if (text_locked()) { // Not allowed when editing the command line. text_locked_msg(); @@ -4378,6 +4429,8 @@ void goto_tabpage(int n) return; } + tabpage_T *tp = NULL; // shut up compiler + if (n == 0) { // No count, go to next tab page, wrap around end. if (curtab->tp_next == NULL) { @@ -4388,8 +4441,8 @@ void goto_tabpage(int n) } else if (n < 0) { // "gT": go to previous tab page, wrap around end. "N gT" repeats // this N times. - ttp = curtab; - for (i = n; i < 0; i++) { + tabpage_T *ttp = curtab; + for (int i = n; i < 0; i++) { for (tp = first_tabpage; tp->tp_next != ttp && tp->tp_next != NULL; tp = tp->tp_next) {} ttp = tp; @@ -4461,16 +4514,15 @@ void goto_tabpage_win(tabpage_T *tp, win_T *wp) // Move the current tab page to after tab page "nr". void tabpage_move(int nr) { - int n = 1; - tabpage_T *tp; - tabpage_T *tp_dst; - assert(curtab != NULL); if (first_tabpage->tp_next == NULL) { return; } + int n = 1; + tabpage_T *tp; + for (tp = first_tabpage; tp->tp_next != NULL && n < nr; tp = tp->tp_next) { n++; } @@ -4480,7 +4532,7 @@ void tabpage_move(int nr) return; } - tp_dst = tp; + tabpage_T *tp_dst = tp; // Remove the current tab page from the list of tab pages. if (curtab == first_tabpage) { @@ -4565,20 +4617,17 @@ tabpage_T *win_find_tabpage(win_T *win) /// @return found window win_T *win_vert_neighbor(tabpage_T *tp, win_T *wp, bool up, long count) { - frame_T *fr; - frame_T *nfr; - frame_T *foundfr; - - foundfr = wp->w_frame; + frame_T *foundfr = wp->w_frame; if (wp->w_floating) { return win_valid(prevwin) && !prevwin->w_floating ? prevwin : firstwin; } while (count--) { + frame_T *nfr; // First go upwards in the tree of frames until we find an upwards or // downwards neighbor. - fr = foundfr; + frame_T *fr = foundfr; for (;;) { if (fr == tp->tp_topframe) { goto end; @@ -4644,20 +4693,17 @@ static void win_goto_ver(bool up, long count) /// @return found window win_T *win_horz_neighbor(tabpage_T *tp, win_T *wp, bool left, long count) { - frame_T *fr; - frame_T *nfr; - frame_T *foundfr; - - foundfr = wp->w_frame; + frame_T *foundfr = wp->w_frame; if (wp->w_floating) { return win_valid(prevwin) && !prevwin->w_floating ? prevwin : firstwin; } while (count--) { + frame_T *nfr; // First go upwards in the tree of frames until we find a left or // right neighbor. - fr = foundfr; + frame_T *fr = foundfr; for (;;) { if (fr == tp->tp_topframe) { goto end; @@ -4850,7 +4896,7 @@ void fix_current_dir(void) // New directory is either the local directory of the window, tab or NULL. char *new_dir = curwin->w_localdir ? curwin->w_localdir : curtab->tp_localdir; char cwd[MAXPATHL]; - if (os_dirname((char_u *)cwd, MAXPATHL) != OK) { + if (os_dirname(cwd, MAXPATHL) != OK) { cwd[0] = NUL; } @@ -4900,12 +4946,11 @@ win_T *buf_jump_open_win(buf_T *buf) if (curwin->w_buffer == buf) { win_enter(curwin, false); return curwin; - } else { - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == buf) { - win_enter(wp, false); - return wp; - } + } + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf) { + win_enter(wp, false); + return wp; } } @@ -4913,7 +4958,7 @@ win_T *buf_jump_open_win(buf_T *buf) } /// Jump to the first open window in any tab page that contains buffer "buf", -/// if one exists. +/// if one exists. First search in the windows present in the current tab page. /// @return the found window, or NULL. win_T *buf_jump_open_tab(buf_T *buf) { @@ -5021,9 +5066,6 @@ void free_wininfo(wininfo_T *wip, buf_T *bp) /// @param tp tab page "win" is in, NULL for current static void win_free(win_T *wp, tabpage_T *tp) { - int i; - wininfo_T *wip; - pmap_del(handle_T)(&window_handles, wp->handle); clearFolding(wp); @@ -5055,7 +5097,7 @@ static void win_free(win_T *wp, tabpage_T *tp) xfree(wp->w_lines); - for (i = 0; i < wp->w_tagstacklen; i++) { + for (int i = 0; i < wp->w_tagstacklen; i++) { xfree(wp->w_tagstack[i].tagname); xfree(wp->w_tagstack[i].user_data); } @@ -5063,16 +5105,19 @@ static void win_free(win_T *wp, tabpage_T *tp) xfree(wp->w_localdir); xfree(wp->w_prevdir); - stl_clear_click_defs(wp->w_status_click_defs, (long)wp->w_status_click_defs_size); + stl_clear_click_defs(wp->w_status_click_defs, wp->w_status_click_defs_size); xfree(wp->w_status_click_defs); - stl_clear_click_defs(wp->w_winbar_click_defs, (long)wp->w_winbar_click_defs_size); + stl_clear_click_defs(wp->w_winbar_click_defs, wp->w_winbar_click_defs_size); xfree(wp->w_winbar_click_defs); + stl_clear_click_defs(wp->w_statuscol_click_defs, wp->w_statuscol_click_defs_size); + xfree(wp->w_statuscol_click_defs); + // Remove the window from the b_wininfo lists, it may happen that the // freed memory is re-used for another window. FOR_ALL_BUFFERS(buf) { - for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) { + for (wininfo_T *wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) { if (wip->wi_win == wp) { wininfo_T *wip2; @@ -5100,6 +5145,9 @@ static void win_free(win_T *wp, tabpage_T *tp) } } + // free the border title text + clear_virttext(&wp->w_float_config.title_chunks); + clear_matches(wp); free_jumplist(wp); @@ -5110,7 +5158,7 @@ static void win_free(win_T *wp, tabpage_T *tp) win_free_grid(wp, false); - if (wp != aucmd_win) { + if (win_valid_any_tab(wp)) { win_remove(wp, tp); } if (autocmd_busy) { @@ -5138,13 +5186,8 @@ void win_free_grid(win_T *wp, bool reinit) // Append window "wp" in the window list after window "after". void win_append(win_T *after, win_T *wp) { - win_T *before; - - if (after == NULL) { // after NULL is in front of the first - before = firstwin; - } else { - before = after->w_next; - } + // after NULL is in front of the first + win_T *before = after == NULL ? firstwin : after->w_next; wp->w_next = before; wp->w_prev = after; @@ -5226,7 +5269,7 @@ void win_new_screensize(void) if (old_Rows != Rows) { // If 'window' uses the whole screen, keep it using that. // Don't change it when set with "-w size" on the command line. - if (p_window == old_Rows - 1 || (old_Rows == 0 && p_window == 0)) { + if (p_window == old_Rows - 1 || (old_Rows == 0 && !option_was_set("window"))) { p_window = Rows - 1; } old_Rows = Rows; @@ -5286,37 +5329,287 @@ void win_new_screen_cols(void) win_reconfig_floats(); // The size of floats might change } -/// Trigger WinScrolled for "curwin" if needed. -void may_trigger_winscrolled(void) +/// Make a snapshot of all the window scroll positions and sizes of the current +/// tab page. +void snapshot_windows_scroll_size(void) { - static bool recursive = false; + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + wp->w_last_topline = wp->w_topline; + wp->w_last_topfill = wp->w_topfill; + wp->w_last_leftcol = wp->w_leftcol; + wp->w_last_skipcol = wp->w_skipcol; + wp->w_last_width = wp->w_width; + wp->w_last_height = wp->w_height; + } +} - if (recursive || !has_event(EVENT_WINSCROLLED)) { - return; +static bool did_initial_scroll_size_snapshot = false; + +void may_make_initial_scroll_size_snapshot(void) +{ + if (!did_initial_scroll_size_snapshot) { + did_initial_scroll_size_snapshot = true; + snapshot_windows_scroll_size(); } +} - win_T *wp = curwin; - if (wp->w_last_topline != wp->w_topline - || wp->w_last_leftcol != wp->w_leftcol - || wp->w_last_skipcol != wp->w_skipcol - || wp->w_last_width != wp->w_width - || wp->w_last_height != wp->w_height) { - char winid[NUMBUFLEN]; - vim_snprintf(winid, sizeof(winid), "%d", wp->handle); +/// Create a dictionary with information about size and scroll changes in a +/// window. +/// Returns the dictionary with refcount set to one. +/// Returns NULL on internal error. +static dict_T *make_win_info_dict(int width, int height, int topline, int topfill, int leftcol, + int skipcol) +{ + dict_T *const d = tv_dict_alloc(); + d->dv_refcount = 1; + + // not actually looping, for breaking out on error + while (1) { + typval_T tv = { + .v_lock = VAR_UNLOCKED, + .v_type = VAR_NUMBER, + }; + + tv.vval.v_number = width; + if (tv_dict_add_tv(d, S_LEN("width"), &tv) == FAIL) { + break; + } + tv.vval.v_number = height; + if (tv_dict_add_tv(d, S_LEN("height"), &tv) == FAIL) { + break; + } + tv.vval.v_number = topline; + if (tv_dict_add_tv(d, S_LEN("topline"), &tv) == FAIL) { + break; + } + tv.vval.v_number = topfill; + if (tv_dict_add_tv(d, S_LEN("topfill"), &tv) == FAIL) { + break; + } + tv.vval.v_number = leftcol; + if (tv_dict_add_tv(d, S_LEN("leftcol"), &tv) == FAIL) { + break; + } + tv.vval.v_number = skipcol; + if (tv_dict_add_tv(d, S_LEN("skipcol"), &tv) == FAIL) { + break; + } + return d; + } + tv_dict_unref(d); + return NULL; +} - recursive = true; - apply_autocmds(EVENT_WINSCROLLED, winid, winid, false, wp->w_buffer); - recursive = false; +/// Return values of check_window_scroll_resize(): +enum { + CWSR_SCROLLED = 1, ///< at least one window scrolled + CWSR_RESIZED = 2, ///< at least one window size changed +}; + +/// This function is used for three purposes: +/// 1. Goes over all windows in the current tab page and returns: +/// 0 no scrolling and no size changes found +/// CWSR_SCROLLED at least one window scrolled +/// CWSR_RESIZED at least one window changed size +/// CWSR_SCROLLED + CWSR_RESIZED both +/// "size_count" is set to the nr of windows with size changes. +/// "first_scroll_win" is set to the first window with any relevant changes. +/// "first_size_win" is set to the first window with size changes. +/// +/// 2. When the first three arguments are NULL and "winlist" is not NULL, +/// "winlist" is set to the list of window IDs with size changes. +/// +/// 3. When the first three arguments are NULL and "v_event" is not NULL, +/// information about changed windows is added to "v_event". +static int check_window_scroll_resize(int *size_count, win_T **first_scroll_win, + win_T **first_size_win, list_T *winlist, dict_T *v_event) +{ + int result = 0; + // int listidx = 0; + int tot_width = 0; + int tot_height = 0; + int tot_topline = 0; + int tot_topfill = 0; + int tot_leftcol = 0; + int tot_skipcol = 0; - // an autocmd may close the window, "wp" may be invalid now - if (win_valid_any_tab(wp)) { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + // Skip floating windows that do not have a snapshot (usually because they are newly-created), + // as unlike split windows, creating floating windows do not cause other windows to resize. + if (wp->w_floating && wp->w_last_topline == 0) { wp->w_last_topline = wp->w_topline; + wp->w_last_topfill = wp->w_topfill; wp->w_last_leftcol = wp->w_leftcol; wp->w_last_skipcol = wp->w_skipcol; wp->w_last_width = wp->w_width; wp->w_last_height = wp->w_height; + continue; + } + + const bool size_changed = wp->w_last_width != wp->w_width + || wp->w_last_height != wp->w_height; + if (size_changed) { + result |= CWSR_RESIZED; + if (winlist != NULL) { + // Add this window to the list of changed windows. + typval_T tv = { + .v_lock = VAR_UNLOCKED, + .v_type = VAR_NUMBER, + .vval.v_number = wp->handle, + }; + // tv_list_set_item(winlist, listidx++, &tv); + tv_list_append_owned_tv(winlist, tv); + } else if (size_count != NULL) { + assert(first_size_win != NULL && first_scroll_win != NULL); + (*size_count)++; + if (*first_size_win == NULL) { + *first_size_win = wp; + } + // For WinScrolled the first window with a size change is used + // even when it didn't scroll. + if (*first_scroll_win == NULL) { + *first_scroll_win = wp; + } + } + } + + const bool scroll_changed = wp->w_last_topline != wp->w_topline + || wp->w_last_topfill != wp->w_topfill + || wp->w_last_leftcol != wp->w_leftcol + || wp->w_last_skipcol != wp->w_skipcol; + if (scroll_changed) { + result |= CWSR_SCROLLED; + if (first_scroll_win != NULL && *first_scroll_win == NULL) { + *first_scroll_win = wp; + } + } + + if ((size_changed || scroll_changed) && v_event != NULL) { + // Add info about this window to the v:event dictionary. + int width = wp->w_width - wp->w_last_width; + int height = wp->w_height - wp->w_last_height; + int topline = wp->w_topline - wp->w_last_topline; + int topfill = wp->w_topfill - wp->w_last_topfill; + int leftcol = wp->w_leftcol - wp->w_last_leftcol; + int skipcol = wp->w_skipcol - wp->w_last_skipcol; + dict_T *d = make_win_info_dict(width, height, topline, + topfill, leftcol, skipcol); + if (d == NULL) { + break; + } + char winid[NUMBUFLEN]; + int key_len = vim_snprintf(winid, sizeof(winid), "%d", wp->handle); + if (tv_dict_add_dict(v_event, winid, (size_t)key_len, d) == FAIL) { + tv_dict_unref(d); + break; + } + d->dv_refcount--; + + tot_width += abs(width); + tot_height += abs(height); + tot_topline += abs(topline); + tot_topfill += abs(topfill); + tot_leftcol += abs(leftcol); + tot_skipcol += abs(skipcol); + } + } + + if (v_event != NULL) { + dict_T *alldict = make_win_info_dict(tot_width, tot_height, tot_topline, + tot_topfill, tot_leftcol, tot_skipcol); + if (alldict != NULL) { + if (tv_dict_add_dict(v_event, S_LEN("all"), alldict) == FAIL) { + tv_dict_unref(alldict); + } else { + alldict->dv_refcount--; + } } } + + return result; +} + +/// Trigger WinScrolled and/or WinResized if any window in the current tab page +/// scrolled or changed size. +void may_trigger_win_scrolled_resized(void) +{ + static bool recursive = false; + const bool do_resize = has_event(EVENT_WINRESIZED); + const bool do_scroll = has_event(EVENT_WINSCROLLED); + + if (recursive + || !(do_scroll || do_resize) + || !did_initial_scroll_size_snapshot) { + return; + } + + int size_count = 0; + win_T *first_scroll_win = NULL, *first_size_win = NULL; + int cwsr = check_window_scroll_resize(&size_count, + &first_scroll_win, &first_size_win, + NULL, NULL); + int trigger_resize = do_resize && size_count > 0; + int trigger_scroll = do_scroll && cwsr != 0; + if (!trigger_resize && !trigger_scroll) { + return; // no relevant changes + } + + list_T *windows_list = NULL; + if (trigger_resize) { + // Create the list for v:event.windows before making the snapshot. + // windows_list = tv_list_alloc_with_items(size_count); + windows_list = tv_list_alloc(size_count); + (void)check_window_scroll_resize(NULL, NULL, NULL, windows_list, NULL); + } + + dict_T *scroll_dict = NULL; + if (trigger_scroll) { + // Create the dict with entries for v:event before making the snapshot. + scroll_dict = tv_dict_alloc(); + scroll_dict->dv_refcount = 1; + (void)check_window_scroll_resize(NULL, NULL, NULL, NULL, scroll_dict); + } + + // WinScrolled/WinResized are triggered only once, even when multiple + // windows scrolled or changed size. Store the current values before + // triggering the event, if a scroll or resize happens as a side effect + // then WinScrolled/WinResized is triggered for that later. + snapshot_windows_scroll_size(); + + recursive = true; + + // If both are to be triggered do WinResized first. + if (trigger_resize) { + save_v_event_T save_v_event; + dict_T *v_event = get_v_event(&save_v_event); + + if (tv_dict_add_list(v_event, S_LEN("windows"), windows_list) == OK) { + tv_dict_set_keys_readonly(v_event); + + char winid[NUMBUFLEN]; + vim_snprintf(winid, sizeof(winid), "%d", first_size_win->handle); + apply_autocmds(EVENT_WINRESIZED, winid, winid, false, first_size_win->w_buffer); + } + restore_v_event(v_event, &save_v_event); + } + + if (trigger_scroll) { + save_v_event_T save_v_event; + dict_T *v_event = get_v_event(&save_v_event); + + // Move the entries from scroll_dict to v_event. + tv_dict_extend(v_event, scroll_dict, "move"); + tv_dict_set_keys_readonly(v_event); + tv_dict_unref(scroll_dict); + + char winid[NUMBUFLEN]; + vim_snprintf(winid, sizeof(winid), "%d", first_scroll_win->handle); + apply_autocmds(EVENT_WINSCROLLED, winid, winid, false, first_scroll_win->w_buffer); + + restore_v_event(v_event, &save_v_event); + } + + recursive = false; } // Save the size of all windows in "gap". @@ -5392,12 +5685,7 @@ void win_reconfig_floats(void) // to the bottom-right position plus one. static void frame_comp_pos(frame_T *topfrp, int *row, int *col) { - win_T *wp; - frame_T *frp; - int startcol; - int startrow; - - wp = topfrp->fr_win; + win_T *wp = topfrp->fr_win; if (wp != NULL) { if (wp->w_winrow != *row || wp->w_wincol != *col) { @@ -5412,8 +5700,9 @@ static void frame_comp_pos(frame_T *topfrp, int *row, int *col) *row += h > topfrp->fr_height ? topfrp->fr_height : h; *col += wp->w_width + wp->w_vsep_width; } else { - startrow = *row; - startcol = *col; + int startrow = *row; + int startcol = *col; + frame_T *frp; FOR_ALL_FRAMES(frp, topfrp->fr_child) { if (topfrp->fr_layout == FR_ROW) { *row = startrow; // all frames are at the same row @@ -5486,14 +5775,6 @@ void win_setheight_win(int height, win_T *win) // At the top level we can also use change the command line height. static void frame_setheight(frame_T *curfrp, int height) { - int room; // total number of lines available - int take; // number of lines taken from other windows - int room_cmdline; // lines available from cmdline - int run; - frame_T *frp; - int h; - int room_reserved; - // If the height already is the desired value, nothing to do. if (curfrp->fr_height == height) { return; @@ -5501,11 +5782,15 @@ static void frame_setheight(frame_T *curfrp, int height) if (curfrp->fr_parent == NULL) { // topframe: can only change the command line height + // Avoid doing so with external messages. + if (ui_has(kUIMessages)) { + return; + } if (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 = (int)MIN((p_ch > 0 ? ROWS_AVAIL + (p_ch - 1) : ROWS_AVAIL), height); + // `cmdheight` doesn't go below 1 if it wasn't set to 0 explicitly. + height = (int)MIN(ROWS_AVAIL + p_ch - !p_ch_was_zero, height); } if (height > 0) { frame_new_height(curfrp, height, false, false); @@ -5513,7 +5798,7 @@ static void frame_setheight(frame_T *curfrp, int height) } else if (curfrp->fr_parent->fr_layout == FR_ROW) { // Row of frames: Also need to resize frames left and right of this // one. First check for the minimal height of these. - h = frame_minheight(curfrp->fr_parent, NULL); + int h = frame_minheight(curfrp->fr_parent, NULL); if (height < h) { height = h; } @@ -5521,14 +5806,19 @@ static void frame_setheight(frame_T *curfrp, int height) } else { // Column of frames: try to change only frames in this column. + int room; // total number of lines available + int room_cmdline; // lines available from cmdline + int room_reserved; + // Do this twice: // 1: compute room available, if it's not enough try resizing the // containing frame. // 2: compute the room available and adjust the height to it. // Try not to reduce the height of a window with 'winfixheight' set. - for (run = 1; run <= 2; run++) { + for (int run = 1; run <= 2; run++) { room = 0; room_reserved = 0; + frame_T *frp; FOR_ALL_FRAMES(frp, curfrp->fr_parent->fr_child) { if (frp != curfrp && frp->fr_win != NULL @@ -5565,7 +5855,7 @@ static void frame_setheight(frame_T *curfrp, int height) // Compute the number of lines we will take from others frames (can be // negative!). - take = height - curfrp->fr_height; + int take = height - curfrp->fr_height; // If there is not enough room, also reduce the height of a window // with 'winfixheight' set. @@ -5593,14 +5883,13 @@ static void frame_setheight(frame_T *curfrp, int height) // First take lines from the frames after the current frame. If // that is not enough, takes lines from frames above the current // frame. - for (run = 0; run < 2; run++) { - if (run == 0) { - frp = curfrp->fr_next; // 1st run: start with next window - } else { - frp = curfrp->fr_prev; // 2nd run: start with prev window - } + for (int run = 0; run < 2; run++) { + // 1st run: start with next window + // 2nd run: start with prev window + frame_T *frp = run == 0 ? curfrp->fr_next : curfrp->fr_prev; + while (frp != NULL && take != 0) { - h = frame_minheight(frp, NULL); + int h = frame_minheight(frp, NULL); if (room_reserved > 0 && frp->fr_win != NULL && frp->fr_win->w_p_wfh) { @@ -5674,13 +5963,6 @@ void win_setwidth_win(int width, win_T *wp) // Strategy is similar to frame_setheight(). static void frame_setwidth(frame_T *curfrp, int width) { - int room; // total number of lines available - int take; // number of lines taken from other windows - int run; - frame_T *frp; - int w; - int room_reserved; - // If the width already is the desired value, nothing to do. if (curfrp->fr_width == width) { return; @@ -5694,7 +5976,7 @@ static void frame_setwidth(frame_T *curfrp, int width) if (curfrp->fr_parent->fr_layout == FR_COL) { // Column of frames: Also need to resize frames above and below of // this one. First check for the minimal width of these. - w = frame_minwidth(curfrp->fr_parent, NULL); + int w = frame_minwidth(curfrp->fr_parent, NULL); if (width < w) { width = w; } @@ -5706,9 +5988,13 @@ static void frame_setwidth(frame_T *curfrp, int width) // 1: compute room available, if it's not enough try resizing the // containing frame. // 2: compute the room available and adjust the width to it. - for (run = 1; run <= 2; run++) { + + int room; // total number of lines available + int room_reserved; + for (int run = 1; run <= 2; run++) { room = 0; room_reserved = 0; + frame_T *frp; FOR_ALL_FRAMES(frp, curfrp->fr_parent->fr_child) { if (frp != curfrp && frp->fr_win != NULL @@ -5734,7 +6020,7 @@ static void frame_setwidth(frame_T *curfrp, int width) // Compute the number of lines we will take from others frames (can be // negative!). - take = width - curfrp->fr_width; + int take = width - curfrp->fr_width; // If there is not enough room, also reduce the width of a window // with 'winfixwidth' set. @@ -5753,14 +6039,13 @@ static void frame_setwidth(frame_T *curfrp, int width) // First take lines from the frames right of the current frame. If // that is not enough, takes lines from frames left of the current // frame. - for (run = 0; run < 2; run++) { - if (run == 0) { - frp = curfrp->fr_next; // 1st run: start with next window - } else { - frp = curfrp->fr_prev; // 2nd run: start with prev window - } + for (int run = 0; run < 2; run++) { + // 1st run: start with next window + // 2nd run: start with prev window + frame_T *frp = run == 0 ? curfrp->fr_next : curfrp->fr_prev; + while (frp != NULL && take != 0) { - w = frame_minwidth(frp, NULL); + int w = frame_minwidth(frp, NULL); if (room_reserved > 0 && frp->fr_win != NULL && frp->fr_win->w_p_wfw) { @@ -5836,22 +6121,14 @@ void win_setminwidth(void) /// Status line of dragwin is dragged "offset" lines down (negative is up). void win_drag_status_line(win_T *dragwin, int offset) { - frame_T *curfr; - frame_T *fr; - int room; - int row; - bool up; // if true, drag status line up, otherwise down - int n; - static bool p_ch_was_zero = false; + frame_T *fr = dragwin->w_frame; - // If the user explicitly set 'cmdheight' to zero, then allow for dragging - // the status line making it zero again. - if (p_ch == 0) { - p_ch_was_zero = true; + // Avoid changing command line height with external messages. + if (fr->fr_next == NULL && ui_has(kUIMessages)) { + return; } - fr = dragwin->w_frame; - curfr = fr; + frame_T *curfr = fr; if (fr != topframe) { // more than one window fr = fr->fr_parent; // When the parent frame is not a column of frames, its parent should @@ -5876,8 +6153,10 @@ void win_drag_status_line(win_T *dragwin, int offset) } } - if (offset < 0) { // drag up - up = true; + int room; + const bool up = offset < 0; // if true, drag status line up, otherwise down + + if (up) { // drag up offset = -offset; // sum up the room of the current frame and above it if (fr == curfr) { @@ -5894,7 +6173,6 @@ void win_drag_status_line(win_T *dragwin, int offset) } fr = curfr->fr_next; // put fr at frame that grows } else { // drag down - up = false; // Only dragging the last status line can reduce p_ch. room = Rows - cmdline_row; if (curfr->fr_next != NULL) { @@ -5932,7 +6210,7 @@ void win_drag_status_line(win_T *dragwin, int offset) } // Now make the other frames smaller. while (fr != NULL && offset > 0) { - n = frame_minheight(fr, NULL); + int n = frame_minheight(fr, NULL); if (fr->fr_height - offset <= n) { offset -= fr->fr_height - n; frame_new_height(fr, n, !up, false); @@ -5946,7 +6224,7 @@ void win_drag_status_line(win_T *dragwin, int offset) fr = fr->fr_next; } } - row = win_comp_pos(); + int row = win_comp_pos(); grid_fill(&default_grid, row, cmdline_row, 0, Columns, ' ', ' ', 0); if (msg_grid.chars) { clear_cmdline = true; @@ -5966,17 +6244,11 @@ void win_drag_status_line(win_T *dragwin, int offset) // Separator line of dragwin is dragged "offset" lines right (negative is left). void win_drag_vsep_line(win_T *dragwin, int offset) { - frame_T *curfr; - frame_T *fr; - int room; - bool left; // if true, drag separator line left, otherwise right - int n; - - fr = dragwin->w_frame; + frame_T *fr = dragwin->w_frame; if (fr == topframe) { // only one window (cannot happen?) return; } - curfr = fr; + frame_T *curfr = fr; fr = fr->fr_parent; // When the parent frame is not a row of frames, its parent should be. if (fr->fr_layout != FR_ROW) { @@ -6001,8 +6273,10 @@ void win_drag_vsep_line(win_T *dragwin, int offset) } } - if (offset < 0) { // drag left - left = true; + int room; + const bool left = offset < 0; // if true, drag separator line left, otherwise right + + if (left) { // drag left offset = -offset; // sum up the room of the current frame and left of it room = 0; @@ -6014,7 +6288,6 @@ void win_drag_vsep_line(win_T *dragwin, int offset) } fr = curfr->fr_next; // put fr at frame that grows } else { // drag right - left = false; // sum up the room of frames right of the current one room = 0; FOR_ALL_FRAMES(fr, curfr->fr_next) { @@ -6049,7 +6322,7 @@ void win_drag_vsep_line(win_T *dragwin, int offset) fr = curfr->fr_next; // next frame gets smaller } while (fr != NULL && offset > 0) { - n = frame_minwidth(fr, NULL); + int n = frame_minwidth(fr, NULL); if (fr->fr_width - offset <= n) { offset -= fr->fr_width - n; frame_new_width(fr, n, !left, false); @@ -6118,7 +6391,6 @@ void win_fix_scroll(int resize) invalidate_botline_win(wp); validate_botline(wp); } - win_comp_scroll(wp); wp->w_prev_height = wp->w_height; wp->w_prev_winrow = wp->w_winrow; } @@ -6192,15 +6464,10 @@ void win_new_height(win_T *wp, int height) wp->w_height = height; wp->w_pos_changed = true; win_set_inner_size(wp, true); - if (wp->w_status_height) { - wp->w_redr_status = true; - } } void scroll_to_fraction(win_T *wp, int prev_height) { - linenr_T lnum; - int sline, line_size; int height = wp->w_height_inner; // Don't change w_topline in any of these cases: @@ -6214,13 +6481,13 @@ void scroll_to_fraction(win_T *wp, int prev_height) || wp->w_topline > 1)) { // Find a value for w_topline that shows the cursor at the same // relative position in the window as before (more or less). - lnum = wp->w_cursor.lnum; + linenr_T lnum = wp->w_cursor.lnum; if (lnum < 1) { // can happen when starting up lnum = 1; } wp->w_wrow = (int)((long)wp->w_fraction * (long)height - 1L) / FRACTION_MULT; - line_size = plines_win_col(wp, lnum, (long)(wp->w_cursor.col)) - 1; - sline = wp->w_wrow - line_size; + int line_size = plines_win_col(wp, lnum, (long)(wp->w_cursor.col)) - 1; + int sline = wp->w_wrow - line_size; if (sline >= 0) { // Make sure the whole cursor line is visible, if possible. @@ -6291,9 +6558,7 @@ void scroll_to_fraction(win_T *wp, int prev_height) wp->w_prev_fraction_row = wp->w_wrow; } - win_comp_scroll(wp); redraw_later(wp, UPD_SOME_VALID); - wp->w_redr_status = true; invalidate_botline_win(wp); } @@ -6326,6 +6591,7 @@ void win_set_inner_size(win_T *wp, bool valid_cursor) } wp->w_skipcol = 0; wp->w_height_inner = height; + win_comp_scroll(wp); // There is no point in adjusting the scroll position when exiting. Some // values might be invalid. @@ -6359,6 +6625,7 @@ void win_set_inner_size(win_T *wp, bool valid_cursor) wp->w_width_outer = (wp->w_width_inner + win_border_width(wp)); wp->w_winrow_off = wp->w_border_adj[0] + wp->w_winbar_height; wp->w_wincol_off = wp->w_border_adj[3]; + wp->w_redr_status = true; } static int win_border_height(win_T *wp) @@ -6375,10 +6642,8 @@ static int win_border_width(win_T *wp) void win_new_width(win_T *wp, int width) { wp->w_width = width; - win_set_inner_size(wp, true); - - wp->w_redr_status = true; wp->w_pos_changed = true; + win_set_inner_size(wp, true); } void win_comp_scroll(win_T *wp) @@ -6399,8 +6664,6 @@ void win_comp_scroll(win_T *wp) /// command_height: called whenever p_ch has been changed. void command_height(void) { - int h; - frame_T *frp; int old_p_ch = (int)curtab->tp_ch_used; // Use the value of p_ch that we remembered. This is needed for when the @@ -6419,7 +6682,7 @@ void command_height(void) } // Find bottom frame with width of screen. - frp = lastwin_nofloating()->w_frame; + frame_T *frp = lastwin_nofloating()->w_frame; while (frp->fr_width != Columns && frp->fr_parent != NULL) { frp = frp->fr_parent; } @@ -6442,7 +6705,7 @@ void command_height(void) cmdline_row = Rows - (int)p_ch; break; } - h = frp->fr_height - frame_minheight(frp, NULL); + int h = frp->fr_height - frame_minheight(frp, NULL); if (h > p_ch - old_p_ch) { h = (int)p_ch - old_p_ch; } @@ -6493,7 +6756,7 @@ static void frame_add_height(frame_T *frp, int n) // Get the file name at the cursor. // If Visual mode is active, use the selected text if it's in one line. // Returns the name in allocated memory, NULL for failure. -char_u *grab_file_name(long count, linenr_T *file_lnum) +char *grab_file_name(long count, linenr_T *file_lnum) { int options = FNAME_MESS | FNAME_EXP | FNAME_REL | FNAME_UNESC; if (VIsual_active) { @@ -6503,12 +6766,12 @@ char_u *grab_file_name(long count, linenr_T *file_lnum) return NULL; } // Only recognize ":123" here - if (file_lnum != NULL && ptr[len] == ':' && isdigit(ptr[len + 1])) { + if (file_lnum != NULL && ptr[len] == ':' && isdigit((uint8_t)ptr[len + 1])) { char *p = ptr + len + 1; *file_lnum = (linenr_T)getdigits_long(&p, false, 0); } - return (char_u *)find_file_name_in_path(ptr, len, options, count, curbuf->b_ffname); + return find_file_name_in_path(ptr, len, options, count, curbuf->b_ffname); } return file_name_at_cursor(options | FNAME_HYP, count, file_lnum); } @@ -6524,10 +6787,10 @@ char_u *grab_file_name(long count, linenr_T *file_lnum) // FNAME_EXP expand to path // FNAME_HYP check for hypertext link // FNAME_INCL apply "includeexpr" -char_u *file_name_at_cursor(int options, long count, linenr_T *file_lnum) +char *file_name_at_cursor(int options, long count, linenr_T *file_lnum) { - return file_name_in_line((char_u *)get_cursor_line_ptr(), - curwin->w_cursor.col, options, count, (char_u *)curbuf->b_ffname, + return file_name_in_line(get_cursor_line_ptr(), + curwin->w_cursor.col, options, count, curbuf->b_ffname, file_lnum); } @@ -6535,16 +6798,11 @@ char_u *file_name_at_cursor(int options, long count, linenr_T *file_lnum) /// @param file_lnum line number after the file name /// /// @return the name of the file under or after ptr[col]. Otherwise like file_name_at_cursor(). -char_u *file_name_in_line(char_u *line, int col, int options, long count, char_u *rel_fname, - linenr_T *file_lnum) +char *file_name_in_line(char *line, int col, int options, long count, char *rel_fname, + linenr_T *file_lnum) { - char *ptr; - size_t len; - bool in_type = true; - bool is_url = false; - // search forward for what could be the start of a file name - ptr = (char *)line + col; + char *ptr = line + col; while (*ptr != NUL && !vim_isfilec((uint8_t)(*ptr))) { MB_PTR_ADV(ptr); } @@ -6555,10 +6813,14 @@ char_u *file_name_in_line(char_u *line, int col, int options, long count, char_u return NULL; } + size_t len; + bool in_type = true; + bool is_url = false; + // Search backward for first char of the file name. // Go one char back to ":" before "//" even when ':' is not in 'isfname'. - while ((char_u *)ptr > line) { - if ((len = (size_t)(utf_head_off((char *)line, ptr - 1))) > 0) { + while (ptr > line) { + if ((len = (size_t)(utf_head_off(line, ptr - 1))) > 0) { ptr -= len + 1; } else if (vim_isfilec((uint8_t)ptr[-1]) || ((options & FNAME_HYP) && path_is_url(ptr - 1))) { @@ -6573,7 +6835,7 @@ char_u *file_name_in_line(char_u *line, int col, int options, long count, char_u len = 0; while (vim_isfilec((uint8_t)ptr[len]) || (ptr[len] == '\\' && ptr[len + 1] == ' ') || ((options & FNAME_HYP) && path_is_url(ptr + len)) - || (is_url && vim_strchr(":?&=", ptr[len]) != NULL)) { + || (is_url && vim_strchr(":?&=", (uint8_t)ptr[len]) != NULL)) { // After type:// we also include :, ?, & and = as valid characters, so that // http://google.com:8080?q=this&that=ok works. if ((ptr[len] >= 'A' && ptr[len] <= 'Z') @@ -6594,39 +6856,38 @@ char_u *file_name_in_line(char_u *line, int col, int options, long count, char_u // If there is trailing punctuation, remove it. // But don't remove "..", could be a directory name. - if (len > 2 && vim_strchr(".,:;!", ptr[len - 1]) != NULL + if (len > 2 && vim_strchr(".,:;!", (uint8_t)ptr[len - 1]) != NULL && ptr[len - 2] != '.') { len--; } if (file_lnum != NULL) { - char *p; const char *line_english = " line "; const char *line_transl = _(line_msg); // Get the number after the file name and a separator character. // Also accept " line 999" with and without the same translation as // used in last_set_msg(). - p = ptr + len; - if (STRNCMP(p, line_english, strlen(line_english)) == 0) { + char *p = ptr + len; + if (strncmp(p, line_english, strlen(line_english)) == 0) { p += strlen(line_english); - } else if (STRNCMP(p, line_transl, strlen(line_transl)) == 0) { + } else if (strncmp(p, line_transl, strlen(line_transl)) == 0) { p += strlen(line_transl); } else { p = skipwhite(p); } if (*p != NUL) { - if (!isdigit(*p)) { + if (!isdigit((uint8_t)(*p))) { p++; // skip the separator } p = skipwhite(p); - if (isdigit(*p)) { + if (isdigit((uint8_t)(*p))) { *file_lnum = (linenr_T)getdigits_long(&p, false, 0); } } } - return (char_u *)find_file_name_in_path(ptr, len, options, count, (char *)rel_fname); + return find_file_name_in_path(ptr, len, options, count, rel_fname); } /// Add or remove a status line from window(s), according to the @@ -6651,7 +6912,7 @@ static void win_remove_status_line(win_T *wp, bool add_hsep) } comp_col(); - stl_clear_click_defs(wp->w_status_click_defs, (long)wp->w_status_click_defs_size); + stl_clear_click_defs(wp->w_status_click_defs, wp->w_status_click_defs_size); xfree(wp->w_status_click_defs); wp->w_status_click_defs_size = 0; wp->w_status_click_defs = NULL; @@ -6710,23 +6971,19 @@ static bool resize_frame_for_winbar(frame_T *fr) if (fp == NULL || fp == fr) { emsg(_(e_noroom)); return false; - } else { - frame_new_height(fp, fp->fr_height - 1, false, false); - win_new_height(wp, wp->w_height + 1); - frame_fix_height(wp); - (void)win_comp_pos(); } + frame_new_height(fp, fp->fr_height - 1, false, false); + win_new_height(wp, wp->w_height + 1); + frame_fix_height(wp); + (void)win_comp_pos(); return true; } 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; + win_T *wp = fr->fr_win; bool is_last = is_bottom_win(wp); if (is_last) { @@ -6756,6 +7013,7 @@ static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global) } } else { // For a column or row frame, recursively call this function for all child frames + frame_T *fp; FOR_ALL_FRAMES(fp, fr->fr_child) { last_status_rec(fp, statusline, is_stl_global); } @@ -6786,11 +7044,10 @@ int set_winbar_win(win_T *wp, bool make_room, bool valid_cursor) } wp->w_winbar_height = winbar_height; win_set_inner_size(wp, valid_cursor); - wp->w_redr_status = wp->w_redr_status || winbar_height; if (winbar_height == 0) { // When removing winbar, deallocate the w_winbar_click_defs array - stl_clear_click_defs(wp->w_winbar_click_defs, (long)wp->w_winbar_click_defs_size); + stl_clear_click_defs(wp->w_winbar_click_defs, wp->w_winbar_click_defs_size); xfree(wp->w_winbar_click_defs); wp->w_winbar_click_defs_size = 0; wp->w_winbar_click_defs = NULL; @@ -6876,7 +7133,7 @@ bool only_one_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_buffer != NULL && (!((bt_help(wp->w_buffer) && !bt_help(curbuf)) || wp->w_floating - || wp->w_p_pvw) || wp == curwin) && wp != aucmd_win) { + || wp->w_p_pvw) || wp == curwin) && !is_aucmd_win(wp)) { count++; } } @@ -7036,13 +7293,11 @@ static win_T *get_snapshot_curwin(int idx) /// @param close_curwin closing current window void restore_snapshot(int idx, int close_curwin) { - win_T *wp; - if (curtab->tp_snapshot[idx] != NULL && curtab->tp_snapshot[idx]->fr_width == topframe->fr_width && curtab->tp_snapshot[idx]->fr_height == topframe->fr_height && check_snapshot_rec(curtab->tp_snapshot[idx], topframe) == OK) { - wp = restore_snapshot_rec(curtab->tp_snapshot[idx], topframe); + win_T *wp = restore_snapshot_rec(curtab->tp_snapshot[idx], topframe); (void)win_comp_pos(); if (wp != NULL && close_curwin) { win_goto(wp); @@ -7075,7 +7330,6 @@ static int check_snapshot_rec(frame_T *sn, frame_T *fr) static win_T *restore_snapshot_rec(frame_T *sn, frame_T *fr) { win_T *wp = NULL; - win_T *wp2; fr->fr_height = sn->fr_height; fr->fr_width = sn->fr_width; @@ -7085,13 +7339,13 @@ static win_T *restore_snapshot_rec(frame_T *sn, frame_T *fr) wp = sn->fr_win; } if (sn->fr_next != NULL) { - wp2 = restore_snapshot_rec(sn->fr_next, fr->fr_next); + win_T *wp2 = restore_snapshot_rec(sn->fr_next, fr->fr_next); if (wp2 != NULL) { wp = wp2; } } if (sn->fr_child != NULL) { - wp2 = restore_snapshot_rec(sn->fr_child, fr->fr_child); + win_T *wp2 = restore_snapshot_rec(sn->fr_child, fr->fr_child); if (wp2 != NULL) { wp = wp2; } @@ -7099,114 +7353,6 @@ static win_T *restore_snapshot_rec(frame_T *sn, frame_T *fr) return wp; } -/// Set "win" to be the curwin and "tp" to be the current tab page. -/// restore_win() MUST be called to undo, also when FAIL is returned. -/// No autocommands will be executed until restore_win() is called. -/// -/// @param no_display if true the display won't be affected, no redraw is -/// triggered, another tabpage access is limited. -/// -/// @return FAIL if switching to "win" failed. -int switch_win(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display) -{ - block_autocmds(); - return switch_win_noblock(switchwin, win, tp, no_display); -} - -// As switch_win() but without blocking autocommands. -int switch_win_noblock(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display) -{ - CLEAR_POINTER(switchwin); - 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) { - switchwin->sw_curtab = curtab; - if (no_display) { - curtab->tp_firstwin = firstwin; - curtab->tp_lastwin = lastwin; - curtab = tp; - firstwin = curtab->tp_firstwin; - lastwin = curtab->tp_lastwin; - } else { - goto_tabpage_tp(tp, false, false); - } - } - if (!win_valid(win)) { - return FAIL; - } - curwin = win; - curbuf = curwin->w_buffer; - return OK; -} - -// 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(switchwin_T *switchwin, bool no_display) -{ - restore_win_noblock(switchwin, no_display); - unblock_autocmds(); -} - -// As restore_win() but without unblocking autocommands. -void restore_win_noblock(switchwin_T *switchwin, bool no_display) -{ - if (switchwin->sw_curtab != NULL && valid_tabpage(switchwin->sw_curtab)) { - if (no_display) { - curtab->tp_firstwin = firstwin; - curtab->tp_lastwin = lastwin; - curtab = switchwin->sw_curtab; - firstwin = curtab->tp_firstwin; - lastwin = curtab->tp_lastwin; - } else { - goto_tabpage_tp(switchwin->sw_curtab, false, false); - } - } - - 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; - } -} - -/// Make "buf" the current buffer. -/// -/// restore_buffer() MUST be called to undo. -/// No autocommands will be executed. Use aucmd_prepbuf() if there are any. -void switch_buffer(bufref_T *save_curbuf, buf_T *buf) -{ - block_autocmds(); - set_bufref(save_curbuf, curbuf); - curbuf->b_nwindows--; - curbuf = buf; - curwin->w_buffer = buf; - curbuf->b_nwindows++; -} - -/// Restore the current buffer after using switch_buffer(). -void restore_buffer(bufref_T *save_curbuf) -{ - unblock_autocmds(); - // Check for valid buffer, just in case. - if (bufref_valid(save_curbuf)) { - curbuf->b_nwindows--; - curwin->w_buffer = save_curbuf->br_buf; - curbuf = save_curbuf->br_buf; - curbuf->b_nwindows++; - } -} - /// Check that "topfrp" and its children are at the right height. /// /// @param topfrp top frame pointer @@ -7260,17 +7406,14 @@ static int int_cmp(const void *a, const void *b) /// @return error message, NULL if it's OK. char *check_colorcolumn(win_T *wp) { - char *s; - int col; - unsigned int count = 0; - int color_cols[256]; - int j = 0; - if (wp->w_buffer == NULL) { return NULL; // buffer was closed } - for (s = wp->w_p_cc; *s != NUL && count < 255;) { + unsigned int count = 0; + int color_cols[256]; + for (char *s = wp->w_p_cc; *s != NUL && count < 255;) { + int col; if (*s == '-' || *s == '+') { // -N and +N: add to 'textwidth' col = (*s == '-') ? -1 : 1; @@ -7319,6 +7462,7 @@ skip: // win_line() qsort(color_cols, count, sizeof(int), int_cmp); + int j = 0; for (unsigned int i = 0; i < count; i++) { // skip duplicates if (j == 0 || wp->w_p_cc_cols[j - 1] != color_cols[i]) { @@ -7331,43 +7475,6 @@ skip: return NULL; // no error } -int win_getid(typval_T *argvars) -{ - if (argvars[0].v_type == VAR_UNKNOWN) { - return curwin->handle; - } - int winnr = (int)tv_get_number(&argvars[0]); - win_T *wp; - if (winnr > 0) { - if (argvars[1].v_type == VAR_UNKNOWN) { - wp = firstwin; - } else { - tabpage_T *tp = NULL; - int tabnr = (int)tv_get_number(&argvars[1]); - FOR_ALL_TABS(tp2) { - if (--tabnr == 0) { - tp = tp2; - break; - } - } - if (tp == NULL) { - return -1; - } - if (tp == curtab) { - wp = firstwin; - } else { - wp = tp->tp_firstwin; - } - } - for (; wp != NULL; wp = wp->w_next) { - if (--winnr == 0) { - return wp->handle; - } - } - } - return 0; -} - void win_get_tabwin(handle_T id, int *tabnr, int *winnr) { *tabnr = 0; @@ -7388,99 +7495,6 @@ void win_get_tabwin(handle_T id, int *tabnr, int *winnr) } } -void win_id2tabwin(typval_T *const argvars, typval_T *const rettv) -{ - int winnr = 1; - int tabnr = 1; - handle_T id = (handle_T)tv_get_number(&argvars[0]); - - win_get_tabwin(id, &tabnr, &winnr); - - list_T *const list = tv_list_alloc_ret(rettv, 2); - tv_list_append_number(list, tabnr); - tv_list_append_number(list, winnr); -} - -win_T *win_id2wp(int id) -{ - return win_id2wp_tp(id, NULL); -} - -// Return the window and tab pointer of window "id". -win_T *win_id2wp_tp(int id, tabpage_T **tpp) -{ - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->handle == id) { - if (tpp != NULL) { - *tpp = tp; - } - return wp; - } - } - - return NULL; -} - -int win_id2win(typval_T *argvars) -{ - int nr = 1; - int id = (int)tv_get_number(&argvars[0]); - - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->handle == id) { - return nr; - } - nr++; - } - return 0; -} - -void win_findbuf(typval_T *argvars, list_T *list) -{ - int bufnr = (int)tv_get_number(&argvars[0]); - - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer->b_fnum == bufnr) { - tv_list_append_number(list, wp->handle); - } - } -} - -// Get the layout of the given tab page for winlayout(). -void get_framelayout(const frame_T *fr, list_T *l, bool outer) -{ - list_T *fr_list; - - if (fr == NULL) { - return; - } - - if (outer) { - // outermost call from f_winlayout() - fr_list = l; - } else { - fr_list = tv_list_alloc(2); - tv_list_append_list(l, fr_list); - } - - if (fr->fr_layout == FR_LEAF) { - if (fr->fr_win != NULL) { - tv_list_append_string(fr_list, "leaf", -1); - tv_list_append_number(fr_list, fr->fr_win->handle); - } - } else { - tv_list_append_string(fr_list, fr->fr_layout == FR_ROW ? "row" : "col", -1); - - list_T *const win_list = tv_list_alloc(kListLenUnknown); - tv_list_append_list(fr_list, win_list); - const frame_T *child = fr->fr_child; - while (child != NULL) { - get_framelayout(child, win_list, false); - child = child->fr_next; - } - } -} - void win_ui_flush(bool validate) { FOR_ALL_TAB_WINDOWS(tp, wp) { diff --git a/src/nvim/window.h b/src/nvim/window.h index a564a0cfad..4ab2bea60a 100644 --- a/src/nvim/window.h +++ b/src/nvim/window.h @@ -2,10 +2,15 @@ #define NVIM_WINDOW_H #include <stdbool.h> +#include <stddef.h> +#include "nvim/buffer.h" #include "nvim/buffer_defs.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/os/os.h" +#include "nvim/os/os_defs.h" +#include "nvim/vim.h" // Values for file_name_in_line() #define FNAME_MESS 1 // give error message @@ -31,62 +36,8 @@ #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 cwd_[MAXPATHL]; \ - char 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((char_u *)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((char_u *)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(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) - +// Set to true if 'cmdheight' was explicitly set to 0. +EXTERN bool p_ch_was_zero INIT(= false); #ifdef INCLUDE_GENERATED_DECLARATIONS # include "window.h.generated.h" #endif diff --git a/src/uncrustify.cfg b/src/uncrustify.cfg index 2b9396e635..f9e6617d40 100644 --- a/src/uncrustify.cfg +++ b/src/uncrustify.cfg @@ -125,7 +125,7 @@ sp_before_assign = ignore # ignore/add/remove/force/not_defined # Add or remove space after assignment operator '=', '+=', etc. # # Overrides sp_assign. -sp_after_assign = ignore # ignore/add/remove/force/not_defined +sp_after_assign = force # ignore/add/remove/force/not_defined # Add or remove space in 'enum {'. # @@ -1642,7 +1642,7 @@ nl_end_of_file = force # ignore/add/remove/force/not_defined nl_end_of_file_min = 1 # unsigned number # Add or remove newline between '=' and '{'. -nl_assign_brace = ignore # ignore/add/remove/force/not_defined +nl_assign_brace = remove # ignore/add/remove/force/not_defined # (D) Add or remove newline between '=' and '['. nl_assign_square = ignore # ignore/add/remove/force/not_defined @@ -1714,7 +1714,7 @@ nl_try_brace = ignore # ignore/add/remove/force/not_defined nl_getset_brace = ignore # ignore/add/remove/force/not_defined # Add or remove newline between 'for' and '{'. -nl_for_brace = ignore # ignore/add/remove/force/not_defined +nl_for_brace = remove # ignore/add/remove/force/not_defined # Add or remove newline before the '{' of a 'catch' statement, as in # 'catch (decl) <here> {'. @@ -1738,7 +1738,7 @@ nl_brace_square = ignore # ignore/add/remove/force/not_defined nl_brace_fparen = remove # ignore/add/remove/force/not_defined # Add or remove newline between 'while' and '{'. -nl_while_brace = ignore # ignore/add/remove/force/not_defined +nl_while_brace = remove # ignore/add/remove/force/not_defined # (D) Add or remove newline between 'scope (x)' and '{'. nl_scope_brace = ignore # ignore/add/remove/force/not_defined @@ -1763,7 +1763,7 @@ nl_do_brace = remove # ignore/add/remove/force/not_defined nl_brace_while = remove # ignore/add/remove/force/not_defined # Add or remove newline between 'switch' and '{'. -nl_switch_brace = ignore # ignore/add/remove/force/not_defined +nl_switch_brace = remove # ignore/add/remove/force/not_defined # Add or remove newline between 'synchronized' and '{'. nl_synchronized_brace = ignore # ignore/add/remove/force/not_defined @@ -1777,7 +1777,7 @@ nl_multi_line_cond = false # true/false # Add a newline after '(' if an if/for/while/switch condition spans multiple # lines -nl_multi_line_sparen_open = ignore # ignore/add/remove/force/not_defined +nl_multi_line_sparen_open = remove # ignore/add/remove/force/not_defined # Add a newline before ')' if an if/for/while/switch condition spans multiple # lines. Overrides nl_before_if_closing_paren if both are specified. @@ -1908,7 +1908,7 @@ nl_func_paren_empty = ignore # ignore/add/remove/force/not_defined # Add or remove newline between a function name and the opening '(' in the # definition. -nl_func_def_paren = ignore # ignore/add/remove/force/not_defined +nl_func_def_paren = remove # ignore/add/remove/force/not_defined # Overrides nl_func_def_paren for functions with no parameters. nl_func_def_paren_empty = ignore # ignore/add/remove/force/not_defined @@ -1941,7 +1941,7 @@ nl_func_decl_start_multi_line = false # true/false nl_func_def_start_multi_line = false # true/false # Add or remove newline after each ',' in a function declaration. -nl_func_decl_args = ignore # ignore/add/remove/force/not_defined +nl_func_decl_args = remove # ignore/add/remove/force/not_defined # Add or remove newline after each ',' in a function definition. nl_func_def_args = remove # ignore/add/remove/force/not_defined @@ -1958,7 +1958,7 @@ nl_func_decl_args_multi_line = false # true/false nl_func_def_args_multi_line = false # true/false # Add or remove newline before the ')' in a function declaration. -nl_func_decl_end = ignore # ignore/add/remove/force/not_defined +nl_func_decl_end = remove # ignore/add/remove/force/not_defined # Add or remove newline before the ')' in a function definition. nl_func_def_end = remove # ignore/add/remove/force/not_defined @@ -1991,7 +1991,7 @@ nl_func_call_empty = ignore # ignore/add/remove/force/not_defined nl_func_call_start = remove # ignore/add/remove/force/not_defined # Whether to add a newline before ')' in a function call. -nl_func_call_end = ignore # ignore/add/remove/force/not_defined +nl_func_call_end = remove # ignore/add/remove/force/not_defined # Whether to add a newline after '(' in a function call if '(' and ')' are in # different lines. @@ -3515,5 +3515,5 @@ set QUESTION REAL_FATTR_CONST set QUESTION REAL_FATTR_NONNULL_ALL set QUESTION REAL_FATTR_PURE set QUESTION REAL_FATTR_WARN_UNUSED_RESULT -# option(s) with 'not default' value: 102 +# option(s) with 'not default' value: 112 # diff --git a/src/unicode/Copyright.txt b/src/unicode/Copyright.txt index bfae4154b6..9d281d674a 100644 --- a/src/unicode/Copyright.txt +++ b/src/unicode/Copyright.txt @@ -2,7 +2,7 @@ COPYRIGHT AND PERMISSION NOTICE Copyright © 1991-2015 Unicode, Inc. All rights reserved. Distributed under the Terms of Use in -http://www.unicode.org/copyright.html. +https://www.unicode.org/copyright.html. Permission is hereby granted, free of charge, to any person obtaining a copy of the Unicode data files and any associated documentation @@ -34,4 +34,4 @@ PERFORMANCE OF THE DATA FILES OR SOFTWARE. Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in these Data Files or Software without prior -written authorization of the copyright holder.
\ No newline at end of file +written authorization of the copyright holder. diff --git a/test/benchmark/bench_re_freeze_spec.lua b/test/benchmark/bench_regexp_spec.lua index ea41953014..903af5f574 100644 --- a/test/benchmark/bench_re_freeze_spec.lua +++ b/test/benchmark/bench_regexp_spec.lua @@ -1,4 +1,4 @@ --- Test for benchmarking RE engine. +-- Test for benchmarking the RE engine. local helpers = require('test.functional.helpers')(after_each) local insert, source = helpers.insert, helpers.source @@ -7,19 +7,19 @@ local clear, command = helpers.clear, helpers.command -- Temporary file for gathering benchmarking results for each regexp engine. local result_file = 'benchmark.out' -- Fixture containing an HTML fragment that can make a search appear to freeze. -local sample_file = 'test/benchmark/samples/re.freeze.txt' +local sample_file = 'src/nvim/testdir/samples/re.freeze.txt' -- Vim script code that does both the work and the benchmarking of that work. local measure_cmd = [[call Measure(%d, ']] .. sample_file .. [[', '\s\+\%%#\@<!$', '+5')]] local measure_script = [[ - func! Measure(re, file, pattern, arg) - let sstart=reltime() + func Measure(re, file, pattern, arg) + let sstart = reltime() - execute 'set re=' . a:re + execute 'set re=' .. a:re execute 'split' a:arg a:file call search(a:pattern, '', '', 10000) - q! + quit! $put =printf('file: %s, re: %d, time: %s', a:file, a:re, reltimestr(reltime(sstart))) endfunc]] diff --git a/test/cmakeconfig/paths.lua.in b/test/cmakeconfig/paths.lua.in index e3979981ba..6be238d838 100644 --- a/test/cmakeconfig/paths.lua.in +++ b/test/cmakeconfig/paths.lua.in @@ -6,7 +6,6 @@ for p in ("${TEST_INCLUDE_DIRS}" .. ";"):gmatch("[^;]+") do end module.test_build_dir = "${CMAKE_BINARY_DIR}" -module.test_include_path = module.test_build_dir .. "/test/includes/post" module.test_libnvim_path = "${TEST_LIBNVIM_PATH}" module.test_source_path = "${CMAKE_SOURCE_DIR}" module.test_lua_prg = "${LUA_PRG}" diff --git a/test/functional/api/autocmd_spec.lua b/test/functional/api/autocmd_spec.lua index a923f5df0e..22a1311ee9 100644 --- a/test/functional/api/autocmd_spec.lua +++ b/test/functional/api/autocmd_spec.lua @@ -14,14 +14,14 @@ 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", { + it('"command" and "callback" are mutually exclusive', function() + local rv = pcall_err(meths.create_autocmd, "BufReadPost", { pattern = "*.py,*.pyi", command = "echo 'Should Have Errored", - callback = "not allowed", + callback = "NotAllowed", }) - eq(false, ok) + eq("specify either 'callback' or 'command', not both", rv) end) it('doesnt leak when you use ++once', function() @@ -60,13 +60,13 @@ describe('autocmd api', function() end) it('does not allow passing buffer and patterns', function() - local ok = pcall(meths.create_autocmd, "Filetype", { + local rv = pcall_err(meths.create_autocmd, "Filetype", { command = "let g:called = g:called + 1", buffer = 0, pattern = "*.py", }) - eq(false, ok) + eq("cannot pass both: 'pattern' and 'buffer' for the same autocmd", rv) end) it('does not allow passing invalid buffers', function() @@ -613,6 +613,20 @@ describe('autocmd api', function() eq(false, success) matches("'group' must be a string or an integer", code) end) + + it('raises error for invalid pattern array', function() + local success, code = unpack(meths.exec_lua([[ + return {pcall(function() + vim.api.nvim_create_autocmd("FileType", { + pattern = {{}}, + command = "echo 'hello'", + }) + end)} + ]], {})) + + eq(false, success) + matches("All entries in 'pattern' must be strings", code) + end) end) describe('patterns', function() diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index 8f6fc666c9..6b13729994 100644 --- a/test/functional/api/buffer_spec.lua +++ b/test/functional/api/buffer_spec.lua @@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen') local clear, nvim, buffer = helpers.clear, helpers.nvim, helpers.buffer local curbuf, curwin, eq = helpers.curbuf, helpers.curwin, helpers.eq local curbufmeths, ok = helpers.curbufmeths, helpers.ok +local describe_lua_and_rpc = helpers.describe_lua_and_rpc(describe) local meths = helpers.meths local funcs = helpers.funcs local request = helpers.request @@ -185,12 +186,13 @@ describe('api/buf', function() end) end) - describe('nvim_buf_get_lines, nvim_buf_set_lines', function() - local get_lines, set_lines = curbufmeths.get_lines, curbufmeths.set_lines - local line_count = curbufmeths.line_count + describe_lua_and_rpc('nvim_buf_get_lines, nvim_buf_set_lines', function(api) + local get_lines = api.curbufmeths.get_lines + local set_lines = api.curbufmeths.set_lines + local line_count = api.curbufmeths.line_count it('fails correctly when input is not valid', function() - eq(1, curbufmeths.get_number()) + eq(1, api.curbufmeths.get_number()) eq([[String cannot contain newlines]], pcall_err(bufmeths.set_lines, 1, 1, 2, false, {'b\na'})) end) @@ -198,7 +200,7 @@ describe('api/buf', function() it("fails if 'nomodifiable'", function() command('set nomodifiable') eq([[Buffer is not 'modifiable']], - pcall_err(bufmeths.set_lines, 1, 1, 2, false, {'a','b'})) + pcall_err(api.bufmeths.set_lines, 1, 1, 2, false, {'a','b'})) end) it('has correct line_count when inserting and deleting', function() @@ -354,7 +356,7 @@ describe('api/buf', function() Who would win? A real window with proper text]]) - local buf = meths.create_buf(false,true) + local buf = api.meths.create_buf(false,true) screen:expect([[ Who would win? | A real window | @@ -363,7 +365,7 @@ describe('api/buf', function() | ]]) - meths.buf_set_lines(buf, 0, -1, true, {'or some', 'scratchy text'}) + api.meths.buf_set_lines(buf, 0, -1, true, {'or some', 'scratchy text'}) feed('i') -- provoke redraw screen:expect([[ Who would win? | @@ -379,15 +381,15 @@ describe('api/buf', function() visible buffer line 1 line 2 ]]) - local hiddenbuf = meths.create_buf(false,true) + local hiddenbuf = api.meths.create_buf(false,true) command('vsplit') command('vsplit') feed('<c-w>l<c-w>l<c-w>l') eq(3, funcs.winnr()) feed('<c-w>h') eq(2, funcs.winnr()) - meths.buf_set_lines(hiddenbuf, 0, -1, true, - {'hidden buffer line 1', 'line 2'}) + api.meths.buf_set_lines(hiddenbuf, 0, -1, true, + {'hidden buffer line 1', 'line 2'}) feed('<c-w>p') eq(3, funcs.winnr()) end) @@ -579,13 +581,13 @@ describe('api/buf', function() end) end) - describe('nvim_buf_get_text', function() - local get_text = curbufmeths.get_text - + describe_lua_and_rpc('nvim_buf_get_text', function(api) + local get_text = api.curbufmeths.get_text before_each(function() insert([[ hello foo! - text]]) + text + more]]) end) it('works', function() @@ -593,16 +595,17 @@ describe('api/buf', function() 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({'foo!', 'tex'}, get_text(-3, 6, -2, 3, {})) eq({''}, get_text(0, 18, 0, 20, {})) - eq({'ext'}, get_text(-1, 1, -1, 4, {})) + eq({'ext'}, get_text(-2, 1, -2, 4, {})) + eq({'hello foo!', 'text', 'm'}, get_text(0, 0, 2, 1, {})) end) it('errors on out-of-range', function() - eq('Index out of bounds', pcall_err(get_text, 2, 0, 3, 0, {})) - eq('Index out of bounds', pcall_err(get_text, -3, 0, 0, 0, {})) - eq('Index out of bounds', pcall_err(get_text, 0, 0, 2, 0, {})) - eq('Index out of bounds', pcall_err(get_text, 0, 0, -3, 0, {})) + eq('Index out of bounds', pcall_err(get_text, 2, 0, 4, 0, {})) + eq('Index out of bounds', pcall_err(get_text, -4, 0, 0, 0, {})) + eq('Index out of bounds', pcall_err(get_text, 0, 0, 3, 0, {})) + eq('Index out of bounds', pcall_err(get_text, 0, 0, -4, 0, {})) -- no ml_get errors should happen #19017 eq('', meths.get_vvar('errmsg')) end) diff --git a/test/functional/api/buffer_updates_spec.lua b/test/functional/api/buffer_updates_spec.lua index 3d257e9477..d5f06c8f1f 100644 --- a/test/functional/api/buffer_updates_spec.lua +++ b/test/functional/api/buffer_updates_spec.lua @@ -810,7 +810,7 @@ describe('API: buffer events:', function() local newlines = args[5] -- Size of the contained nvim instance is 23 lines, this might change - -- with the test setup. Note updates are continguous. + -- with the test setup. Note updates are contiguous. assert(#newlines <= 23) for i = 1,#newlines do diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua index f19d7a362b..d0fb26edc7 100644 --- a/test/functional/api/command_spec.lua +++ b/test/functional/api/command_spec.lua @@ -114,6 +114,7 @@ describe('nvim_create_user_command', function() ]] eq({ + name = "CommandWithLuaCallback", args = [[this\ is a\ test]], fargs = {"this ", "is", "a test"}, bang = false, @@ -150,6 +151,7 @@ describe('nvim_create_user_command', function() ]=]) eq({ + name = "CommandWithLuaCallback", args = [[this includes\ a backslash: \\]], fargs = {"this", "includes a", "backslash:", "\\"}, bang = false, @@ -186,6 +188,7 @@ describe('nvim_create_user_command', function() ]=]) eq({ + name = "CommandWithLuaCallback", args = "a\\b", fargs = {"a\\b"}, bang = false, @@ -222,6 +225,7 @@ describe('nvim_create_user_command', function() ]=]) eq({ + name = "CommandWithLuaCallback", args = 'h\tey ', fargs = {[[h]], [[ey]]}, bang = true, @@ -258,6 +262,7 @@ describe('nvim_create_user_command', function() ]=]) eq({ + name = "CommandWithLuaCallback", args = "h", fargs = {"h"}, bang = false, @@ -294,6 +299,7 @@ describe('nvim_create_user_command', function() ]]) eq({ + name = "CommandWithLuaCallback", args = "", fargs = {}, -- fargs works without args bang = false, @@ -342,6 +348,7 @@ describe('nvim_create_user_command', function() ]] eq({ + name = "CommandWithOneOrNoArg", args = "hello I'm one argument", fargs = {"hello I'm one argument"}, -- Doesn't split args bang = false, @@ -379,6 +386,7 @@ describe('nvim_create_user_command', function() -- f-args is an empty table if no args were passed eq({ + name = "CommandWithOneOrNoArg", args = "", fargs = {}, bang = false, @@ -427,6 +435,7 @@ describe('nvim_create_user_command', function() }) ]] eq({ + name = "CommandWithNoArgs", args = "", fargs = {}, bang = false, @@ -463,6 +472,7 @@ describe('nvim_create_user_command', function() ]]) -- register can be specified eq({ + name = "CommandWithNoArgs", args = "", fargs = {}, bang = false, diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index 3b36563d21..5941d4c68b 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -11,6 +11,9 @@ local ok = helpers.ok local assert_alive = helpers.assert_alive describe('API: highlight',function() + clear() + Screen.new() -- initialize Screen.colors + local expected_rgb = { background = Screen.colors.Yellow, foreground = Screen.colors.Red, @@ -29,13 +32,16 @@ describe('API: highlight',function() italic = true, reverse = true, underline = true, - undercurl = true, - underdouble = true, - underdotted = true, - underdashed = true, strikethrough = true, + altfont = true, nocombine = true, } + local expected_undercurl = { + background = Screen.colors.Yellow, + foreground = Screen.colors.Red, + special = Screen.colors.Blue, + undercurl = true, + } before_each(function() clear() @@ -56,9 +62,13 @@ describe('API: highlight',function() eq('Invalid highlight id: 30000', string.match(emsg, 'Invalid.*')) -- Test all highlight properties. - command('hi NewHighlight gui=underline,bold,undercurl,underdouble,underdotted,underdashed,italic,reverse,strikethrough,nocombine') + command('hi NewHighlight gui=underline,bold,italic,reverse,strikethrough,altfont,nocombine') eq(expected_rgb2, nvim("get_hl_by_id", hl_id, true)) + -- Test undercurl + command('hi NewHighlight gui=undercurl') + eq(expected_undercurl, nvim("get_hl_by_id", hl_id, true)) + -- Test nil argument. err, emsg = pcall(meths.get_hl_by_id, { nil }, false) eq(false, err) @@ -204,17 +214,14 @@ describe("API: set highlight", function() bold = true, italic = true, reverse = true, - undercurl = true, underline = true, - underdashed = true, - underdotted = true, - underdouble = true, strikethrough = true, + altfont = true, cterm = { italic = true, reverse = true, - undercurl = true, strikethrough = true, + altfont = true, nocombine = true, } } @@ -224,20 +231,17 @@ describe("API: set highlight", function() bold = true, italic = true, reverse = true, - undercurl = true, underline = true, - underdashed = true, - underdotted = true, - underdouble = true, strikethrough = true, + altfont = true, } local highlight3_result_cterm = { background = highlight_color.ctermbg, foreground = highlight_color.ctermfg, italic = true, reverse = true, - undercurl = true, strikethrough = true, + altfont = true, nocombine = true, } @@ -293,7 +297,7 @@ describe("API: set highlight", function() exec_capture('highlight Test_hl')) meths.set_hl(0, 'Test_hl2', highlight3_config) - eq('Test_hl2 xxx cterm=undercurl,italic,reverse,strikethrough,nocombine ctermfg=8 ctermbg=15 gui=bold,underline,undercurl,underdouble,underdotted,underdashed,italic,reverse,strikethrough guifg=#ff0000 guibg=#0032aa', + eq('Test_hl2 xxx cterm=italic,reverse,strikethrough,altfont,nocombine ctermfg=8 ctermbg=15 gui=bold,underline,italic,reverse,strikethrough,altfont guifg=#ff0000 guibg=#0032aa', exec_capture('highlight Test_hl2')) -- Colors are stored with the name they are defined, but diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua index 30c351b26a..5be4425162 100644 --- a/test/functional/api/keymap_spec.lua +++ b/test/functional/api/keymap_spec.lua @@ -1066,7 +1066,7 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() eq({'rhs'}, bufmeths.get_lines(0, 0, 1, 1)) end) - it("does not crash when setting keymap in a non-existing buffer #13541", function() + it("does not crash when setting mapping in a non-existing buffer #13541", function() pcall_err(bufmeths.set_keymap, 100, '', 'lsh', 'irhs<Esc>', {}) helpers.assert_alive() end) diff --git a/test/functional/api/proc_spec.lua b/test/functional/api/proc_spec.lua index 0fbf58a8e7..2028a8fba5 100644 --- a/test/functional/api/proc_spec.lua +++ b/test/functional/api/proc_spec.lua @@ -3,12 +3,12 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local eq = helpers.eq local funcs = helpers.funcs -local iswin = helpers.iswin local neq = helpers.neq local nvim_argv = helpers.nvim_argv local request = helpers.request local retry = helpers.retry local NIL = helpers.NIL +local is_os = helpers.is_os describe('API', function() before_each(clear) @@ -19,26 +19,26 @@ describe('API', function() -- Might be non-zero already (left-over from some other test?), -- but this is not what is tested here. - local initial_childs = request('nvim_get_proc_children', this_pid) + local initial_children = request('nvim_get_proc_children', this_pid) local job1 = funcs.jobstart(nvim_argv) retry(nil, nil, function() - eq(#initial_childs + 1, #request('nvim_get_proc_children', this_pid)) + eq(#initial_children + 1, #request('nvim_get_proc_children', this_pid)) end) local job2 = funcs.jobstart(nvim_argv) retry(nil, nil, function() - eq(#initial_childs + 2, #request('nvim_get_proc_children', this_pid)) + eq(#initial_children + 2, #request('nvim_get_proc_children', this_pid)) end) funcs.jobstop(job1) retry(nil, nil, function() - eq(#initial_childs + 1, #request('nvim_get_proc_children', this_pid)) + eq(#initial_children + 1, #request('nvim_get_proc_children', this_pid)) end) funcs.jobstop(job2) retry(nil, nil, function() - eq(#initial_childs, #request('nvim_get_proc_children', this_pid)) + eq(#initial_children, #request('nvim_get_proc_children', this_pid)) end) end) @@ -62,7 +62,7 @@ describe('API', function() it('returns process info', function() local pid = funcs.getpid() local pinfo = request('nvim_get_proc', pid) - eq((iswin() and 'nvim.exe' or 'nvim'), pinfo.name) + eq((is_os('win') and 'nvim.exe' or 'nvim'), pinfo.name) eq(pid, pinfo.pid) eq('number', type(pinfo.ppid)) neq(pid, pinfo.ppid) diff --git a/test/functional/api/server_notifications_spec.lua b/test/functional/api/server_notifications_spec.lua index 1c554b05a3..53642858b2 100644 --- a/test/functional/api/server_notifications_spec.lua +++ b/test/functional/api/server_notifications_spec.lua @@ -1,13 +1,16 @@ local helpers = require('test.functional.helpers')(after_each) +local assert_log = helpers.assert_log local eq, clear, eval, command, nvim, next_msg = helpers.eq, helpers.clear, helpers.eval, helpers.command, helpers.nvim, helpers.next_msg local meths = helpers.meths local exec_lua = helpers.exec_lua local retry = helpers.retry -local isCI = helpers.isCI +local is_ci = helpers.is_ci local assert_alive = helpers.assert_alive -local uname = helpers.uname +local skip = helpers.skip + +local testlog = 'Xtest-server-notify-log' describe('notify', function() local channel @@ -17,6 +20,10 @@ describe('notify', function() channel = nvim('get_api_info')[1] end) + after_each(function() + os.remove(testlog) + end) + describe('passing a valid channel id', function() it('sends the notification/args to the corresponding channel', function() eval('rpcnotify('..channel..', "test-event", 1, 2, 3)') @@ -72,23 +79,18 @@ describe('notify', function() end) it('unsubscribe non-existing event #8745', function() + clear{env={ + NVIM_LOG_FILE=testlog, + }} nvim('subscribe', 'event1') nvim('unsubscribe', 'doesnotexist') + assert_log("tried to unsubscribe unknown event 'doesnotexist'", testlog, 10) nvim('unsubscribe', 'event1') assert_alive() end) it('cancels stale events on channel close', function() - if uname() == 'freebsd' then - pending('Failing FreeBSD test') - end - 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 + skip(is_ci(), 'hangs on CI #14083 #15251') local catchan = eval("jobstart(['cat'], {'rpc': v:true})") local catpath = eval('exepath("cat")') eq({id=catchan, argv={catpath}, stream='job', mode='rpc', client = {}}, exec_lua ([[ diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua index 00a4dd041d..ceff390dc5 100644 --- a/test/functional/api/server_requests_spec.lua +++ b/test/functional/api/server_requests_spec.lua @@ -250,7 +250,7 @@ describe('server -> client', function() pcall(funcs.jobstop, jobid) end) - if helpers.pending_win32(pending) then return end + if helpers.skip(helpers.is_os('win')) then return end it('rpc and text stderr can be combined', function() local status, rv = pcall(funcs.rpcrequest, jobid, 'poll') diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index af6cee7e90..8fcdd9620b 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -12,7 +12,6 @@ local exec = helpers.exec local eval = helpers.eval local expect = helpers.expect local funcs = helpers.funcs -local iswin = helpers.iswin local meths = helpers.meths local matches = helpers.matches local pesc = helpers.pesc @@ -30,6 +29,7 @@ local exec_lua = helpers.exec_lua local exc_exec = helpers.exc_exec local insert = helpers.insert local expect_exit = helpers.expect_exit +local skip = helpers.skip local pcall_err = helpers.pcall_err local format_string = helpers.format_string @@ -166,7 +166,7 @@ describe('API', function() echo nvim_exec('echo Avast_ye_hades(''ahoy!'')', 1) ]], true)) - eq('Vim(call):E5555: API call: Vim(echo):E121: Undefined variable: s:pirate', + matches('Vim%(echo%):E121: Undefined variable: s:pirate$', pcall_err(request, 'nvim_exec', [[ let s:pirate = 'script-scoped varrrrr' call nvim_exec('echo s:pirate', 1) @@ -208,12 +208,12 @@ describe('API', function() end) it('execution error', function() - eq('Vim:E492: Not an editor command: bogus_command', + eq('nvim_exec(): Vim:E492: Not an editor command: bogus_command', pcall_err(request, 'nvim_exec', 'bogus_command', false)) eq('', nvim('eval', 'v:errmsg')) -- v:errmsg was not updated. eq('', eval('v:exception')) - eq('Vim(buffer):E86: Buffer 23487 does not exist', + eq('nvim_exec(): Vim(buffer):E86: Buffer 23487 does not exist', pcall_err(request, 'nvim_exec', 'buffer 23487', false)) eq('', eval('v:errmsg')) -- v:errmsg was not updated. eq('', eval('v:exception')) @@ -399,7 +399,7 @@ describe('API', function() end) it('returns shell |:!| output', function() - local win_lf = iswin() and '\r' or '' + local win_lf = is_os('win') and '\r' or '' eq(':!echo foo\r\n\nfoo'..win_lf..'\n', nvim('command_output', [[!echo foo]])) end) @@ -485,7 +485,7 @@ describe('API', function() throw 'wtf' endfunction ]]) - eq('wtf', pcall_err(request, 'nvim_call_function', 'Foo', {})) + eq('function Foo, line 1: wtf', pcall_err(request, 'nvim_call_function', 'Foo', {})) eq('', eval('v:exception')) eq('', eval('v:errmsg')) -- v:errmsg was not updated. end) @@ -604,10 +604,10 @@ describe('API', function() eq([[Error loading lua: [string "<nvim>"]:0: unexpected symbol]], pcall_err(meths.exec_lua, 'aa=bb\0', {})) - eq([[Error executing lua: [string "<nvim>"]:0: attempt to call global 'bork' (a nil value)]], + eq([[attempt to call global 'bork' (a nil value)]], pcall_err(meths.exec_lua, 'bork()', {})) - eq('Error executing lua: [string "<nvim>"]:0: did\nthe\nfail', + eq('did\nthe\nfail', pcall_err(meths.exec_lua, 'error("did\\nthe\\nfail")', {})) end) @@ -1108,6 +1108,14 @@ describe('API', function() nvim('paste', 'a', true, -1) eq('a', funcs.getcmdline()) end) + it('pasted text is saved in cmdline history when <CR> comes from mapping #20957', function() + command('cnoremap <CR> <CR>') + feed(':') + nvim('paste', 'echo', true, -1) + eq('', funcs.histget(':')) + feed('<CR>') + eq('echo', funcs.histget(':')) + end) it('pasting with empty last chunk in Cmdline mode', function() local screen = Screen.new(20, 4) screen:attach() @@ -1140,7 +1148,7 @@ describe('API', function() end) it('vim.paste() failure', function() nvim('exec_lua', 'vim.paste = (function(lines, phase) error("fake fail") end)', {}) - eq([[Error executing lua: [string "<nvim>"]:0: fake fail]], + eq('fake fail', pcall_err(request, 'nvim_paste', 'line 1\nline 2\nline 3', false, 1)) end) end) @@ -1799,9 +1807,11 @@ describe('API', function() }, ['jumps'] = eval(([[ - filter(map(getjumplist()[0], 'filter( - { "f": expand("#".v:val.bufnr.":p"), "l": v:val.lnum }, - { k, v -> k != "l" || v != 1 })'), '!empty(v:val.f)') + filter(map(add( + getjumplist()[0], { 'bufnr': bufnr('%'), 'lnum': getcurpos()[1] }), + 'filter( + { "f": expand("#".v:val.bufnr.":p"), "l": v:val.lnum }, + { k, v -> k != "l" || v != 1 })'), '!empty(v:val.f)') ]]):gsub('\n', '')), ['bufs'] = eval([[ @@ -2116,7 +2126,7 @@ describe('API', function() pty='?', } local event = meths.get_var("opened_event") - if not iswin() then + if not is_os('win') then info.pty = event.info.pty neq(nil, string.match(info.pty, "^/dev/")) end @@ -2132,7 +2142,7 @@ describe('API', function() stream = 'job', id = 4, argv = ( - iswin() and { + is_os('win') and { eval('&shell'), '/s', '/c', @@ -2154,7 +2164,7 @@ describe('API', function() -- :terminal with args + stopped process. eq(1, eval('jobstop(&channel)')) eval('jobwait([&channel], 1000)') -- Wait. - expected2.pty = (iswin() and '?' or '') -- pty stream was closed. + expected2.pty = (is_os('win') and '?' or '') -- pty stream was closed. eq(expected2, eval('nvim_get_chan_info(&channel)')) end) end) @@ -2315,12 +2325,6 @@ describe('API', function() meths.set_option('isident', '') end) - local it_maybe_pending = it - if helpers.isCI() and os.getenv('CONFIGURATION') == 'MSVC_32' then - -- For "works with &opt" (flaky on MSVC_32), but not easy to skip alone. #10241 - it_maybe_pending = pending - end - local function simplify_east_api_node(line, east_api_node) if east_api_node == NIL then return nil @@ -2517,7 +2521,7 @@ describe('API', function() end end require('test.unit.viml.expressions.parser_tests')( - it_maybe_pending, _check_parsing, hl, fmtn) + it, _check_parsing, hl, fmtn) end) describe('nvim_list_uis', function() @@ -2721,7 +2725,7 @@ 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 + skip(is_os('win')) eq("Vim:E220: Missing }.", pcall_err(meths.get_runtime_file, "{", false)) @@ -3040,7 +3044,7 @@ describe('API', function() meths.buf_set_mark(buf, 'F', 2, 2, {}) meths.buf_set_name(buf, "mybuf") local mark = meths.get_mark('F', {}) - -- Compare the path tail ony + -- Compare the path tail only assert(string.find(mark[4], "mybuf$")) eq({2, 2, buf.id, mark[4]}, mark) end) @@ -3124,6 +3128,24 @@ describe('API', function() eq('E539: Illegal character <}>', pcall_err(meths.eval_statusline, '%{%}', {})) end) + it('supports various items', function() + eq({ str = '0', width = 1 }, + meths.eval_statusline('%l', { maxwidth = 5 })) + command('set readonly') + eq({ str = '[RO]', width = 4 }, + meths.eval_statusline('%r', { maxwidth = 5 })) + local screen = Screen.new(80, 24) + screen:attach() + command('set showcmd') + feed('1234') + screen:expect({any = '1234'}) + eq({ str = '1234', width = 4 }, + meths.eval_statusline('%S', { maxwidth = 5 })) + feed('56') + screen:expect({any = '123456'}) + eq({ str = '<3456', width = 5 }, + meths.eval_statusline('%S', { maxwidth = 5 })) + end) describe('highlight parsing', function() it('works', function() eq({ @@ -3191,6 +3213,17 @@ describe('API', function() 'TextWithNoHighlight%#WarningMsg#TextWithWarningHighlight', { use_winbar = true, highlights = true })) end) + it('no memory leak with click functions', function() + meths.eval_statusline('%@ClickFunc@StatusLineStringWithClickFunc%T', {}) + eq({ + str = 'StatusLineStringWithClickFunc', + width = 29 + }, + meths.eval_statusline( + '%@ClickFunc@StatusLineStringWithClickFunc%T', + {}) + ) + end) end) end) describe('nvim_parse_cmd', function() @@ -3851,14 +3884,16 @@ describe('API', function() eq("", meths.cmd({ cmd = "Foo", bang = false }, { output = true })) end) it('works with modifiers', function() - -- with :silent output is still captured + -- with silent = true output is still captured eq('1', meths.cmd({ cmd = 'echomsg', args = { '1' }, mods = { silent = true } }, { output = true })) -- but message isn't added to message history eq('', meths.cmd({ cmd = 'messages' }, { output = true })) + meths.create_user_command("Foo", 'set verbose', {}) eq(" verbose=1", meths.cmd({ cmd = "Foo", mods = { verbose = 1 } }, { output = true })) + meths.create_user_command("Mods", "echo '<mods>'", {}) eq('keepmarks keeppatterns silent 3verbose aboveleft horizontal', meths.cmd({ cmd = "Mods", mods = { @@ -3870,6 +3905,7 @@ describe('API', function() verbose = 3, } }, { output = true })) eq(0, meths.get_option_value("verbose", {})) + command('edit foo.txt | edit bar.txt') eq(' 1 #h "foo.txt" line 1', meths.cmd({ cmd = "buffers", mods = { filter = { pattern = "foo", force = false } } }, @@ -3877,6 +3913,13 @@ describe('API', function() eq(' 2 %a "bar.txt" line 1', meths.cmd({ cmd = "buffers", mods = { filter = { pattern = "foo", force = true } } }, { output = true })) + + -- with emsg_silent = true error is suppresed + feed([[:lua vim.api.nvim_cmd({ cmd = 'call', mods = { emsg_silent = true } }, {})<CR>]]) + eq('', meths.cmd({ cmd = 'messages' }, { output = true })) + -- error from the next command typed is not suppressed #21420 + feed(':call<CR><CR>') + eq('E471: Argument required', meths.cmd({ cmd = 'messages' }, { output = true })) end) it('works with magic.file', function() exec_lua([[ diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 7c65cf9c37..ecab6a4713 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -7,6 +7,7 @@ local clear, nvim, curbuf, curbuf_contents, window, curwin, eq, neq, helpers.tabpage local poke_eventloop = helpers.poke_eventloop local curwinmeths = helpers.curwinmeths +local exec = helpers.exec local funcs = helpers.funcs local request = helpers.request local NIL = helpers.NIL @@ -229,6 +230,46 @@ describe('API/win', function() | ]]) end) + + it('updates cursorcolumn 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}, -- CursorColumn + [3] = {bold = true, reverse = true}, -- StatusLine + [4] = {reverse = true}, -- StatusLineNC + }) + screen:attach() + command('set cursorcolumn') + insert([[ + aaa + bbb + ccc + ddd]]) + local oldwin = curwin() + command('vsplit') + screen:expect([[ + aa{2:a} │aa{2:a} | + bb{2:b} │bb{2:b} | + cc{2:c} │cc{2:c} | + dd^d │ddd | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {3:[No Name] [+] }{4:[No Name] [+] }| + | + ]]) + window('set_cursor', oldwin, {2, 0}) + screen:expect([[ + aa{2:a} │{2:a}aa | + bb{2:b} │bbb | + cc{2:c} │{2:c}cc | + dd^d │{2:d}dd | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {3:[No Name] [+] }{4:[No Name] [+] }| + | + ]]) + end) end) describe('{get,set}_height', function() @@ -243,6 +284,22 @@ describe('API/win', function() window('set_height', nvim('list_wins')[2], 2) eq(2, window('get_height', nvim('list_wins')[2])) end) + + it('do not cause ml_get errors with foldmethod=expr #19989', function() + insert([[ + aaaaa + bbbbb + ccccc]]) + command('set foldmethod=expr') + exec([[ + new + let w = nvim_get_current_win() + wincmd w + call nvim_win_set_height(w, 5) + ]]) + feed('l') + eq('', meths.get_vvar('errmsg')) + end) end) describe('{get,set}_width', function() @@ -257,6 +314,22 @@ describe('API/win', function() window('set_width', nvim('list_wins')[2], 2) eq(2, window('get_width', nvim('list_wins')[2])) end) + + it('do not cause ml_get errors with foldmethod=expr #19989', function() + insert([[ + aaaaa + bbbbb + ccccc]]) + command('set foldmethod=expr') + exec([[ + vnew + let w = nvim_get_current_win() + wincmd w + call nvim_win_set_width(w, 5) + ]]) + feed('l') + eq('', meths.get_vvar('errmsg')) + end) end) describe('{get,set,del}_var', function() @@ -450,6 +523,8 @@ describe('API/win', function() it('closing current (float) window of another tabpage #15313', function() command('tabedit') + command('botright split') + local prevwin = curwin().id eq(2, eval('tabpagenr()')) local win = meths.open_win(0, true, { relative='editor', row=10, col=10, width=50, height=10 @@ -459,7 +534,7 @@ describe('API/win', function() eq(1, eval('tabpagenr()')) meths.win_close(win, false) - eq(1001, meths.tabpage_get_win(tab).id) + eq(prevwin, meths.tabpage_get_win(tab).id) assert_alive() end) end) diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua index 90254b7415..fb5bab445c 100644 --- a/test/functional/autocmd/autocmd_spec.lua +++ b/test/functional/autocmd/autocmd_spec.lua @@ -9,6 +9,7 @@ local neq = helpers.neq local eval = helpers.eval local feed = helpers.feed local clear = helpers.clear +local matches = helpers.matches local meths = helpers.meths local pcall_err = helpers.pcall_err local funcs = helpers.funcs @@ -424,17 +425,50 @@ describe('autocmd', function() end) it('gives E814 when there are no other floating windows', function() - eq('Vim(close):E814: Cannot close window, only autocmd window would remain', + eq('BufAdd Autocommands for "Xa.txt": 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', + eq('BufAdd Autocommands for "Xa.txt": Vim(close):E814: Cannot close window, only autocmd window would remain', pcall_err(command, 'doautoall BufAdd')) end) end) + it('closing `aucmd_win` using API gives E813', function() + exec_lua([[ + vim.cmd('tabnew') + _G.buf = vim.api.nvim_create_buf(true, true) + ]]) + matches('Vim:E813: Cannot close autocmd window$', pcall_err(exec_lua, [[ + vim.api.nvim_buf_call(_G.buf, function() + local win = vim.api.nvim_get_current_win() + vim.api.nvim_win_close(win, true) + end) + ]])) + matches('Vim:E813: Cannot close autocmd window$', pcall_err(exec_lua, [[ + vim.api.nvim_buf_call(_G.buf, function() + local win = vim.api.nvim_get_current_win() + vim.cmd('tabnext') + vim.api.nvim_win_close(win, true) + end) + ]])) + matches('Vim:E813: Cannot close autocmd window$', pcall_err(exec_lua, [[ + vim.api.nvim_buf_call(_G.buf, function() + local win = vim.api.nvim_get_current_win() + vim.api.nvim_win_hide(win) + end) + ]])) + matches('Vim:E813: Cannot close autocmd window$', pcall_err(exec_lua, [[ + vim.api.nvim_buf_call(_G.buf, function() + local win = vim.api.nvim_get_current_win() + vim.cmd('tabnext') + vim.api.nvim_win_hide(win) + end) + ]])) + end) + it(':doautocmd does not warn "No matching autocommands" #10689', function() local screen = Screen.new(32, 3) screen:attach() @@ -476,14 +510,14 @@ describe('autocmd', function() it('during RecordingLeave event', function() command([[autocmd RecordingLeave * let v:event.regname = '']]) - eq('Vim(let):E46: Cannot change read-only variable "v:event.regname"', + eq('RecordingLeave Autocommands for "*": 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"', + eq('TermClose Autocommands for "*": Vim(let):E46: Cannot change read-only variable "v:event.status"', pcall_err(command, 'bdelete!')) end) end) diff --git a/test/functional/autocmd/cmdline_spec.lua b/test/functional/autocmd/cmdline_spec.lua index 8ec06dc148..82fb9b9444 100644 --- a/test/functional/autocmd/cmdline_spec.lua +++ b/test/functional/autocmd/cmdline_spec.lua @@ -73,7 +73,7 @@ describe('cmdline autocommands', function() {1:~ }| {4: }| : | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):FAIL} | + {2:CmdlineEnter Autocommands for "*": Vim(echoerr):FAIL} | :^ | ]]) @@ -82,9 +82,9 @@ describe('cmdline autocommands', function() | {4: }| : | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):FAIL} | + {2:CmdlineEnter Autocommands for "*": Vim(echoerr):FAIL} | :put ='lorem ipsum' | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):very error} | + {2:CmdlineLeave Autocommands for "*": Vim(echoerr):very error} | | {3:Press ENTER or type command to continue}^ | ]]) @@ -111,9 +111,9 @@ describe('cmdline autocommands', function() lorem ipsum | {4: }| : | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):FAIL} | + {2:CmdlineEnter Autocommands for "*": Vim(echoerr):FAIL} | :put ='lorem ipsum' | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):change erreor} | + {2:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | :put ='lorem ipsum'^ | ]]) @@ -123,9 +123,9 @@ describe('cmdline autocommands', function() lorem ipsum | {4: }| : | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):FAIL} | + {2:CmdlineEnter Autocommands for "*": Vim(echoerr):FAIL} | :put ='lorem ipsum' | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):change erreor} | + {2:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | :put ='lorem ipsum^' | ]]) @@ -134,22 +134,22 @@ describe('cmdline autocommands', function() screen:expect([[ {4: }| : | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):FAIL} | + {2:CmdlineEnter Autocommands for "*": Vim(echoerr):FAIL} | :put ='lorem ipsum' | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):change erreor} | + {2:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | :put ='lorem ipsum.' | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):change erreor} | + {2:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | :put ='lorem ipsum.^' | ]]) feed('<cr>') screen:expect([[ :put ='lorem ipsum' | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):change erreor} | + {2:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | :put ='lorem ipsum.' | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):change erreor} | + {2:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | :put ='lorem ipsum.' | - {2:E5500: autocmd has thrown an exception: Vim(echoerr):very error} | + {2:CmdlineLeave Autocommands for "*": Vim(echoerr):very error} | | {3:Press ENTER or type command to continue}^ | ]]) @@ -185,6 +185,14 @@ describe('cmdline autocommands', function() eq({'notification', 'CmdlineLeave', {{cmdtype='=', cmdlevel=2, abort=false}}}, next_msg()) end) + it('no crash with recursive use of v:event #19484', function() + command('autocmd CmdlineEnter * normal :') + feed(':') + eq({'notification', 'CmdlineEnter', {{cmdtype=':', cmdlevel=1}}}, next_msg()) + feed('<CR>') + eq({'notification', 'CmdlineLeave', {{cmdtype=':', cmdlevel=1, abort=false}}}, next_msg()) + end) + it('supports CmdlineChanged' ,function() command("autocmd CmdlineChanged * call rpcnotify(g:channel, 'CmdlineChanged', v:event, getcmdline())") feed(':') @@ -215,7 +223,6 @@ describe('cmdline autocommands', function() eq({'notification', 'CmdlineChanged', {{cmdtype='=', cmdlevel=2}, "1+1"}}, next_msg()) feed('<cr>') eq({'notification', 'CmdlineLeave', {{cmdtype='=', cmdlevel=2, abort=false}}}, next_msg()) - eq({'notification', 'CmdlineChanged', {{cmdtype=':', cmdlevel=1}, "let x = "}}, next_msg()) eq({'notification', 'CmdlineChanged', {{cmdtype=':', cmdlevel=1}, "let x = 2"}}, next_msg()) feed('<cr>') eq({'notification', 'CmdlineLeave', {{cmdtype=':', cmdlevel=1, abort=false}}}, next_msg()) diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua index 45dc06b39b..828cffa460 100644 --- a/test/functional/autocmd/dirchanged_spec.lua +++ b/test/functional/autocmd/dirchanged_spec.lua @@ -1,12 +1,12 @@ local lfs = require('lfs') -local h = require('test.functional.helpers')(after_each) +local helpers = require('test.functional.helpers')(after_each) -local clear = h.clear -local command = h.command -local eq = h.eq -local eval = h.eval -local request = h.request -local iswin = h.iswin +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local eval = helpers.eval +local request = helpers.request +local is_os = helpers.is_os describe('autocmd DirChanged and DirChangedPre', function() local curdir = string.gsub(lfs.currentdir(), '\\', '/') @@ -21,8 +21,8 @@ describe('autocmd DirChanged and DirChangedPre', function() curdir .. '\\XTEST-FUNCTIONAL-AUTOCMD-DIRCHANGED.DIR3', } - setup(function() for _, dir in pairs(dirs) do h.mkdir(dir) end end) - teardown(function() for _, dir in pairs(dirs) do h.rmdir(dir) end end) + setup(function() for _, dir in pairs(dirs) do helpers.mkdir(dir) end end) + teardown(function() for _, dir in pairs(dirs) do helpers.rmdir(dir) end end) before_each(function() clear() @@ -159,7 +159,7 @@ describe('autocmd DirChanged and DirChangedPre', function() eq(1, eval('g:cdprecount')) eq(1, eval('g:cdcount')) - if iswin() then + if is_os('win') then command('lcd '..win_dirs[1]) eq({}, eval('g:evpre')) eq({}, eval('g:ev')) @@ -182,7 +182,7 @@ describe('autocmd DirChanged and DirChangedPre', function() eq(2, eval('g:cdprecount')) eq(2, eval('g:cdcount')) - if iswin() then + if is_os('win') then command('tcd '..win_dirs[2]) eq({}, eval('g:evpre')) eq({}, eval('g:ev')) @@ -204,7 +204,7 @@ describe('autocmd DirChanged and DirChangedPre', function() eq(3, eval('g:cdprecount')) eq(3, eval('g:cdcount')) - if iswin() then + if is_os('win') then command('cd '..win_dirs[3]) eq({}, eval('g:evpre')) eq({}, eval('g:ev')) @@ -229,7 +229,7 @@ describe('autocmd DirChanged and DirChangedPre', function() eq(4, eval('g:cdprecount')) eq(4, eval('g:cdcount')) - if iswin() then + if is_os('win') then command('split '..win_dirs[1]..'/baz') eq({}, eval('g:evpre')) eq({}, eval('g:ev')) @@ -278,7 +278,7 @@ describe('autocmd DirChanged and DirChangedPre', function() eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event - if iswin() then + if is_os('win') 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 diff --git a/test/functional/autocmd/focus_spec.lua b/test/functional/autocmd/focus_spec.lua index e3c9e1f9ee..d7a87e17ed 100644 --- a/test/functional/autocmd/focus_spec.lua +++ b/test/functional/autocmd/focus_spec.lua @@ -6,7 +6,7 @@ local nvim_prog = helpers.nvim_prog local feed_command = helpers.feed_command local feed_data = thelpers.feed_data -if helpers.pending_win32(pending) then return end +if helpers.skip(helpers.is_os('win')) then return end describe('autoread TUI FocusGained/FocusLost', function() local f1 = 'xtest-foo' @@ -33,18 +33,37 @@ describe('autoread TUI FocusGained/FocusLost', function() helpers.write_file(path, '') lfs.touch(path, os.time() - 10) - feed_command('edit '..path) - feed_data('\027[O') screen:expect{grid=[[ {1: } | {4:~ }| {4:~ }| {4:~ }| + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]]} + feed_command('edit '..path) + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| {5:xtest-foo }| :edit xtest-foo | {3:-- TERMINAL --} | ]]} + feed_data('\027[O') + feed_data('\027[O') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:xtest-foo }| + :edit xtest-foo | + {3:-- TERMINAL --} | + ]], unchanged=true} helpers.write_file(path, expected_addition) diff --git a/test/functional/autocmd/signal_spec.lua b/test/functional/autocmd/signal_spec.lua index d4f65cc61d..738064090a 100644 --- a/test/functional/autocmd/signal_spec.lua +++ b/test/functional/autocmd/signal_spec.lua @@ -5,11 +5,10 @@ local command = helpers.command local eq = helpers.eq local funcs = helpers.funcs local next_msg = helpers.next_msg +local is_os = helpers.is_os +local skip = helpers.skip -if helpers.pending_win32(pending) then - -- Only applies to POSIX systems. - return -end +if skip(is_os('win'), 'Only applies to POSIX systems') then return end local function posix_kill(signame, pid) os.execute('kill -s '..signame..' -- '..pid..' >/dev/null') diff --git a/test/functional/autocmd/termxx_spec.lua b/test/functional/autocmd/termxx_spec.lua index 859c2ebf44..0a33f1b2ac 100644 --- a/test/functional/autocmd/termxx_spec.lua +++ b/test/functional/autocmd/termxx_spec.lua @@ -5,11 +5,13 @@ local clear, command, nvim, testprg = helpers.clear, helpers.command, helpers.nvim, helpers.testprg local eval, eq, neq, retry = helpers.eval, helpers.eq, helpers.neq, helpers.retry +local matches = helpers.matches local ok = helpers.ok local feed = helpers.feed local pcall_err = helpers.pcall_err local assert_alive = helpers.assert_alive -local iswin = helpers.iswin +local skip = helpers.skip +local is_os = helpers.is_os describe('autocmd TermClose', function() before_each(function() @@ -22,7 +24,8 @@ describe('autocmd TermClose', function() local function test_termclose_delete_own_buf() command('autocmd TermClose * bdelete!') command('terminal') - eq('Vim(bdelete):E937: Attempt to delete a buffer that is in use', pcall_err(command, 'bdelete!')) + matches('^TermClose Autocommands for "%*": Vim%(bdelete%):E937: Attempt to delete a buffer that is in use: term://', + pcall_err(command, 'bdelete!')) assert_alive() end @@ -45,7 +48,7 @@ describe('autocmd TermClose', function() end) it('triggers when long-running terminal job gets stopped', function() - nvim('set_option', 'shell', iswin() and 'cmd.exe' or 'sh') + nvim('set_option', 'shell', is_os('win') and 'cmd.exe' or 'sh') command('autocmd TermClose * let g:test_termclose = 23') command('terminal') command('call jobstop(b:terminal_job_id)') @@ -53,7 +56,7 @@ describe('autocmd TermClose', function() end) it('kills job trapping SIGTERM', function() - if iswin() then return end + skip(is_os('win')) nvim('set_option', 'shell', 'sh') nvim('set_option', 'shellcmdflag', '-c') command([[ let g:test_job = jobstart('trap "" TERM && echo 1 && sleep 60', { ]] @@ -73,7 +76,7 @@ describe('autocmd TermClose', function() end) it('kills PTY job trapping SIGHUP and SIGTERM', function() - if iswin() then return end + skip(is_os('win')) nvim('set_option', 'shell', 'sh') nvim('set_option', 'shellcmdflag', '-c') command([[ let g:test_job = jobstart('trap "" HUP TERM && echo 1 && sleep 60', { ]] diff --git a/test/functional/autocmd/win_scrolled_resized_spec.lua b/test/functional/autocmd/win_scrolled_resized_spec.lua new file mode 100644 index 0000000000..4957f56dd4 --- /dev/null +++ b/test/functional/autocmd/win_scrolled_resized_spec.lua @@ -0,0 +1,329 @@ +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 exec = helpers.exec +local command = helpers.command +local feed = helpers.feed +local meths = helpers.meths +local assert_alive = helpers.assert_alive + +before_each(clear) + +describe('WinResized', function() + -- oldtest: Test_WinResized() + it('works', function() + exec([[ + set scrolloff=0 + call setline(1, ['111', '222']) + vnew + call setline(1, ['aaa', 'bbb']) + new + call setline(1, ['foo', 'bar']) + + let g:resized = 0 + au WinResized * let g:resized += 1 + au WinResized * let g:v_event = deepcopy(v:event) + ]]) + eq(0, eval('g:resized')) + + -- increase window height, two windows will be reported + feed('<C-W>+') + eq(1, eval('g:resized')) + eq({windows = {1002, 1001}}, eval('g:v_event')) + + -- increase window width, three windows will be reported + feed('<C-W>>') + eq(2, eval('g:resized')) + eq({windows = {1002, 1001, 1000}}, eval('g:v_event')) + end) +end) + +describe('WinScrolled', function() + 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)) + exec([[ + 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>')) + au WinScrolled * let g:v_event = deepcopy(v:event) + ]]) + 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() + 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')) + eq({ + all = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + feed('<C-Y>') + eq(2, eval('g:scrolled')) + eq({ + all = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 0, topline = -1, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + end) + + it('is triggered by scrolling horizontally', function() + 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')) + eq({ + all = {leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + feed('zh') + eq(2, eval('g:scrolled')) + eq({ + all = {leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = -1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + end) + + 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')) + eq({ + all = {leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + feed('zl') + eq(2, eval('g:scrolled')) + eq({ + all = {leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + feed('h') + eq(3, eval('g:scrolled')) + eq({ + all = {leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = -1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + feed('zh') + eq(4, eval('g:scrolled')) + eq({ + all = {leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = -1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + end) + + -- oldtest: Test_WinScrolled_long_wrapped() + it('is triggered by scrolling on a long wrapped line #19968', function() + local height = meths.win_get_height(0) + local width = meths.win_get_width(0) + meths.buf_set_lines(0, 0, -1, true, {('foo'):rep(height * width)}) + meths.win_set_cursor(0, {1, height * width - 1}) + eq(0, eval('g:scrolled')) + + feed('gj') + eq(1, eval('g:scrolled')) + eq({ + all = {leftcol = 0, topline = 0, topfill = 0, width = 0, height = 0, skipcol = width}, + ['1000'] = {leftcol = 0, topline = 0, topfill = 0, width = 0, height = 0, skipcol = width}, + }, eval('g:v_event')) + + feed('0') + eq(2, eval('g:scrolled')) + eq({ + all = {leftcol = 0, topline = 0, topfill = 0, width = 0, height = 0, skipcol = width}, + ['1000'] = {leftcol = 0, topline = 0, topfill = 0, width = 0, height = 0, skipcol = -width}, + }, eval('g:v_event')) + + feed('$') + eq(3, eval('g:scrolled')) + end) + + 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('M') + eq(0, eval('g:scrolled')) + + feed('i<C-X><C-E><Esc>') + eq(1, eval('g:scrolled')) + eq({ + all = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + feed('i<C-X><C-Y><Esc>') + eq(2, eval('g:scrolled')) + eq({ + all = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 0, topline = -1, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + feed('L') + eq(2, eval('g:scrolled')) + + feed('A<CR><Esc>') + eq(3, eval('g:scrolled')) + eq({ + all = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + end) +end) + +describe('WinScrolled', function() + -- oldtest: Test_WinScrolled_mouse() + it('is triggered by mouse scrolling in another window', function() + local screen = Screen.new(75, 10) + screen:attach() + exec([[ + set nowrap scrolloff=0 + set mouse=a + call setline(1, ['foo']->repeat(32)) + split + let g:scrolled = 0 + au WinScrolled * let g:scrolled += 1 + ]]) + eq(0, eval('g:scrolled')) + + -- With the upper split focused, send a scroll-down event to the unfocused one. + meths.input_mouse('wheel', 'down', '', 0, 6, 0) + eq(1, eval('g:scrolled')) + + -- Again, but this time while we're in insert mode. + feed('i') + meths.input_mouse('wheel', 'down', '', 0, 6, 0) + feed('<Esc>') + eq(2, eval('g:scrolled')) + end) + + -- oldtest: Test_WinScrolled_close_curwin() + it('closing window does not cause use-after-free #13265', function() + exec([[ + set nowrap scrolloff=0 + call setline(1, ['aaa', 'bbb']) + vsplit + au WinScrolled * close + ]]) + + -- This was using freed memory + feed('<C-E>') + assert_alive() + end) + + -- oldtest: Test_WinScrolled_diff() + it('is triggered for both windows when scrolling in diff mode', function() + exec([[ + set diffopt+=foldcolumn:0 + call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']) + vnew + call setline(1, ['d', 'e', 'f', 'g', 'h', 'i']) + windo diffthis + au WinScrolled * let g:v_event = deepcopy(v:event) + ]]) + + feed('<C-E>') + eq({ + all = {leftcol = 0, topline = 1, topfill = 1, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1001'] = {leftcol = 0, topline = 0, topfill = -1, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + feed('2<C-E>') + eq({ + all = {leftcol = 0, topline = 2, topfill = 2, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 0, topline = 2, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1001'] = {leftcol = 0, topline = 0, topfill = -2, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + feed('<C-E>') + eq({ + all = {leftcol = 0, topline = 2, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1001'] = {leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + feed('2<C-Y>') + eq({ + all = {leftcol = 0, topline = 3, topfill = 1, width = 0, height = 0, skipcol = 0}, + ['1000'] = {leftcol = 0, topline = -2, topfill = 0, width = 0, height = 0, skipcol = 0}, + ['1001'] = {leftcol = 0, topline = -1, topfill = 1, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + end) + + it('is triggered by mouse scrolling in unfocused floating window #18222', function() + local screen = Screen.new(80, 24) + screen:attach() + + exec([[ + let g:scrolled = 0 + autocmd WinScrolled * let g:scrolled += 1 + autocmd WinScrolled * let g:amatch = expand('<amatch>') + autocmd WinScrolled * let g:v_event = deepcopy(v:event) + ]]) + eq(0, eval('g:scrolled')) + + local buf = meths.create_buf(true, true) + meths.buf_set_lines(buf, 0, -1, false, {'a', 'b', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n'}) + local win = meths.open_win(buf, false, { + height = 5, + width = 10, + col = 0, + row = 1, + relative = 'editor', + style = 'minimal' + }) + local winid_str = tostring(win.id) + -- WinScrolled should not be triggered when creating a new floating window + eq(0, eval('g:scrolled')) + + meths.input_mouse('wheel', 'down', '', 0, 3, 3) + eq(1, eval('g:scrolled')) + eq(winid_str, eval('g:amatch')) + eq({ + all = {leftcol = 0, topline = 3, topfill = 0, width = 0, height = 0, skipcol = 0}, + [winid_str] = {leftcol = 0, topline = 3, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + + meths.input_mouse('wheel', 'up', '', 0, 3, 3) + eq(2, eval('g:scrolled')) + eq(tostring(win.id), eval('g:amatch')) + eq({ + all = {leftcol = 0, topline = 3, topfill = 0, width = 0, height = 0, skipcol = 0}, + [winid_str] = {leftcol = 0, topline = -3, topfill = 0, width = 0, height = 0, skipcol = 0}, + }, eval('g:v_event')) + end) +end) diff --git a/test/functional/autocmd/winscrolled_spec.lua b/test/functional/autocmd/winscrolled_spec.lua deleted file mode 100644 index 12b8e7c42d..0000000000 --- a/test/functional/autocmd/winscrolled_spec.lua +++ /dev/null @@ -1,99 +0,0 @@ -local helpers = require('test.functional.helpers')(after_each) - -local clear = helpers.clear -local eq = helpers.eq -local eval = helpers.eval -local command = helpers.command -local feed = helpers.feed -local meths = helpers.meths -local assert_alive = helpers.assert_alive - -before_each(clear) - -describe('WinScrolled', function() - 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() - 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() - 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 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 by scrolling on a long wrapped line #19968', function() - local height = meths.win_get_height(0) - local width = meths.win_get_width(0) - meths.buf_set_lines(0, 0, -1, true, {('foo'):rep(height * width)}) - meths.win_set_cursor(0, {1, height * width - 1}) - eq(0, eval('g:scrolled')) - feed('gj') - eq(1, eval('g:scrolled')) - feed('0') - eq(2, eval('g:scrolled')) - feed('$') - eq(3, eval('g:scrolled')) - end) - - 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 ca52404d3b..8275575c24 100644 --- a/test/functional/core/channels_spec.lua +++ b/test/functional/core/channels_spec.lua @@ -1,5 +1,4 @@ local helpers = require('test.functional.helpers')(after_each) -local uname = helpers.uname local clear, eq, eval, next_msg, ok, source = helpers.clear, helpers.eq, helpers.eval, helpers.next_msg, helpers.ok, helpers.source local command, funcs, meths = helpers.command, helpers.funcs, helpers.meths @@ -12,6 +11,7 @@ local retry = helpers.retry local expect_twostreams = helpers.expect_twostreams local assert_alive = helpers.assert_alive local pcall_err = helpers.pcall_err +local skip = helpers.skip describe('channels', function() local init = [[ @@ -145,7 +145,7 @@ describe('channels', function() end it('can use stdio channel with pty', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) source([[ let g:job_opts = { \ 'on_stdout': function('OnEvent'), @@ -178,8 +178,7 @@ describe('channels', function() command("call chansend(id, 'incomplet\004')") - local is_bsd = not not string.find(uname(), 'bsd') - local bsdlike = is_bsd or is_os('mac') + local bsdlike = is_os('bsd') or is_os('mac') local extra = bsdlike and "^D\008\008" or "" expect_twoline(id, "stdout", "incomplet"..extra, "[1, ['incomplet'], 'stdin']", true) @@ -199,7 +198,7 @@ describe('channels', function() it('stdio channel can use rpc and stderr simultaneously', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) source([[ let g:job_opts = { \ 'on_stderr': function('OnEvent'), diff --git a/test/functional/core/exit_spec.lua b/test/functional/core/exit_spec.lua index 8cad7adfa6..05a69e1992 100644 --- a/test/functional/core/exit_spec.lua +++ b/test/functional/core/exit_spec.lua @@ -89,14 +89,14 @@ describe(':cquit', function() end) it('exits with redir msg for multiple exit codes after :cquit 1 2', function() - test_cq('cquit 1 2', nil, 'Vim(cquit):E488: Trailing characters: 2: cquit 1 2') + test_cq('cquit 1 2', nil, 'nvim_exec(): Vim(cquit):E488: Trailing characters: 2: cquit 1 2') end) it('exits with redir msg for non-number exit code after :cquit X', function() - test_cq('cquit X', nil, 'Vim(cquit):E488: Trailing characters: X: cquit X') + test_cq('cquit X', nil, 'nvim_exec(): Vim(cquit):E488: Trailing characters: X: cquit X') end) it('exits with redir msg for negative exit code after :cquit -1', function() - test_cq('cquit -1', nil, 'Vim(cquit):E488: Trailing characters: -1: cquit -1') + test_cq('cquit -1', nil, 'nvim_exec(): Vim(cquit):E488: Trailing characters: -1: cquit -1') end) end) diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua index d1ff5b8036..4e9891a4de 100644 --- a/test/functional/core/fileio_spec.lua +++ b/test/functional/core/fileio_spec.lua @@ -1,3 +1,4 @@ +local lfs = require('lfs') local helpers = require('test.functional.helpers')(after_each) local assert_log = helpers.assert_log @@ -5,6 +6,7 @@ local assert_nolog = helpers.assert_nolog local clear = helpers.clear local command = helpers.command local eq = helpers.eq +local neq = helpers.neq local ok = helpers.ok local feed = helpers.feed local funcs = helpers.funcs @@ -19,19 +21,21 @@ local read_file = helpers.read_file local tmpname = helpers.tmpname local trim = helpers.trim local currentdir = helpers.funcs.getcwd -local iswin = helpers.iswin local assert_alive = helpers.assert_alive +local check_close = helpers.check_close local expect_exit = helpers.expect_exit local write_file = helpers.write_file local Screen = require('test.functional.ui.screen') local feed_command = helpers.feed_command -local uname = helpers.uname +local skip = helpers.skip +local is_os = helpers.is_os +local is_ci = helpers.is_ci describe('fileio', function() before_each(function() end) after_each(function() - expect_exit(command, ':qall!') + check_close() os.remove('Xtest_startup_shada') os.remove('Xtest_startup_file1') os.remove('Xtest_startup_file1~') @@ -87,9 +91,7 @@ describe('fileio', function() end) it('backup #9709', function() - if uname() == 'freebsd' then - pending('Failing FreeBSD test') - end + skip(is_ci('cirrus')) clear({ args={ '-i', 'Xtest_startup_shada', '--cmd', 'set directory=Xtest_startup_swapdir' } }) @@ -109,9 +111,7 @@ describe('fileio', function() end) it('backup with full path #11214', function() - if uname() == 'freebsd' then - pending('Failing FreeBSD test') - end + skip(is_ci('cirrus')) clear() mkdir('Xtest_backupdir') command('set backup') @@ -124,7 +124,7 @@ describe('fileio', function() -- Backup filename = fullpath, separators replaced with "%". local backup_file_name = string.gsub(currentdir()..'/Xtest_startup_file1', - iswin() and '[:/\\]' or '/', '%%') .. '~' + is_os('win') and '[:/\\]' or '/', '%%') .. '~' local foo_contents = trim(read_file('Xtest_backupdir/'..backup_file_name)) local foobar_contents = trim(read_file('Xtest_startup_file1')) @@ -132,6 +132,53 @@ describe('fileio', function() eq('foo', foo_contents); end) + it('backup symlinked files #11349', function() + skip(is_ci('cirrus')) + clear() + + local initial_content = 'foo' + local link_file_name = 'Xtest_startup_file2' + local backup_file_name = link_file_name .. '~' + + write_file('Xtest_startup_file1', initial_content, false) + lfs.link('Xtest_startup_file1', link_file_name, true) + command('set backup') + command('set backupcopy=yes') + command('edit ' .. link_file_name) + feed('Abar<esc>') + command('write') + + local backup_raw = read_file(backup_file_name) + neq(nil, backup_raw, "Expected backup file " .. backup_file_name .. "to exist but did not") + eq(initial_content, trim(backup_raw), 'Expected backup to contain original contents') + end) + + + it('backup symlinked files in first available backupdir #11349', function() + skip(is_ci('cirrus')) + clear() + + local initial_content = 'foo' + local backup_dir = 'Xtest_backupdir' + local sep = helpers.get_pathsep() + local link_file_name = 'Xtest_startup_file2' + local backup_file_name = backup_dir .. sep .. link_file_name .. '~' + + write_file('Xtest_startup_file1', initial_content, false) + lfs.link('Xtest_startup_file1', link_file_name, true) + mkdir(backup_dir) + command('set backup') + command('set backupcopy=yes') + command('set backupdir=.__this_does_not_exist__,' .. backup_dir) + command('edit ' .. link_file_name) + feed('Abar<esc>') + command('write') + + local backup_raw = read_file(backup_file_name) + neq(nil, backup_raw, "Expected backup file " .. backup_file_name .. " to exist but did not") + eq(initial_content, trim(backup_raw), 'Expected backup to contain original contents') + end) + it('readfile() on multibyte filename #10586', function() clear() local text = { @@ -224,7 +271,7 @@ describe('tmpdir', function() end) after_each(function() - expect_exit(command, ':qall!') + check_close() os.remove(testlog) end) @@ -248,14 +295,10 @@ describe('tmpdir', function() clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=faketmp, } }) matches(tmproot_pat, funcs.stdpath('run')) -- Tickle vim_mktempdir(). -- Assert that broken tmpdir root was handled. - retry(nil, 1000, function() - assert_log('tempdir root not a directory', testlog, 100) - end) + assert_log('tempdir root not a directory', testlog, 100) -- "…/nvim.<user>/" has wrong permissions: - if iswin() then - return -- TODO(justinmk): need setfperm/getfperm on Windows. #8244 - end + skip(is_os('win'), 'TODO(justinmk): need setfperm/getfperm on Windows. #8244') os.remove(testlog) os.remove(tmproot) mkdir(tmproot) @@ -263,9 +306,7 @@ describe('tmpdir', function() clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=faketmp, } }) matches(tmproot_pat, funcs.stdpath('run')) -- Tickle vim_mktempdir(). -- Assert that broken tmpdir root was handled. - retry(nil, 1000, function() - assert_log('tempdir root has invalid permissions', testlog, 100) - end) + assert_log('tempdir root has invalid permissions', testlog, 100) end) it('too long', function() diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 02ff18bdda..1bae626b98 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -13,7 +13,6 @@ local retry = helpers.retry local meths = helpers.meths local NIL = helpers.NIL local poke_eventloop = helpers.poke_eventloop -local iswin = helpers.iswin local get_pathsep = helpers.get_pathsep local pathroot = helpers.pathroot local exec_lua = helpers.exec_lua @@ -23,6 +22,8 @@ local expect_msg_seq = helpers.expect_msg_seq local pcall_err = helpers.pcall_err local matches = helpers.matches local Screen = require('test.functional.ui.screen') +local skip = helpers.skip +local is_os = helpers.is_os describe('jobs', function() local channel @@ -55,7 +56,7 @@ describe('jobs', function() it('must specify env option as a dict', function() command("let g:job_opts.env = v:true") local _, err = pcall(function() - if iswin() then + if is_os('win') then nvim('command', "let j = jobstart('set', g:job_opts)") else nvim('command', "let j = jobstart('env', g:job_opts)") @@ -68,7 +69,7 @@ describe('jobs', function() nvim('command', "let $VAR = 'abc'") nvim('command', "let $TOTO = 'goodbye world'") nvim('command', "let g:job_opts.env = {'TOTO': 'hello world'}") - if iswin() then + if is_os('win') then nvim('command', [[call jobstart('echo %TOTO% %VAR%', g:job_opts)]]) else nvim('command', [[call jobstart('echo $TOTO $VAR', g:job_opts)]]) @@ -87,12 +88,12 @@ describe('jobs', function() end) it('append environment with pty #env', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) nvim('command', "let $VAR = 'abc'") nvim('command', "let $TOTO = 'goodbye world'") nvim('command', "let g:job_opts.pty = v:true") nvim('command', "let g:job_opts.env = {'TOTO': 'hello world'}") - if iswin() then + if is_os('win') then nvim('command', [[call jobstart('echo %TOTO% %VAR%', g:job_opts)]]) else nvim('command', [[call jobstart('echo $TOTO $VAR', g:job_opts)]]) @@ -122,7 +123,7 @@ describe('jobs', function() -- -- Rather than expecting a completely empty environment, ensure that $VAR -- is *not* in the environment but $TOTO is. - if iswin() then + if is_os('win') then nvim('command', [[call jobstart('echo %TOTO% %VAR%', g:job_opts)]]) expect_msg_seq({ {'notification', 'stdout', {0, {'hello world %VAR%', ''}}} @@ -141,7 +142,7 @@ describe('jobs', function() -- Since $Toto is being set in the job, it should take precedence over the -- global $TOTO on Windows nvim('command', "let g:job_opts = {'env': {'Toto': 'def'}, 'stdout_buffered': v:true}") - if iswin() then + if is_os('win') then nvim('command', [[let j = jobstart('set | find /I "toto="', g:job_opts)]]) else nvim('command', [[let j = jobstart('env | grep -i toto=', g:job_opts)]]) @@ -150,7 +151,7 @@ describe('jobs', function() nvim('command', "let g:output = Normalize(g:job_opts.stdout)") local actual = eval('g:output') local expected - if iswin() then + if is_os('win') then -- Toto is normalized to TOTO so we can detect duplicates, and because -- Windows doesn't care about case expected = {'TOTO=def', ''} @@ -164,7 +165,7 @@ describe('jobs', function() it('uses &shell and &shellcmdflag if passed a string', function() nvim('command', "let $VAR = 'abc'") - if iswin() then + if is_os('win') then nvim('command', "let j = jobstart('echo %VAR%', g:job_opts)") else nvim('command', "let j = jobstart('echo $VAR', g:job_opts)") @@ -176,7 +177,7 @@ describe('jobs', function() it('changes to given / directory', function() nvim('command', "let g:job_opts.cwd = '/'") - if iswin() then + if is_os('win') then nvim('command', "let j = jobstart('cd', g:job_opts)") else nvim('command', "let j = jobstart('pwd', g:job_opts)") @@ -191,7 +192,7 @@ describe('jobs', function() local dir = eval("resolve(tempname())"):gsub("/", get_pathsep()) mkdir(dir) nvim('command', "let g:job_opts.cwd = '" .. dir .. "'") - if iswin() then + if is_os('win') then nvim('command', "let j = jobstart('cd', g:job_opts)") else nvim('command', "let j = jobstart('pwd', g:job_opts)") @@ -215,7 +216,7 @@ describe('jobs', function() local dir = eval('resolve(tempname())."-bogus"') local _, err = pcall(function() nvim('command', "let g:job_opts.cwd = '" .. dir .. "'") - if iswin() then + if is_os('win') then nvim('command', "let j = jobstart('cd', g:job_opts)") else nvim('command', "let j = jobstart('pwd', g:job_opts)") @@ -225,7 +226,7 @@ describe('jobs', function() end) it('error on non-executable `cwd`', function() - if iswin() then return end -- N/A for Windows + skip(is_os('win'), 'Not applicable for Windows') local dir = 'Xtest_not_executable_dir' mkdir(dir) @@ -248,7 +249,7 @@ describe('jobs', function() end local executable_jobid = new_job() - local exe = iswin() and './test/functional/fixtures' or './test/functional/fixtures/non_executable.txt' + local exe = is_os('win') and './test/functional/fixtures' or './test/functional/fixtures/non_executable.txt' eq("Vim:E475: Invalid value for argument cmd: '"..exe.."' is not executable", pcall_err(eval, "jobstart(['"..exe.."'])")) eq("", eval("v:errmsg")) @@ -702,7 +703,7 @@ describe('jobs', function() describe('jobwait', function() before_each(function() - if iswin() then + if is_os('win') then helpers.set_shell_powershell() end end) @@ -786,7 +787,7 @@ describe('jobs', function() feed_command('call rpcnotify(g:channel, "ready") | '.. 'call rpcnotify(g:channel, "wait", '.. 'jobwait([jobstart("'.. - (iswin() and 'Start-Sleep 10' or 'sleep 10').. + (is_os('win') and 'Start-Sleep 10' or 'sleep 10').. '; exit 55")]))') eq({'notification', 'ready', {}}, next_msg()) feed('<c-c>') @@ -797,7 +798,7 @@ describe('jobs', function() feed_command('call rpcnotify(g:channel, "ready") | '.. 'call rpcnotify(g:channel, "wait", '.. 'jobwait([jobstart("'.. - (iswin() and 'Start-Sleep 10' or 'sleep 10').. + (is_os('win') and 'Start-Sleep 10' or 'sleep 10').. '; exit 55")], 10000))') eq({'notification', 'ready', {}}, next_msg()) feed('<c-c>') @@ -805,7 +806,7 @@ describe('jobs', function() end) it('can be called recursively', function() - if helpers.pending_win32(pending) then return end -- TODO: Need `cat`. + skip(is_os('win'), "TODO: Need `cat`") source([[ let g:opts = {} let g:counter = 0 @@ -930,7 +931,7 @@ describe('jobs', function() -- ..c.."', '-c', '"..c.."'])") -- Create child with several descendants. - if iswin() then + if is_os('win') then source([[ function! s:formatprocs(pid, prefix) let result = '' @@ -979,13 +980,13 @@ describe('jobs', function() endfunction ]]) end - local sleep_cmd = (iswin() + local sleep_cmd = (is_os('win') and 'ping -n 31 127.0.0.1' or 'sleep 30') local j = eval("jobstart('"..sleep_cmd..' | '..sleep_cmd..' | '..sleep_cmd.."')") local ppid = funcs.jobpid(j) local children - if iswin() then + if is_os('win') then local status, result = pcall(retry, nil, nil, function() children = meths.get_proc_children(ppid) -- On Windows conhost.exe may exist, and @@ -1006,7 +1007,7 @@ describe('jobs', function() -- Assert that nvim_get_proc() sees the children. for _, child_pid in ipairs(children) do local info = meths.get_proc(child_pid) - -- eq((iswin() and 'nvim.exe' or 'nvim'), info.name) + -- eq((is_os('win') and 'nvim.exe' or 'nvim'), info.name) eq(ppid, info.ppid) end -- Kill the root of the tree. @@ -1027,7 +1028,7 @@ describe('jobs', function() end) describe('running tty-test program', function() - if helpers.pending_win32(pending) then return end + if skip(is_os('win')) then return end local function next_chunk() local rv while true do @@ -1124,7 +1125,7 @@ describe("pty process teardown", function() end) it("does not prevent/delay exit. #4798 #4900", function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) -- Use a nested nvim (in :term) to test without --headless. feed_command(":terminal '"..helpers.nvim_prog .."' -u NONE -i NONE --cmd '"..nvim_set.."' " diff --git a/test/functional/core/log_spec.lua b/test/functional/core/log_spec.lua index 3b1ccd9559..f682df4155 100644 --- a/test/functional/core/log_spec.lua +++ b/test/functional/core/log_spec.lua @@ -6,7 +6,6 @@ local eq = helpers.eq local exec_lua = helpers.exec_lua local expect_exit = helpers.expect_exit local request = helpers.request -local retry = helpers.retry describe('log', function() local testlog = 'Xtest_logging' @@ -40,9 +39,7 @@ describe('log', function() }}) local tid = _G._nvim_test_id - retry(nil, 1000, function() - assert_log(tid..'%.%d+%.%d +server_init:%d+: test log message', testlog, 100) - end) + assert_log(tid..'%.%d+%.%d +server_init:%d+: test log message', testlog, 100) exec_lua([[ local j1 = vim.fn.jobstart({ vim.v.progpath, '-es', '-V1', '+foochild', '+qa!' }, vim.empty_dict()) @@ -50,8 +47,6 @@ describe('log', function() ]]) -- Child Nvim spawned by jobstart() appends "/c" to parent name. - retry(nil, 1000, function() - assert_log('%.%d+%.%d/c +server_init:%d+: test log message', testlog, 100) - end) + assert_log('%.%d+%.%d/c +server_init:%d+: test log message', testlog, 100) end) end) diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua index f6fb859ccc..ab11e14a67 100644 --- a/test/functional/core/main_spec.lua +++ b/test/functional/core/main_spec.lua @@ -9,6 +9,8 @@ local clear = helpers.clear local funcs = helpers.funcs local nvim_prog_abs = helpers.nvim_prog_abs local write_file = helpers.write_file +local is_os = helpers.is_os +local skip = helpers.skip describe('Command-line option', function() describe('-s', function() @@ -49,12 +51,12 @@ describe('Command-line option', function() eq(#('100500\n'), attrs.size) end) it('does not crash after reading from stdin in non-headless mode', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) local screen = Screen.new(40, 8) screen:attach() local args = { nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', - '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', + '--cmd', '"set noswapfile shortmess+=IFW fileformats=unix"', '-s', '-' } diff --git a/test/functional/core/path_spec.lua b/test/functional/core/path_spec.lua index 61fae7622c..a786887bbd 100644 --- a/test/functional/core/path_spec.lua +++ b/test/functional/core/path_spec.lua @@ -3,16 +3,16 @@ local clear = helpers.clear local eq = helpers.eq local eval = helpers.eval local command = helpers.command -local iswin = helpers.iswin local insert = helpers.insert local feed = helpers.feed +local is_os = helpers.is_os describe('path collapse', function() local targetdir local expected_path local function join_path(...) - local pathsep = (iswin() and '\\' or '/') + local pathsep = (is_os('win') and '\\' or '/') return table.concat({...}, pathsep) end diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index a32c801c97..1be5de6488 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -12,6 +12,7 @@ local eval = helpers.eval local exec_lua = helpers.exec_lua local feed = helpers.feed local funcs = helpers.funcs +local pesc = helpers.pesc local mkdir = helpers.mkdir local mkdir_p = helpers.mkdir_p local nvim_prog = helpers.nvim_prog @@ -20,11 +21,12 @@ local read_file = helpers.read_file local retry = helpers.retry local rmdir = helpers.rmdir local sleep = helpers.sleep -local iswin = helpers.iswin local startswith = helpers.startswith local write_file = helpers.write_file local meths = helpers.meths local alter_slashes = helpers.alter_slashes +local is_os = helpers.is_os +local dedent = helpers.dedent local testfile = 'Xtest_startuptime' after_each(function() @@ -41,10 +43,36 @@ describe('startup', function() it('--startuptime', function() clear({ args = {'--startuptime', testfile}}) - retry(nil, 1000, function() - assert_log('sourcing', testfile, 100) - assert_log("require%('vim%._editor'%)", testfile, 100) - end) + assert_log('sourcing', testfile, 100) + assert_log("require%('vim%._editor'%)", testfile, 100) + end) + + it('-D does not hang #12647', function() + clear() + local screen + screen = Screen.new(60, 7) + screen:attach() + command([[let g:id = termopen('"]]..nvim_prog.. + [[" -u NONE -i NONE --cmd "set noruler" -D')]]) + screen:expect([[ + ^ | + | + Entering Debug mode. Type "cont" to continue. | + nvim_exec() | + cmd: aunmenu * | + > | + | + ]]) + command([[call chansend(g:id, "cont\n")]]) + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + [No Name] | + | + | + ]]) end) end) @@ -57,6 +85,124 @@ describe('startup', function() os.remove('Xtest_startup_ttyout') end) + describe('-l Lua', function() + local function assert_l_out(expected, nvim_args, lua_args, script, input) + local args = { nvim_prog } + vim.list_extend(args, nvim_args or {}) + vim.list_extend(args, { '-l', (script or 'test/functional/fixtures/startup.lua') }) + vim.list_extend(args, lua_args or {}) + local out = funcs.system(args, input):gsub('\r\n', '\n') + return eq(dedent(expected), out) + end + + it('failure modes', function() + -- nvim -l <empty> + matches('nvim%.?e?x?e?: Argument missing after: "%-l"', funcs.system({ nvim_prog, '-l' })) + eq(1, eval('v:shell_error')) + end) + + it('os.exit() sets Nvim exitcode', function() + -- nvim -l foo.lua -arg1 -- a b c + assert_l_out([[ + bufs: + nvim args: 7 + lua args: { "-arg1", "--exitcode", "73", "--arg2", + [0] = "test/functional/fixtures/startup.lua" + }]], + {}, + { '-arg1', "--exitcode", "73", '--arg2' } + ) + eq(73, eval('v:shell_error')) + end) + + it('Lua-error sets Nvim exitcode', function() + eq(0, eval('v:shell_error')) + matches('E5113: .* my pearls!!', + funcs.system({ nvim_prog, '-l', 'test/functional/fixtures/startup-fail.lua' })) + eq(1, eval('v:shell_error')) + matches('E5113: .* %[string "error%("whoa"%)"%]:1: whoa', + funcs.system({ nvim_prog, '-l', '-' }, 'error("whoa")')) + eq(1, eval('v:shell_error')) + end) + + it('executes stdin "-"', function() + assert_l_out('arg0=- args=2 whoa', + nil, + { 'arg1', 'arg 2' }, + '-', + "print(('arg0=%s args=%d %s'):format(_G.arg[0], #_G.arg, 'whoa'))") + assert_l_out('biiig input: 1000042', + nil, + nil, + '-', + ('print("biiig input: "..("%s"):len())'):format(string.rep('x', (1000 * 1000) + 42))) + eq(0, eval('v:shell_error')) + end) + + it('sets _G.arg', function() + -- nvim -l foo.lua [args] + assert_l_out([[ + bufs: + nvim args: 7 + lua args: { "-arg1", "--arg2", "--", "arg3", + [0] = "test/functional/fixtures/startup.lua" + }]], + {}, + { '-arg1', '--arg2', '--', 'arg3' } + ) + eq(0, eval('v:shell_error')) + + -- nvim file1 file2 -l foo.lua -arg1 -- file3 file4 + assert_l_out([[ + bufs: file1 file2 + nvim args: 10 + lua args: { "-arg1", "arg 2", "--", "file3", "file4", + [0] = "test/functional/fixtures/startup.lua" + }]], + { 'file1', 'file2', }, + { '-arg1', 'arg 2', '--', 'file3', 'file4' } + ) + eq(0, eval('v:shell_error')) + + -- nvim -l foo.lua <vim args> + assert_l_out([[ + bufs: + nvim args: 5 + lua args: { "-c", "set wrap?", + [0] = "test/functional/fixtures/startup.lua" + }]], + {}, + { '-c', 'set wrap?' } + ) + eq(0, eval('v:shell_error')) + + -- nvim <vim args> -l foo.lua <vim args> + assert_l_out( + -- luacheck: ignore 611 (Line contains only whitespaces) + [[ + wrap + + bufs: + nvim args: 7 + lua args: { "-c", "set wrap?", + [0] = "test/functional/fixtures/startup.lua" + }]], + { '-c', 'set wrap?' }, + { '-c', 'set wrap?' } + ) + eq(0, eval('v:shell_error')) + end) + + it('disables swapfile/shada/config/plugins', function() + assert_l_out('updatecount=0 shadafile=NONE loadplugins=false scriptnames=1', + nil, + nil, + '-', + [[print(('updatecount=%d shadafile=%s loadplugins=%s scriptnames=%d'):format( + vim.o.updatecount, vim.o.shadafile, tostring(vim.o.loadplugins), math.max(1, #vim.fn.split(vim.fn.execute('scriptnames'),'\n'))))]]) + end) + end) + it('pipe at both ends: has("ttyin")==0 has("ttyout")==0', function() -- system() puts a pipe at both ends. local out = funcs.system({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless', @@ -65,6 +211,7 @@ describe('startup', function() '+q' }) eq('0 0', out) end) + it('with --embed: has("ttyin")==0 has("ttyout")==0', function() local screen = Screen.new(25, 3) -- Remote UI connected by --embed. @@ -76,10 +223,11 @@ describe('startup', function() 0 0 | ]]) end) + it('in a TTY: has("ttyin")==1 has("ttyout")==1', function() local screen = Screen.new(25, 4) screen:attach() - if iswin() then + if is_os('win') then command([[set shellcmdflag=/s\ /c shellxquote=\"]]) end -- Running in :terminal @@ -94,8 +242,9 @@ describe('startup', function() | ]]) end) + it('output to pipe: has("ttyin")==1 has("ttyout")==0', function() - if iswin() then + if is_os('win') then command([[set shellcmdflag=/s\ /c shellxquote=\"]]) end -- Running in :terminal @@ -110,8 +259,9 @@ describe('startup', function() read_file('Xtest_startup_ttyout')) end) end) + it('input from pipe: has("ttyin")==0 has("ttyout")==1', function() - if iswin() then + if is_os('win') then command([[set shellcmdflag=/s\ /c shellxquote=\"]]) end -- Running in :terminal @@ -127,10 +277,11 @@ describe('startup', function() read_file('Xtest_startup_ttyout')) end) end) + it('input from pipe (implicit) #7679', function() local screen = Screen.new(25, 4) screen:attach() - if iswin() then + if is_os('win') then command([[set shellcmdflag=/s\ /c shellxquote=\"]]) end -- Running in :terminal @@ -146,6 +297,7 @@ describe('startup', function() | ]]) end) + it('input from pipe + file args #7679', function() eq('ohyeah\r\n0 0 bufs=3', funcs.system({nvim_prog, '-n', '-u', 'NONE', '-i', 'NONE', '--headless', @@ -237,11 +389,11 @@ describe('startup', function() it('-es/-Es disables swapfile, user config #8540', function() for _,arg in ipairs({'-es', '-Es'}) do local out = funcs.system({nvim_prog, arg, - '+set swapfile? updatecount? shada?', + '+set swapfile? updatecount? shadafile?', "+put =execute('scriptnames')", '+%print'}) local line1 = string.match(out, '^.-\n') -- updatecount=0 means swapfile was disabled. - eq(" swapfile updatecount=0 shada=!,'100,<50,s10,h\n", line1) + eq(" swapfile updatecount=0 shadafile=\n", line1) -- Standard plugins were loaded, but not user config. eq('health.vim', string.match(out, 'health.vim')) eq(nil, string.match(out, 'init.vim')) @@ -265,11 +417,13 @@ describe('startup', function() { 'put =mode(1)', 'print', '' })) end) - it('fails on --embed with -es/-Es', function() - matches('nvim[.exe]*: %-%-embed conflicts with %-es/%-Es', + it('fails on --embed with -es/-Es/-l', function() + matches('nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l', funcs.system({nvim_prog, '--embed', '-es' })) - matches('nvim[.exe]*: %-%-embed conflicts with %-es/%-Es', + matches('nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l', funcs.system({nvim_prog, '--embed', '-Es' })) + matches('nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l', + funcs.system({nvim_prog, '--embed', '-l', 'foo.lua' })) end) it('does not crash if --embed is given twice', function() @@ -464,6 +618,19 @@ describe('startup', function() clear{args={'--cmd', 'set packpath^=test/functional/fixtures', '--cmd', [[ lua _G.test_loadorder = {} vim.cmd "runtime! filen.lua" ]]}, env={XDG_CONFIG_HOME='test/functional/fixtures/'}} eq({'ordinary', 'FANCY', 'FANCY after', 'ordinary after'}, exec_lua [[ return _G.test_loadorder ]]) end) + + it('window widths are correct when modelines set &columns with tabpages', function() + write_file('tab1.noft', 'vim: columns=81') + write_file('tab2.noft', 'vim: columns=81') + finally(function() + os.remove('tab1.noft') + os.remove('tab2.noft') + end) + clear({args = {'-p', 'tab1.noft', 'tab2.noft'}}) + eq(81, meths.win_get_width(0)) + command('tabnext') + eq(81, meths.win_get_width(0)) + end) end) describe('sysinit', function() @@ -518,32 +685,6 @@ describe('sysinit', function() eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))')) end) - it('fixed hang issue with -D (#12647)', function() - local screen - screen = Screen.new(60, 7) - screen:attach() - command([[let g:id = termopen('"]]..nvim_prog.. - [[" -u NONE -i NONE --cmd "set noruler" -D')]]) - screen:expect([[ - ^ | - Entering Debug mode. Type "cont" to continue. | - nvim_exec() | - cmd: aunmenu * | - > | - <" -u NONE -i NONE --cmd "set noruler" -D 1,1 All| - | - ]]) - command([[call chansend(g:id, "cont\n")]]) - screen:expect([[ - ^ | - ~ | - ~ | - [No Name] | - | - <" -u NONE -i NONE --cmd "set noruler" -D 1,0-1 All| - | - ]]) - end) end) describe('user config init', function() @@ -576,7 +717,81 @@ describe('user config init', function() eq(funcs.fnamemodify(init_lua_path, ':p'), eval('$MYVIMRC')) end) - describe 'with explicitly provided config'(function() + describe('with existing .exrc in cwd', function() + local exrc_path = '.exrc' + local xstate = 'Xstate' + + local function setup_exrc_file(filename) + exrc_path = filename + + if string.find(exrc_path, "%.lua$") then + write_file(exrc_path, string.format([[ + vim.g.exrc_file = "%s" + ]], exrc_path)) + else + write_file(exrc_path, string.format([[ + let g:exrc_file = "%s" + ]], exrc_path)) + end + end + + before_each(function() + write_file(init_lua_path, [[ + vim.o.exrc = true + vim.g.exrc_file = '---' + ]]) + mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim')) + end) + + after_each(function() + os.remove(exrc_path) + rmdir(xstate) + end) + + for _, filename in ipairs({ '.exrc', '.nvimrc', '.nvim.lua' }) do + it('loads ' .. filename, function () + setup_exrc_file(filename) + + clear{ args_rm = {'-u'}, env={ XDG_CONFIG_HOME=xconfig, XDG_STATE_HOME=xstate } } + -- The 'exrc' file is not trusted, and the prompt is skipped because there is no UI. + eq('---', eval('g:exrc_file')) + + local screen = Screen.new(50, 8) + screen:attach() + funcs.termopen({nvim_prog}) + screen:expect({ any = pesc('[i]gnore, (v)iew, (d)eny, (a)llow:') }) + -- `i` to enter Terminal mode, `a` to allow + feed('ia') + screen:expect([[ + | + ~ | + ~ | + ~ | + ~ | + [No Name] 0,0-1 All| + | + -- TERMINAL -- | + ]]) + feed(':echo g:exrc_file<CR>') + screen:expect(string.format([[ + | + ~ | + ~ | + ~ | + ~ | + [No Name] 0,0-1 All| + %s%s| + -- TERMINAL -- | + ]], filename, string.rep(' ', 50 - #filename))) + + clear{ args_rm = {'-u'}, env={ XDG_CONFIG_HOME=xconfig, XDG_STATE_HOME=xstate } } + -- The 'exrc' file is now trusted. + eq(filename, eval('g:exrc_file')) + end) + end + end) + + describe('with explicitly provided config', function() local custom_lua_path = table.concat({xhome, 'custom.lua'}, pathsep) before_each(function() write_file(custom_lua_path, [[ @@ -591,7 +806,7 @@ describe('user config init', function() end) end) - describe 'VIMRC also exists'(function() + describe('VIMRC also exists', function() before_each(function() write_file(table.concat({xconfig, 'nvim', 'init.vim'}, pathsep), [[ let g:vim_rc = 1 @@ -637,7 +852,7 @@ describe('runtime:', function() end) it('loads plugin/*.lua from start packages', function() - local plugin_path = table.concat({xconfig, 'nvim', 'pack', 'catagory', + local plugin_path = table.concat({xconfig, 'nvim', 'pack', 'category', 'start', 'test_plugin'}, pathsep) local plugin_folder_path = table.concat({plugin_path, 'plugin'}, pathsep) local plugin_file_path = table.concat({plugin_folder_path, 'plugin.lua'}, @@ -665,7 +880,7 @@ describe('runtime:', function() end) it('loads plugin/*.lua from site packages', function() - local nvimdata = iswin() and "nvim-data" or "nvim" + local nvimdata = is_os('win') and "nvim-data" or "nvim" local plugin_path = table.concat({xdata, nvimdata, 'site', 'pack', 'xa', 'start', 'yb'}, pathsep) local plugin_folder_path = table.concat({plugin_path, 'plugin'}, pathsep) local plugin_after_path = table.concat({plugin_path, 'after', 'plugin'}, pathsep) diff --git a/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua index 6cdac3c079..22857efe5b 100644 --- a/test/functional/editor/completion_spec.lua +++ b/test/functional/editor/completion_spec.lua @@ -935,6 +935,9 @@ describe('completion', function() eq({'api'}, funcs.getcompletion('vim.ap', 'lua')) eq({'tbl_filter'}, funcs.getcompletion('vim.tbl_fil', 'lua')) eq({'vim'}, funcs.getcompletion('print(vi', 'lua')) + -- fuzzy completion is not supported, so the result should be the same + command('set wildoptions+=fuzzy') + eq({'vim'}, funcs.getcompletion('vi', 'lua')) end) end) @@ -1029,7 +1032,8 @@ describe('completion', function() ]]) end) - it('TextChangedP autocommand', function() + -- oldtest: Test_ChangedP() + it('TextChangedI and TextChangedP autocommands', function() curbufmeths.set_lines(0, 1, false, { 'foo', 'bar', 'foobar'}) source([[ set complete=. completeopt=menuone @@ -1128,6 +1132,49 @@ describe('completion', function() call cursor(4, 1) ]]) + -- v:event.size should be set with ext_popupmenu #20646 + screen:set_option('ext_popupmenu', true) + feed('Sf<C-N>') + screen:expect({grid = [[ + foo | + bar | + foobar | + f^ | + {0:~ }| + {0:~ }| + {0:~ }| + {3:-- Keyword completion (^N^P) }{5:Back at original} | + ]], popupmenu = { + anchor = { 1, 3, 0 }, + items = { { "foo", "", "", "" }, { "foobar", "", "", "" } }, + pos = -1 + }}) + eq({completed_item = {}, width = 0, + height = 2, size = 2, + col = 0, row = 4, scrollbar = false}, + eval('g:event')) + feed('oob') + screen:expect({grid = [[ + foo | + bar | + foobar | + foob^ | + {0:~ }| + {0:~ }| + {0:~ }| + {3:-- Keyword completion (^N^P) }{5:Back at original} | + ]], popupmenu = { + anchor = { 1, 3, 0 }, + items = { { "foobar", "", "", "" } }, + pos = -1 + }}) + eq({completed_item = {}, width = 0, + height = 1, size = 1, + col = 0, row = 4, scrollbar = false}, + eval('g:event')) + feed('<Esc>') + screen:set_option('ext_popupmenu', false) + feed('Sf<C-N>') screen:expect([[ foo | diff --git a/test/functional/editor/mark_spec.lua b/test/functional/editor/mark_spec.lua index 2440867c6e..b3b190ef79 100644 --- a/test/functional/editor/mark_spec.lua +++ b/test/functional/editor/mark_spec.lua @@ -40,59 +40,59 @@ describe('named marks', function() it("errors when set out of range with :mark", function() command("edit " .. file1) local err = pcall_err(helpers.exec_capture, "1000mark x") - eq("Vim(mark):E16: Invalid range: 1000mark x", err) + eq("nvim_exec(): Vim(mark):E16: Invalid range: 1000mark x", err) end) it("errors when set out of range with :k", function() command("edit " .. file1) local err = pcall_err(helpers.exec_capture, "1000kx") - eq("Vim(k):E16: Invalid range: 1000kx", err) + eq("nvim_exec(): Vim(k):E16: Invalid range: 1000kx", err) end) it("errors on unknown mark name with :mark", function() command("edit " .. file1) local err = pcall_err(helpers.exec_capture, "mark #") - eq("Vim(mark):E191: Argument must be a letter or forward/backward quote", err) + eq("nvim_exec(): Vim(mark):E191: Argument must be a letter or forward/backward quote", err) end) it("errors on unknown mark name with '", function() command("edit " .. file1) local err = pcall_err(helpers.exec_capture, "normal! '#") - eq("Vim(normal):E78: Unknown mark", err) + eq("nvim_exec(): Vim(normal):E78: Unknown mark", err) end) it("errors on unknown mark name with `", function() command("edit " .. file1) local err = pcall_err(helpers.exec_capture, "normal! `#") - eq("Vim(normal):E78: Unknown mark", err) + eq("nvim_exec(): Vim(normal):E78: Unknown mark", err) end) it("errors when moving to a mark that is not set with '", function() command("edit " .. file1) local err = pcall_err(helpers.exec_capture, "normal! 'z") - eq("Vim(normal):E20: Mark not set", err) + eq("nvim_exec(): Vim(normal):E20: Mark not set", err) err = pcall_err(helpers.exec_capture, "normal! '.") - eq("Vim(normal):E20: Mark not set", err) + eq("nvim_exec(): Vim(normal):E20: Mark not set", err) end) it("errors when moving to a mark that is not set with `", function() command("edit " .. file1) local err = pcall_err(helpers.exec_capture, "normal! `z") - eq("Vim(normal):E20: Mark not set", err) + eq("nvim_exec(): Vim(normal):E20: Mark not set", err) err = pcall_err(helpers.exec_capture, "normal! `>") - eq("Vim(normal):E20: Mark not set", err) + eq("nvim_exec(): Vim(normal):E20: Mark not set", err) end) it("errors when moving to a global mark that is not set with '", function() command("edit " .. file1) local err = pcall_err(helpers.exec_capture, "normal! 'Z") - eq("Vim(normal):E20: Mark not set", err) + eq("nvim_exec(): Vim(normal):E20: Mark not set", err) end) it("errors when moving to a global mark that is not set with `", function() command("edit " .. file1) local err = pcall_err(helpers.exec_capture, "normal! `Z") - eq("Vim(normal):E20: Mark not set", err) + eq("nvim_exec(): Vim(normal):E20: Mark not set", err) end) it("can move to them using '", function() @@ -153,7 +153,7 @@ describe('named marks', function() command("next") command("bw! " .. file1 ) local err = pcall_err(helpers.exec_capture, "normal! 'A") - eq("Vim(normal):E92: Buffer 1 not found", err) + eq("nvim_exec(): Vim(normal):E92: Buffer 1 not found", err) os.remove(file1) end) @@ -330,7 +330,7 @@ describe('named marks view', function() os.remove(file2) end) - it('is restored', function() + it('is restored in normal mode but not op-pending mode', function() local screen = Screen.new(5, 8) screen:attach() command("edit " .. file1) @@ -358,6 +358,18 @@ describe('named marks view', function() 8 line | | ]]) + -- not in op-pending mode #20886 + feed("ggj=`a") + screen:expect([[ + 1 line | + ^2 line | + 3 line | + 4 line | + 5 line | + 6 line | + 7 line | + | + ]]) end) it('is restored across files', function() diff --git a/test/functional/editor/tabpage_spec.lua b/test/functional/editor/tabpage_spec.lua index f8ca6986bd..a7f629a76b 100644 --- a/test/functional/editor/tabpage_spec.lua +++ b/test/functional/editor/tabpage_spec.lua @@ -144,4 +144,10 @@ describe('tabpage', function() command(' silent :keepalt :: ::: silent! -2 tabmove') eq(1, funcs.nvim_tabpage_get_number(0)) end) + + it(':tabs does not overflow IObuff with long path with comma #20850', function() + meths.buf_set_name(0, ('x'):rep(1024) .. ',' .. ('x'):rep(1024)) + command('tabs') + assert_alive() + end) end) diff --git a/test/functional/editor/undo_spec.lua b/test/functional/editor/undo_spec.lua index a041428cdc..d66ab352ef 100644 --- a/test/functional/editor/undo_spec.lua +++ b/test/functional/editor/undo_spec.lua @@ -9,6 +9,8 @@ local feed = helpers.feed local feed_command = helpers.feed_command local insert = helpers.insert local funcs = helpers.funcs +local exec = helpers.exec +local exec_lua = helpers.exec_lua local function lastmessage() local messages = funcs.split(funcs.execute('messages'), '\n') @@ -67,6 +69,79 @@ describe('u CTRL-R g- g+', function() undo_and_redo(4, 'u', '<C-r>', '1') undo_and_redo(4, 'g-', 'g+', '1') end) + + describe('undo works correctly when writing in Insert mode', function() + before_each(function() + exec([[ + edit Xtestfile.txt + set undolevels=100 undofile + write + ]]) + end) + + after_each(function() + command('bwipe!') + os.remove('Xtestfile.txt') + os.remove('Xtestfile.txt.un~') + end) + + -- oldtest: Test_undo_after_write() + it('using <Cmd> mapping', function() + command('imap . <Cmd>write<CR>') + feed('Otest.<CR>boo!!!<Esc>') + expect([[ + test + boo!!! + ]]) + + feed('u') + expect([[ + test + ]]) + + feed('u') + expect('') + end) + + it('using Lua mapping', function() + exec_lua([[ + vim.api.nvim_set_keymap('i', '.', '', {callback = function() + vim.cmd('write') + end}) + ]]) + feed('Otest.<CR>boo!!!<Esc>') + expect([[ + test + boo!!! + ]]) + + feed('u') + expect([[ + test + ]]) + + feed('u') + expect('') + end) + + it('using RPC call', function() + feed('Otest') + command('write') + feed('<CR>boo!!!<Esc>') + expect([[ + test + boo!!! + ]]) + + feed('u') + expect([[ + test + ]]) + + feed('u') + expect('') + end) + end) end) describe(':undo! command', function() diff --git a/test/functional/ex_cmds/cd_spec.lua b/test/functional/ex_cmds/cd_spec.lua index 42a811f5da..5ed71651c7 100644 --- a/test/functional/ex_cmds/cd_spec.lua +++ b/test/functional/ex_cmds/cd_spec.lua @@ -9,6 +9,8 @@ local clear = helpers.clear local command = helpers.command local exc_exec = helpers.exc_exec local pathsep = helpers.get_pathsep() +local skip = helpers.skip +local is_os = helpers.is_os -- These directories will be created for testing local directories = { @@ -279,9 +281,7 @@ describe("getcwd()", function () end) it("returns empty string if working directory does not exist", function() - if helpers.iswin() then - return - end + skip(is_os('win')) command("cd "..directories.global) command("call delete('../"..directories.global.."', 'd')") eq("", helpers.eval("getcwd()")) diff --git a/test/functional/ex_cmds/map_spec.lua b/test/functional/ex_cmds/map_spec.lua index c6bdd017bd..ec912053b2 100644 --- a/test/functional/ex_cmds/map_spec.lua +++ b/test/functional/ex_cmds/map_spec.lua @@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen') local eq = helpers.eq local exec = helpers.exec +local exec_capture = helpers.exec_capture local feed = helpers.feed local meths = helpers.meths local clear = helpers.clear @@ -30,12 +31,12 @@ describe(':*map', function() expect('-foo-') end) - it('shows <nop> as mapping rhs', function() + it('shows <Nop> as mapping rhs', function() command('nmap asdf <Nop>') eq([[ n asdf <Nop>]], - helpers.exec_capture('nmap asdf')) + exec_capture('nmap asdf')) end) it('mappings with description can be filtered', function() @@ -48,7 +49,7 @@ n asdf3 qwert do the other thing n asdf1 qwert do the one thing]], - helpers.exec_capture('filter the nmap')) + exec_capture('filter the nmap')) end) it('<Plug> mappings ignore nore', function() @@ -84,6 +85,12 @@ n asdf1 qwert eq(2, meths.eval('x')) eq('Some te', eval("getline('.')")) end) + + it(':unmap with rhs works when lhs is in another bucket #21530', function() + command('map F <Plug>Foo') + command('unmap <Plug>Foo') + eq('\nNo mapping found', exec_capture('map F')) + end) end) describe('Screen', function() @@ -161,6 +168,7 @@ describe('Screen', function() ]]) end) + -- oldtest: Test_expr_map_restore_cursor() it('cursor is restored after :map <expr> which redraws statusline vim-patch:8.1.2336', function() exec([[ call setline(1, ['one', 'two', 'three']) @@ -246,6 +254,7 @@ describe('Screen', function() ]]) end) + -- oldtest: Test_map_listing() it('listing mappings clears command line vim-patch:8.2.4401', function() screen:try_resize(40, 5) command('nmap a b') diff --git a/test/functional/ex_cmds/mksession_spec.lua b/test/functional/ex_cmds/mksession_spec.lua index ee8da2932d..0a0c7ca410 100644 --- a/test/functional/ex_cmds/mksession_spec.lua +++ b/test/functional/ex_cmds/mksession_spec.lua @@ -5,7 +5,6 @@ local Screen = require('test.functional.ui.screen') local clear = helpers.clear local command = helpers.command local get_pathsep = helpers.get_pathsep -local iswin = helpers.iswin local eq = helpers.eq local neq = helpers.neq local funcs = helpers.funcs @@ -14,6 +13,8 @@ local pesc = helpers.pesc local rmdir = helpers.rmdir local sleep = helpers.sleep local meths = helpers.meths +local skip = helpers.skip +local is_os = helpers.is_os local file_prefix = 'Xtest-functional-ex_cmds-mksession_spec' @@ -177,7 +178,7 @@ describe(':mksession', function() command('cd ' .. cwd_dir) command('mksession ' .. session_path) command('%bwipeout!') - if iswin() then + if is_os('win') then sleep(100) -- Make sure all child processes have exited. end @@ -188,16 +189,13 @@ describe(':mksession', function() local expected_cwd = cwd_dir .. '/' .. tab_dir matches('^term://' .. pesc(expected_cwd) .. '//%d+:', funcs.expand('%')) command('%bwipeout!') - if iswin() then + if is_os('win') then sleep(100) -- Make sure all child processes have exited. end end) it('restores CWD for :terminal buffer at root directory #16988', function() - if iswin() then - pending('N/A for Windows') - return - end + skip(is_os('win'), 'N/A for Windows') local screen local cwd_dir = funcs.fnamemodify('.', ':p:~'):gsub([[[\/]*$]], '') diff --git a/test/functional/ex_cmds/quickfix_commands_spec.lua b/test/functional/ex_cmds/quickfix_commands_spec.lua index 94b7fa1a84..4d9d8eeb90 100644 --- a/test/functional/ex_cmds/quickfix_commands_spec.lua +++ b/test/functional/ex_cmds/quickfix_commands_spec.lua @@ -1,5 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local feed = helpers.feed local eq = helpers.eq local clear = helpers.clear local funcs = helpers.funcs @@ -123,3 +125,22 @@ describe('quickfix', function() os.remove(file) end) end) + +it(':vimgrep can specify Unicode pattern without delimiters', function() + eq('Vim(vimgrep):E480: No match: →', exc_exec('vimgrep → test/functional/fixtures/tty-test.c')) + local screen = Screen.new(40, 6) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {reverse = true}, -- IncSearch + }) + screen:attach() + feed('i→<Esc>:vimgrep →') + screen:expect([[ + {1:→} | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + :vimgrep →^ | + ]]) +end) diff --git a/test/functional/ex_cmds/source_spec.lua b/test/functional/ex_cmds/source_spec.lua index 10ebefd8cd..64c3464be7 100644 --- a/test/functional/ex_cmds/source_spec.lua +++ b/test/functional/ex_cmds/source_spec.lua @@ -14,9 +14,9 @@ local eval = helpers.eval local exec_capture = helpers.exec_capture local neq = helpers.neq local matches = helpers.matches -local iswin = helpers.iswin local mkdir = helpers.mkdir local rmdir = helpers.rmdir +local is_os = helpers.is_os describe(':source', function() before_each(function() @@ -44,7 +44,7 @@ describe(':source', function() end) it("changing 'shellslash' changes the result of expand()", function() - if not iswin() then + if not is_os('win') then pending("'shellslash' only works on Windows") return end diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua index 69404039ff..8eed00c973 100644 --- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua +++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua @@ -1,12 +1,14 @@ local Screen = require('test.functional.ui.screen') local helpers = require('test.functional.helpers')(after_each) local lfs = require('lfs') +local luv = require('luv') local eq, eval, expect, exec = helpers.eq, helpers.eval, helpers.expect, helpers.exec local assert_alive = helpers.assert_alive local clear = helpers.clear local command = helpers.command local feed = helpers.feed +local funcs = helpers.funcs local nvim_prog = helpers.nvim_prog local ok = helpers.ok local rmdir = helpers.rmdir @@ -100,7 +102,7 @@ describe('swapfile detection', function() -- attempt to create a swapfile in different directory. local init = [[ set directory^=]]..swapdir:gsub([[\]], [[\\]])..[[// - set swapfile fileformat=unix undolevels=-1 hidden + set swapfile fileformat=unix nomodified undolevels=-1 nohidden ]] before_each(function() nvim0 = spawn(new_argv()) @@ -263,4 +265,79 @@ describe('swapfile detection', function() ]]) nvim2:close() end) + + -- oldtest: Test_nocatch_process_still_running() + it('allows deleting swapfile created before boot vim-patch:8.2.2586', function() + local screen = Screen.new(75, 30) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {bold = true, foreground = Screen.colors.SeaGreen}, -- MoreMsg + [2] = {background = Screen.colors.Red, foreground = Screen.colors.White}, -- ErrorMsg + }) + screen:attach() + + exec(init) + command('set nohidden') + + exec([=[ + " Make a copy of the current swap file to "Xswap". + " Return the name of the swap file. + func CopySwapfile() + preserve + " get the name of the swap file + let swname = split(execute("swapname"))[0] + let swname = substitute(swname, '[[:blank:][:cntrl:]]*\(.\{-}\)[[:blank:][:cntrl:]]*$', '\1', '') + " make a copy of the swap file in Xswap + set binary + exe 'sp ' . fnameescape(swname) + w! Xswap + set nobinary + return swname + endfunc + ]=]) + + -- Edit a file and grab its swapfile. + exec([[ + edit Xswaptest + call setline(1, ['a', 'b', 'c']) + ]]) + local swname = funcs.CopySwapfile() + + -- Forget we edited this file + exec([[ + new + only! + bwipe! Xswaptest + ]]) + + os.rename('Xswap', swname) + + feed(':edit Xswaptest<CR>') + screen:expect({any = table.concat({ + pesc('{2:E325: ATTENTION}'), + 'file name: .*Xswaptest', + 'process ID: %d* %(STILL RUNNING%)', + pesc('{1:[O]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort: }^'), + }, '.*')}) + + feed('e') + + -- Forget we edited this file + exec([[ + new + only! + bwipe! Xswaptest + ]]) + + -- pretend that the swapfile was created before boot + lfs.touch(swname, os.time() - luv.uptime() - 10) + + feed(':edit Xswaptest<CR>') + screen:expect({any = table.concat({ + pesc('{2:E325: ATTENTION}'), + pesc('{1:[O]pen Read-Only, (E)dit anyway, (R)ecover, (D)elete it, (Q)uit, (A)bort: }^'), + }, '.*')}) + + feed('e') + end) end) diff --git a/test/functional/ex_cmds/trust_spec.lua b/test/functional/ex_cmds/trust_spec.lua new file mode 100644 index 0000000000..10ee02a790 --- /dev/null +++ b/test/functional/ex_cmds/trust_spec.lua @@ -0,0 +1,176 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local eq = helpers.eq +local clear = helpers.clear +local command = helpers.command +local pathsep = helpers.get_pathsep() +local is_os = helpers.is_os +local funcs = helpers.funcs + +describe(':trust', function() + local xstate = 'Xstate' + + setup(function() + helpers.mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim')) + end) + + teardown(function() + helpers.rmdir(xstate) + end) + + before_each(function() + helpers.write_file('test_file', 'test') + clear{env={XDG_STATE_HOME=xstate}} + end) + + after_each(function() + os.remove('test_file') + end) + + it('trust then deny then remove a file using current buffer', function() + local screen = Screen.new(80, 8) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + }) + + local cwd = funcs.getcwd() + local hash = funcs.sha256(helpers.read_file('test_file')) + + command('edit test_file') + command('trust') + screen:expect([[ + ^test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" trusted.{MATCH:%s+}| + ]]) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('%s %s', hash, cwd .. pathsep .. 'test_file'), vim.trim(trust)) + + command('trust ++deny') + screen:expect([[ + ^test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" denied.{MATCH:%s+}| + ]]) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', cwd .. pathsep .. 'test_file'), vim.trim(trust)) + + command('trust ++remove') + screen:expect([[ + ^test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" removed.{MATCH:%s+}| + ]]) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format(''), vim.trim(trust)) + end) + + it('deny then trust then remove a file using current buffer', function() + local screen = Screen.new(80, 8) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + }) + + local cwd = funcs.getcwd() + local hash = funcs.sha256(helpers.read_file('test_file')) + + command('edit test_file') + command('trust ++deny') + screen:expect([[ + ^test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" denied.{MATCH:%s+}| + ]]) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', cwd .. pathsep .. 'test_file'), vim.trim(trust)) + + command('trust') + screen:expect([[ + ^test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" trusted.{MATCH:%s+}| + ]]) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('%s %s', hash, cwd .. pathsep .. 'test_file'), vim.trim(trust)) + + command('trust ++remove') + screen:expect([[ + ^test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" removed.{MATCH:%s+}| + ]]) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format(''), vim.trim(trust)) + end) + + it('deny then remove a file using file path', function() + local screen = Screen.new(80, 8) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + }) + + local cwd = funcs.getcwd() + + command('trust ++deny test_file') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" denied.{MATCH:%s+}| + ]]) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', cwd .. pathsep .. 'test_file'), vim.trim(trust)) + + command('trust ++remove test_file') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + "]] .. cwd .. pathsep .. [[test_file" removed.{MATCH:%s+}| + ]]) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format(''), vim.trim(trust)) + end) +end) diff --git a/test/functional/ex_cmds/verbose_spec.lua b/test/functional/ex_cmds/verbose_spec.lua index e6f67ef18e..000e746f1c 100644 --- a/test/functional/ex_cmds/verbose_spec.lua +++ b/test/functional/ex_cmds/verbose_spec.lua @@ -77,7 +77,7 @@ nohlsearch script_location), result) end) - it('"Last set" for keymap set by Lua', function() + it('"Last set" for mapping set by Lua', function() local result = exec_capture(':verbose map <leader>key1') eq(string.format([[ @@ -86,7 +86,7 @@ n \key1 * :echo "test"<CR> script_location), result) end) - it('"Last set" for keymap set by vim.keymap', function() + it('"Last set" for mapping set by vim.keymap', function() local result = exec_capture(':verbose map <leader>key2') eq(string.format([[ diff --git a/test/functional/ex_cmds/write_spec.lua b/test/functional/ex_cmds/write_spec.lua index 14035a4341..1ccd27875e 100644 --- a/test/functional/ex_cmds/write_spec.lua +++ b/test/functional/ex_cmds/write_spec.lua @@ -8,8 +8,9 @@ local command = helpers.command local feed_command = helpers.feed_command local funcs = helpers.funcs local meths = helpers.meths -local iswin = helpers.iswin -local uname = helpers.uname +local skip = helpers.skip +local is_os = helpers.is_os +local is_ci = helpers.is_ci local fname = 'Xtest-functional-ex_cmds-write' local fname_bak = fname .. '~' @@ -20,6 +21,9 @@ describe(':write', function() os.remove('test_bkc_file.txt') os.remove('test_bkc_link.txt') os.remove('test_fifo') + os.remove('test/write/p_opt.txt') + os.remove('test/write') + os.remove('test') os.remove(fname) os.remove(fname_bak) os.remove(fname_broken) @@ -35,7 +39,7 @@ describe(':write', function() it('&backupcopy=auto preserves symlinks', function() command('set backupcopy=auto') write_file('test_bkc_file.txt', 'content0') - if iswin() then + if is_os('win') then command("silent !mklink test_bkc_link.txt test_bkc_file.txt") else command("silent !ln -s test_bkc_file.txt test_bkc_link.txt") @@ -53,12 +57,10 @@ describe(':write', function() end) it('&backupcopy=no replaces symlink with new file', function() - if uname() == 'freebsd' then - pending('Failing FreeBSD test') - end + skip(is_ci('cirrus')) command('set backupcopy=no') write_file('test_bkc_file.txt', 'content0') - if iswin() then + if is_os('win') then command("silent !mklink test_bkc_link.txt test_bkc_file.txt") else command("silent !ln -s test_bkc_file.txt test_bkc_link.txt") @@ -77,7 +79,7 @@ describe(':write', function() it("appends FIFO file", function() -- mkfifo creates read-only .lnk files on Windows - if iswin() or eval("executable('mkfifo')") == 0 then + if is_os('win') or eval("executable('mkfifo')") == 0 then pending('missing "mkfifo" command') end @@ -94,14 +96,36 @@ describe(':write', function() fifo:close() end) - it('errors out correctly', function() - if uname() == 'freebsd' then - pending('Failing FreeBSD test') + it("++p creates missing parent directories", function() + eq(0, eval("filereadable('p_opt.txt')")) + command("write ++p p_opt.txt") + eq(1, eval("filereadable('p_opt.txt')")) + os.remove("p_opt.txt") + + eq(0, eval("filereadable('p_opt.txt')")) + command("write ++p ./p_opt.txt") + eq(1, eval("filereadable('p_opt.txt')")) + os.remove("p_opt.txt") + + eq(0, eval("filereadable('test/write/p_opt.txt')")) + command("write ++p test/write/p_opt.txt") + eq(1, eval("filereadable('test/write/p_opt.txt')")) + + eq(('Vim(write):E32: No file name'), pcall_err(command, 'write ++p test_write/')) + if not is_os('win') then + eq(('Vim(write):E17: "'..funcs.fnamemodify('.', ':p:h')..'" is a directory'), + pcall_err(command, 'write ++p .')) + eq(('Vim(write):E17: "'..funcs.fnamemodify('.', ':p:h')..'" is a directory'), + pcall_err(command, 'write ++p ./')) end + end) + + it('errors out correctly', function() + skip(is_ci('cirrus')) command('let $HOME=""') eq(funcs.fnamemodify('.', ':p:h'), funcs.fnamemodify('.', ':p:h:~')) -- Message from check_overwrite - if not iswin() then + if not is_os('win') then eq(('Vim(write):E17: "'..funcs.fnamemodify('.', ':p:h')..'" is a directory'), pcall_err(command, 'write .')) end @@ -120,7 +144,7 @@ describe(':write', function() funcs.setfperm(fname, 'r--------') eq('Vim(write):E505: "Xtest-functional-ex_cmds-write" is read-only (add ! to override)', pcall_err(command, 'write')) - if iswin() then + if is_os('win') then eq(0, os.execute('del /q/f ' .. fname)) eq(0, os.execute('rd /q/s ' .. fname_bak)) else @@ -128,8 +152,7 @@ describe(':write', function() eq(true, os.remove(fname_bak)) end write_file(fname_bak, 'TTYX') - -- FIXME: exc_exec('write!') outputs 0 in Windows - if iswin() then return end + skip(is_os('win'), [[FIXME: exc_exec('write!') outputs 0 in Windows]]) lfs.link(fname_bak .. ('/xxxxx'):rep(20), fname, true) eq('Vim(write):E166: Can\'t open linked file for writing', pcall_err(command, 'write!')) diff --git a/test/functional/ex_cmds/wviminfo_spec.lua b/test/functional/ex_cmds/wviminfo_spec.lua index 7c00daf1d7..861a977ea6 100644 --- a/test/functional/ex_cmds/wviminfo_spec.lua +++ b/test/functional/ex_cmds/wviminfo_spec.lua @@ -3,14 +3,14 @@ local lfs = require('lfs') local clear = helpers.clear local command, eq, neq, write_file = helpers.command, helpers.eq, helpers.neq, helpers.write_file -local iswin = helpers.iswin local read_file = helpers.read_file +local is_os = helpers.is_os describe(':wshada', function() local shada_file = 'wshada_test' before_each(function() - clear{args={'-i', iswin() and 'nul' or '/dev/null', + clear{args={'-i', is_os('win') and 'nul' or '/dev/null', -- Need 'swapfile' for these tests. '--cmd', 'set swapfile undodir=. directory=. viewdir=. backupdir=. belloff= noshowcmd noruler'}, args_rm={'-n', '-i', '--cmd'}} diff --git a/test/functional/fixtures/CMakeLists.txt b/test/functional/fixtures/CMakeLists.txt index a5410c2f8c..6e64b1e4dc 100644 --- a/test/functional/fixtures/CMakeLists.txt +++ b/test/functional/fixtures/CMakeLists.txt @@ -1,14 +1,23 @@ -add_executable(tty-test EXCLUDE_FROM_ALL tty-test.c) -target_link_libraries(tty-test ${LIBUV_LIBRARIES}) +add_library(test_lib INTERFACE) +if(MINGW) + target_link_libraries(test_lib INTERFACE -municode) +endif() +if(WIN32) + target_compile_definitions(test_lib INTERFACE MSWIN) +endif() +target_link_libraries(test_lib INTERFACE nvim) +add_executable(tty-test EXCLUDE_FROM_ALL tty-test.c) add_executable(shell-test EXCLUDE_FROM_ALL shell-test.c) # Fake pwsh (powershell) for testing make_filter_cmd(). #16271 add_executable(pwsh-test EXCLUDE_FROM_ALL shell-test.c) add_executable(printargs-test EXCLUDE_FROM_ALL printargs-test.c) add_executable(printenv-test EXCLUDE_FROM_ALL printenv-test.c) -if(MINGW) - set_target_properties(printenv-test PROPERTIES LINK_FLAGS -municode) -endif() - add_executable(streams-test EXCLUDE_FROM_ALL streams-test.c) -target_link_libraries(streams-test ${LIBUV_LIBRARIES}) + +target_link_libraries(tty-test PRIVATE test_lib) +target_link_libraries(shell-test PRIVATE test_lib) +target_link_libraries(pwsh-test PRIVATE test_lib) +target_link_libraries(printargs-test PRIVATE test_lib) +target_link_libraries(printenv-test PRIVATE test_lib) +target_link_libraries(streams-test PRIVATE test_lib) diff --git a/test/functional/fixtures/startup-fail.lua b/test/functional/fixtures/startup-fail.lua new file mode 100644 index 0000000000..adcfe2a201 --- /dev/null +++ b/test/functional/fixtures/startup-fail.lua @@ -0,0 +1,7 @@ +-- Test "nvim -l foo.lua …" with a Lua error. + +local function main() + error('my pearls!!') +end + +main() diff --git a/test/functional/fixtures/startup.lua b/test/functional/fixtures/startup.lua new file mode 100644 index 0000000000..d0e60309bd --- /dev/null +++ b/test/functional/fixtures/startup.lua @@ -0,0 +1,35 @@ +-- Test "nvim -l foo.lua …" + +local function printbufs() + local bufs = '' + for _, v in ipairs(vim.api.nvim_list_bufs()) do + local b = vim.fn.bufname(v) + if b:len() > 0 then + bufs = ('%s %s'):format(bufs, b) + end + end + print(('bufs:%s'):format(bufs)) +end + +local function parseargs(args) + local exitcode = nil + for i = 1, #args do + if args[i] == '--exitcode' then + exitcode = tonumber(args[i + 1]) + end + end + return exitcode +end + +local function main() + printbufs() + print('nvim args:', #vim.v.argv) + print('lua args:', vim.inspect(_G.arg)) + + local exitcode = parseargs(_G.arg) + if type(exitcode) == 'number' then + os.exit(exitcode) + end +end + +main() diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 723d2ccfa4..6400db9f87 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -1,5 +1,4 @@ require('coxpcall') -local busted = require('busted') local luv = require('luv') local lfs = require('lfs') local mpack = require('mpack') @@ -54,7 +53,6 @@ if module.nvim_dir == module.nvim_prog then module.nvim_dir = "." end -local iswin = global_helpers.iswin local prepend_argv if os.getenv('VALGRIND') then @@ -112,6 +110,10 @@ function module.request(method, ...) return rv end +function module.request_lua(method, ...) + return module.exec_lua([[return vim.api[...](select(2, ...))]], method, ...) +end + function module.next_msg(timeout) return session:next_message(timeout and timeout or 10000) end @@ -240,7 +242,7 @@ function module.run_session(lsession, request_cb, notification_cb, setup_cb, tim end loop_running = true - session:run(on_request, on_notification, on_setup, timeout) + lsession:run(on_request, on_notification, on_setup, timeout) loop_running = false if last_error then local err = last_error @@ -248,7 +250,7 @@ function module.run_session(lsession, request_cb, notification_cb, setup_cb, tim error(err) end - return session.eof_err + return lsession.eof_err end function module.run(request_cb, notification_cb, setup_cb, timeout) @@ -299,12 +301,18 @@ function module.eval(expr) return module.request('nvim_eval', expr) end --- Executes a VimL function. +-- Executes a VimL function via RPC. -- Fails on VimL error, but does not update v:errmsg. function module.call(name, ...) return module.request('nvim_call_function', name, {...}) end +-- Executes a VimL function via Lua. +-- Fails on VimL error, but does not update v:errmsg. +function module.call_lua(name, ...) + return module.exec_lua([[return vim.call(...)]], name, ...) +end + -- Sends user input to Nvim. -- Does not fail on VimL error, but v:errmsg will be updated. local function nvim_feed(input) @@ -388,9 +396,12 @@ local function remove_args(args, args_rm) return new_args end -function module.check_close(old_session) +function module.check_close() + if not session then + return + end local start_time = luv.now() - old_session:close() + session:close() luv.update_time() -- Update cached value of luv.now() (libuv: uv_now()). local end_time = luv.now() local delta = end_time - start_time @@ -399,12 +410,13 @@ function module.check_close(old_session) "This indicates a likely problem with the test even if it passed!\n") io.stdout:flush() end + session = nil end --- @param io_extra used for stdin_fd, see :help ui-option function module.spawn(argv, merge, env, keep, io_extra) - if session and not keep then - module.check_close(session) + if not keep then + module.check_close() end local child_stream = ChildProcessStream.spawn( @@ -421,28 +433,6 @@ function module.connect(file_or_address) return Session.new(stream) end --- Calls fn() until it succeeds, up to `max` times or until `max_ms` --- milliseconds have passed. -function module.retry(max, max_ms, fn) - assert(max == nil or max > 0) - assert(max_ms == nil or max_ms > 0) - local tries = 1 - local timeout = (max_ms and max_ms or 10000) - local start_time = luv.now() - while true do - local status, result = pcall(fn) - if status then - return result - end - luv.update_time() -- Update cached value of luv.now() (libuv: uv_now()). - if (max and tries >= max) or (luv.now() - start_time > timeout) then - busted.fail(string.format("retry() attempts: %d\n%s", tries, tostring(result)), 2) - end - tries = tries + 1 - luv.sleep(20) -- Avoid hot loop... - end -end - -- Starts a new global Nvim session. -- -- Parameters are interpreted as startup args, OR a map with these keys: @@ -456,8 +446,14 @@ end -- clear('-e') -- clear{args={'-e'}, args_rm={'-i'}, env={TERM=term}} function module.clear(...) + module.set_session(module.spawn_argv(false, ...)) +end + +-- same params as clear, but does returns the session instead +-- of replacing the default session +function module.spawn_argv(keep, ...) local argv, env, io_extra = module.new_argv(...) - module.set_session(module.spawn(argv, nil, env, nil, io_extra)) + return module.spawn(argv, nil, env, keep, io_extra) end -- Builds an argument list for use in clear(). @@ -547,7 +543,7 @@ function module.source(code) end function module.has_powershell() - return module.eval('executable("'..(iswin() and 'powershell' or 'pwsh')..'")') == 1 + return module.eval('executable("'..(is_os('win') and 'powershell' or 'pwsh')..'")') == 1 end --- Sets Nvim shell to powershell. @@ -560,9 +556,9 @@ function module.set_shell_powershell(fake) if not fake then assert(found) end - local shell = found and (iswin() and 'powershell' or 'pwsh') or module.testprg('pwsh-test') + local shell = found and (is_os('win') and 'powershell' or 'pwsh') or module.testprg('pwsh-test') local set_encoding = '[Console]::InputEncoding=[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new();' - local cmd = set_encoding..'Remove-Item -Force '..table.concat(iswin() + local cmd = set_encoding..'Remove-Item -Force '..table.concat(is_os('win') and {'alias:cat', 'alias:echo', 'alias:sleep', 'alias:sort'} or {'alias:echo'}, ',')..';' module.exec([[ @@ -575,8 +571,16 @@ function module.set_shell_powershell(fake) return found end -function module.nvim(method, ...) - return module.request('nvim_'..method, ...) +function module.create_callindex(func) + local table = {} + setmetatable(table, { + __index = function(tbl, arg1) + local ret = function(...) return func(arg1, ...) end + tbl[arg1] = ret + return ret + end, + }) + return table end local function ui(method, ...) @@ -587,23 +591,83 @@ function module.nvim_async(method, ...) session:notify('nvim_'..method, ...) end -function module.buffer(method, ...) - return module.request('nvim_buf_'..method, ...) -end +module.async_meths = module.create_callindex(module.nvim_async) +module.uimeths = module.create_callindex(ui) -function module.window(method, ...) - return module.request('nvim_win_'..method, ...) -end +local function create_api(request, call) + local m = {} + function m.nvim(method, ...) + return request('nvim_'..method, ...) + end + + function m.buffer(method, ...) + return request('nvim_buf_'..method, ...) + end + + function m.window(method, ...) + return request('nvim_win_'..method, ...) + end + + function m.tabpage(method, ...) + return request('nvim_tabpage_'..method, ...) + end + + function m.curbuf(method, ...) + if not method then + return m.nvim('get_current_buf') + end + return m.buffer(method, 0, ...) + end + + function m.curwin(method, ...) + if not method then + return m.nvim('get_current_win') + end + return m.window(method, 0, ...) + end + + function m.curtab(method, ...) + if not method then + return m.nvim('get_current_tabpage') + end + return m.tabpage(method, 0, ...) + end + + m.funcs = module.create_callindex(call) + m.meths = module.create_callindex(m.nvim) + m.bufmeths = module.create_callindex(m.buffer) + m.winmeths = module.create_callindex(m.window) + m.tabmeths = module.create_callindex(m.tabpage) + m.curbufmeths = module.create_callindex(m.curbuf) + m.curwinmeths = module.create_callindex(m.curwin) + m.curtabmeths = module.create_callindex(m.curtab) -function module.tabpage(method, ...) - return module.request('nvim_tabpage_'..method, ...) + return m end -function module.curbuf(method, ...) - if not method then - return module.nvim('get_current_buf') +module.rpc = { + api = create_api(module.request, module.call), +} + +module.lua = { + api = create_api(module.request_lua, module.call_lua), +} + +module.describe_lua_and_rpc = function(describe) + return function(what, tests) + local function d(flavour) + describe(string.format('%s (%s)', what, flavour), function(...) + return tests(module[flavour].api, ...) + end) + end + + d('rpc') + d('lua') end - return module.buffer(method, 0, ...) +end + +for name, fn in pairs(module.rpc.api) do + module[name] = fn end function module.poke_eventloop() @@ -622,20 +686,6 @@ function module.curbuf_contents() return table.concat(module.curbuf('get_lines', 0, -1, true), '\n') end -function module.curwin(method, ...) - if not method then - return module.nvim('get_current_win') - end - return module.window(method, 0, ...) -end - -function module.curtab(method, ...) - if not method then - return module.nvim('get_current_tabpage') - end - return module.tabpage(method, 0, ...) -end - function module.expect(contents) return eq(dedent(contents), module.curbuf_contents()) end @@ -751,25 +801,10 @@ function module.exc_exec(cmd) return ret end -function module.create_callindex(func) - local table = {} - setmetatable(table, { - __index = function(tbl, arg1) - local ret = function(...) return func(arg1, ...) end - tbl[arg1] = ret - return ret - end, - }) - return table -end - --- Helper to skip tests. Returns true in Windows systems. --- pending_fn is pending() from busted -function module.pending_win32(pending_fn) - if iswin() then - if pending_fn ~= nil then - pending_fn('FIXME: Windows', function() end) - end +function module.skip(cond, reason) + if cond then + local pending = getfenv(2).pending + pending(reason or 'FIXME') return true else return false @@ -792,17 +827,6 @@ function module.skip_fragile(pending_fn, cond) return false end -module.funcs = module.create_callindex(module.call) -module.meths = module.create_callindex(module.nvim) -module.async_meths = module.create_callindex(module.nvim_async) -module.uimeths = module.create_callindex(ui) -module.bufmeths = module.create_callindex(module.buffer) -module.winmeths = module.create_callindex(module.window) -module.tabmeths = module.create_callindex(module.tabpage) -module.curbufmeths = module.create_callindex(module.curbuf) -module.curwinmeths = module.create_callindex(module.curwin) -module.curtabmeths = module.create_callindex(module.curtab) - function module.exec(code) return module.meths.exec(code, false) end @@ -816,13 +840,13 @@ function module.exec_lua(code, ...) end function module.get_pathsep() - return iswin() and '\\' or '/' + return is_os('win') and '\\' or '/' end --- Gets the filesystem root dir, namely "/" or "C:/". function module.pathroot() local pathsep = package.config:sub(1,1) - return iswin() and (module.nvim_dir:sub(1,2)..pathsep) or '/' + return is_os('win') and (module.nvim_dir:sub(1,2)..pathsep) or '/' end --- Gets the full `…/build/bin/{name}` path of a test program produced by @@ -830,7 +854,7 @@ end --- --- @param name (string) Name of the test program. function module.testprg(name) - local ext = module.iswin() and '.exe' or '' + local ext = module.is_os('win') and '.exe' or '' return ('%s/%s%s'):format(module.nvim_dir, name, ext) end @@ -857,7 +881,7 @@ function module.missing_provider(provider) end function module.alter_slashes(obj) - if not iswin() then + if not is_os('win') then return obj end if type(obj) == 'string' then @@ -875,7 +899,7 @@ function module.alter_slashes(obj) end local load_factor = 1 -if global_helpers.isCI() then +if global_helpers.is_ci() then -- Compute load factor only once (but outside of any tests). module.clear() module.request('nvim_command', 'source src/nvim/testdir/load.vim') @@ -908,14 +932,14 @@ end -- Kill process with given pid function module.os_kill(pid) - return os.execute((iswin() + return os.execute((is_os('win') and 'taskkill /f /t /pid '..pid..' > nul' or 'kill -9 '..pid..' > /dev/null')) end -- Create folder with non existing parents function module.mkdir_p(path) - return os.execute((iswin() + return os.execute((is_os('win') and 'mkdir '..path or 'mkdir -p '..path)) end diff --git a/test/functional/legacy/011_autocommands_spec.lua b/test/functional/legacy/011_autocommands_spec.lua index 0fa9290f3c..7ae851467f 100644 --- a/test/functional/legacy/011_autocommands_spec.lua +++ b/test/functional/legacy/011_autocommands_spec.lua @@ -18,11 +18,11 @@ local clear, feed_command, expect, eq, neq, dedent, write_file, feed = helpers.clear, helpers.feed_command, helpers.expect, helpers.eq, helpers.neq, helpers.dedent, helpers.write_file, helpers.feed local command = helpers.command -local iswin = helpers.iswin local read_file = helpers.read_file +local is_os = helpers.is_os local function has_gzip() - local null = iswin() and 'nul' or '/dev/null' + local null = is_os('win') and 'nul' or '/dev/null' return os.execute('gzip --help >' .. null .. ' 2>&1') == 0 end diff --git a/test/functional/legacy/025_jump_tag_hidden_spec.lua b/test/functional/legacy/025_jump_tag_hidden_spec.lua index dd89a3680e..15bd56a601 100644 --- a/test/functional/legacy/025_jump_tag_hidden_spec.lua +++ b/test/functional/legacy/025_jump_tag_hidden_spec.lua @@ -23,7 +23,7 @@ describe('jump to a tag with hidden set', function() feed_command('set hidden') -- Create a link from test25.dir to the current directory. - if helpers.iswin() then + if helpers.is_os('win') then feed_command('!rd /q/s test25.dir') feed_command('!mklink /j test25.dir .') else @@ -33,7 +33,7 @@ describe('jump to a tag with hidden set', function() -- Create tags.text, with the current directory name inserted. feed_command('/tags line') - feed_command('r !' .. (helpers.iswin() and 'cd' or 'pwd')) + feed_command('r !' .. (helpers.is_os('win') and 'cd' or 'pwd')) feed('d$/test<cr>') feed('hP:.w! tags.test<cr>') @@ -44,7 +44,7 @@ describe('jump to a tag with hidden set', function() feed('G<C-]> x:yank a<cr>') feed_command("call delete('tags.test')") feed_command("call delete('Xxx')") - if helpers.iswin() then + if helpers.is_os('win') then feed_command('!rd /q test25.dir') else feed_command('!rm -f test25.dir') diff --git a/test/functional/legacy/045_folding_spec.lua b/test/functional/legacy/045_folding_spec.lua deleted file mode 100644 index 7d7856fd37..0000000000 --- a/test/functional/legacy/045_folding_spec.lua +++ /dev/null @@ -1,214 +0,0 @@ --- Tests for folding. -local Screen = require('test.functional.ui.screen') - -local helpers = require('test.functional.helpers')(after_each) -local feed, insert, feed_command, expect_any = - helpers.feed, helpers.insert, helpers.feed_command, helpers.expect_any - -describe('folding', function() - local screen - - before_each(function() - helpers.clear() - - screen = Screen.new(20, 8) - screen:attach() - end) - - it('creation, opening, moving (to the end) and closing', function() - insert([[ - 1 aa - 2 bb - 3 cc - last - ]]) - - -- Basic test if a fold can be created, opened, moving to the end and - -- closed. - feed_command('1') - feed('zf2j') - feed_command('call append("$", "manual " . getline(foldclosed(".")))') - feed('zo') - feed_command('call append("$", foldclosed("."))') - feed(']z') - feed_command('call append("$", getline("."))') - feed('zc') - feed_command('call append("$", getline(foldclosed(".")))') - - expect_any([[ - manual 1 aa - -1 - 3 cc - 1 aa]]) - end) - - it("foldmethod=marker", function() - screen:try_resize(20, 10) - insert([[ - dd {{{ - ee {{{ }}} - ff }}} - ]]) - feed_command('set fdm=marker fdl=1') - feed_command('2') - feed_command('call append("$", "line 2 foldlevel=" . foldlevel("."))') - feed('[z') - feed_command('call append("$", foldlevel("."))') - feed('jo{{ <esc>r{jj') -- writes '{{{' and moves 2 lines bot - feed_command('call append("$", foldlevel("."))') - feed('kYpj') - feed_command('call append("$", foldlevel("."))') - - helpers.poke_eventloop() - screen:expect([[ - dd {{{ | - ee {{{ }}} | - {{{ | - ff }}} | - ff }}} | - ^ | - line 2 foldlevel=2 | - 1 | - 1 | - | - ]]) - - end) - - it("foldmethod=indent", function() - screen:try_resize(20, 8) - feed_command('set fdm=indent sw=2') - insert([[ - aa - bb - cc - last - ]]) - feed_command('call append("$", "foldlevel line3=" . foldlevel(3))') - feed_command('call append("$", foldlevel(2))') - feed('zR') - - helpers.poke_eventloop() - screen:expect([[ - aa | - bb | - cc | - last | - ^ | - foldlevel line3=2 | - 1 | - | - ]]) - end) - - it("foldmethod=syntax", function() - screen:try_resize(35, 15) - insert([[ - 1 aa - 2 bb - 3 cc - 4 dd {{{ - 5 ee {{{ }}} - 6 ff }}} - 7 gg - 8 hh - 9 ii - a jj - b kk - last]]) - feed_command('set fdm=syntax fdl=0') - feed_command('syn region Hup start="dd" end="ii" fold contains=Fd1,Fd2,Fd3') - feed_command('syn region Fd1 start="ee" end="ff" fold contained') - feed_command('syn region Fd2 start="gg" end="hh" fold contained') - feed_command('syn region Fd3 start="commentstart" end="commentend" fold contained') - feed('Gzk') - feed_command('call append("$", "folding " . getline("."))') - feed('k') - feed_command('call append("$", getline("."))') - feed('jAcommentstart <esc>Acommentend<esc>') - feed_command('set fdl=1') - feed('3j') - feed_command('call append("$", getline("."))') - feed_command('set fdl=0') - feed('zO<C-L>j') -- <C-L> redraws screen - feed_command('call append("$", getline("."))') - feed_command('set fdl=0') - expect_any([[ - folding 9 ii - 3 cc - 9 ii - a jj]]) - end) - - it("foldmethod=expression", function() - insert([[ - 1 aa - 2 bb - 3 cc - 4 dd {{{ - 5 ee {{{ }}} - 6 ff }}} - 7 gg - 8 hh - 9 ii - a jj - b kk - last ]]) - - feed_command([[ - fun Flvl() - let l = getline(v:lnum) - if l =~ "bb$" - return 2 - elseif l =~ "gg$" - return "s1" - elseif l =~ "ii$" - return ">2" - elseif l =~ "kk$" - return "0" - endif - return "=" - endfun - ]]) - feed_command('set fdm=expr fde=Flvl()') - feed_command('/bb$') - feed_command('call append("$", "expr " . foldlevel("."))') - feed_command('/hh$') - feed_command('call append("$", foldlevel("."))') - feed_command('/ii$') - feed_command('call append("$", foldlevel("."))') - feed_command('/kk$') - feed_command('call append("$", foldlevel("."))') - - expect_any([[ - expr 2 - 1 - 2 - 0]]) - end) - - it('can be opened after :move', function() - -- luacheck: ignore - screen:try_resize(35, 8) - insert([[ - Test fdm=indent and :move bug END - line2 - Test fdm=indent START - line3 - line4]]) - feed_command('set noai nosta ') - feed_command('set fdm=indent') - feed_command('1m1') - feed('2jzc') - feed_command('m0') - feed('zR') - - expect_any([[ - Test fdm=indent START - line3 - line4 - Test fdm=indent and :move bug END - line2]]) - end) -end) - diff --git a/test/functional/legacy/055_list_and_dict_types_spec.lua b/test/functional/legacy/055_list_and_dict_types_spec.lua index c0ff3ed17a..75294b3786 100644 --- a/test/functional/legacy/055_list_and_dict_types_spec.lua +++ b/test/functional/legacy/055_list_and_dict_types_spec.lua @@ -330,214 +330,6 @@ describe('list and dictionary types', function() same list: 1]]) end) - it('locked variables (part 1)', function() - source([=[ - let l = [] - for depth in range(5) - $put ='depth is ' . depth - for u in range(3) - unlet l - let l = [0, [1, [2, 3]], {4: 5, 6: {7: 8}}] - exe "lockvar " . depth . " l" - if u == 1 - exe "unlockvar l" - elseif u == 2 - exe "unlockvar " . depth . " l" - endif - let ps = islocked("l") . islocked("l[1]") . islocked("l[1][1]") . - \ islocked("l[1][1][0]") . '-' . islocked("l[2]") . - \ islocked("l[2]['6']") . islocked("l[2]['6'][7]") - $put =ps - let ps = '' - try - let l[1][1][0] = 99 - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - let l[1][1] = [99] - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - let l[1] = [99] - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - let l[2]['6'][7] = 99 - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - let l[2][6] = {99: 99} - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - let l[2] = {99: 99} - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - let l = [99] - let ps .= 'p' - catch - let ps .= 'F' - endtry - $put =ps - endfor - endfor]=]) - expect([[ - - depth is 0 - 0000-000 - ppppppp - 0000-000 - ppppppp - 0000-000 - ppppppp - depth is 1 - 1000-000 - ppppppF - 0000-000 - ppppppp - 0000-000 - ppppppp - depth is 2 - 1100-100 - ppFppFF - 0000-000 - ppppppp - 0000-000 - ppppppp - depth is 3 - 1110-110 - pFFpFFF - 0010-010 - pFppFpp - 0000-000 - ppppppp - depth is 4 - 1111-111 - FFFFFFF - 0011-011 - FFpFFpp - 0000-000 - ppppppp]]) - end) - - -- TODO In the original test the 5th line of this source() call was used. - -- But now the test only passes if I comment it. - it('unletting locked variables', function() - source([=[ - let l = [] - for depth in range(5) - $put ='depth is ' . depth - for u in range(3) - "unlet l - let l = [0, [1, [2, 3]], {4: 5, 6: {7: 8}}] - exe "lockvar " . depth . " l" - if u == 1 - exe "unlockvar l" - elseif u == 2 - exe "unlockvar " . depth . " l" - endif - let ps = islocked("l") . islocked("l[1]") . islocked("l[1][1]") . - \ islocked("l[1][1][0]") . '-' . islocked("l[2]") . - \ islocked("l[2]['6']") . islocked("l[2]['6'][7]") - $put =ps - let ps = '' - try - unlet l[2]['6'][7] - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - unlet l[2][6] - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - unlet l[2] - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - unlet l[1][1][0] - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - unlet l[1][1] - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - unlet l[1] - let ps .= 'p' - catch - let ps .= 'F' - endtry - try - unlet l - let ps .= 'p' - catch - let ps .= 'F' - endtry - $put =ps - endfor - endfor]=]) - expect([[ - - depth is 0 - 0000-000 - ppppppp - 0000-000 - ppppppp - 0000-000 - ppppppp - depth is 1 - 1000-000 - ppFppFp - 0000-000 - ppppppp - 0000-000 - ppppppp - depth is 2 - 1100-100 - pFFpFFp - 0000-000 - ppppppp - 0000-000 - ppppppp - depth is 3 - 1110-110 - FFFFFFp - 0010-010 - FppFppp - 0000-000 - ppppppp - depth is 4 - 1111-111 - FFFFFFp - 0011-011 - FppFppp - 0000-000 - ppppppp]]) - end) - it('locked variables and :unlet or list / dict functions', function() source([[ $put ='Locks and commands or functions:' @@ -676,30 +468,6 @@ describe('list and dictionary types', function() ['a', 'b', 3]]=]) end) - it('locked variables (part 2)', function() - feed_command( - 'let l = [1, 2, 3, 4]', - 'lockvar! l', - '$put =string(l)', - 'unlockvar l[1]', - 'unlet l[0:1]', - '$put =string(l)', - 'unlet l[1:2]', - '$put =string(l)', - 'unlockvar l[1]', - 'let l[0:1] = [0, 1]', - '$put =string(l)', - 'let l[1:2] = [0, 1]', - '$put =string(l)') - expect([=[ - - [1, 2, 3, 4] - [1, 2, 3, 4] - [1, 2, 3, 4] - [1, 2, 3, 4] - [1, 2, 3, 4]]=]) - end) - it(':lockvar/islocked() triggering script autoloading.', function() source([[ set rtp+=test/functional/fixtures diff --git a/test/functional/legacy/097_glob_path_spec.lua b/test/functional/legacy/097_glob_path_spec.lua index dd5a26ad3b..a62dc4d4c8 100644 --- a/test/functional/legacy/097_glob_path_spec.lua +++ b/test/functional/legacy/097_glob_path_spec.lua @@ -10,7 +10,7 @@ describe('glob() and globpath()', function() setup(clear) setup(function() - if helpers.iswin() then + if helpers.is_os('win') then os.execute("md sautest\\autoload") os.execute(".>sautest\\autoload\\Test104.vim 2>nul") os.execute(".>sautest\\autoload\\footest.vim 2>nul") @@ -28,7 +28,7 @@ describe('glob() and globpath()', function() -- Consistent sorting of file names command('set nofileignorecase') - if helpers.iswin() then + if helpers.is_os('win') then command([[$put =glob('Xxx{')]]) command([[$put =glob('Xxx$')]]) @@ -72,7 +72,7 @@ describe('glob() and globpath()', function() end) teardown(function() - if helpers.iswin() then + if helpers.is_os('win') then os.execute('del /q/f Xxx{ Xxx$') os.execute('rd /q /s sautest') else diff --git a/test/functional/legacy/breakindent_spec.lua b/test/functional/legacy/breakindent_spec.lua new file mode 100644 index 0000000000..d7779684a4 --- /dev/null +++ b/test/functional/legacy/breakindent_spec.lua @@ -0,0 +1,44 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local exec = helpers.exec +local feed = helpers.feed + +before_each(clear) + +describe('breakindent', function() + -- oldtest: Test_cursor_position_with_showbreak() + it('cursor shown at correct position with showbreak', function() + local screen = Screen.new(75, 6) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue}, -- SignColumn + [2] = {bold = true}, -- ModeMsg + }) + screen:attach() + exec([[ + let &signcolumn = 'yes' + let &showbreak = '+' + let leftcol = win_getid()->getwininfo()->get(0, {})->get('textoff') + eval repeat('x', &columns - leftcol - 1)->setline(1) + eval 'second line'->setline(2) + ]]) + screen:expect([[ + {1: }^xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | + {1: }second line | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + feed('AX') + screen:expect([[ + {1: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxX| + {1: }^second line | + {0:~ }| + {0:~ }| + {0:~ }| + {2:-- INSERT --} | + ]]) + end) +end) diff --git a/test/functional/legacy/delete_spec.lua b/test/functional/legacy/delete_spec.lua index 4ba4c8d356..cefcd6c6c4 100644 --- a/test/functional/legacy/delete_spec.lua +++ b/test/functional/legacy/delete_spec.lua @@ -48,7 +48,7 @@ describe('Test for delete()', function() it('symlink directory delete', function() command("call mkdir('Xdir1')") - if helpers.iswin() then + if helpers.is_os('win') then command("silent !mklink /j Xlink Xdir1") else command("silent !ln -s Xdir1 Xlink") diff --git a/test/functional/legacy/digraph_spec.lua b/test/functional/legacy/digraph_spec.lua new file mode 100644 index 0000000000..0cb0bb84be --- /dev/null +++ b/test/functional/legacy/digraph_spec.lua @@ -0,0 +1,46 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local feed = helpers.feed + +before_each(clear) + +describe('digraph', function() + -- oldtest: Test_entering_digraph() + it('characters displayed on the screen', function() + local screen = Screen.new(10, 6) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {foreground = Screen.colors.Blue}, -- SpecialKey + [2] = {bold = true}, -- ModeMsg + }) + screen:attach() + feed('i<C-K>') + screen:expect([[ + {1:^?} | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {2:-- INSERT -} | + ]]) + feed('1') + screen:expect([[ + {1:^1} | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {2:-- INSERT -} | + ]]) + feed('2') + screen:expect([[ + ½^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {2:-- INSERT -} | + ]]) + end) +end) diff --git a/test/functional/legacy/display_spec.lua b/test/functional/legacy/display_spec.lua index 9160129a02..f9b78f5dcd 100644 --- a/test/functional/legacy/display_spec.lua +++ b/test/functional/legacy/display_spec.lua @@ -58,6 +58,51 @@ describe('display', function() ]]) end) + -- oldtest: Test_matchparen_clear_highlight() + it('matchparen highlight is cleared when switching buffer', function() + local screen = Screen.new(20, 5) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, + [1] = {background = Screen.colors.Cyan}, + }) + screen:attach() + + local screen1 = [[ + {1:^()} | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]] + local screen2 = [[ + ^aa | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]] + + exec([[ + source $VIMRUNTIME/plugin/matchparen.vim + set hidden + call setline(1, ['()']) + normal 0 + ]]) + screen:expect(screen1) + + exec([[ + enew + exe "normal iaa\<Esc>0" + ]]) + screen:expect(screen2) + + feed('<C-^>') + screen:expect(screen1) + + feed('<C-^>') + screen:expect(screen2) + end) + local function run_test_display_lastline(euro) local screen = Screen.new(75, 10) screen:set_default_attr_ids({ diff --git a/test/functional/legacy/edit_spec.lua b/test/functional/legacy/edit_spec.lua index 7fc5f11a79..362d33a0fd 100644 --- a/test/functional/legacy/edit_spec.lua +++ b/test/functional/legacy/edit_spec.lua @@ -1,4 +1,5 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local clear = helpers.clear local command = helpers.command local expect = helpers.expect @@ -7,20 +8,51 @@ local sleep = helpers.sleep before_each(clear) --- oldtest: Test_autoindent_remove_indent() -it('autoindent removes indent when Insert mode is stopped', function() - command('set autoindent') - -- leaving insert mode in a new line with indent added by autoindent, should - -- remove the indent. - feed('i<Tab>foo<CR><Esc>') - -- Need to delay for sometime, otherwise the code in getchar.c will not be - -- exercised. - sleep(50) - -- when a line is wrapped and the cursor is at the start of the second line, - -- leaving insert mode, should move the cursor back to the first line. - feed('o' .. ('x'):rep(20) .. '<Esc>') - -- Need to delay for sometime, otherwise the code in getchar.c will not be - -- exercised. - sleep(50) - expect('\tfoo\n\n' .. ('x'):rep(20)) +describe('edit', function() + -- oldtest: Test_autoindent_remove_indent() + it('autoindent removes indent when Insert mode is stopped', function() + command('set autoindent') + -- leaving insert mode in a new line with indent added by autoindent, should + -- remove the indent. + feed('i<Tab>foo<CR><Esc>') + -- Need to delay for sometime, otherwise the code in getchar.c will not be + -- exercised. + sleep(50) + -- when a line is wrapped and the cursor is at the start of the second line, + -- leaving insert mode, should move the cursor back to the first line. + feed('o' .. ('x'):rep(20) .. '<Esc>') + -- Need to delay for sometime, otherwise the code in getchar.c will not be + -- exercised. + sleep(50) + expect('\tfoo\n\n' .. ('x'):rep(20)) + end) + + -- oldtest: Test_edit_insert_reg() + it('inserting a register using CTRL-R', function() + local screen = Screen.new(10, 6) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {foreground = Screen.colors.Blue}, -- SpecialKey + [2] = {bold = true}, -- ModeMsg + }) + screen:attach() + feed('a<C-R>') + screen:expect([[ + {1:^"} | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {2:-- INSERT -} | + ]]) + feed('=') + screen:expect([[ + {1:"} | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + =^ | + ]]) + end) end) diff --git a/test/functional/legacy/eval_spec.lua b/test/functional/legacy/eval_spec.lua index 05d853622e..b5e45a86c1 100644 --- a/test/functional/legacy/eval_spec.lua +++ b/test/functional/legacy/eval_spec.lua @@ -639,7 +639,7 @@ describe('eval', function() end) it('function name includes a colon', function() - eq('Vim(function):E128: Function name must start with a capital or "s:": b:test()\\nendfunction', + eq('Vim(function):E884: Function name cannot contain a colon: b:test()\\nendfunction', exc_exec(dedent([[ function! b:test() endfunction]]))) diff --git a/test/functional/legacy/excmd_spec.lua b/test/functional/legacy/excmd_spec.lua index ece88d26bd..eb480a6689 100644 --- a/test/functional/legacy/excmd_spec.lua +++ b/test/functional/legacy/excmd_spec.lua @@ -7,12 +7,12 @@ local exec_lua = helpers.exec_lua local expect_exit = helpers.expect_exit local feed = helpers.feed local funcs = helpers.funcs -local iswin = helpers.iswin local meths = helpers.meths local read_file = helpers.read_file local source = helpers.source local eq = helpers.eq local write_file = helpers.write_file +local is_os = helpers.is_os local function sizeoflong() if not exec_lua('return pcall(require, "ffi")') then @@ -376,7 +376,7 @@ describe(':confirm command dialog', function() {3:(Y)es, [N]o: }^ | ]]) feed('Y') - if iswin() then + if is_os('win') then screen:expect([[ foobar | {0:~ }| @@ -416,7 +416,7 @@ describe(':confirm command dialog', function() {3:(Y)es, [N]o: }^ | ]]) feed('Y') - if iswin() then + if is_os('win') then screen:expect([[ foobar | {1: }| @@ -493,7 +493,7 @@ describe(':confirm command dialog', function() {3:(Y)es, [N]o: }^ | ]]) feed('Y') - if iswin() then + if is_os('win') then screen:expect([[ a | b | diff --git a/test/functional/legacy/filechanged_spec.lua b/test/functional/legacy/filechanged_spec.lua index 1f23528d61..cea1d6ac30 100644 --- a/test/functional/legacy/filechanged_spec.lua +++ b/test/functional/legacy/filechanged_spec.lua @@ -1,6 +1,8 @@ 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 is_os = helpers.is_os +local skip = helpers.skip local function expected_empty() eq({}, meths.get_vvar('errors')) @@ -15,7 +17,7 @@ describe('file changed dialog', function() end) it('works', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) source([[ func Test_file_changed_dialog() au! FileChangedShell diff --git a/test/functional/legacy/fold_spec.lua b/test/functional/legacy/fold_spec.lua new file mode 100644 index 0000000000..83513a3f94 --- /dev/null +++ b/test/functional/legacy/fold_spec.lua @@ -0,0 +1,335 @@ +-- Tests for folding. +local Screen = require('test.functional.ui.screen') + +local helpers = require('test.functional.helpers')(after_each) +local feed, insert, feed_command, expect_any = + helpers.feed, helpers.insert, helpers.feed_command, helpers.expect_any +local command = helpers.command +local exec = helpers.exec + +describe('folding', function() + local screen + + before_each(function() + helpers.clear() + + screen = Screen.new(45, 8) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [2] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey}, -- Folded + [3] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.Grey}, -- FoldColumn + [4] = {foreground = Screen.colors.Brown}, -- LineNr + }) + screen:attach() + end) + + it('creation, opening, moving (to the end) and closing', function() + insert([[ + 1 aa + 2 bb + 3 cc + last + ]]) + + -- Basic test if a fold can be created, opened, moving to the end and + -- closed. + feed_command('1') + feed('zf2j') + feed_command('call append("$", "manual " . getline(foldclosed(".")))') + feed('zo') + feed_command('call append("$", foldclosed("."))') + feed(']z') + feed_command('call append("$", getline("."))') + feed('zc') + feed_command('call append("$", getline(foldclosed(".")))') + + expect_any([[ + manual 1 aa + -1 + 3 cc + 1 aa]]) + end) + + it("foldmethod=marker", function() + screen:try_resize(20, 10) + insert([[ + dd {{{ + ee {{{ }}} + ff }}} + ]]) + feed_command('set fdm=marker fdl=1') + feed_command('2') + feed_command('call append("$", "line 2 foldlevel=" . foldlevel("."))') + feed('[z') + feed_command('call append("$", foldlevel("."))') + feed('jo{{ <esc>r{jj') -- writes '{{{' and moves 2 lines bot + feed_command('call append("$", foldlevel("."))') + feed('kYpj') + feed_command('call append("$", foldlevel("."))') + + helpers.poke_eventloop() + screen:expect([[ + dd {{{ | + ee {{{ }}} | + {{{ | + ff }}} | + ff }}} | + ^ | + line 2 foldlevel=2 | + 1 | + 1 | + | + ]]) + + end) + + it("foldmethod=indent", function() + screen:try_resize(20, 8) + feed_command('set fdm=indent sw=2') + insert([[ + aa + bb + cc + last + ]]) + feed_command('call append("$", "foldlevel line3=" . foldlevel(3))') + feed_command('call append("$", foldlevel(2))') + feed('zR') + + helpers.poke_eventloop() + screen:expect([[ + aa | + bb | + cc | + last | + ^ | + foldlevel line3=2 | + 1 | + | + ]]) + end) + + it("foldmethod=syntax", function() + screen:try_resize(35, 15) + insert([[ + 1 aa + 2 bb + 3 cc + 4 dd {{{ + 5 ee {{{ }}} + 6 ff }}} + 7 gg + 8 hh + 9 ii + a jj + b kk + last]]) + feed_command('set fdm=syntax fdl=0') + feed_command('syn region Hup start="dd" end="ii" fold contains=Fd1,Fd2,Fd3') + feed_command('syn region Fd1 start="ee" end="ff" fold contained') + feed_command('syn region Fd2 start="gg" end="hh" fold contained') + feed_command('syn region Fd3 start="commentstart" end="commentend" fold contained') + feed('Gzk') + feed_command('call append("$", "folding " . getline("."))') + feed('k') + feed_command('call append("$", getline("."))') + feed('jAcommentstart <esc>Acommentend<esc>') + feed_command('set fdl=1') + feed('3j') + feed_command('call append("$", getline("."))') + feed_command('set fdl=0') + feed('zO<C-L>j') -- <C-L> redraws screen + feed_command('call append("$", getline("."))') + feed_command('set fdl=0') + expect_any([[ + folding 9 ii + 3 cc + 9 ii + a jj]]) + end) + + it("foldmethod=expression", function() + insert([[ + 1 aa + 2 bb + 3 cc + 4 dd {{{ + 5 ee {{{ }}} + 6 ff }}} + 7 gg + 8 hh + 9 ii + a jj + b kk + last ]]) + + feed_command([[ + fun Flvl() + let l = getline(v:lnum) + if l =~ "bb$" + return 2 + elseif l =~ "gg$" + return "s1" + elseif l =~ "ii$" + return ">2" + elseif l =~ "kk$" + return "0" + endif + return "=" + endfun + ]]) + feed_command('set fdm=expr fde=Flvl()') + feed_command('/bb$') + feed_command('call append("$", "expr " . foldlevel("."))') + feed_command('/hh$') + feed_command('call append("$", foldlevel("."))') + feed_command('/ii$') + feed_command('call append("$", foldlevel("."))') + feed_command('/kk$') + feed_command('call append("$", foldlevel("."))') + + expect_any([[ + expr 2 + 1 + 2 + 0]]) + end) + + it('can be opened after :move', function() + -- luacheck: ignore + screen:try_resize(35, 8) + insert([[ + Test fdm=indent and :move bug END + line2 + Test fdm=indent START + line3 + line4]]) + feed_command('set noai nosta ') + feed_command('set fdm=indent') + feed_command('1m1') + feed('2jzc') + feed_command('m0') + feed('zR') + + expect_any([[ + Test fdm=indent START + line3 + line4 + Test fdm=indent and :move bug END + line2]]) + end) + + -- oldtest: Test_folds_with_rnu() + it('with relative line numbers', function() + command('set fdm=marker rnu foldcolumn=2') + command('call setline(1, ["{{{1", "nline 1", "{{{1", "line 2"])') + + screen:expect([[ + {3:+ }{4: 0 }{2:^+-- 2 lines: ·························}| + {3:+ }{4: 1 }{2:+-- 2 lines: ·························}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + feed("j") + screen:expect([[ + {3:+ }{4: 1 }{2:+-- 2 lines: ·························}| + {3:+ }{4: 0 }{2:^+-- 2 lines: ·························}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) + + -- oldtest: Test_foldclose_opt() + it('foldclose=all', function() + exec([[ + set foldmethod=manual foldclose=all foldopen=all + call setline(1, ['one', 'two', 'three', 'four']) + 2,3fold + ]]) + + screen:expect([[ + ^one | + {2:+-- 2 lines: two····························}| + four | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + feed('2G') + screen:expect([[ + one | + ^two | + three | + four | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + feed('4G') + screen:expect([[ + one | + {2:+-- 2 lines: two····························}| + ^four | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + feed('3G') + screen:expect([[ + one | + two | + ^three | + four | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + feed('1G') + screen:expect([[ + ^one | + {2:+-- 2 lines: two····························}| + four | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + feed('2G') + screen:expect([[ + one | + ^two | + three | + four | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + feed('k') + screen:expect([[ + ^one | + {2:+-- 2 lines: two····························}| + four | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) +end) diff --git a/test/functional/legacy/gf_spec.lua b/test/functional/legacy/gf_spec.lua index f1b1790ba1..9f725446be 100644 --- a/test/functional/legacy/gf_spec.lua +++ b/test/functional/legacy/gf_spec.lua @@ -10,6 +10,7 @@ describe('gf', function() it('is not allowed when buffer is locked', function() command('au OptionSet diff norm! gf') command([[call setline(1, ['Xfile1', 'line2', 'line3', 'line4'])]]) - eq('Vim(normal):E788: Not allowed to edit another buffer now', pcall_err(command, 'diffthis')) + eq('OptionSet Autocommands for "diff": Vim(normal):E788: Not allowed to edit another buffer now', + pcall_err(command, 'diffthis')) end) end) diff --git a/test/functional/legacy/increment_spec.lua b/test/functional/legacy/increment_spec.lua index d51f9a2e02..d35f4bdae6 100644 --- a/test/functional/legacy/increment_spec.lua +++ b/test/functional/legacy/increment_spec.lua @@ -285,7 +285,7 @@ describe('Ctrl-A/Ctrl-X on visual selections', function() " 1 " 1 " 1 - " Expexted: + " Expected: " 1) g Ctrl-A on block selected indented lines " 2 " 1 diff --git a/test/functional/legacy/match_spec.lua b/test/functional/legacy/match_spec.lua index 271f844f9d..b6e45c396c 100644 --- a/test/functional/legacy/match_spec.lua +++ b/test/functional/legacy/match_spec.lua @@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear = helpers.clear local exec = helpers.exec +local feed = helpers.feed before_each(clear) @@ -36,3 +37,90 @@ describe('matchaddpos()', function() ]]) end) end) + +describe('match highlighting', function() + -- oldtest: Test_match_in_linebreak() + it('does not continue in linebreak vim-patch:8.2.3698', function() + local screen = Screen.new(75, 10) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {background = Screen.colors.Red, foreground = Screen.colors.White}, -- ErrorMsg + }) + screen:attach() + exec([=[ + set breakindent linebreak breakat+=] + call printf('%s]%s', repeat('x', 50), repeat('x', 70))->setline(1) + call matchaddpos('ErrorMsg', [[1, 51]]) + ]=]) + screen:expect([[ + ^xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx{1:]} | + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) + + it('is shown with incsearch vim-patch:8.2.3940', function() + local screen = Screen.new(75, 6) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {background = Screen.colors.Yellow}, -- Search + [2] = {background = Screen.colors.Red, foreground = Screen.colors.White}, -- ErrorMsg + }) + screen:attach() + exec([[ + set incsearch + call setline(1, range(20)) + call matchaddpos('ErrorMsg', [3]) + ]]) + screen:expect([[ + ^0 | + 1 | + {2:2} | + 3 | + 4 | + | + ]]) + feed(':s/0') + screen:expect([[ + {1:0} | + 1 | + {2:2} | + 3 | + 4 | + :s/0^ | + ]]) + end) + + it('on a Tab vim-patch:8.2.4062', function() + local screen = Screen.new(75, 10) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {background = Screen.colors.Red, foreground = Screen.colors.White}, -- ErrorMsg + }) + screen:attach() + exec([[ + set linebreak + call setline(1, "\tix") + call matchadd('ErrorMsg', '\t') + ]]) + screen:expect([[ + {1: ^ }ix | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) +end) diff --git a/test/functional/legacy/memory_usage_spec.lua b/test/functional/legacy/memory_usage_spec.lua index eec89aa919..59839157ea 100644 --- a/test/functional/legacy/memory_usage_spec.lua +++ b/test/functional/legacy/memory_usage_spec.lua @@ -3,15 +3,14 @@ local clear = helpers.clear local eval = helpers.eval local eq = helpers.eq local feed_command = helpers.feed_command -local iswin = helpers.iswin local retry = helpers.retry local ok = helpers.ok 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 is_os = helpers.is_os +local is_ci = helpers.is_ci local function isasan() local version = eval('execute("version")') @@ -22,8 +21,8 @@ clear() if isasan() then pending('ASAN build is difficult to estimate memory usage', function() end) return -elseif iswin() then - if isCI('github') then +elseif is_os('win') then + if is_ci('github') then pending('Windows runners in Github Actions do not have a stable environment to estimate memory usage', function() end) return elseif eval("executable('wmic')") == 0 then @@ -38,7 +37,7 @@ end local monitor_memory_usage = { memory_usage = function(self) local handle - if iswin() then + if is_os('win') then handle = io.popen('wmic process where processid=' ..self.pid..' get WorkingSetSize') else handle = io.popen('ps -o rss= -p '..self.pid) @@ -167,9 +166,9 @@ describe('memory usage', function() local last = monitor_memory_usage(pid) -- The usage may be a bit less than the last value, use 80%. -- Allow for 20% tolerance at the upper limit. That's very permissive, but - -- otherwise the test fails sometimes. On Sourcehut CI with FreeBSD we need to - -- be even much more permissive. - local upper_multiplier = uname() == 'freebsd' and 19 or 12 + -- otherwise the test fails sometimes. On FreeBSD we need to be even much + -- more permissive. + local upper_multiplier = is_os('freebsd') and 19 or 12 local lower = before.last * 8 / 10 local upper = load_adjust((after.max + (after.last - before.last)) * upper_multiplier / 10) check_result({before=before, after=after, last=last}, @@ -179,7 +178,7 @@ describe('memory usage', function() end) it('releases memory when closing windows when folds exist', function() - if helpers.is_os('mac') then + if is_os('mac') then pending('macOS memory compression causes flakiness') end local pid = eval('getpid()') diff --git a/test/functional/legacy/search_spec.lua b/test/functional/legacy/search_spec.lua index 5a94fca794..3f1f85cf28 100644 --- a/test/functional/legacy/search_spec.lua +++ b/test/functional/legacy/search_spec.lua @@ -14,7 +14,7 @@ describe('search cmdline', function() before_each(function() clear() - command('set nohlsearch') + command('set nohlsearch inccommand=') screen = Screen.new(20, 3) screen:attach() screen:set_default_attr_ids({ @@ -472,8 +472,8 @@ describe('search cmdline', function() funcs.winsaveview()) end) + -- oldtest: Test_search_cmdline4(). it("CTRL-G with 'incsearch' and ? goes in the right direction", function() - -- oldtest: Test_search_cmdline4(). screen:try_resize(40, 4) command('enew!') funcs.setline(1, {' 1 the first', ' 2 the second', ' 3 the third'}) @@ -573,8 +573,8 @@ describe('search cmdline', function() ]]) end) + -- oldtest: Test_incsearch_sort_dump(). it('incsearch works with :sort', function() - -- oldtest: Test_incsearch_sort_dump(). screen:try_resize(20, 4) command('set incsearch hlsearch scrolloff=0') funcs.setline(1, {'another one 2', 'that one 3', 'the one 1'}) @@ -589,8 +589,8 @@ describe('search cmdline', function() feed('<esc>') end) + -- oldtest: Test_incsearch_vimgrep_dump(). it('incsearch works with :vimgrep family', function() - -- oldtest: Test_incsearch_vimgrep_dump(). screen:try_resize(30, 4) command('set incsearch hlsearch scrolloff=0') funcs.setline(1, {'another one 2', 'that one 3', 'the one 1'}) @@ -640,6 +640,41 @@ describe('search cmdline', function() ]]) feed('<esc>') end) + + -- oldtest: Test_incsearch_substitute_dump2() + it('detects empty pattern properly vim-patch:8.2.2295', function() + screen:try_resize(70, 6) + exec([[ + set incsearch hlsearch scrolloff=0 + for n in range(1, 4) + call setline(n, "foo " . n) + endfor + call setline(5, "abc|def") + 3 + ]]) + + feed([[:%s/\vabc|]]) + screen:expect([[ + foo 1 | + foo 2 | + foo 3 | + foo 4 | + abc|def | + :%s/\vabc|^ | + ]]) + feed('<Esc>') + + -- The following should not be highlighted + feed([[:1,5s/\v|]]) + screen:expect([[ + foo 1 | + foo 2 | + foo 3 | + foo 4 | + abc|def | + :1,5s/\v|^ | + ]]) + end) end) describe('Search highlight', function() diff --git a/test/functional/legacy/search_stat_spec.lua b/test/functional/legacy/search_stat_spec.lua index c2ca393a56..9fcf798836 100644 --- a/test/functional/legacy/search_stat_spec.lua +++ b/test/functional/legacy/search_stat_spec.lua @@ -17,6 +17,7 @@ describe('search stat', function() screen:attach() end) + -- oldtest: Test_search_stat_screendump() it('right spacing with silent mapping vim-patch:8.1.1970', function() exec([[ set shortmess-=S @@ -57,6 +58,7 @@ describe('search stat', function() ]]) end) + -- oldtest: Test_search_stat_foldopen() it('when only match is in fold vim-patch:8.2.0840', function() exec([[ set shortmess-=S @@ -86,6 +88,7 @@ describe('search stat', function() screen:expect_unchanged() end) + -- oldtest: Test_search_stat_then_gd() it('is cleared by gd and gD vim-patch:8.2.3583', function() exec([[ call setline(1, ['int cat;', 'int dog;', 'cat = dog;']) @@ -120,6 +123,7 @@ describe('search stat', function() ]]) end) + -- oldtest: Test_search_stat_and_incsearch() it('is not broken by calling searchcount() in tabline vim-patch:8.2.4378', function() exec([[ call setline(1, ['abc--c', '--------abc', '--abc']) diff --git a/test/functional/legacy/source_spec.lua b/test/functional/legacy/source_spec.lua new file mode 100644 index 0000000000..f31521607d --- /dev/null +++ b/test/functional/legacy/source_spec.lua @@ -0,0 +1,32 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local feed = helpers.feed +local write_file = helpers.write_file + +before_each(clear) + +describe(':source!', function() + -- oldtest: Test_nested_script() + it('gives E22 when scripts nested too deep', function() + write_file('Xscript.vim', [[ + :source! Xscript.vim + ]]) + local screen = Screen.new(75, 6) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {background = Screen.colors.Red, foreground = Screen.colors.White}, -- ErrorMsg + }) + screen:attach() + feed(':source! Xscript.vim\n') + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {1:E22: Scripts nested too deep} | + ]]) + os.remove('Xscript.vim') + end) +end) diff --git a/test/functional/legacy/statusline_spec.lua b/test/functional/legacy/statusline_spec.lua index e2b30a7c82..c5b17f8749 100644 --- a/test/functional/legacy/statusline_spec.lua +++ b/test/functional/legacy/statusline_spec.lua @@ -68,4 +68,87 @@ describe('statusline', function() | ]]) end) + + -- oldtest: Test_statusline_showcmd() + it('showcmdloc=statusline works', function() + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {background = Screen.colors.LightGrey}, -- Visual + [2] = {bold = true}, -- MoreMsg + [3] = {bold = true, reverse = true}, -- StatusLine + [5] = {background = Screen.colors.LightGrey, foreground = Screen.colors.DarkBlue}, -- Folded + }) + exec([[ + func MyStatusLine() + return '%S' + endfunc + + set showcmd + set laststatus=2 + set statusline=%S + set showcmdloc=statusline + call setline(1, ['a', 'b', 'c']) + set foldopen+=jump + 1,2fold + 3 + ]]) + + feed('g') + screen:expect([[ + {5:+-- 2 lines: a···································}| + ^c | + {0:~ }| + {0:~ }| + {0:~ }| + {3:g }| + | + ]]) + + -- typing "gg" should open the fold + feed('g') + screen:expect([[ + ^a | + b | + c | + {0:~ }| + {0:~ }| + {3: }| + | + ]]) + + feed('<C-V>Gl') + screen:expect([[ + {1:a} | + {1:b} | + {1:c}^ | + {0:~ }| + {0:~ }| + {3:3x2 }| + {2:-- VISUAL BLOCK --} | + ]]) + + feed('<Esc>1234') + screen:expect([[ + a | + b | + ^c | + {0:~ }| + {0:~ }| + {3:1234 }| + | + ]]) + + feed('<Esc>:set statusline=<CR>') + feed(':<CR>') + feed('1234') + screen:expect([[ + a | + b | + ^c | + {0:~ }| + {0:~ }| + {3:[No Name] [+] 1234 }| + : | + ]]) + end) end) diff --git a/test/functional/legacy/syn_attr_spec.lua b/test/functional/legacy/syn_attr_spec.lua index 06e8427e27..e6573da5d3 100644 --- a/test/functional/legacy/syn_attr_spec.lua +++ b/test/functional/legacy/syn_attr_spec.lua @@ -4,10 +4,10 @@ local command = helpers.command local eq = helpers.eq local eval = helpers.eval -before_each(clear) - -- oldtest: Test_missing_attr() -it('synIDattr() works', function() +describe('synIDattr()', function() + setup(clear) + local bool_attrs = { 'bold', 'italic', @@ -22,39 +22,55 @@ it('synIDattr() works', function() 'nocombine', } - command('hi Mine cterm=NONE gui=NONE') - eq('Mine', eval([[synIDattr(hlID("Mine"), "name")]])) - for _, mode in ipairs({'cterm', 'gui'}) do - eq('', eval(([[synIDattr("Mine"->hlID(), "bg", '%s')]]):format(mode))) - eq('', eval(([[synIDattr("Mine"->hlID(), "fg", '%s')]]):format(mode))) - eq('', eval(([[synIDattr("Mine"->hlID(), "sp", '%s')]]):format(mode))) - for _, attr in ipairs(bool_attrs) do - eq('', eval(([[synIDattr(hlID("Mine"), "%s", '%s')]]):format(attr, mode))) - eq('', eval(([[synIDattr(hlID("Mine"), "%s", '%s')]]):format(attr, mode))) - eq('', eval(([[synIDattr(hlID("Mine"), "%s", '%s')]]):format(attr, mode))) + describe(':hi Mine cterm=NONE gui=NONE', function() + setup(function() + command(':hi Mine cterm=NONE gui=NONE') + end) + + it('"name"', function() + eq('Mine', eval([[synIDattr(hlID("Mine"), "name")]])) + end) + + local function none_test(attr, mode) + it(('"%s"'):format(attr), function() + eq('', eval(([[synIDattr(hlID("Mine"), "%s", '%s')]]):format(attr, mode))) + end) end - eq('', eval(([[synIDattr(hlID("Mine"), "inverse", '%s')]]):format(mode))) + + for _, mode in ipairs({'cterm', 'gui'}) do + describe(('"%s"'):format(mode), function() + for _, attr in ipairs(bool_attrs) do + none_test(attr, mode) + end + for _, attr in ipairs({'inverse', 'bg', 'fg', 'sp'}) do + none_test(attr, mode) + end + end) + end + end) + + local function attr_test(attr1, attr2) + local cmd = (':hi Mine cterm=%s gui=%s'):format(attr1, attr2) + it(cmd, function() + command(cmd) + eq('1', eval(([[synIDattr("Mine"->hlID(), "%s", 'cterm')]]):format(attr1))) + eq('', eval(([[synIDattr(hlID("Mine"), "%s", 'cterm')]]):format(attr2))) + eq('', eval(([[synIDattr("Mine"->hlID(), "%s", 'gui')]]):format(attr1))) + eq('1', eval(([[synIDattr(hlID("Mine"), "%s", 'gui')]]):format(attr2))) + end) end for i, attr1 in ipairs(bool_attrs) do local attr2 = bool_attrs[i - 1] or bool_attrs[#bool_attrs] - - command(('hi Mine cterm=%s gui=%s'):format(attr1, attr2)) - eq('1', eval(([[synIDattr(hlID("Mine"), "%s", 'cterm')]]):format(attr1))) - eq('', eval(([[synIDattr(hlID("Mine"), "%s", 'cterm')]]):format(attr2))) - eq('', eval(([[synIDattr("Mine"->hlID(), "%s", 'gui')]]):format(attr1))) - eq('1', eval(([[synIDattr("Mine"->hlID(), "%s", 'gui')]]):format(attr2))) - - command(('hi Mine cterm=%s gui=%s'):format(attr2, attr1)) - eq('', eval(([[synIDattr("Mine"->hlID(), "%s", 'cterm')]]):format(attr1))) - eq('1', eval(([[synIDattr("Mine"->hlID(), "%s", 'cterm')]]):format(attr2))) - eq('1', eval(([[synIDattr(hlID("Mine"), "%s", 'gui')]]):format(attr1))) - eq('', eval(([[synIDattr(hlID("Mine"), "%s", 'gui')]]):format(attr2))) + attr_test(attr1, attr2) + attr_test(attr2, attr1) end - command('hi Mine cterm=reverse gui=inverse') - eq('1', eval([[synIDattr(hlID("Mine"), "reverse", 'cterm')]])) - eq('1', eval([[synIDattr(hlID("Mine"), "inverse", 'cterm')]])) - eq('1', eval([[synIDattr(hlID("Mine"), "reverse", 'gui')]])) - eq('1', eval([[synIDattr(hlID("Mine"), "inverse", 'gui')]])) + it(':hi Mine cterm=reverse gui=inverse', function() + command(':hi Mine cterm=reverse gui=inverse') + eq('1', eval([[synIDattr(hlID("Mine"), "reverse", 'cterm')]])) + eq('1', eval([[synIDattr(hlID("Mine"), "inverse", 'cterm')]])) + eq('1', eval([[synIDattr(hlID("Mine"), "reverse", 'gui')]])) + eq('1', eval([[synIDattr(hlID("Mine"), "inverse", 'gui')]])) + end) end) diff --git a/test/functional/legacy/tabline_spec.lua b/test/functional/legacy/tabline_spec.lua new file mode 100644 index 0000000000..6b368d1857 --- /dev/null +++ b/test/functional/legacy/tabline_spec.lua @@ -0,0 +1,100 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local exec = helpers.exec +local feed = helpers.feed + +before_each(clear) + +describe('tabline', function() + local screen + + before_each(function() + screen = Screen.new(50, 7) + screen:attach() + end) + + -- oldtest: Test_tabline_showcmd() + it('showcmdloc=tabline works', function() + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {background = Screen.colors.LightGrey}, -- Visual + [2] = {bold = true}, -- MoreMsg, TabLineSel + [3] = {reverse = true}, -- TabLineFill + [4] = {background = Screen.colors.LightGrey, underline = true}, -- TabLine + [5] = {background = Screen.colors.LightGrey, foreground = Screen.colors.DarkBlue}, -- Folded + }) + exec([[ + func MyTabLine() + return '%S' + endfunc + + set showcmd + set showtabline=2 + set tabline=%!MyTabLine() + set showcmdloc=tabline + call setline(1, ['a', 'b', 'c']) + set foldopen+=jump + 1,2fold + 3 + ]]) + + feed('g') + screen:expect([[ + {3:g }| + {5:+-- 2 lines: a···································}| + ^c | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + + -- typing "gg" should open the fold + feed('g') + screen:expect([[ + {3: }| + ^a | + b | + c | + {0:~ }| + {0:~ }| + | + ]]) + + feed('<C-V>Gl') + screen:expect([[ + {3:3x2 }| + {1:a} | + {1:b} | + {1:c}^ | + {0:~ }| + {0:~ }| + {2:-- VISUAL BLOCK --} | + ]]) + + feed('<Esc>1234') + screen:expect([[ + {3:1234 }| + a | + b | + ^c | + {0:~ }| + {0:~ }| + | + ]]) + + feed('<Esc>:set tabline=<CR>') + feed(':<CR>') + feed('1234') + screen:expect([[ + {2: + [No Name] }{3: }{4:1234}{3: }| + a | + b | + ^c | + {0:~ }| + {0:~ }| + : | + ]]) + end) +end) diff --git a/test/functional/legacy/vimscript_spec.lua b/test/functional/legacy/vimscript_spec.lua new file mode 100644 index 0000000000..f59a87f824 --- /dev/null +++ b/test/functional/legacy/vimscript_spec.lua @@ -0,0 +1,90 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local exec = helpers.exec +local feed = helpers.feed +local meths = helpers.meths + +before_each(clear) + +describe('Vim script', function() + -- oldtest: Test_deep_nest() + it('Error when if/for/while/try/function is nested too deep',function() + local screen = Screen.new(80, 24) + screen:attach() + meths.set_option('laststatus', 2) + exec([[ + " Deep nesting of if ... endif + func Test1() + let @a = join(repeat(['if v:true'], 51), "\n") + let @a ..= "\n" + let @a ..= join(repeat(['endif'], 51), "\n") + @a + let @a = '' + endfunc + + " Deep nesting of for ... endfor + func Test2() + let @a = join(repeat(['for i in [1]'], 51), "\n") + let @a ..= "\n" + let @a ..= join(repeat(['endfor'], 51), "\n") + @a + let @a = '' + endfunc + + " Deep nesting of while ... endwhile + func Test3() + let @a = join(repeat(['while v:true'], 51), "\n") + let @a ..= "\n" + let @a ..= join(repeat(['endwhile'], 51), "\n") + @a + let @a = '' + endfunc + + " Deep nesting of try ... endtry + func Test4() + let @a = join(repeat(['try'], 51), "\n") + let @a ..= "\necho v:true\n" + let @a ..= join(repeat(['endtry'], 51), "\n") + @a + let @a = '' + endfunc + + " Deep nesting of function ... endfunction + func Test5() + let @a = join(repeat(['function X()'], 51), "\n") + let @a ..= "\necho v:true\n" + let @a ..= join(repeat(['endfunction'], 51), "\n") + @a + let @a = '' + endfunc + ]]) + screen:expect({any = '%[No Name%]'}) + feed(':call Test1()<CR>') + screen:expect({any = 'E579: '}) + feed('<C-C>') + screen:expect({any = '%[No Name%]'}) + feed(':call Test2()<CR>') + screen:expect({any = 'E585: '}) + feed('<C-C>') + screen:expect({any = '%[No Name%]'}) + feed(':call Test3()<CR>') + screen:expect({any = 'E585: '}) + feed('<C-C>') + screen:expect({any = '%[No Name%]'}) + feed(':call Test4()<CR>') + screen:expect({any = 'E601: '}) + feed('<C-C>') + screen:expect({any = '%[No Name%]'}) + feed(':call Test5()<CR>') + screen:expect({any = 'E1058: '}) + end) + + -- oldtest: Test_typed_script_var() + it('using s: with a typed command', function() + local screen = Screen.new(80, 24) + screen:attach() + feed(":echo get(s:, 'foo', 'x')\n") + screen:expect({any = 'E116: '}) + end) +end) diff --git a/test/functional/legacy/visual_mode_spec.lua b/test/functional/legacy/visual_mode_spec.lua index 8b5dd0c2dc..1a08fb4c0e 100644 --- a/test/functional/legacy/visual_mode_spec.lua +++ b/test/functional/legacy/visual_mode_spec.lua @@ -1,31 +1,28 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') -local call = helpers.call local clear = helpers.clear local feed = helpers.feed -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 +before_each(clear) +describe('visual line mode', function() + -- oldtest: Test_visual_block_scroll() it('redraws properly after scrolling with matchparen loaded and scrolloff=1', function() - clear{args={'-u', 'NORC'}} - screen = Screen.new(30, 7) + local screen = Screen.new(30, 7) screen:attach() screen:set_default_attr_ids({ [1] = {bold = true}, [2] = {background = Screen.colors.LightGrey}, }) - eq(1, meths.get_var('loaded_matchparen')) - feed_command('set scrolloff=1') - funcs.setline(1, {'a', 'b', 'c', 'd', 'e', '', '{', '}', '{', 'f', 'g', '}'}) - call('cursor', 5, 1) + exec([[ + source $VIMRUNTIME/plugin/matchparen.vim + set scrolloff=1 + call setline(1, ['a', 'b', 'c', 'd', 'e', '', '{', '}', '{', 'f', 'g', '}']) + call cursor(5, 1) + ]]) feed('V<c-d><c-d>') screen:expect([[ @@ -41,8 +38,8 @@ describe('visual line mode', function() end) describe('visual block mode', function() + -- oldtest: Test_visual_block_with_virtualedit() it('shows selection correctly with virtualedit=block', function() - clear() local screen = Screen.new(30, 7) screen:set_default_attr_ids({ [1] = {bold = true}, -- ModeMsg diff --git a/test/functional/legacy/window_cmd_spec.lua b/test/functional/legacy/window_cmd_spec.lua index 8b89c55f5b..0e9775060d 100644 --- a/test/functional/legacy/window_cmd_spec.lua +++ b/test/functional/legacy/window_cmd_spec.lua @@ -6,9 +6,11 @@ local exec_lua = helpers.exec_lua local feed = helpers.feed describe('splitkeep', function() - local screen = Screen.new() + local screen + before_each(function() clear('--cmd', 'set splitkeep=screen') + screen = Screen.new() screen:attach() end) @@ -193,4 +195,33 @@ describe('splitkeep', function() :quit | ]]) end) + + -- oldtest: Test_splitkeep_status() + it('does not scroll when split in callback', function() + exec([[ + call setline(1, ['a', 'b', 'c']) + set nomodified + set splitkeep=screen + let win = winnr() + wincmd s + wincmd j + ]]) + feed(':call win_move_statusline(win, 1)<CR>') + screen:expect([[ + a | + b | + c | + ~ | + ~ | + ~ | + ~ | + [No Name] | + ^a | + b | + c | + ~ | + [No Name] | + | + ]]) + end) end) diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index 28a8679205..d364986ad7 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -16,7 +16,7 @@ describe('vim.diagnostic', function() exec_lua [[ require('vim.diagnostic') - function make_diagnostic(msg, x1, y1, x2, y2, severity, source) + function make_diagnostic(msg, x1, y1, x2, y2, severity, source, code) return { lnum = x1, col = y1, @@ -25,23 +25,24 @@ describe('vim.diagnostic', function() message = msg, severity = severity, source = source, + code = code, } end - function make_error(msg, x1, y1, x2, y2, source) - return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.ERROR, source) + function make_error(msg, x1, y1, x2, y2, source, code) + return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.ERROR, source, code) end - function make_warning(msg, x1, y1, x2, y2, source) - return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.WARN, source) + function make_warning(msg, x1, y1, x2, y2, source, code) + return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.WARN, source, code) end - function make_info(msg, x1, y1, x2, y2, source) - return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.INFO, source) + function make_info(msg, x1, y1, x2, y2, source, code) + return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.INFO, source, code) end - function make_hint(msg, x1, y1, x2, y2, source) - return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.HINT, source) + function make_hint(msg, x1, y1, x2, y2, source, code) + return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.HINT, source, code) end function count_diagnostics(bufnr, severity, namespace) @@ -89,20 +90,25 @@ describe('vim.diagnostic', function() 'DiagnosticFloatingError', 'DiagnosticFloatingHint', 'DiagnosticFloatingInfo', + 'DiagnosticFloatingOk', 'DiagnosticFloatingWarn', 'DiagnosticHint', 'DiagnosticInfo', + 'DiagnosticOk', 'DiagnosticSignError', 'DiagnosticSignHint', 'DiagnosticSignInfo', + 'DiagnosticSignOk', 'DiagnosticSignWarn', 'DiagnosticUnderlineError', 'DiagnosticUnderlineHint', 'DiagnosticUnderlineInfo', + 'DiagnosticUnderlineOk', 'DiagnosticUnderlineWarn', 'DiagnosticVirtualTextError', 'DiagnosticVirtualTextHint', 'DiagnosticVirtualTextInfo', + 'DiagnosticVirtualTextOk', 'DiagnosticVirtualTextWarn', 'DiagnosticWarn', }, exec_lua([[return vim.fn.getcompletion('Diagnostic', 'highlight')]])) @@ -159,6 +165,24 @@ describe('vim.diagnostic', function() ]]) end) + it('removes diagnostic from stale cache on reset', function() + local diagnostics = exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic #1', 1, 1, 1, 1), + make_error('Diagnostic #2', 2, 1, 2, 1), + }) + local other_bufnr = vim.fn.bufadd('test | test') + vim.cmd('noautocmd bwipeout! ' .. diagnostic_bufnr) + return vim.diagnostic.get(diagnostic_bufnr) + ]] + eq(2, #diagnostics) + diagnostics = exec_lua [[ + vim.diagnostic.reset() + return vim.diagnostic.get() + ]] + eq(0, #diagnostics) + end) + it('resolves buffer number 0 to the current buffer', function() eq(2, exec_lua [[ vim.api.nvim_set_current_buf(diagnostic_bufnr) @@ -1160,6 +1184,44 @@ end) eq(" some_linter: 👀 Warning", result[1][2][1]) eq(" another_linter: 🔥 Error", result[2][2][1]) end) + + it('can add a suffix to virtual text', function() + eq(' Some error ✘', exec_lua [[ + local diagnostics = { + make_error('Some error', 0, 0, 0, 0), + } + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics, { + underline = false, + virtual_text = { + prefix = '', + suffix = ' ✘', + } + }) + + local extmarks = get_virt_text_extmarks(diagnostic_ns) + local virt_text = extmarks[1][4].virt_text[2][1] + return virt_text + ]]) + + eq(' Some error [err-code]', exec_lua [[ + local diagnostics = { + make_error('Some error', 0, 0, 0, 0, nil, 'err-code'), + } + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics, { + underline = false, + virtual_text = { + prefix = '', + suffix = function(diag) return string.format(' [%s]', diag.code) end, + } + }) + + local extmarks = get_virt_text_extmarks(diagnostic_ns) + local virt_text = extmarks[1][4].virt_text[2][1] + return virt_text + ]]) + end) end) describe('set()', function() @@ -1776,10 +1838,55 @@ end) return lines ]]) - eq("Error executing lua: .../diagnostic.lua:0: prefix: expected 'string' or 'table' or 'function', got 42", + eq(".../diagnostic.lua:0: prefix: expected string|table|function, got number", pcall_err(exec_lua, [[ vim.diagnostic.open_float({ prefix = 42 }) ]])) end) + it('can add a suffix to diagnostics', function() + -- Default is to render the diagnostic error code + eq({'1. Syntax error [code-x]', '2. Some warning [code-y]'}, exec_lua [[ + local diagnostics = { + make_error("Syntax error", 0, 1, 0, 3, nil, "code-x"), + make_warning("Some warning", 1, 1, 1, 3, nil, "code-y"), + } + 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({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 + ]]) + + eq({'1. Syntax error', '2. Some warning'}, exec_lua [[ + local diagnostics = { + make_error("Syntax error", 0, 1, 0, 3, nil, "code-x"), + make_warning("Some warning", 1, 1, 1, 3, nil, "code-y"), + } + 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({header = false, scope = "buffer", suffix = ""}) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) + + -- Suffix is rendered on the last line of a multiline diagnostic + eq({'1. Syntax error', ' More context [code-x]'}, exec_lua [[ + local diagnostics = { + make_error("Syntax error\nMore context", 0, 1, 0, 3, nil, "code-x"), + } + 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({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 + ]]) + + eq(".../diagnostic.lua:0: suffix: expected string|table|function, got number", + pcall_err(exec_lua, [[ vim.diagnostic.open_float({ suffix = 42 }) ]])) + end) + it('works with the old signature', function() eq({'1. Syntax error'}, exec_lua [[ local diagnostics = { @@ -2017,5 +2124,31 @@ end) return vim.g.diagnostic_autocmd_triggered == diagnostic_bufnr ]]) end) + + it("checks if diagnostics are disabled in a buffer", function() + eq({true, true, true , true}, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic #1', 1, 1, 1, 1), + }) + vim.api.nvim_set_current_buf(diagnostic_bufnr) + vim.diagnostic.disable() + return { + vim.diagnostic.is_disabled(), + vim.diagnostic.is_disabled(diagnostic_bufnr), + vim.diagnostic.is_disabled(diagnostic_bufnr, diagnostic_ns), + vim.diagnostic.is_disabled(_, diagnostic_ns), + } + ]]) + + eq({false, false, false , false}, exec_lua [[ + vim.diagnostic.enable() + return { + vim.diagnostic.is_disabled(), + vim.diagnostic.is_disabled(diagnostic_bufnr), + vim.diagnostic.is_disabled(diagnostic_bufnr, diagnostic_ns), + vim.diagnostic.is_disabled(_, diagnostic_ns), + } + ]]) + end) end) end) diff --git a/test/functional/lua/ffi_spec.lua b/test/functional/lua/ffi_spec.lua index 80c01a2b8c..18b13a8959 100644 --- a/test/functional/lua/ffi_spec.lua +++ b/test/functional/lua/ffi_spec.lua @@ -29,32 +29,37 @@ describe('ffi.cdef', function() typedef struct window_S win_T; typedef struct {} stl_hlrec_t; typedef struct {} StlClickRecord; + typedef struct {} statuscol_T; typedef struct {} Error; win_T *find_window_by_handle(int Window, Error *err); int build_stl_str_hl( win_T *wp, - char_u *out, + char *out, size_t outlen, - char_u *fmt, - int use_sandbox, - char_u fillchar, + char *fmt, + char *opt_name, + int opt_scope, + int fillchar, int maxwidth, stl_hlrec_t **hltab, - StlClickRecord **tabtab + StlClickRecord **tabtab, + statuscol_T *scp ); ]] return ffi.C.build_stl_str_hl( ffi.C.find_window_by_handle(0, ffi.new('Error')), - ffi.new('char_u[1024]'), + ffi.new('char[1024]'), 1024, - ffi.cast('char_u*', 'StatusLineOfLength20'), + ffi.cast('char*', 'StatusLineOfLength20'), + nil, 0, 0, 0, nil, + nil, nil ) ]=]) diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua index 3123ec324c..642d36f63a 100644 --- a/test/functional/lua/fs_spec.lua +++ b/test/functional/lua/fs_spec.lua @@ -1,4 +1,5 @@ local helpers = require('test.functional.helpers')(after_each) +local lfs = require('lfs') local clear = helpers.clear local exec_lua = helpers.exec_lua @@ -7,10 +8,43 @@ local mkdir_p = helpers.mkdir_p local rmdir = helpers.rmdir local nvim_dir = helpers.nvim_dir local test_build_dir = helpers.test_build_dir -local iswin = helpers.iswin local nvim_prog = helpers.nvim_prog +local is_os = helpers.is_os -local nvim_prog_basename = iswin() and 'nvim.exe' or 'nvim' +local nvim_prog_basename = is_os('win') and 'nvim.exe' or 'nvim' + +local test_basename_dirname_eq = { + '~/foo/', + '~/foo', + '~/foo/bar.lua', + 'foo.lua', + ' ', + '', + '.', + '..', + '../', + '~', + '/usr/bin', + '/usr/bin/gcc', + '/', + '/usr/', + '/usr', + 'c:/usr', + 'c:/', + 'c:', + 'c:/users/foo', + 'c:/users/foo/bar.lua', + 'c:/users/foo/bar/../', +} + +local tests_windows_paths = { + 'c:\\usr', + 'c:\\', + 'c:', + 'c:\\users\\foo', + 'c:\\users\\foo\\bar.lua', + 'c:\\users\\foo\\bar\\..\\', +} before_each(clear) @@ -41,19 +75,73 @@ describe('vim.fs', function() local nvim_dir = ... return vim.fs.dirname(nvim_dir) ]], nvim_dir)) + + local function test_paths(paths) + for _, path in ipairs(paths) do + eq( + exec_lua([[ + local path = ... + return vim.fn.fnamemodify(path,':h'):gsub('\\', '/') + ]], path), + exec_lua([[ + local path = ... + return vim.fs.dirname(path) + ]], path), + path + ) + end + end + + test_paths(test_basename_dirname_eq) + if is_os('win') then + test_paths(tests_windows_paths) + end end) end) describe('basename()', function() it('works', function() + eq(nvim_prog_basename, exec_lua([[ local nvim_prog = ... return vim.fs.basename(nvim_prog) ]], nvim_prog)) + + local function test_paths(paths) + for _, path in ipairs(paths) do + eq( + exec_lua([[ + local path = ... + return vim.fn.fnamemodify(path,':t'):gsub('\\', '/') + ]], path), + exec_lua([[ + local path = ... + return vim.fs.basename(path) + ]], path), + path + ) + end + end + + test_paths(test_basename_dirname_eq) + if is_os('win') then + test_paths(tests_windows_paths) + end end) end) describe('dir()', function() + before_each(function() + lfs.mkdir('testd') + lfs.mkdir('testd/a') + lfs.mkdir('testd/a/b') + lfs.mkdir('testd/a/b/c') + end) + + after_each(function() + rmdir('testd') + end) + it('works', function() eq(true, exec_lua([[ local dir, nvim = ... @@ -65,6 +153,71 @@ describe('vim.fs', function() return false ]], nvim_dir, nvim_prog_basename)) end) + + it('works with opts.depth and opts.skip', function() + io.open('testd/a1', 'w'):close() + io.open('testd/b1', 'w'):close() + io.open('testd/c1', 'w'):close() + io.open('testd/a/a2', 'w'):close() + io.open('testd/a/b2', 'w'):close() + io.open('testd/a/c2', 'w'):close() + io.open('testd/a/b/a3', 'w'):close() + io.open('testd/a/b/b3', 'w'):close() + io.open('testd/a/b/c3', 'w'):close() + io.open('testd/a/b/c/a4', 'w'):close() + io.open('testd/a/b/c/b4', 'w'):close() + io.open('testd/a/b/c/c4', 'w'):close() + + local function run(dir, depth, skip) + local r = exec_lua([[ + local dir, depth, skip = ... + local r = {} + local skip_f + if skip then + skip_f = function(n) + if vim.tbl_contains(skip or {}, n) then + return false + end + end + end + for name, type_ in vim.fs.dir(dir, { depth = depth, skip = skip_f }) do + r[name] = type_ + end + return r + ]], dir, depth, skip) + return r + end + + local exp = {} + + exp['a1'] = 'file' + exp['b1'] = 'file' + exp['c1'] = 'file' + exp['a'] = 'directory' + + eq(exp, run('testd', 1)) + + exp['a/a2'] = 'file' + exp['a/b2'] = 'file' + exp['a/c2'] = 'file' + exp['a/b'] = 'directory' + + eq(exp, run('testd', 2)) + + exp['a/b/a3'] = 'file' + exp['a/b/b3'] = 'file' + exp['a/b/c3'] = 'file' + exp['a/b/c'] = 'directory' + + eq(exp, run('testd', 3)) + eq(exp, run('testd', 999, {'a/b/c'})) + + exp['a/b/c/a4'] = 'file' + exp['a/b/c/b4'] = 'file' + exp['a/b/c/c4'] = 'file' + + eq(exp, run('testd', 999)) + end) end) describe('find()', function() @@ -77,6 +230,11 @@ describe('vim.fs', function() local dir, nvim = ... return vim.fs.find(nvim, { path = dir, type = 'file' }) ]], test_build_dir, nvim_prog_basename)) + eq({nvim_dir}, exec_lua([[ + local dir = ... + local parent, name = dir:match('^(.*/)([^/]+)$') + return vim.fs.find(name, { path = parent, upward = true, type = 'directory' }) + ]], nvim_dir)) end) it('accepts predicate as names', function() @@ -102,7 +260,7 @@ describe('vim.fs', function() eq('C:/Users/jdoe', exec_lua [[ return vim.fs.normalize('C:\\Users\\jdoe') ]]) end) it('works with ~', function() - if iswin() then + if is_os('win') then pending([[$HOME does not exist on Windows ¯\_(ツ)_/¯]]) end eq(os.getenv('HOME') .. '/src/foo', exec_lua [[ return vim.fs.normalize('~/src/foo') ]]) diff --git a/test/functional/lua/help_spec.lua b/test/functional/lua/help_spec.lua index 251275b5cc..b396e2ba30 100644 --- a/test/functional/lua/help_spec.lua +++ b/test/functional/lua/help_spec.lua @@ -19,10 +19,11 @@ describe(':help docs', function() local rv = exec_lua([[return require('scripts.gen_help_html').validate('./build/runtime/doc')]]) -- Check that we actually found helpfiles. ok(rv.helpfiles > 100, '>100 :help files', rv.helpfiles) + eq({}, rv.invalid_links, 'invalid tags in :help docs') + eq({}, rv.invalid_urls, 'invalid URLs in :help docs') -- Check that parse errors did not increase wildly. -- TODO: Fix all parse errors in :help files. - ok(rv.err_count < 150, '<150 parse errors', rv.err_count) - eq({}, rv.invalid_links, exec_lua([[return 'found invalid :help tag links:\n'..vim.inspect(...)]], rv.invalid_links)) + ok(rv.err_count < 250, '<250 parse errors', rv.err_count) end) it('gen_help_html.lua generates HTML', function() @@ -43,7 +44,7 @@ describe(':help docs', function() tmpdir ) eq(4, #rv.helpfiles) - ok(rv.err_count <= 1, '<=1 parse errors', rv.err_count) - eq({}, rv.invalid_links, exec_lua([[return 'found invalid :help tag links:\n'..vim.inspect(...)]], rv.invalid_links)) + eq(0, rv.err_count, 'parse errors in :help docs') + eq({}, rv.invalid_links, 'invalid tags in :help docs') end) end) diff --git a/test/functional/lua/inspector_spec.lua b/test/functional/lua/inspector_spec.lua new file mode 100644 index 0000000000..5e488bb082 --- /dev/null +++ b/test/functional/lua/inspector_spec.lua @@ -0,0 +1,56 @@ +local helpers = require('test.functional.helpers')(after_each) +local exec_lua = helpers.exec_lua +local eq = helpers.eq +local eval = helpers.eval +local clear = helpers.clear + +describe('vim.inspect_pos', function() + before_each(function() + clear() + end) + + it('it returns items', function() + local ret = exec_lua([[ + local buf = vim.api.nvim_create_buf(true, false) + vim.api.nvim_set_current_buf(buf) + vim.api.nvim_buf_set_lines(0, 0, -1, false, {"local a = 123"}) + vim.api.nvim_buf_set_option(buf, "filetype", "lua") + vim.cmd("syntax on") + return {buf, vim.inspect_pos(0, 0, 10)} + ]]) + local buf, items = unpack(ret) + eq('', eval('v:errmsg')) + eq({ + buffer = buf, + col = 10, + row = 0, + extmarks = {}, + treesitter = {}, + semantic_tokens = {}, + syntax = { + { + hl_group = 'luaNumber', + hl_group_link = 'Constant', + }, + }, + }, items) + end) +end) + +describe('vim.show_pos', function() + before_each(function() + clear() + end) + + it('it does not error', function() + exec_lua([[ + local buf = vim.api.nvim_create_buf(true, false) + vim.api.nvim_set_current_buf(buf) + vim.api.nvim_buf_set_lines(0, 0, -1, false, {"local a = 123"}) + vim.api.nvim_buf_set_option(buf, "filetype", "lua") + vim.cmd("syntax on") + return {buf, vim.show_pos(0, 0, 10)} + ]]) + eq('', eval('v:errmsg')) + end) +end) diff --git a/test/functional/lua/overrides_spec.lua b/test/functional/lua/overrides_spec.lua index 32c1615a45..3f107811ae 100644 --- a/test/functional/lua/overrides_spec.lua +++ b/test/functional/lua/overrides_spec.lua @@ -8,12 +8,12 @@ local feed = helpers.feed local clear = helpers.clear local funcs = helpers.funcs local meths = helpers.meths -local iswin = helpers.iswin local command = helpers.command local write_file = helpers.write_file local exec_capture = helpers.exec_capture local exec_lua = helpers.exec_lua local pcall_err = helpers.pcall_err +local is_os = helpers.is_os local screen @@ -135,7 +135,7 @@ describe('print', function() print("very slow") vim.api.nvim_command("sleep 1m") -- force deferred event processing end - ]], (iswin() and "timeout 1") or "sleep 0.1") + ]], (is_os('win') and "timeout 1") or "sleep 0.1") eq('very slow\nvery fast', exec_capture('lua test()')) end) end) diff --git a/test/functional/lua/runtime_spec.lua b/test/functional/lua/runtime_spec.lua index e9c34c9228..b659f2eacb 100644 --- a/test/functional/lua/runtime_spec.lua +++ b/test/functional/lua/runtime_spec.lua @@ -4,6 +4,7 @@ local clear = helpers.clear local eq = helpers.eq local eval = helpers.eval local exec = helpers.exec +local funcs = helpers.funcs local mkdir_p = helpers.mkdir_p local rmdir = helpers.rmdir local write_file = helpers.write_file @@ -29,6 +30,7 @@ describe('runtime:', function() after_each(function() rmdir(plug_dir) + exec('bwipe!') end) describe('colors', function() @@ -39,6 +41,8 @@ describe('runtime:', function() mkdir_p(colorscheme_folder) write_file(colorscheme_file, [[vim.g.lua_colorscheme = 1]]) + eq({'new_colorscheme'}, funcs.getcompletion('new_c', 'color')) + exec('colorscheme new_colorscheme') eq(1, eval('g:lua_colorscheme')) @@ -64,23 +68,25 @@ describe('runtime:', function() it('loads lua compilers', function() local compiler_file = compiler_folder .. sep .. 'new_compiler.lua' mkdir_p(compiler_folder) - write_file(compiler_file, [[vim.g.lua_compiler = 1]]) + write_file(compiler_file, [[vim.b.lua_compiler = 1]]) + + eq({'new_compiler'}, funcs.getcompletion('new_c', 'compiler')) exec('compiler new_compiler') - eq(1, eval('g:lua_compiler')) + eq(1, eval('b:lua_compiler')) rmdir(compiler_folder) end) it('loads vim compilers when both lua and vim version exist', function() local compiler_file = compiler_folder .. sep .. 'new_compiler' mkdir_p(compiler_folder) - write_file(compiler_file..'.vim', [[let g:compiler = 'vim']]) - write_file(compiler_file..'.lua', [[vim.g.compiler = 'lua']]) + write_file(compiler_file..'.vim', [[let b:compiler = 'vim']]) + write_file(compiler_file..'.lua', [[vim.b.compiler = 'lua']]) exec('compiler new_compiler') - eq('vim', eval('g:compiler')) + eq('vim', eval('b:compiler')) rmdir(compiler_folder) end) end) @@ -91,10 +97,12 @@ describe('runtime:', function() it('loads lua ftplugins', function() local ftplugin_file = table.concat({ftplugin_folder , 'new-ft.lua'}, sep) mkdir_p(ftplugin_folder) - write_file(ftplugin_file , [[vim.g.lua_ftplugin = 1]]) + write_file(ftplugin_file , [[vim.b.lua_ftplugin = 1]]) + + eq({'new-ft'}, funcs.getcompletion('new-f', 'filetype')) exec [[set filetype=new-ft]] - eq(1, eval('g:lua_ftplugin')) + eq(1, eval('b:lua_ftplugin')) rmdir(ftplugin_folder) end) end) @@ -105,10 +113,12 @@ describe('runtime:', function() it('loads lua indents', function() local indent_file = table.concat({indent_folder , 'new-ft.lua'}, sep) mkdir_p(indent_folder) - write_file(indent_file , [[vim.g.lua_indent = 1]]) + write_file(indent_file , [[vim.b.lua_indent = 1]]) + + eq({'new-ft'}, funcs.getcompletion('new-f', 'filetype')) exec [[set filetype=new-ft]] - eq(1, eval('g:lua_indent')) + eq(1, eval('b:lua_indent')) rmdir(indent_folder) end) end) @@ -116,24 +126,32 @@ describe('runtime:', function() describe('syntax', function() local syntax_folder = table.concat({plug_dir, 'syntax'}, sep) - it('loads lua syntaxes on filetype change', function() + before_each(function() local syntax_file = table.concat({syntax_folder , 'my-lang.lua'}, sep) mkdir_p(syntax_folder) - write_file(syntax_file , [[vim.g.lua_syntax = 1]]) + write_file(syntax_file , [[vim.b.current_syntax = 'my-lang']]) + exec([[let b:current_syntax = '']]) + end) + it('loads lua syntaxes on filetype change', function() exec('set filetype=my-lang') - eq(1, eval('g:lua_syntax')) - rmdir(syntax_folder) + eq('my-lang', eval('b:current_syntax')) end) it('loads lua syntaxes on syntax change', function() - local syntax_file = table.concat({syntax_folder , 'my-lang.lua'}, sep) - mkdir_p(syntax_folder) - write_file(syntax_file , [[vim.g.lua_syntax = 5]]) - exec('set syntax=my-lang') - eq(5, eval('g:lua_syntax')) - rmdir(syntax_folder) + eq('my-lang', eval('b:current_syntax')) + end) + + it('loads lua syntaxes for :ownsyntax', function() + exec('ownsyntax my-lang') + eq('my-lang', eval('w:current_syntax')) + eq('', eval('b:current_syntax')) + end) + + it('lua syntaxes are included in cmdline completion', function() + eq({'my-lang'}, funcs.getcompletion('my-l', 'filetype')) + eq({'my-lang'}, funcs.getcompletion('my-l', 'syntax')) end) end) diff --git a/test/functional/lua/secure_spec.lua b/test/functional/lua/secure_spec.lua new file mode 100644 index 0000000000..2647b2be46 --- /dev/null +++ b/test/functional/lua/secure_spec.lua @@ -0,0 +1,284 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local eq = helpers.eq +local clear = helpers.clear +local command = helpers.command +local pathsep = helpers.get_pathsep() +local is_os = helpers.is_os +local curbufmeths = helpers.curbufmeths +local exec_lua = helpers.exec_lua +local feed_command = helpers.feed_command +local feed = helpers.feed +local funcs = helpers.funcs +local pcall_err = helpers.pcall_err +local matches = helpers.matches + +describe('vim.secure', function() + describe('read()', function() + local xstate = 'Xstate' + + setup(function() + helpers.mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim')) + end) + + teardown(function() + helpers.rmdir(xstate) + end) + + before_each(function() + helpers.write_file('Xfile', [[ + let g:foobar = 42 + ]]) + clear{env={XDG_STATE_HOME=xstate}} + end) + + after_each(function() + os.remove('Xfile') + helpers.rmdir(xstate) + end) + + it('works', function() + local screen = Screen.new(80, 8) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {bold = true, reverse = true}, + [3] = {bold = true, foreground = Screen.colors.SeaGreen}, + [4] = {reverse = true}, + }) + + local cwd = funcs.getcwd() + + -- Need to use feed_command instead of exec_lua because of the confirmation prompt + feed_command([[lua vim.secure.read('Xfile')]]) + screen:expect{grid=[[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + :lua vim.secure.read('Xfile') | + {3:]] .. cwd .. pathsep .. [[Xfile is untrusted}{MATCH:%s+}| + {3:[i]gnore, (v)iew, (d)eny, (a)llow: }^ | + ]]} + feed('d') + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', cwd .. pathsep .. 'Xfile'), vim.trim(trust)) + eq(helpers.NIL, exec_lua([[return vim.secure.read('Xfile')]])) + + os.remove(funcs.stdpath('state') .. pathsep .. 'trust') + + feed_command([[lua vim.secure.read('Xfile')]]) + screen:expect{grid=[[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + :lua vim.secure.read('Xfile') | + {3:]] .. cwd .. pathsep .. [[Xfile is untrusted}{MATCH:%s+}| + {3:[i]gnore, (v)iew, (d)eny, (a)llow: }^ | + ]]} + feed('a') + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + local hash = funcs.sha256(helpers.read_file('Xfile')) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('%s %s', hash, cwd .. pathsep .. 'Xfile'), vim.trim(trust)) + eq(helpers.NIL, exec_lua([[vim.secure.read('Xfile')]])) + + os.remove(funcs.stdpath('state') .. pathsep .. 'trust') + + feed_command([[lua vim.secure.read('Xfile')]]) + screen:expect{grid=[[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + :lua vim.secure.read('Xfile') | + {3:]] .. cwd .. pathsep .. [[Xfile is untrusted}{MATCH:%s+}| + {3:[i]gnore, (v)iew, (d)eny, (a)llow: }^ | + ]]} + feed('i') + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + -- Trust database is not updated + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(nil, trust) + + feed_command([[lua vim.secure.read('Xfile')]]) + screen:expect{grid=[[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + :lua vim.secure.read('Xfile') | + {3:]] .. cwd .. pathsep .. [[Xfile is untrusted}{MATCH:%s+}| + {3:[i]gnore, (v)iew, (d)eny, (a)llow: }^ | + ]]} + feed('v') + screen:expect{grid=[[ + ^let g:foobar = 42 | + {1:~ }| + {1:~ }| + {2:]] .. funcs.fnamemodify(cwd, ':~') .. pathsep .. [[Xfile [RO]{MATCH:%s+}| + | + {1:~ }| + {4:[No Name] }| + | + ]]} + + -- Trust database is not updated + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(nil, trust) + + -- Cannot write file + pcall_err(command, 'write') + eq(true, curbufmeths.get_option('readonly')) + end) + end) + + describe('trust()', function() + local xstate = 'Xstate' + + setup(function() + helpers.mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim')) + end) + + teardown(function() + helpers.rmdir(xstate) + end) + + before_each(function() + helpers.write_file('test_file', 'test') + end) + + after_each(function() + os.remove('test_file') + end) + + it('returns error when passing both path and bufnr', function() + matches('"path" and "bufnr" are mutually exclusive', + pcall_err(exec_lua, [[vim.secure.trust({action='deny', bufnr=0, path='test_file'})]])) + end) + + it('returns error when passing neither path or bufnr', function() + matches('one of "path" or "bufnr" is required', + pcall_err(exec_lua, [[vim.secure.trust({action='deny'})]])) + end) + + it('trust then deny then remove a file using bufnr', function() + local cwd = funcs.getcwd() + local hash = funcs.sha256(helpers.read_file('test_file')) + local full_path = cwd .. pathsep .. 'test_file' + + command('edit test_file') + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('%s %s', hash, full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='deny', bufnr=0})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='remove', bufnr=0})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq('', vim.trim(trust)) + end) + + it('deny then trust then remove a file using bufnr', function() + local cwd = funcs.getcwd() + local hash = funcs.sha256(helpers.read_file('test_file')) + local full_path = cwd .. pathsep .. 'test_file' + + command('edit test_file') + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='deny', bufnr=0})}]])) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('%s %s', hash, full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='remove', bufnr=0})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq('', vim.trim(trust)) + end) + + it('trust using bufnr then deny then remove a file using path', function() + local cwd = funcs.getcwd() + local hash = funcs.sha256(helpers.read_file('test_file')) + local full_path = cwd .. pathsep .. 'test_file' + + command('edit test_file') + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('%s %s', hash, full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='deny', path='test_file'})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='remove', path='test_file'})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq('', vim.trim(trust)) + end) + + it('deny then trust then remove a file using bufnr', function() + local cwd = funcs.getcwd() + local hash = funcs.sha256(helpers.read_file('test_file')) + local full_path = cwd .. pathsep .. 'test_file' + + command('edit test_file') + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='deny', path='test_file'})}]])) + local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('! %s', full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq(string.format('%s %s', hash, full_path), vim.trim(trust)) + + eq({true, full_path}, exec_lua([[return {vim.secure.trust({action='remove', path='test_file'})}]])) + trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust') + eq('', vim.trim(trust)) + end) + + it('trust returns error when buffer not associated to file', function() + command('new') + eq({false, 'buffer is not associated with a file'}, + exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])) + end) + end) +end) diff --git a/test/functional/lua/spell_spec.lua b/test/functional/lua/spell_spec.lua index 7e831f16a7..b3de6a0912 100644 --- a/test/functional/lua/spell_spec.lua +++ b/test/functional/lua/spell_spec.lua @@ -15,7 +15,7 @@ describe('vim.spell', function() end it('can handle nil', function() - eq([[Error executing lua: [string "<nvim>"]:0: bad argument #1 to 'check' (expected string)]], + eq([[bad argument #1 to 'check' (expected string)]], pcall_err(exec_lua, [[vim.spell.check(nil)]])) end) diff --git a/test/functional/lua/thread_spec.lua b/test/functional/lua/thread_spec.lua index e183ce3a57..c7f2783cf3 100644 --- a/test/functional/lua/thread_spec.lua +++ b/test/functional/lua/thread_spec.lua @@ -272,7 +272,7 @@ describe('threadpool', function() work:queue({}) ]]) - eq([[Error executing lua: [string "<nvim>"]:0: Error: thread arg not support type 'function' at 1]], + eq([[Error: thread arg not support type 'function' at 1]], status) end) diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua index 57ffcf7b4e..6481da900e 100644 --- a/test/functional/lua/ui_event_spec.lua +++ b/test/functional/lua/ui_event_spec.lua @@ -117,4 +117,31 @@ describe('vim.ui_attach', function() }) eq(0, helpers.eval('v:shell_error')) end) + + it('can receive accurate message kinds even if they are history', function() + exec_lua([[ + vim.cmd.echomsg("'message1'") + print('message2') + vim.ui_attach(ns, { ext_messages = true }, on_event) + vim.cmd.echomsg("'message3'") + ]]) + feed(':messages<cr>') + feed('<cr>') + + local actual = exec_lua([[ + return vim.tbl_filter(function (event) + return event[1] == "msg_history_show" + end, events) + ]]) + eq({ + { + 'msg_history_show', + { + { 'echomsg', { { 0, 'message1' } } }, + { '', { { 0, 'message2' } } }, + { 'echomsg', { { 0, 'message3' } } }, + }, + }, + }, actual, inspect(actual)) + end) end) diff --git a/test/functional/lua/ui_spec.lua b/test/functional/lua/ui_spec.lua index 3fcb2dec8d..9ee99b4905 100644 --- a/test/functional/lua/ui_spec.lua +++ b/test/functional/lua/ui_spec.lua @@ -4,6 +4,7 @@ local exec_lua = helpers.exec_lua local clear = helpers.clear local feed = helpers.feed local eval = helpers.eval +local poke_eventloop = helpers.poke_eventloop describe('vim.ui', function() before_each(function() @@ -83,5 +84,50 @@ describe('vim.ui', function() feed('abcdefg<cr>') eq('abcdefg', exec_lua('return result')) end) + + it('can input empty text #18144', function() + feed(':lua vim.ui.input({}, function(input) result = input end)<cr>') + feed('<cr>') + eq('', exec_lua('return result')) + end) + + it('can input empty text with cancelreturn opt #18144', function() + feed(':lua vim.ui.input({ cancelreturn = "CANCEL" }, function(input) result = input end)<cr>') + feed('<cr>') + eq('', exec_lua('return result')) + end) + + it('can return nil when aborted with ESC #18144', function() + feed(':lua result = "on_confirm not called"<cr>') + feed(':lua vim.ui.input({}, function(input) result = input end)<cr>') + feed('Inputted Text<esc>') + -- Note: When `result == nil`, exec_lua('returns result') returns vim.NIL + eq(true, exec_lua('return (nil == result)')) + end) + + it('can return opts.cacelreturn when aborted with ESC with cancelreturn opt #18144', function() + feed(':lua result = "on_confirm not called"<cr>') + feed(':lua vim.ui.input({ cancelreturn = "CANCEL" }, function(input) result = input end)<cr>') + feed('Inputted Text<esc>') + eq('CANCEL', exec_lua('return result')) + end) + + it('can return nil when interrupted with Ctrl-C #18144', function() + feed(':lua result = "on_confirm not called"<cr>') + feed(':lua vim.ui.input({}, function(input) result = input end)<cr>') + poke_eventloop() -- This is needed because Ctrl-C flushes input + feed('Inputted Text<c-c>') + eq(true, exec_lua('return (nil == result)')) + end) + + it('can return the identical object when an arbitrary opts.cancelreturn object is given', function() + feed(':lua fn = function() return 42 end<CR>') + eq(42, exec_lua('return fn()')) + feed(':lua vim.ui.input({ cancelreturn = fn }, function(input) result = input end)<cr>') + feed('cancel<esc>') + eq(true, exec_lua('return (result == fn)')) + eq(42, exec_lua('return result()')) + end) + end) end) diff --git a/test/functional/lua/uri_spec.lua b/test/functional/lua/uri_spec.lua index 2cb0b26c6d..416e9e1f02 100644 --- a/test/functional/lua/uri_spec.lua +++ b/test/functional/lua/uri_spec.lua @@ -2,6 +2,8 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local exec_lua = helpers.exec_lua local eq = helpers.eq +local is_os = helpers.is_os +local skip = helpers.skip local write_file = require('test.helpers').write_file describe('URI methods', function() @@ -167,7 +169,7 @@ describe('URI methods', function() describe('uri from bufnr', function() it('Windows paths should not be treated as uris', function() - if not helpers.iswin() then return end + skip(not is_os('win'), "Not applicable on non-Windows") local file = helpers.tmpname() write_file(file, 'Test content') diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 47a0004183..867f366d06 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') +local nvim_prog = helpers.nvim_prog local funcs = helpers.funcs local meths = helpers.meths local command = helpers.command @@ -22,7 +23,6 @@ local remove_trace = helpers.remove_trace local mkdir_p = helpers.mkdir_p local rmdir = helpers.rmdir local write_file = helpers.write_file -local expect_exit = helpers.expect_exit local poke_eventloop = helpers.poke_eventloop local assert_alive = helpers.assert_alive @@ -160,31 +160,31 @@ describe('lua stdlib', function() it("vim.str_utfindex/str_byteindex", function() exec_lua([[_G.test_text = "xy åäö ɧ 汉语 ↥ 🤦x🦄 å بِيَّ\000ъ"]]) - local indicies32 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,29,33,34,35,37,38,40,42,44,46,48,49,51} - local indicies16 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,28,29,33,33,34,35,37,38,40,42,44,46,48,49,51} - for i,k in pairs(indicies32) do + local indices32 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,29,33,34,35,37,38,40,42,44,46,48,49,51} + local indices16 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,28,29,33,33,34,35,37,38,40,42,44,46,48,49,51} + for i,k in pairs(indices32) do eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ...)", i), i) end - for i,k in pairs(indicies16) do + for i,k in pairs(indices16) do eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ..., true)", i), i) end - matches(": index out of range$", pcall_err(exec_lua, "return vim.str_byteindex(_G.test_text, ...)", #indicies32 + 1)) - matches(": index out of range$", pcall_err(exec_lua, "return vim.str_byteindex(_G.test_text, ..., true)", #indicies16 + 1)) + eq("index out of range", pcall_err(exec_lua, "return vim.str_byteindex(_G.test_text, ...)", #indices32 + 1)) + eq("index out of range", pcall_err(exec_lua, "return vim.str_byteindex(_G.test_text, ..., true)", #indices16 + 1)) local i32, i16 = 0, 0 local len = 51 for k = 0,len do - if indicies32[i32] < k then + if indices32[i32] < k then i32 = i32 + 1 end - if indicies16[i16] < k then + if indices16[i16] < k then i16 = i16 + 1 - if indicies16[i16+1] == indicies16[i16] then + if indices16[i16+1] == indices16[i16] then i16 = i16 + 1 end end eq({i32, i16}, exec_lua("return {vim.str_utfindex(_G.test_text, ...)}", k), k) end - matches(": index out of range$", pcall_err(exec_lua, "return vim.str_utfindex(_G.test_text, ...)", len + 1)) + eq("index out of range", pcall_err(exec_lua, "return vim.str_utfindex(_G.test_text, ...)", len + 1)) end) it("vim.str_utf_start", function() @@ -419,6 +419,12 @@ describe('lua stdlib', function() return getmetatable(t2) == mt ]])) + ok(exec_lua([[ + local t1 = {a = vim.NIL} + local t2 = vim.deepcopy(t1) + return t2.a == vim.NIL + ]])) + matches('Cannot deepcopy object of type thread', pcall_err(exec_lua, [[ local thread = coroutine.create(function () return 0 end) @@ -430,6 +436,8 @@ describe('lua stdlib', function() it('vim.pesc', function() eq('foo%-bar', exec_lua([[return vim.pesc('foo-bar')]])) eq('foo%%%-bar', exec_lua([[return vim.pesc(vim.pesc('foo-bar'))]])) + -- pesc() returns one result. #20751 + eq({'x'}, exec_lua([[return {vim.pesc('x')}]])) -- Validates args. matches('s: expected string, got number', @@ -504,6 +512,8 @@ describe('lua stdlib', function() eq(NIL, exec_lua("return vim.tbl_get({ unindexable = function () end }, 'unindexable', 'missing_key')")) eq(NIL, exec_lua("return vim.tbl_get({}, 'missing_key')")) eq(NIL, exec_lua("return vim.tbl_get({})")) + eq(1, exec_lua("return select('#', vim.tbl_get({}))")) + eq(1, exec_lua("return select('#', vim.tbl_get({ nested = {} }, 'nested', 'missing_key'))")) end) it('vim.tbl_extend', function() @@ -753,6 +763,20 @@ describe('lua stdlib', function() pcall_err(exec_lua, code)) end) + it('vim.spairs', function() + local res = '' + local table = { + ccc=1, + bbb=2, + ddd=3, + aaa=4 + } + for key, _ in vim.spairs(table) do + res = res .. key + end + matches('aaabbbcccddd', res) + end) + it('vim.call, vim.fn', function() eq(true, exec_lua([[return vim.call('sin', 0.0) == 0.0 ]])) eq(true, exec_lua([[return vim.fn.sin(0.0) == 0.0 ]])) @@ -1016,11 +1040,11 @@ describe('lua stdlib', function() eq('hi', funcs.luaeval "vim.g.testing") eq(123, funcs.luaeval "vim.g.other") eq(5120.1, funcs.luaeval "vim.g.floaty") - eq(NIL, funcs.luaeval "vim.g.nonexistant") + eq(NIL, funcs.luaeval "vim.g.nonexistent") eq(NIL, funcs.luaeval "vim.g.nullvar") -- lost over RPC, so test locally: eq({false, true}, exec_lua [[ - return {vim.g.nonexistant == vim.NIL, vim.g.nullvar == vim.NIL} + return {vim.g.nonexistent == vim.NIL, vim.g.nullvar == vim.NIL} ]]) eq({hello="world"}, funcs.luaeval "vim.g.to_delete") @@ -1123,12 +1147,12 @@ describe('lua stdlib', function() eq('bye', funcs.luaeval "vim.b[BUF].testing") eq(123, funcs.luaeval "vim.b.other") eq(5120.1, funcs.luaeval "vim.b.floaty") - eq(NIL, funcs.luaeval "vim.b.nonexistant") - eq(NIL, funcs.luaeval "vim.b[BUF].nonexistant") + eq(NIL, funcs.luaeval "vim.b.nonexistent") + eq(NIL, funcs.luaeval "vim.b[BUF].nonexistent") eq(NIL, funcs.luaeval "vim.b.nullvar") -- lost over RPC, so test locally: eq({false, true}, exec_lua [[ - return {vim.b.nonexistant == vim.NIL, vim.b.nullvar == vim.NIL} + return {vim.b.nonexistent == vim.NIL, vim.b.nullvar == vim.NIL} ]]) matches([[attempt to index .* nil value]], @@ -1207,7 +1231,7 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.b.testing") eq(NIL, funcs.luaeval "vim.b.other") - eq(NIL, funcs.luaeval "vim.b.nonexistant") + eq(NIL, funcs.luaeval "vim.b.nonexistent") end) it('vim.w', function() @@ -1226,8 +1250,8 @@ describe('lua stdlib', function() eq('hi', funcs.luaeval "vim.w.testing") eq('bye', funcs.luaeval "vim.w[WIN].testing") eq(123, funcs.luaeval "vim.w.other") - eq(NIL, funcs.luaeval "vim.w.nonexistant") - eq(NIL, funcs.luaeval "vim.w[WIN].nonexistant") + eq(NIL, funcs.luaeval "vim.w.nonexistent") + eq(NIL, funcs.luaeval "vim.w[WIN].nonexistent") matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.w[WIN][0].testing')) @@ -1305,7 +1329,7 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.w.testing") eq(NIL, funcs.luaeval "vim.w.other") - eq(NIL, funcs.luaeval "vim.w.nonexistant") + eq(NIL, funcs.luaeval "vim.w.nonexistent") end) it('vim.t', function() @@ -1317,10 +1341,10 @@ describe('lua stdlib', function() eq('hi', funcs.luaeval "vim.t.testing") eq(123, funcs.luaeval "vim.t.other") - eq(NIL, funcs.luaeval "vim.t.nonexistant") + eq(NIL, funcs.luaeval "vim.t.nonexistent") eq('hi', funcs.luaeval "vim.t[0].testing") eq(123, funcs.luaeval "vim.t[0].other") - eq(NIL, funcs.luaeval "vim.t[0].nonexistant") + eq(NIL, funcs.luaeval "vim.t[0].nonexistent") matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.t[0][0].testing')) @@ -1387,7 +1411,7 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.t.testing") eq(NIL, funcs.luaeval "vim.t.other") - eq(NIL, funcs.luaeval "vim.t.nonexistant") + eq(NIL, funcs.luaeval "vim.t.nonexistent") end) it('vim.env', function() @@ -2179,6 +2203,22 @@ describe('lua stdlib', function() end) end) -- vim.opt + describe('opt_local', function() + it('should be able to append to an array list type option', function() + eq({ "foo,bar,baz,qux" }, exec_lua [[ + local result = {} + + vim.opt.tags = "foo,bar" + vim.opt_local.tags:append("baz") + vim.opt_local.tags:append("qux") + + table.insert(result, vim.bo.tags) + + return result + ]]) + end) + end) + it('vim.cmd', function() exec_lua [[ vim.cmd "autocmd BufNew * ++once lua BUF = vim.fn.expand('<abuf>')" @@ -2227,13 +2267,19 @@ describe('lua stdlib', function() eq(true, exec_lua[[return vim.g.test]]) end) - it('vim.region', function() - insert(helpers.dedent( [[ - text tααt tααt text - text tαxt txtα tex - text tαxt tαxt - ]])) - eq({5,15}, exec_lua[[ return vim.region(0,{1,5},{1,14},'v',true)[1] ]]) + describe('vim.region', function() + it('charwise', function() + insert(helpers.dedent( [[ + text tααt tααt text + text tαxt txtα tex + text tαxt tαxt + ]])) + eq({5,15}, exec_lua[[ return vim.region(0,{1,5},{1,14},'v',true)[1] ]]) + end) + it('blockwise', function() + insert([[αα]]) + eq({0,5}, exec_lua[[ return vim.region(0,{0,0},{0,4},'3',true)[0] ]]) + end) end) describe('vim.on_key', function() @@ -2665,6 +2711,46 @@ describe('lua stdlib', function() a.nvim_buf_call(a.nvim_create_buf(false, true), function() vim.cmd "redraw" end) ]] end) + + it('can be nested crazily with hidden buffers', function() + eq(true, exec_lua([[ + local function scratch_buf_call(fn) + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_option(buf, 'cindent', true) + return vim.api.nvim_buf_call(buf, function() + return vim.api.nvim_get_current_buf() == buf + and vim.api.nvim_buf_get_option(buf, 'cindent') + and fn() + end) and vim.api.nvim_buf_delete(buf, {}) == nil + end + + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return scratch_buf_call(function() + return true + end) + end) + end) + end) + end) + end) + end) + end) + end) + end) + end) + end) + ]])) + end) end) describe('vim.api.nvim_win_call', function() @@ -2838,9 +2924,14 @@ describe('lua: builtin modules', function() end) - it('does not work when disabled without runtime', function() - clear{args={'--luamod-dev'}, env={VIMRUNTIME='fixtures/a'}} - expect_exit(exec_lua, [[return vim.tbl_count {x=1,y=2}]]) + it('fails when disabled without runtime', function() + clear() + command("let $VIMRUNTIME='fixtures/a'") + -- Use system([nvim,…]) instead of clear() to avoid stderr noise. #21844 + local out = funcs.system({nvim_prog, '--clean', '--luamod-dev', + [[+call nvim_exec_lua('return vim.tbl_count {x=1,y=2}')]], '+qa!'}):gsub('\r\n', '\n') + eq(1, eval('v:shell_error')) + matches("'vim%.shared' not found", out) end) end) diff --git a/test/functional/lua/xdiff_spec.lua b/test/functional/lua/xdiff_spec.lua index d55268fc78..3121ac051f 100644 --- a/test/functional/lua/xdiff_spec.lua +++ b/test/functional/lua/xdiff_spec.lua @@ -74,11 +74,13 @@ describe('xdiff bindings', function() end) it('with error callback', function() - exec_lua([[on_hunk = function(sa, ca, sb, cb) + exec_lua[[ + on_hunk = function(sa, ca, sb, cb) error('ERROR1') - end]]) + end + ]] - eq([[Error executing lua: [string "<nvim>"]:0: error running function on_hunk: [string "<nvim>"]:0: ERROR1]], + eq([[error running function on_hunk: [string "<nvim>"]:0: ERROR1]], pcall_err(exec_lua, [[vim.diff(a1, b1, {on_hunk = on_hunk})]])) end) @@ -135,19 +137,19 @@ describe('xdiff bindings', function() end) it('can handle bad args', function() - eq([[Error executing lua: [string "<nvim>"]:0: Expected at least 2 arguments]], + eq([[Expected at least 2 arguments]], pcall_err(exec_lua, [[vim.diff('a')]])) - eq([[Error executing lua: [string "<nvim>"]:0: bad argument #1 to 'diff' (expected string)]], + eq([[bad argument #1 to 'diff' (expected string)]], pcall_err(exec_lua, [[vim.diff(1, 2)]])) - eq([[Error executing lua: [string "<nvim>"]:0: bad argument #3 to 'diff' (expected table)]], + eq([[bad argument #3 to 'diff' (expected table)]], pcall_err(exec_lua, [[vim.diff('a', 'b', true)]])) - eq([[Error executing lua: [string "<nvim>"]:0: unexpected key: bad_key]], + eq([[unexpected key: bad_key]], pcall_err(exec_lua, [[vim.diff('a', 'b', { bad_key = true })]])) - eq([[Error executing lua: [string "<nvim>"]:0: on_hunk is not a function]], + eq([[on_hunk is not a function]], pcall_err(exec_lua, [[vim.diff('a', 'b', { on_hunk = true })]])) end) diff --git a/test/functional/options/autochdir_spec.lua b/test/functional/options/autochdir_spec.lua index 74959a8e76..0b6fe9533c 100644 --- a/test/functional/options/autochdir_spec.lua +++ b/test/functional/options/autochdir_spec.lua @@ -16,7 +16,7 @@ describe("'autochdir'", function() -- 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, funcs.getcwd()) + eq(helpers.is_os('win') and expected:gsub('/', '\\') or expected, funcs.getcwd()) end) it('is not overwritten by getwinvar() call #17609',function() diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index 130ed73c34..84ec43f4cb 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -3,6 +3,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local assert_alive = helpers.assert_alive +local assert_log = helpers.assert_log local meths = helpers.meths local command = helpers.command local clear = helpers.clear @@ -12,13 +13,15 @@ local eq = helpers.eq local ok = helpers.ok local funcs = helpers.funcs local insert = helpers.insert -local iswin = helpers.iswin local neq = helpers.neq local mkdir = helpers.mkdir local rmdir = helpers.rmdir local alter_slashes = helpers.alter_slashes local tbl_contains = helpers.tbl_contains local expect_exit = helpers.expect_exit +local is_os = helpers.is_os + +local testlog = 'Xtest-defaults-log' describe('startup defaults', function() describe(':filetype', function() @@ -240,7 +243,7 @@ describe('startup defaults', function() describe('$NVIM_LOG_FILE', function() local xdgdir = 'Xtest-startup-xdg-logpath' - local xdgstatedir = iswin() and xdgdir..'/nvim-data' or xdgdir..'/nvim' + local xdgstatedir = is_os('win') and xdgdir..'/nvim-data' or xdgdir..'/nvim' after_each(function() os.remove('Xtest-logpath') rmdir(xdgdir) @@ -275,11 +278,15 @@ describe('XDG defaults', function() -- Need separate describe() blocks to not run clear() twice. -- Do not put before_each() here for the same reasons. + after_each(function() + os.remove(testlog) + end) + it("&runtimepath data-dir matches stdpath('data') #9910", function() clear() local rtp = eval('split(&runtimepath, ",")') local rv = {} - local expected = (iswin() + local expected = (is_os('win') and { [[\nvim-data\site]], [[\nvim-data\site\after]], } or { '/nvim/site', '/nvim/site/after', }) @@ -327,16 +334,17 @@ describe('XDG defaults', function() return vimruntime, libdir end - local env_sep = iswin() and ';' or ':' - local data_dir = iswin() and 'nvim-data' or 'nvim' - local state_dir = iswin() and 'nvim-data' or 'nvim' - local root_path = iswin() and 'C:' or '' + local env_sep = is_os('win') and ';' or ':' + local data_dir = is_os('win') and 'nvim-data' or 'nvim' + local state_dir = is_os('win') and 'nvim-data' or 'nvim' + local root_path = is_os('win') and 'C:' or '' describe('with too long XDG variables', function() before_each(function() clear({ args_rm={'runtimepath'}, env={ + NVIM_LOG_FILE=testlog, XDG_CONFIG_HOME=(root_path .. ('/x'):rep(4096)), XDG_CONFIG_DIRS=(root_path .. ('/a'):rep(2048) .. env_sep.. root_path .. ('/b'):rep(2048) @@ -351,6 +359,10 @@ describe('XDG defaults', function() end) it('are correctly set', function() + if not is_os('win') then + assert_log('Failed to start server: no such file or directory: /X/X/X', testlog, 10) + end + local vimruntime, libdir = vimruntime_and_libdir() eq(((root_path .. ('/x'):rep(4096) .. '/nvim' @@ -412,6 +424,7 @@ describe('XDG defaults', function() clear({ args_rm={'runtimepath'}, env={ + NVIM_LOG_FILE=testlog, XDG_CONFIG_HOME='$XDG_DATA_HOME', XDG_CONFIG_DIRS='$XDG_DATA_DIRS', XDG_DATA_HOME='$XDG_CONFIG_HOME', @@ -422,6 +435,10 @@ describe('XDG defaults', function() end) it('are not expanded', function() + if not is_os('win') then + assert_log('Failed to start server: no such file or directory: %$XDG_RUNTIME_DIR%/', testlog, 10) + end + local vimruntime, libdir = vimruntime_and_libdir() eq((('$XDG_DATA_HOME/nvim' .. ',$XDG_DATA_DIRS/nvim' @@ -497,7 +514,7 @@ describe('XDG defaults', function() it('are escaped properly', function() local vimruntime, libdir = vimruntime_and_libdir() - local path_sep = iswin() and '\\' or '/' + local path_sep = is_os('win') and '\\' or '/' eq(('\\, \\, \\,' .. path_sep .. 'nvim' .. ',\\,-\\,-\\,' .. path_sep .. 'nvim' .. ',-\\,-\\,-' .. path_sep .. 'nvim' @@ -549,9 +566,9 @@ end) describe('stdpath()', function() -- Windows appends 'nvim-data' instead of just 'nvim' to prevent collisions -- due to XDG_CONFIG_HOME, XDG_DATA_HOME and XDG_STATE_HOME being the same. - local datadir = iswin() and 'nvim-data' or 'nvim' - local statedir = iswin() and 'nvim-data' or 'nvim' - local env_sep = iswin() and ';' or ':' + local datadir = is_os('win') and 'nvim-data' or 'nvim' + local statedir = is_os('win') and 'nvim-data' or 'nvim' + local env_sep = is_os('win') and ';' or ':' it('acceptance', function() clear() -- Do not explicitly set any env vars. @@ -704,7 +721,7 @@ describe('stdpath()', function() context('returns a List', function() -- Some OS specific variables the system would have set. local function base_env() - if iswin() then + if is_os('win') then return { HOME='C:\\Users\\docwhat', -- technically, is not a usual PATH HOMEDRIVE='C:', diff --git a/test/functional/options/mousescroll_spec.lua b/test/functional/options/mousescroll_spec.lua index 2c9b2d175e..5bff45a836 100644 --- a/test/functional/options/mousescroll_spec.lua +++ b/test/functional/options/mousescroll_spec.lua @@ -97,6 +97,24 @@ describe("'mousescroll'", function() eq(10, screencol()) scroll('left') eq(10, screencol()) + + -- vertical scrolling is still disabled with non-zero 'scrolloff' value + command('set scrolloff=1') + + eq(10, screenrow()) + scroll('up') + eq(10, screenrow()) + scroll('down') + eq(10, screenrow()) + + -- also in insert mode + feed('i') + + eq(10, screenrow()) + scroll('up') + eq(10, screenrow()) + scroll('down') + eq(10, screenrow()) end) local test_vertical_scrolling = function() diff --git a/test/functional/plugin/ccomplete_spec.lua b/test/functional/plugin/ccomplete_spec.lua new file mode 100644 index 0000000000..903f16fc73 --- /dev/null +++ b/test/functional/plugin/ccomplete_spec.lua @@ -0,0 +1,61 @@ +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 write_file = helpers.write_file + +describe('ccomplete#Complete', function() + setup(function() + -- Realistic tags generated from neovim source tree using `ctags -R *` + write_file( + 'Xtags', + [[ +augroup_del src/nvim/autocmd.c /^void augroup_del(char *name, bool stupid_legacy_mode)$/;" f typeref:typename:void +augroup_exists src/nvim/autocmd.c /^bool augroup_exists(const char *name)$/;" f typeref:typename:bool +augroup_find src/nvim/autocmd.c /^int augroup_find(const char *name)$/;" f typeref:typename:int +aupat_get_buflocal_nr src/nvim/autocmd.c /^int aupat_get_buflocal_nr(char *pat, int patlen)$/;" f typeref:typename:int +aupat_is_buflocal src/nvim/autocmd.c /^bool aupat_is_buflocal(char *pat, int patlen)$/;" f typeref:typename:bool +expand_get_augroup_name src/nvim/autocmd.c /^char *expand_get_augroup_name(expand_T *xp, int idx)$/;" f typeref:typename:char * +expand_get_event_name src/nvim/autocmd.c /^char *expand_get_event_name(expand_T *xp, int idx)$/;" f typeref:typename:char * +]] + ) + end) + + before_each(function() + clear() + command('set tags=Xtags') + end) + + teardown(function() + os.remove('Xtags') + end) + + it('can complete from Xtags', function() + local completed = eval('ccomplete#Complete(0, "a")') + eq(5, #completed) + eq('augroup_del(', completed[1].word) + eq('f', completed[1].kind) + + local aupat = eval('ccomplete#Complete(0, "aupat")') + eq(2, #aupat) + eq('aupat_get_buflocal_nr(', aupat[1].word) + eq('f', aupat[1].kind) + end) + + it('does not error when returning no matches', function() + local completed = eval('ccomplete#Complete(0, "doesnotmatch")') + eq({}, completed) + end) + + it('can find the beginning of a word for C', function() + command('set filetype=c') + feed('i int something = augroup') + local result = eval('ccomplete#Complete(1, "")') + eq(#' int something = ', result) + + local completed = eval('ccomplete#Complete(0, "augroup")') + eq(3, #completed) + end) +end) diff --git a/test/functional/plugin/cfilter_spec.lua b/test/functional/plugin/cfilter_spec.lua new file mode 100644 index 0000000000..8b1e75b495 --- /dev/null +++ b/test/functional/plugin/cfilter_spec.lua @@ -0,0 +1,106 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local funcs = helpers.funcs + +describe('cfilter.lua', function() + before_each(function() + clear() + command('packadd cfilter') + end) + + for _, list in ipairs({ + { + name = 'Cfilter', + get = funcs.getqflist, + set = funcs.setqflist, + }, + { + name = 'Lfilter', + get = function() + return funcs.getloclist(0) + end, + set = function(items) + return funcs.setloclist(0, items) + end, + }, + }) do + local filter = function(s, bang) + if not bang then + bang = '' + else + bang = '!' + end + + command(string.format('%s%s %s', list.name, bang, s)) + end + + describe((':%s'):format(list.name), function() + it('does not error on empty list', function() + filter('nothing') + eq({}, funcs.getqflist()) + end) + + it('requires an argument', function() + local ok = pcall(filter, '') + eq(false, ok) + end) + + local test = function(name, s, res, map, bang) + it(('%s (%s)'):format(name, s), function() + list.set({ + { filename = 'foo', lnum = 1, text = 'bar' }, + { filename = 'foo', lnum = 2, text = 'baz' }, + { filename = 'foo', lnum = 3, text = 'zed' }, + }) + + filter(s, bang) + + local got = list.get() + if map then + got = map(got) + end + eq(res, got) + end) + end + + local toname = function(qflist) + return funcs.map(qflist, 'v:val.text') + end + + test('filters with no matches', 'does not match', {}) + + test('filters with matches', 'ba', { 'bar', 'baz' }, toname) + test('filters with matches', 'z', { 'baz', 'zed' }, toname) + test('filters with matches', '^z', { 'zed' }, toname) + test('filters with not matches', '^z', { 'bar', 'baz' }, toname, true) + + it('also supports using the / register', function() + list.set({ + { filename = 'foo', lnum = 1, text = 'bar' }, + { filename = 'foo', lnum = 2, text = 'baz' }, + { filename = 'foo', lnum = 3, text = 'zed' }, + }) + + funcs.setreg('/', 'ba') + filter('/') + + eq({ 'bar', 'baz' }, toname(list.get())) + end) + + it('also supports using the / register with bang', function() + list.set({ + { filename = 'foo', lnum = 1, text = 'bar' }, + { filename = 'foo', lnum = 2, text = 'baz' }, + { filename = 'foo', lnum = 3, text = 'zed' }, + }) + + funcs.setreg('/', 'ba') + filter('/', true) + + eq({ 'zed' }, toname(list.get())) + end) + end) + end +end) diff --git a/test/functional/plugin/editorconfig_spec.lua b/test/functional/plugin/editorconfig_spec.lua new file mode 100644 index 0000000000..e6a2550aba --- /dev/null +++ b/test/functional/plugin/editorconfig_spec.lua @@ -0,0 +1,210 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local pathsep = helpers.get_pathsep() +local curbufmeths = helpers.curbufmeths +local funcs = helpers.funcs +local meths = helpers.meths + +local testdir = 'Xtest-editorconfig' + +local function test_case(name, expected) + local filename = testdir .. pathsep .. name + command('edit ' .. filename) + for opt, val in pairs(expected) do + eq(val, curbufmeths.get_option(opt), name) + end +end + +setup(function() + helpers.mkdir_p(testdir) + helpers.write_file( + testdir .. pathsep .. '.editorconfig', + [[ + root = true + + [3_space.txt] + indent_style = space + indent_size = 3 + tab_width = 3 + + [4_space.py] + indent_style = space + indent_size = 4 + tab_width = 8 + + [space.txt] + indent_style = space + indent_size = tab + + [tab.txt] + indent_style = tab + + [4_tab.txt] + indent_style = tab + indent_size = 4 + tab_width = 4 + + [4_tab_width_of_8.txt] + indent_style = tab + indent_size = 4 + tab_width = 8 + + [lf.txt] + end_of_line = lf + + [crlf.txt] + end_of_line = crlf + + [cr.txt] + end_of_line = cr + + [utf-8.txt] + charset = utf-8 + + [utf-8-bom.txt] + charset = utf-8-bom + + [utf-16be.txt] + charset = utf-16be + + [utf-16le.txt] + charset = utf-16le + + [latin1.txt] + charset = latin1 + + [with_newline.txt] + insert_final_newline = true + + [without_newline.txt] + insert_final_newline = false + + [trim.txt] + trim_trailing_whitespace = true + + [no_trim.txt] + trim_trailing_whitespace = false + + [max_line_length.txt] + max_line_length = 42 + ]] + ) +end) + +teardown(function() + helpers.rmdir(testdir) +end) + +describe('editorconfig', function() + before_each(function() + -- Remove -u NONE so that plugins (i.e. editorconfig.lua) are loaded + clear({ args_rm = { '-u' } }) + end) + + it('sets indent options', function() + test_case('3_space.txt', { + expandtab = true, + shiftwidth = 3, + softtabstop = -1, + tabstop = 3, + }) + + test_case('4_space.py', { + expandtab = true, + shiftwidth = 4, + softtabstop = -1, + tabstop = 8, + }) + + test_case('space.txt', { + expandtab = true, + shiftwidth = 0, + softtabstop = 0, + }) + + test_case('tab.txt', { + expandtab = false, + shiftwidth = 0, + softtabstop = 0, + }) + + test_case('4_tab.txt', { + expandtab = false, + shiftwidth = 4, + softtabstop = -1, + tabstop = 4, + }) + + test_case('4_tab_width_of_8.txt', { + expandtab = false, + shiftwidth = 4, + softtabstop = -1, + tabstop = 8, + }) + end) + + it('sets end-of-line options', function() + test_case('lf.txt', { fileformat = 'unix' }) + test_case('crlf.txt', { fileformat = 'dos' }) + test_case('cr.txt', { fileformat = 'mac' }) + end) + + it('sets encoding options', function() + test_case('utf-8.txt', { fileencoding = 'utf-8', bomb = false }) + test_case('utf-8-bom.txt', { fileencoding = 'utf-8', bomb = true }) + test_case('utf-16be.txt', { fileencoding = 'utf-16', bomb = false }) + test_case('utf-16le.txt', { fileencoding = 'utf-16le', bomb = false }) + test_case('latin1.txt', { fileencoding = 'latin1', bomb = false }) + end) + + it('sets newline options', function() + test_case('with_newline.txt', { fixendofline = true, endofline = true }) + test_case('without_newline.txt', { fixendofline = false, endofline = false }) + end) + + it('respects trim_trailing_whitespace', function() + local filename = testdir .. pathsep .. 'trim.txt' + -- luacheck: push ignore 613 + local untrimmed = [[ +This line ends in whitespace +So does this one +And this one +But not this one +]] + -- luacheck: pop + local trimmed = untrimmed:gsub('%s+\n', '\n') + + helpers.write_file(filename, untrimmed) + command('edit ' .. filename) + command('write') + command('bdelete') + eq(trimmed, helpers.read_file(filename)) + + filename = testdir .. pathsep .. 'no_trim.txt' + helpers.write_file(filename, untrimmed) + command('edit ' .. filename) + command('write') + command('bdelete') + eq(untrimmed, helpers.read_file(filename)) + end) + + it('sets textwidth', function() + test_case('max_line_length.txt', { textwidth = 42 }) + end) + + it('can be disabled globally', function() + meths.set_var('editorconfig', false) + meths.set_option_value('shiftwidth', 42, {}) + test_case('3_space.txt', { shiftwidth = 42 }) + end) + + it('can be disabled per-buffer', function() + meths.set_option_value('shiftwidth', 42, {}) + local bufnr = funcs.bufadd(testdir .. pathsep .. '3_space.txt') + meths.buf_set_var(bufnr, 'editorconfig', false) + test_case('3_space.txt', { shiftwidth = 42 }) + test_case('4_space.py', { shiftwidth = 4 }) + end) +end) diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index ba66117fb1..97d32313e5 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -5,7 +5,7 @@ local Screen = require('test.functional.ui.screen') local clear = helpers.clear local curbuf_contents = helpers.curbuf_contents local command = helpers.command -local eq, neq = helpers.eq, helpers.neq +local eq, neq, matches = helpers.eq, helpers.neq, helpers.matches local getcompletion = helpers.funcs.getcompletion describe(':checkhealth', function() @@ -29,8 +29,7 @@ describe(':checkhealth', function() -- Do this after startup, otherwise it just breaks $VIMRUNTIME. command("let $VIM='zub'") command("checkhealth nvim") - eq("ERROR: $VIM is invalid: zub", - string.match(curbuf_contents(), "ERROR: $VIM .* zub")) + matches('ERROR $VIM .* zub', curbuf_contents()) end) it('completions can be listed via getcompletion()', function() clear() @@ -56,21 +55,22 @@ describe('health.vim', function() command("checkhealth full_render") helpers.expect([[ + ============================================================================== full_render: health#full_render#check - ======================================================================== - ## report 1 - - OK: life is fine - - WARNING: no what installed - - ADVICE: - - pip what - - make what - - ## report 2 - - INFO: stuff is stable - - ERROR: why no hardcopy - - ADVICE: - - :help |:hardcopy| - - :help |:TOhtml| + + report 1 ~ + - OK life is fine + - WARNING no what installed + - ADVICE: + - pip what + - make what + + report 2 ~ + - stuff is stable + - ERROR why no hardcopy + - ADVICE: + - :help |:hardcopy| + - :help |:TOhtml| ]]) end) @@ -78,26 +78,29 @@ describe('health.vim', function() command("checkhealth success1 success2 test_plug") helpers.expect([[ + ============================================================================== success1: health#success1#check - ======================================================================== - ## report 1 - - OK: everything is fine - ## report 2 - - OK: nothing to see here + report 1 ~ + - OK everything is fine + + report 2 ~ + - OK nothing to see here + ============================================================================== success2: health#success2#check - ======================================================================== - ## another 1 - - OK: ok + another 1 ~ + - OK ok + + ============================================================================== test_plug: require("test_plug.health").check() - ======================================================================== - ## report 1 - - OK: everything is fine - ## report 2 - - OK: nothing to see here + report 1 ~ + - OK everything is fine + + report 2 ~ + - OK nothing to see here ]]) end) @@ -107,13 +110,14 @@ describe('health.vim', function() -- and the Lua healthcheck is used instead. helpers.expect([[ + ============================================================================== test_plug: require("test_plug.health").check() - ======================================================================== - ## report 1 - - OK: everything is fine - ## report 2 - - OK: nothing to see here + report 1 ~ + - OK everything is fine + + report 2 ~ + - OK nothing to see here ]]) end) @@ -121,13 +125,14 @@ describe('health.vim', function() command("checkhealth test_plug.submodule") helpers.expect([[ + ============================================================================== test_plug.submodule: require("test_plug.submodule.health").check() - ======================================================================== - ## report 1 - - OK: everything is fine - ## report 2 - - OK: nothing to see here + report 1 ~ + - OK everything is fine + + report 2 ~ + - OK nothing to see here ]]) end) @@ -138,30 +143,34 @@ describe('health.vim', function() local received = table.concat(buf_lines, '\n', 1, #buf_lines - 5) local expected = helpers.dedent([[ + ============================================================================== test_plug: require("test_plug.health").check() - ======================================================================== - ## report 1 - - OK: everything is fine - ## report 2 - - OK: nothing to see here + report 1 ~ + - OK everything is fine + + report 2 ~ + - OK nothing to see here + ============================================================================== test_plug.submodule: require("test_plug.submodule.health").check() - ======================================================================== - ## report 1 - - OK: everything is fine - ## report 2 - - OK: nothing to see here + report 1 ~ + - OK everything is fine + 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. + - 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 20]]) + + - ERROR Failed to run healthcheck for "test_plug.submodule_failed" plugin. Exception: + function health#check, line 25]]) eq(expected, received) end) @@ -169,11 +178,12 @@ describe('health.vim', function() command("checkhealth broken") helpers.expect([[ + ============================================================================== broken: health#broken#check - ======================================================================== - - ERROR: Failed to run healthcheck for "broken" plugin. Exception: - function health#check[20]..health#broken#check, line 1 - caused an error + + - ERROR Failed to run healthcheck for "broken" plugin. Exception: + function health#check[25]..health#broken#check, line 1 + caused an error ]]) end) @@ -181,9 +191,10 @@ describe('health.vim', 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. + + - ERROR The healthcheck report for "test_plug.submodule_empty" plugin is empty. ]]) end) @@ -198,38 +209,38 @@ describe('health.vim', function() local expected = global_helpers.dedent([[ + ============================================================================== 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 20]]) + + - ERROR Failed to run healthcheck for "test_plug.submodule_failed" plugin. Exception: + function health#check, line 25]]) eq(expected, received) end) it("highlights OK, ERROR", function() - local screen = Screen.new(72, 10) + local screen = Screen.new(50, 12) screen:attach() screen:set_default_attr_ids({ Ok = { foreground = Screen.colors.Grey3, background = 6291200 }, Error = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, - Heading = { bold=true, foreground=Screen.colors.Magenta }, - Heading2 = { foreground = Screen.colors.SlateBlue }, - Bar = { foreground = 0x6a0dad }, - Bullet = { bold=true, foreground=Screen.colors.Brown }, + Heading = { foreground = tonumber('0x6a0dad') }, + Bar = { foreground = Screen.colors.LightGrey, background = Screen.colors.DarkGrey }, }) command("checkhealth foo success1") - command("1tabclose") - command("set laststatus=0") + command("set nowrap laststatus=0") screen:expect{grid=[[ - ^ | - {Heading:foo: } | - {Bar:========================================================================}| - {Bullet: -} {Error:ERROR}: No healthcheck found for "foo" plugin. | - | - {Heading:success1: health#success1#check} | - {Bar:========================================================================}| - {Heading2:##}{Heading: report 1} | - {Bullet: -} {Ok:OK}: everything is fine | - | + ^ | + {Bar:──────────────────────────────────────────────────}| + {Heading:foo: } | + | + - {Error:ERROR} No healthcheck found for "foo" plugin. | + | + {Bar:──────────────────────────────────────────────────}| + {Heading:success1: health#success1#check} | + | + {Heading:report 1} | + - {Ok:OK} everything is fine | + | ]]} end) @@ -238,9 +249,10 @@ describe('health.vim', function() -- luacheck: ignore 613 helpers.expect([[ + ============================================================================== non_existent_healthcheck: - ======================================================================== - - ERROR: No healthcheck found for "non_existent_healthcheck" plugin. + + - ERROR No healthcheck found for "non_existent_healthcheck" plugin. ]]) end) diff --git a/test/functional/plugin/lsp/codelens_spec.lua b/test/functional/plugin/lsp/codelens_spec.lua index ecc2f579b8..3d7a15a191 100644 --- a/test/functional/plugin/lsp/codelens_spec.lua +++ b/test/functional/plugin/lsp/codelens_spec.lua @@ -58,6 +58,41 @@ describe('vim.lsp.codelens', function() ]], bufnr) eq({[1] = {'Lens1', 'LspCodeLens'}}, virtual_text_chunks) + end) + + it('can clear all lens', function() + local fake_uri = "file:///fake/uri" + local bufnr = exec_lua([[ + fake_uri = ... + local bufnr = vim.uri_to_bufnr(fake_uri) + local lines = {'So', 'many', 'lines'} + vim.fn.bufload(bufnr) + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + return bufnr + ]], fake_uri) + + exec_lua([[ + local bufnr = ... + local lenses = { + { + range = { + start = { line = 0, character = 0, }, + ['end'] = { line = 0, character = 0 } + }, + command = { title = 'Lens1', command = 'Dummy' } + }, + } + vim.lsp.codelens.on_codelens(nil, lenses, {method='textDocument/codeLens', client_id=1, bufnr=bufnr}) + ]], bufnr) + + local stored_lenses = exec_lua('return vim.lsp.codelens.get(...)', bufnr) + eq(1, #stored_lenses) + + exec_lua([[ + vim.lsp.codelens.clear() + ]]) + stored_lenses = exec_lua('return vim.lsp.codelens.get(...)', bufnr) + eq(0, #stored_lenses) end) end) diff --git a/test/functional/plugin/lsp/helpers.lua b/test/functional/plugin/lsp/helpers.lua new file mode 100644 index 0000000000..028ccb9e2c --- /dev/null +++ b/test/functional/plugin/lsp/helpers.lua @@ -0,0 +1,176 @@ +local helpers = require('test.functional.helpers')(nil) + +local clear = helpers.clear +local exec_lua = helpers.exec_lua +local run = helpers.run +local stop = helpers.stop +local NIL = helpers.NIL + +local M = {} + +function M.clear_notrace() + -- problem: here be dragons + -- solution: don't look too closely for dragons + clear {env={ + NVIM_LUA_NOTRACK="1"; + VIMRUNTIME=os.getenv"VIMRUNTIME"; + }} +end + +M.create_server_definition = [[ + function _create_server(opts) + opts = opts or {} + local server = {} + server.messages = {} + + function server.cmd(dispatchers) + local closing = false + local handlers = opts.handlers or {} + local srv = {} + + function srv.request(method, params, callback) + table.insert(server.messages, { + method = method, + params = params, + }) + local handler = handlers[method] + if handler then + local response, err = handler(method, params) + callback(err, response) + elseif method == 'initialize' then + callback(nil, { + capabilities = opts.capabilities or {} + }) + elseif method == 'shutdown' then + callback(nil, nil) + end + local request_id = #server.messages + return true, request_id + end + + function srv.notify(method, params) + table.insert(server.messages, { + method = method, + params = params + }) + if method == 'exit' then + dispatchers.on_exit(0, 15) + end + end + + function srv.is_closing() + return closing + end + + function srv.terminate() + closing = true + end + + return srv + end + + return server + end +]] + +-- Fake LSP server. +M.fake_lsp_code = 'test/functional/fixtures/fake-lsp-server.lua' +M.fake_lsp_logfile = 'Xtest-fake-lsp.log' + +local function fake_lsp_server_setup(test_name, timeout_ms, options, settings) + exec_lua([=[ + lsp = require('vim.lsp') + local test_name, fixture_filename, logfile, timeout, options, settings = ... + TEST_RPC_CLIENT_ID = lsp.start_client { + cmd_env = { + NVIM_LOG_FILE = logfile; + NVIM_LUA_NOTRACK = "1"; + }; + cmd = { + vim.v.progpath, '-Es', '-u', 'NONE', '--headless', + "-c", string.format("lua TEST_NAME = %q", test_name), + "-c", string.format("lua TIMEOUT = %d", timeout), + "-c", "luafile "..fixture_filename, + }; + handlers = setmetatable({}, { + __index = function(t, method) + return function(...) + return vim.rpcrequest(1, 'handler', ...) + end + end; + }); + workspace_folders = {{ + uri = 'file://' .. vim.loop.cwd(), + name = 'test_folder', + }}; + on_init = function(client, result) + TEST_RPC_CLIENT = client + vim.rpcrequest(1, "init", result) + end; + flags = { + allow_incremental_sync = options.allow_incremental_sync or false; + debounce_text_changes = options.debounce_text_changes or 0; + }; + settings = settings; + on_exit = function(...) + vim.rpcnotify(1, "exit", ...) + end; + } + ]=], test_name, M.fake_lsp_code, M.fake_lsp_logfile, timeout_ms or 1e3, options or {}, settings or {}) +end + +function M.test_rpc_server(config) + if config.test_name then + M.clear_notrace() + fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3, config.options, config.settings) + end + local client = setmetatable({}, { + __index = function(_, name) + -- Workaround for not being able to yield() inside __index for Lua 5.1 :( + -- Otherwise I would just return the value here. + return function(...) + return exec_lua([=[ + local name = ... + if type(TEST_RPC_CLIENT[name]) == 'function' then + return TEST_RPC_CLIENT[name](select(2, ...)) + else + return TEST_RPC_CLIENT[name] + end + ]=], name, ...) + end + end; + }) + local code, signal + local function on_request(method, args) + if method == "init" then + if config.on_init then + config.on_init(client, unpack(args)) + end + return NIL + end + if method == 'handler' then + if config.on_handler then + config.on_handler(unpack(args)) + end + end + return NIL + end + local function on_notify(method, args) + if method == 'exit' then + code, signal = unpack(args) + return stop() + end + end + -- TODO specify timeout? + -- run(on_request, on_notify, config.on_setup, 1000) + run(on_request, on_notify, config.on_setup) + if config.on_exit then + config.on_exit(code, signal) + end + stop() + if config.test_name then + exec_lua("vim.api.nvim_exec_autocmds('VimLeavePre', { modeline = false })") + end +end + +return M diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua new file mode 100644 index 0000000000..9c1ba86fe1 --- /dev/null +++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua @@ -0,0 +1,1219 @@ +local helpers = require('test.functional.helpers')(after_each) +local lsp_helpers = require('test.functional.plugin.lsp.helpers') +local Screen = require('test.functional.ui.screen') + +local command = helpers.command +local dedent = helpers.dedent +local eq = helpers.eq +local exec_lua = helpers.exec_lua +local feed = helpers.feed +local feed_command = helpers.feed_command +local insert = helpers.insert +local matches = helpers.matches + +local clear_notrace = lsp_helpers.clear_notrace +local create_server_definition = lsp_helpers.create_server_definition + +before_each(function() + clear_notrace() +end) + +after_each(function() + exec_lua("vim.api.nvim_exec_autocmds('VimLeavePre', { modeline = false })") +end) + +describe('semantic token highlighting', function() + + describe('general', function() + local text = dedent([[ + #include <iostream> + + int main() + { + int x; + #ifdef __cplusplus + std::cout << x << "\n"; + #else + printf("%d\n", x); + #endif + } + }]]) + + local legend = [[{ + "tokenTypes": [ + "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment" + ], + "tokenModifiers": [ + "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope" + ] + }]] + + local response = [[{ + "data": [ 2, 4, 4, 3, 8193, 2, 8, 1, 1, 1025, 1, 7, 11, 19, 8192, 1, 4, 3, 15, 8448, 0, 5, 4, 0, 8448, 0, 8, 1, 1, 1024, 1, 0, 5, 20, 0, 1, 0, 22, 20, 0, 1, 0, 6, 20, 0 ], + "resultId": 1 + }]] + + local edit_response = [[{ + "edits": [ {"data": [ 2, 8, 1, 3, 8193, 1, 7, 11, 19, 8192, 1, 4, 3, 15, 8448, 0, 5, 4, 0, 8448, 0, 8, 1, 3, 8192 ], "deleteCount": 25, "start": 5 } ], + "resultId":"2" + }]] + + local screen + before_each(function() + screen = Screen.new(40, 16) + screen:attach() + screen:set_default_attr_ids { + [1] = { bold = true, foreground = Screen.colors.Blue1 }; + [2] = { foreground = Screen.colors.DarkCyan }; + [3] = { foreground = Screen.colors.SlateBlue }; + [4] = { bold = true, foreground = Screen.colors.SeaGreen }; + [5] = { foreground = tonumber('0x6a0dad') }; + [6] = { foreground = Screen.colors.Blue1 }; + [7] = { bold = true, foreground = Screen.colors.DarkCyan }; + [8] = { bold = true, foreground = Screen.colors.SlateBlue }; + } + command([[ hi link @namespace Type ]]) + command([[ hi link @function Special ]]) + command([[ hi @declaration gui=bold ]]) + + exec_lua(create_server_definition) + exec_lua([[ + local legend, response, edit_response = ... + server = _create_server({ + capabilities = { + semanticTokensProvider = { + full = { delta = true }, + legend = vim.fn.json_decode(legend), + }, + }, + handlers = { + ['textDocument/semanticTokens/full'] = function() + return vim.fn.json_decode(response) + end, + ['textDocument/semanticTokens/full/delta'] = function() + return vim.fn.json_decode(edit_response) + end, + } + }) + ]], legend, response, edit_response) + end) + + it('buffer is highlighted when attached', function() + exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + ]]) + + insert(text) + + screen:expect { grid = [[ + #include <iostream> | + | + int {8:main}() | + { | + int {7:x}; | + #ifdef {5:__cplusplus} | + {4:std}::{2:cout} << {2:x} << "\n"; | + {6:#else} | + {6: printf("%d\n", x);} | + {6:#endif} | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] } + end) + + it('buffer is unhighlighted when client is detached', function() + exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + ]]) + + insert(text) + + exec_lua([[ + vim.notify = function() end + vim.lsp.buf_detach_client(bufnr, client_id) + ]]) + + screen:expect { grid = [[ + #include <iostream> | + | + int main() | + { | + int x; | + #ifdef __cplusplus | + std::cout << x << "\n"; | + #else | + printf("%d\n", x); | + #endif | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] } + end) + + it('buffer is highlighted and unhighlighted when semantic token highlighting is started and stopped' + , function() + exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + ]]) + + insert(text) + + exec_lua([[ + vim.notify = function() end + vim.lsp.semantic_tokens.stop(bufnr, client_id) + ]]) + + screen:expect { grid = [[ + #include <iostream> | + | + int main() | + { | + int x; | + #ifdef __cplusplus | + std::cout << x << "\n"; | + #else | + printf("%d\n", x); | + #endif | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] } + + exec_lua([[ + vim.lsp.semantic_tokens.start(bufnr, client_id) + ]]) + + screen:expect { grid = [[ + #include <iostream> | + | + int {8:main}() | + { | + int {7:x}; | + #ifdef {5:__cplusplus} | + {4:std}::{2:cout} << {2:x} << "\n"; | + {6:#else} | + {6: printf("%d\n", x);} | + {6:#endif} | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] } + end) + + it('buffer is re-highlighted when force refreshed', function() + exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + ]]) + + insert(text) + + screen:expect { grid = [[ + #include <iostream> | + | + int {8:main}() | + { | + int {7:x}; | + #ifdef {5:__cplusplus} | + {4:std}::{2:cout} << {2:x} << "\n"; | + {6:#else} | + {6: printf("%d\n", x);} | + {6:#endif} | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] } + + exec_lua([[ + vim.lsp.semantic_tokens.force_refresh(bufnr) + ]]) + + screen:expect { grid = [[ + #include <iostream> | + | + int {8:main}() | + { | + int {7:x}; | + #ifdef {5:__cplusplus} | + {4:std}::{2:cout} << {2:x} << "\n"; | + {6:#else} | + {6: printf("%d\n", x);} | + {6:#endif} | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]], unchanged = true } + + local messages = exec_lua('return server.messages') + local token_request_count = 0 + for _, message in ipairs(messages) do + assert(message.method ~= 'textDocument/semanticTokens/full/delta', 'delta request received') + if message.method == 'textDocument/semanticTokens/full' then + token_request_count = token_request_count + 1 + end + end + eq(2, token_request_count) + end) + + it('destroys the highlighter if the buffer is deleted', function() + exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + ]]) + + insert(text) + + local highlighters = exec_lua([[ + vim.api.nvim_buf_delete(bufnr, { force = true }) + local semantic_tokens = vim.lsp.semantic_tokens + return semantic_tokens.__STHighlighter.active + ]]) + + eq({}, highlighters) + end) + + it('updates highlights with delta request on buffer change', function() + exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + ]]) + + insert(text) + screen:expect { grid = [[ + #include <iostream> | + | + int {8:main}() | + { | + int {7:x}; | + #ifdef {5:__cplusplus} | + {4:std}::{2:cout} << {2:x} << "\n"; | + {6:#else} | + {6: printf("%d\n", x);} | + {6:#endif} | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] } + feed_command('%s/int x/int x()/') + feed_command('noh') + screen:expect { grid = [[ + #include <iostream> | + | + int {8:main}() | + { | + ^int {8:x}(); | + #ifdef {5:__cplusplus} | + {4:std}::{2:cout} << {3:x} << "\n"; | + {6:#else} | + {6: printf("%d\n", x);} | + {6:#endif} | + } | + } | + {1:~ }| + {1:~ }| + {1:~ }| + :noh | + ]] } + end) + + it('prevents starting semantic token highlighting with invalid conditions', function() + exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start_client({ name = 'dummy', cmd = server.cmd }) + notifications = {} + vim.notify = function(...) table.insert(notifications, 1, {...}) end + ]]) + eq(false, exec_lua("return vim.lsp.buf_is_attached(bufnr, client_id)")) + + insert(text) + + local notifications = exec_lua([[ + vim.lsp.semantic_tokens.start(bufnr, client_id) + return notifications + ]]) + matches('%[LSP%] Client with id %d not attached to buffer %d', notifications[1][1]) + + notifications = exec_lua([[ + vim.lsp.semantic_tokens.start(bufnr, client_id + 1) + return notifications + ]]) + matches('%[LSP%] No client with id %d', notifications[1][1]) + end) + + it('opt-out: does not activate semantic token highlighting if disabled in client attach', + function() + exec_lua([[ + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ + name = 'dummy', + cmd = server.cmd, + on_attach = vim.schedule_wrap(function(client, bufnr) + client.server_capabilities.semanticTokensProvider = nil + end), + }) + ]]) + eq(true, exec_lua("return vim.lsp.buf_is_attached(bufnr, client_id)")) + + insert(text) + + screen:expect { grid = [[ + #include <iostream> | + | + int main() | + { | + int x; | + #ifdef __cplusplus | + std::cout << x << "\n"; | + #else | + printf("%d\n", x); | + #endif | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] } + + local notifications = exec_lua([[ + local notifications = {} + vim.notify = function(...) table.insert(notifications, 1, {...}) end + vim.lsp.semantic_tokens.start(bufnr, client_id) + return notifications + ]]) + eq('[LSP] Server does not support semantic tokens', notifications[1][1]) + + screen:expect { grid = [[ + #include <iostream> | + | + int main() | + { | + int x; | + #ifdef __cplusplus | + std::cout << x << "\n"; | + #else | + printf("%d\n", x); | + #endif | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]], unchanged = true } + end) + + it('ignores null responses from the server', function() + exec_lua([[ + local legend, response, edit_response = ... + server2 = _create_server({ + capabilities = { + semanticTokensProvider = { + full = { delta = false }, + }, + }, + handlers = { + ['textDocument/semanticTokens/full'] = function() + return nil + end, + ['textDocument/semanticTokens/full/delta'] = function() + return nil + end, + } + }) + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server2.cmd }) + ]]) + eq(true, exec_lua("return vim.lsp.buf_is_attached(bufnr, client_id)")) + + insert(text) + + screen:expect { grid = [[ + #include <iostream> | + | + int main() | + { | + int x; | + #ifdef __cplusplus | + std::cout << x << "\n"; | + #else | + printf("%d\n", x); | + #endif | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] } + end) + + it('does not send delta requests if not supported by server', function() + exec_lua([[ + local legend, response, edit_response = ... + server2 = _create_server({ + capabilities = { + semanticTokensProvider = { + full = { delta = false }, + legend = vim.fn.json_decode(legend), + }, + }, + handlers = { + ['textDocument/semanticTokens/full'] = function() + return vim.fn.json_decode(response) + end, + ['textDocument/semanticTokens/full/delta'] = function() + return vim.fn.json_decode(edit_response) + end, + } + }) + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server2.cmd }) + ]], legend, response, edit_response) + + insert(text) + screen:expect { grid = [[ + #include <iostream> | + | + int {8:main}() | + { | + int {7:x}; | + #ifdef {5:__cplusplus} | + {4:std}::{2:cout} << {2:x} << "\n"; | + {6:#else} | + {6: printf("%d\n", x);} | + {6:#endif} | + } | + ^} | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]] } + feed_command('%s/int x/int x()/') + feed_command('noh') + + -- the highlights don't change because our fake server sent the exact + -- same result for the same method (the full request). "x" would have + -- changed to highlight index 3 had we sent a delta request + screen:expect { grid = [[ + #include <iostream> | + | + int {8:main}() | + { | + ^int {7:x}(); | + #ifdef {5:__cplusplus} | + {4:std}::{2:cout} << {2:x} << "\n"; | + {6:#else} | + {6: printf("%d\n", x);} | + {6:#endif} | + } | + } | + {1:~ }| + {1:~ }| + {1:~ }| + :noh | + ]] } + local messages = exec_lua('return server2.messages') + local token_request_count = 0 + for _, message in ipairs(messages) do + assert(message.method ~= 'textDocument/semanticTokens/full/delta', 'delta request received') + if message.method == 'textDocument/semanticTokens/full' then + token_request_count = token_request_count + 1 + end + end + eq(2, token_request_count) + end) + end) + + describe('token array decoding', function() + for _, test in ipairs({ + { + it = 'clangd-15 on C', + text = [[char* foo = "\n";]], + response = [[{"data": [0, 6, 3, 0, 8193], "resultId": "1"}]], + legend = [[{ + "tokenTypes": [ + "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment" + ], + "tokenModifiers": [ + "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope" + ] + }]], + expected = { + { + line = 0, + modifiers = { + 'declaration', + 'globalScope', + }, + start_col = 6, + end_col = 9, + type = 'variable', + extmark_added = true, + }, + }, + }, + { + it = 'clangd-15 on C++', + text = [[#include <iostream> +int main() +{ + #ifdef __cplusplus + const int x = 1; + std::cout << x << std::endl; + #else + comment + #endif +}]], + response = [[{"data": [1, 4, 4, 3, 8193, 2, 9, 11, 19, 8192, 1, 12, 1, 1, 1033, 1, 2, 3, 15, 8448, 0, 5, 4, 0, 8448, 0, 8, 1, 1, 1032, 0, 5, 3, 15, 8448, 0, 5, 4, 3, 8448, 1, 0, 7, 20, 0, 1, 0, 11, 20, 0, 1, 0, 8, 20, 0], "resultId": "1"}]], + legend = [[{ + "tokenTypes": [ + "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment" + ], + "tokenModifiers": [ + "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope" + ] + }]], + expected = { + { -- main + line = 1, + modifiers = { 'declaration', 'globalScope' }, + start_col = 4, + end_col = 8, + type = 'function', + extmark_added = true, + }, + { -- __cplusplus + line = 3, + modifiers = { 'globalScope' }, + start_col = 9, + end_col = 20, + type = 'macro', + extmark_added = true, + }, + { -- x + line = 4, + modifiers = { 'declaration', 'readonly', 'functionScope' }, + start_col = 12, + end_col = 13, + type = 'variable', + extmark_added = true, + }, + { -- std + line = 5, + modifiers = { 'defaultLibrary', 'globalScope' }, + start_col = 2, + end_col = 5, + type = 'namespace', + extmark_added = true, + }, + { -- cout + line = 5, + modifiers = { 'defaultLibrary', 'globalScope' }, + start_col = 7, + end_col = 11, + type = 'variable', + extmark_added = true, + }, + { -- x + line = 5, + modifiers = { 'readonly', 'functionScope' }, + start_col = 15, + end_col = 16, + type = 'variable', + extmark_added = true, + }, + { -- std + line = 5, + modifiers = { 'defaultLibrary', 'globalScope' }, + start_col = 20, + end_col = 23, + type = 'namespace', + extmark_added = true, + }, + { -- endl + line = 5, + modifiers = { 'defaultLibrary', 'globalScope' }, + start_col = 25, + end_col = 29, + type = 'function', + extmark_added = true, + }, + { -- #else comment #endif + line = 6, + modifiers = {}, + start_col = 0, + end_col = 7, + type = 'comment', + extmark_added = true, + }, + { + line = 7, + modifiers = {}, + start_col = 0, + end_col = 11, + type = 'comment', + extmark_added = true, + }, + { + line = 8, + modifiers = {}, + start_col = 0, + end_col = 8, + type = 'comment', + extmark_added = true, + }, + }, + }, + { + it = 'sumneko_lua', + text = [[-- comment +local a = 1 +b = "as"]], + response = [[{"data": [0, 0, 10, 17, 0, 1, 6, 1, 8, 1, 1, 0, 1, 8, 8]}]], + legend = [[{ + "tokenTypes": [ + "namespace", "type", "class", "enum", "interface", "struct", "typeParameter", "parameter", "variable", "property", "enumMember", "event", "function", "method", "macro", "keyword", "modifier", "comment", "string", "number", "regexp", "operator" + ], + "tokenModifiers": [ + "declaration", "definition", "readonly", "static", "deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary" + ] + }]], + expected = { + { + line = 0, + modifiers = {}, + start_col = 0, + end_col = 10, + type = 'comment', -- comment + extmark_added = true, + }, + { + line = 1, + modifiers = { 'declaration' }, -- a + start_col = 6, + end_col = 7, + type = 'variable', + extmark_added = true, + }, + { + line = 2, + modifiers = { 'static' }, -- b (global) + start_col = 0, + end_col = 1, + type = 'variable', + extmark_added = true, + }, + }, + }, + { + it = 'rust-analyzer', + text = [[pub fn main() { + break rust; + /// what? +} +]], + response = [[{"data": [0, 0, 3, 1, 0, 0, 4, 2, 1, 0, 0, 3, 4, 14, 524290, 0, 4, 1, 45, 0, 0, 1, 1, 45, 0, 0, 2, 1, 26, 0, 1, 4, 5, 1, 8192, 0, 6, 4, 52, 0, 0, 4, 1, 48, 0, 1, 4, 9, 0, 1, 1, 0, 1, 26, 0], "resultId": "1"}]], + legend = [[{ + "tokenTypes": [ + "comment", "keyword", "string", "number", "regexp", "operator", "namespace", "type", "struct", "class", "interface", "enum", "enumMember", "typeParameter", "function", "method", "property", "macro", "variable", + "parameter", "angle", "arithmetic", "attribute", "attributeBracket", "bitwise", "boolean", "brace", "bracket", "builtinAttribute", "builtinType", "character", "colon", "comma", "comparison", "constParameter", "derive", + "dot", "escapeSequence", "formatSpecifier", "generic", "label", "lifetime", "logical", "macroBang", "operator", "parenthesis", "punctuation", "selfKeyword", "semicolon", "typeAlias", "toolModule", "union", "unresolvedReference" + ], + "tokenModifiers": [ + "documentation", "declaration", "definition", "static", "abstract", "deprecated", "readonly", "defaultLibrary", "async", "attribute", "callable", "constant", "consuming", "controlFlow", "crateRoot", "injected", "intraDocLink", + "library", "mutable", "public", "reference", "trait", "unsafe" + ] + }]], + expected = { + { + line = 0, + modifiers = {}, + start_col = 0, + end_col = 3, -- pub + type = 'keyword', + extmark_added = true, + }, + { + line = 0, + modifiers = {}, + start_col = 4, + end_col = 6, -- fn + type = 'keyword', + extmark_added = true, + }, + { + line = 0, + modifiers = { 'declaration', 'public' }, + start_col = 7, + end_col = 11, -- main + type = 'function', + extmark_added = true, + }, + { + line = 0, + modifiers = {}, + start_col = 11, + end_col = 12, + type = 'parenthesis', + extmark_added = true, + }, + { + line = 0, + modifiers = {}, + start_col = 12, + end_col = 13, + type = 'parenthesis', + extmark_added = true, + }, + { + line = 0, + modifiers = {}, + start_col = 14, + end_col = 15, + type = 'brace', + extmark_added = true, + }, + { + line = 1, + modifiers = { 'controlFlow' }, + start_col = 4, + end_col = 9, -- break + type = 'keyword', + extmark_added = true, + }, + { + line = 1, + modifiers = {}, + start_col = 10, + end_col = 13, -- rust + type = 'unresolvedReference', + extmark_added = true, + }, + { + line = 1, + modifiers = {}, + start_col = 13, + end_col = 13, + type = 'semicolon', + extmark_added = true, + }, + { + line = 2, + modifiers = { 'documentation' }, + start_col = 4, + end_col = 11, + type = 'comment', -- /// what? + extmark_added = true, + }, + { + line = 3, + modifiers = {}, + start_col = 0, + end_col = 1, + type = 'brace', + extmark_added = true, + }, + }, + }, + }) do + it(test.it, function() + exec_lua(create_server_definition) + exec_lua([[ + local legend, resp = ... + server = _create_server({ + capabilities = { + semanticTokensProvider = { + full = { delta = false }, + legend = vim.fn.json_decode(legend), + }, + }, + handlers = { + ['textDocument/semanticTokens/full'] = function() + return vim.fn.json_decode(resp) + end, + } + }) + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + ]], test.legend, test.response) + + insert(test.text) + + local highlights = exec_lua([[ + local semantic_tokens = vim.lsp.semantic_tokens + return semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights + ]]) + eq(test.expected, highlights) + end) + end + end) + + describe('token decoding with deltas', function() + for _, test in ipairs({ + { + it = 'semantic_tokens_delta: clangd-15 on C', + legend = [[{ + "tokenTypes": [ + "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment" + ], + "tokenModifiers": [ + "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope" + ] + }]], + text1 = [[char* foo = "\n";]], + edit = [[ggO<Esc>]], + response1 = [[{"data": [0, 6, 3, 0, 8193], "resultId": "1"}]], + response2 = [[{"edits": [{ "start": 0, "deleteCount": 1, "data": [1] }], "resultId": "2"}]], + expected1 = { + { + line = 0, + modifiers = { + 'declaration', + 'globalScope', + }, + start_col = 6, + end_col = 9, + type = 'variable', + extmark_added = true, + } + }, + expected2 = { + { + line = 1, + modifiers = { + 'declaration', + 'globalScope', + }, + start_col = 6, + end_col = 9, + type = 'variable', + extmark_added = true, + } + }, + }, + { + it = 'response with multiple delta edits', + legend = [[{ + "tokenTypes": [ + "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment" + ], + "tokenModifiers": [ + "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope" + ] + }]], + text1 = dedent([[ + #include <iostream> + + int main() + { + int x; + #ifdef __cplusplus + std::cout << x << "\n"; + #else + printf("%d\n", x); + #endif + }]]), + text2 = [[#include <iostream> + +int main() +{ + int x(); + double y; +#ifdef __cplusplus + std::cout << x << "\n"; +#else + printf("%d\n", x); +#endif +}]], + response1 = [[{ + "data": [ 2, 4, 4, 3, 8193, 2, 8, 1, 1, 1025, 1, 7, 11, 19, 8192, 1, 4, 3, 15, 8448, 0, 5, 4, 0, 8448, 0, 8, 1, 1, 1024, 1, 0, 5, 20, 0, 1, 0, 22, 20, 0, 1, 0, 6, 20, 0 ], + "resultId": 1 + }]], + response2 = [[{ + "edits": [ {"data": [ 2, 8, 1, 3, 8193, 1, 11, 1, 1, 1025 ], "deleteCount": 5, "start": 5}, {"data": [ 0, 8, 1, 3, 8192 ], "deleteCount": 5, "start": 25 } ], + "resultId":"2" + }]], + expected1 = { + { + line = 2, + start_col = 4, + end_col = 8, + modifiers = { 'declaration', 'globalScope' }, + type = 'function', + extmark_added = true, + }, + { + line = 4, + start_col = 8, + end_col = 9, + modifiers = { 'declaration', 'functionScope' }, + type = 'variable', + extmark_added = true, + }, + { + line = 5, + start_col = 7, + end_col = 18, + modifiers = { 'globalScope' }, + type = 'macro', + extmark_added = true, + }, + { + line = 6, + start_col = 4, + end_col = 7, + modifiers = { 'defaultLibrary', 'globalScope' }, + type = 'namespace', + extmark_added = true, + }, + { + line = 6, + start_col = 9, + end_col = 13, + modifiers = { 'defaultLibrary', 'globalScope' }, + type = 'variable', + extmark_added = true, + }, + { + line = 6, + start_col = 17, + end_col = 18, + extmark_added = true, + modifiers = { 'functionScope' }, + type = 'variable', + }, + { + line = 7, + start_col = 0, + end_col = 5, + extmark_added = true, + modifiers = {}, + type = 'comment', + }, + { + line = 8, + end_col = 22, + modifiers = {}, + start_col = 0, + type = 'comment', + extmark_added = true, + }, + { + line = 9, + start_col = 0, + end_col = 6, + modifiers = {}, + type = 'comment', + extmark_added = true, + } + }, + expected2 = { + { + line = 2, + start_col = 4, + end_col = 8, + modifiers = { 'declaration', 'globalScope' }, + type = 'function', + extmark_added = true, + }, + { + line = 4, + start_col = 8, + end_col = 9, + modifiers = { 'declaration', 'globalScope' }, + type = 'function', + extmark_added = true, + }, + { + line = 5, + end_col = 12, + start_col = 11, + modifiers = { 'declaration', 'functionScope' }, + type = 'variable', + extmark_added = true, + }, + { + line = 6, + start_col = 7, + end_col = 18, + modifiers = { 'globalScope' }, + type = 'macro', + extmark_added = true, + }, + { + line = 7, + start_col = 4, + end_col = 7, + modifiers = { 'defaultLibrary', 'globalScope' }, + type = 'namespace', + extmark_added = true, + }, + { + line = 7, + start_col = 9, + end_col = 13, + modifiers = { 'defaultLibrary', 'globalScope' }, + type = 'variable', + extmark_added = true, + }, + { + line = 7, + start_col = 17, + end_col = 18, + extmark_added = true, + modifiers = { 'globalScope' }, + type = 'function', + }, + { + line = 8, + start_col = 0, + end_col = 5, + extmark_added = true, + modifiers = {}, + type = 'comment', + }, + { + line = 9, + end_col = 22, + modifiers = {}, + start_col = 0, + type = 'comment', + extmark_added = true, + }, + { + line = 10, + start_col = 0, + end_col = 6, + modifiers = {}, + type = 'comment', + extmark_added = true, + } + }, + }, + { + it = 'optional token_edit.data on deletion', + legend = [[{ + "tokenTypes": [ + "comment", "keyword", "operator", "string", "number", "regexp", "type", "class", "interface", "enum", "enumMember", "typeParameter", "function", "method", "property", "variable", "parameter", "module", "intrinsic", "selfParameter", "clsParameter", "magicFunction", "builtinConstant", "parenthesis", "curlybrace", "bracket", "colon", "semicolon", "arrow" + ], + "tokenModifiers": [ + "declaration", "static", "abstract", "async", "documentation", "typeHint", "typeHintComment", "readonly", "decorator", "builtin" + ] + }]], + text1 = [[string = "test"]], + text2 = [[]], + response1 = [[{"data": [0, 0, 6, 15, 1], "resultId": "1"}]], + response2 = [[{"edits": [{ "start": 0, "deleteCount": 5 }], "resultId": "2"}]], + expected1 = { + { + line = 0, + modifiers = { + 'declaration', + }, + start_col = 0, + end_col = 6, + type = 'variable', + extmark_added = true, + } + }, + expected2 = { + }, + }, + }) do + it(test.it, function() + exec_lua(create_server_definition) + exec_lua([[ + local legend, resp1, resp2 = ... + server = _create_server({ + capabilities = { + semanticTokensProvider = { + full = { delta = true }, + legend = vim.fn.json_decode(legend), + }, + }, + handlers = { + ['textDocument/semanticTokens/full'] = function() + return vim.fn.json_decode(resp1) + end, + ['textDocument/semanticTokens/full/delta'] = function() + return vim.fn.json_decode(resp2) + end, + } + }) + bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + + -- speed up vim.api.nvim_buf_set_lines calls by changing debounce to 10 for these tests + semantic_tokens = vim.lsp.semantic_tokens + vim.schedule(function() + semantic_tokens.stop(bufnr, client_id) + semantic_tokens.start(bufnr, client_id, { debounce = 10 }) + end) + ]], test.legend, test.response1, test.response2) + + insert(test.text1) + + local highlights = exec_lua([[ + return semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights + ]]) + + eq(test.expected1, highlights) + + if test.edit then + feed(test.edit) + else + exec_lua([[ + local text = ... + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, vim.fn.split(text, "\n")) + vim.wait(15) -- wait fot debounce + ]], test.text2) + end + + highlights = exec_lua([[ + return semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights + ]]) + + eq(test.expected2, highlights) + end) + end + end) +end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 425427be54..5229022564 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1,8 +1,9 @@ local helpers = require('test.functional.helpers')(after_each) +local lsp_helpers = require('test.functional.plugin.lsp.helpers') local assert_log = helpers.assert_log -local clear = helpers.clear local buf_lines = helpers.buf_lines +local clear = helpers.clear local command = helpers.command local dedent = helpers.dedent local exec_lua = helpers.exec_lua @@ -14,133 +15,28 @@ local pesc = helpers.pesc local insert = helpers.insert local funcs = helpers.funcs local retry = helpers.retry +local stop = helpers.stop local NIL = helpers.NIL local read_file = require('test.helpers').read_file local write_file = require('test.helpers').write_file -local isCI = helpers.isCI +local is_ci = helpers.is_ci local meths = helpers.meths +local is_os = helpers.is_os +local skip = helpers.skip --- Use these to get access to a coroutine so that I can run async tests and use --- yield. -local run, stop = helpers.run, helpers.stop +local clear_notrace = lsp_helpers.clear_notrace +local create_server_definition = lsp_helpers.create_server_definition +local fake_lsp_code = lsp_helpers.fake_lsp_code +local fake_lsp_logfile = lsp_helpers.fake_lsp_logfile +local test_rpc_server = lsp_helpers.test_rpc_server -- TODO(justinmk): hangs on Windows https://github.com/neovim/neovim/pull/11837 -if helpers.pending_win32(pending) then return end - --- Fake LSP server. -local fake_lsp_code = 'test/functional/fixtures/fake-lsp-server.lua' -local fake_lsp_logfile = 'Xtest-fake-lsp.log' +if skip(is_os('win')) then return end teardown(function() os.remove(fake_lsp_logfile) end) -local function clear_notrace() - -- problem: here be dragons - -- solution: don't look for dragons to closely - clear {env={ - NVIM_LUA_NOTRACK="1"; - VIMRUNTIME=os.getenv"VIMRUNTIME"; - }} -end - - -local function fake_lsp_server_setup(test_name, timeout_ms, options, settings) - exec_lua([=[ - lsp = require('vim.lsp') - local test_name, fixture_filename, logfile, timeout, options, settings = ... - TEST_RPC_CLIENT_ID = lsp.start_client { - cmd_env = { - NVIM_LOG_FILE = logfile; - NVIM_LUA_NOTRACK = "1"; - }; - cmd = { - vim.v.progpath, '-Es', '-u', 'NONE', '--headless', - "-c", string.format("lua TEST_NAME = %q", test_name), - "-c", string.format("lua TIMEOUT = %d", timeout), - "-c", "luafile "..fixture_filename, - }; - handlers = setmetatable({}, { - __index = function(t, method) - return function(...) - return vim.rpcrequest(1, 'handler', ...) - end - end; - }); - workspace_folders = {{ - uri = 'file://' .. vim.loop.cwd(), - name = 'test_folder', - }}; - on_init = function(client, result) - TEST_RPC_CLIENT = client - vim.rpcrequest(1, "init", result) - end; - flags = { - allow_incremental_sync = options.allow_incremental_sync or false; - debounce_text_changes = options.debounce_text_changes or 0; - }; - settings = settings; - on_exit = function(...) - vim.rpcnotify(1, "exit", ...) - end; - } - ]=], test_name, fake_lsp_code, fake_lsp_logfile, timeout_ms or 1e3, options or {}, settings or {}) -end - -local function test_rpc_server(config) - if config.test_name then - clear_notrace() - fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3, config.options, config.settings) - end - local client = setmetatable({}, { - __index = function(_, name) - -- Workaround for not being able to yield() inside __index for Lua 5.1 :( - -- Otherwise I would just return the value here. - return function(...) - return exec_lua([=[ - local name = ... - if type(TEST_RPC_CLIENT[name]) == 'function' then - return TEST_RPC_CLIENT[name](select(2, ...)) - else - return TEST_RPC_CLIENT[name] - end - ]=], name, ...) - end - end; - }) - local code, signal - local function on_request(method, args) - if method == "init" then - if config.on_init then - config.on_init(client, unpack(args)) - end - return NIL - end - if method == 'handler' then - if config.on_handler then - config.on_handler(unpack(args)) - end - end - return NIL - end - local function on_notify(method, args) - if method == 'exit' then - code, signal = unpack(args) - return stop() - end - end - -- TODO specify timeout? - -- run(on_request, on_notify, config.on_setup, 1000) - run(on_request, on_notify, config.on_setup) - if config.on_exit then - config.on_exit(code, signal) - end - stop() - if config.test_name then - exec_lua("vim.api.nvim_exec_autocmds('VimLeavePre', { modeline = false })") - end -end - describe('LSP', function() before_each(function() clear_notrace() @@ -236,9 +132,9 @@ describe('LSP', function() end) it('should invalid cmd argument', function() - eq('Error executing lua: .../lsp.lua:0: cmd: expected list, got nvim', + eq('.../lsp.lua:0: cmd: expected list, got nvim', pcall_err(_cmd_parts, 'nvim')) - eq('Error executing lua: .../lsp.lua:0: cmd argument: expected string, got number', + eq('.../lsp.lua:0: cmd argument: expected string, got number', pcall_err(_cmd_parts, {'nvim', 1})) end) end) @@ -316,7 +212,7 @@ describe('LSP', function() end) it('should succeed with manual shutdown', function() - if isCI() then + if is_ci() then pending('hangs the build on CI #14028, re-enable with freeze timeout #14204') return elseif helpers.skip_fragile(pending) then @@ -420,24 +316,12 @@ describe('LSP', function() it('should detach buffer on bufwipe', function() clear() + exec_lua(create_server_definition) local result = exec_lua([[ - local server = function(dispatchers) - local closing = false - return { - request = function(method, params, callback) - if method == 'initialize' then - callback(nil, { capabilities = {} }) - end - end, - notify = function(...) - end, - is_closing = function() return closing end, - terminate = function() closing = true end - } - end + local server = _create_server() local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_set_current_buf(bufnr) - local client_id = vim.lsp.start({ name = 'detach-dummy', cmd = server }) + local client_id = vim.lsp.start({ name = 'detach-dummy', cmd = server.cmd }) assert(client_id, "lsp.start must return client_id") local client = vim.lsp.get_client_by_id(client_id) local num_attached_before = vim.tbl_count(client.attached_buffers) @@ -572,6 +456,70 @@ describe('LSP', function() } end) + it('BufWritePre does not send notifications if server lacks willSave capabilities', function() + clear() + exec_lua(create_server_definition) + local messages = exec_lua([[ + local server = _create_server({ + capabilities = { + textDocumentSync = { + willSave = false, + willSaveWaitUntil = false, + } + }, + }) + local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + local buf = vim.api.nvim_get_current_buf() + vim.api.nvim_exec_autocmds('BufWritePre', { buffer = buf, modeline = false }) + vim.lsp.stop_client(client_id) + return server.messages + ]]) + eq(#messages, 4) + eq(messages[1].method, 'initialize') + eq(messages[2].method, 'initialized') + eq(messages[3].method, 'shutdown') + eq(messages[4].method, 'exit') + end) + + it('BufWritePre sends willSave / willSaveWaitUntil, applies textEdits', function() + clear() + exec_lua(create_server_definition) + local result = exec_lua([[ + local server = _create_server({ + capabilities = { + textDocumentSync = { + willSave = true, + willSaveWaitUntil = true, + } + }, + handlers = { + ['textDocument/willSaveWaitUntil'] = function() + local text_edit = { + range = { + start = { line = 0, character = 0 }, + ['end'] = { line = 0, character = 0 }, + }, + newText = 'Hello' + } + return { text_edit, } + end + }, + }) + local buf = vim.api.nvim_get_current_buf() + local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + vim.api.nvim_exec_autocmds('BufWritePre', { buffer = buf, modeline = false }) + vim.lsp.stop_client(client_id) + return { + messages = server.messages, + lines = vim.api.nvim_buf_get_lines(buf, 0, -1, true) + } + ]]) + local messages = result.messages + eq('textDocument/willSave', messages[3].method) + eq('textDocument/willSaveWaitUntil', messages[4].method) + eq({'Hello'}, result.lines) + end) + it('saveas sends didOpen if filename changed', function() local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, @@ -2470,7 +2418,7 @@ describe('LSP', function() }, uri = "file:///test_a" }, - contanerName = "TestAContainer" + containerName = "TestAContainer" }, { deprecated = false, @@ -2489,7 +2437,7 @@ describe('LSP', function() }, uri = "file:///test_b" }, - contanerName = "TestBContainer" + containerName = "TestBContainer" } } return vim.lsp.util.symbols_to_items(sym_info, nil) @@ -2649,6 +2597,33 @@ describe('LSP', function() eq(10, pos.col) end) + it('jumps to a Location if focus is true via handler', function() + exec_lua(create_server_definition) + local result = exec_lua([[ + local server = _create_server() + local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + local result = { + uri = 'file:///fake/uri', + selection = { + start = { line = 0, character = 9 }, + ['end'] = { line = 0, character = 9 } + }, + takeFocus = true, + } + local ctx = { + client_id = client_id, + method = 'window/showDocument', + } + vim.lsp.handlers['window/showDocument'](nil, result, ctx) + vim.lsp.stop_client(client_id) + return { + cursor = vim.api.nvim_win_get_cursor(0) + } + ]]) + eq(1, result.cursor[1]) + eq(9, result.cursor[2]) + end) + it('jumps to a Location if focus not set', function() local pos = show_document(location(0, 9, 0, 9), nil, true) eq(1, pos.line) @@ -3434,51 +3409,60 @@ describe('LSP', function() } end) it('format formats range in visual mode', function() + exec_lua(create_server_definition) local result = exec_lua([[ - local messages = {} - local server = function(dispatchers) - local closing = false - return { - request = function(method, params, callback) - table.insert(messages, { - method = method, - params = params, - }) - if method == 'initialize' then - callback(nil, { - capabilities = { - documentFormattingProvider = true, - documentRangeFormattingProvider = true, - } - }) - end - end, - notify = function(...) - end, - is_closing = function() - return closing - end, - terminate = function() - closing = true - end - } - end + local server = _create_server({ capabilities = { + documentFormattingProvider = true, + documentRangeFormattingProvider = true, + }}) local bufnr = vim.api.nvim_get_current_buf() - local client_id = vim.lsp.start({ name = 'dummy', cmd = server }) + local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) vim.api.nvim_win_set_buf(0, bufnr) vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {'foo', 'bar'}) vim.api.nvim_win_set_cursor(0, { 1, 0 }) vim.cmd.normal('v') vim.api.nvim_win_set_cursor(0, { 2, 3 }) vim.lsp.buf.format({ bufnr = bufnr, false }) - return messages + return server.messages ]]) - eq("textDocument/rangeFormatting", result[2].method) + eq("textDocument/rangeFormatting", result[3].method) local expected_range = { start = { line = 0, character = 0 }, ['end'] = { line = 1, character = 4 }, } - eq(expected_range, result[2].params.range) + eq(expected_range, result[3].params.range) + end) + it('Aborts with notify if no clients support requested method', function() + exec_lua(create_server_definition) + exec_lua([[ + vim.notify = function(msg, _) + notify_msg = msg + end + ]]) + local fail_msg = "[LSP] Format request failed, no matching language servers." + local function check_notify(name, formatting, range_formatting) + local timeout_msg = "[LSP][" .. name .. "] timeout" + exec_lua([[ + local formatting, range_formatting, name = ... + local server = _create_server({ capabilities = { + documentFormattingProvider = formatting, + documentRangeFormattingProvider = range_formatting, + }}) + vim.lsp.start({ name = name, cmd = server.cmd }) + notify_msg = nil + vim.lsp.buf.format({ name = name, timeout_ms = 1 }) + ]], formatting, range_formatting, name) + eq(formatting and timeout_msg or fail_msg, exec_lua('return notify_msg')) + exec_lua([[ + notify_msg = nil + vim.lsp.buf.format({ name = name, timeout_ms = 1, range = {start={1, 0}, ['end']={1, 0}}}) + ]]) + eq(range_formatting and timeout_msg or fail_msg, exec_lua('return notify_msg')) + end + check_notify("none", false, false) + check_notify("formatting", true, false) + check_notify("rangeFormatting", false, true) + check_notify("both", true, true) end) end) describe('cmd', function() diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua index 3e63c5df9a..c6c7d2b03d 100644 --- a/test/functional/plugin/man_spec.lua +++ b/test/functional/plugin/man_spec.lua @@ -6,6 +6,10 @@ local exec_lua = helpers.exec_lua local funcs = helpers.funcs local nvim_prog = helpers.nvim_prog local matches = helpers.matches +local write_file = helpers.write_file +local tmpname = helpers.tmpname +local skip = helpers.skip +local is_ci = helpers.is_ci clear() if funcs.executable('man') == 0 then @@ -156,4 +160,17 @@ describe(':Man', function() local args = {nvim_prog, '--headless', '+autocmd VimLeave * echo "quit works!!"', '+Man!', '+call nvim_input("q")'} matches('quit works!!', funcs.system(args, {'manpage contents'})) end) + + it('reports non-existent man pages for absolute paths', function() + skip(is_ci('cirrus')) + local actual_file = tmpname() + -- actual_file must be an absolute path to an existent file for us to test against it + matches('^/.+', actual_file) + write_file(actual_file, '') + local args = {nvim_prog, '--headless', '+:Man ' .. actual_file, '+q'} + matches(('Error detected while processing command line:\r\n' .. + 'man.lua: "no manual entry for %s"'):format(actual_file), + funcs.system(args, {''})) + os.remove(actual_file) + end) end) diff --git a/test/functional/plugin/shada_spec.lua b/test/functional/plugin/shada_spec.lua index dda8077f05..93cf6d2b77 100644 --- a/test/functional/plugin/shada_spec.lua +++ b/test/functional/plugin/shada_spec.lua @@ -2140,7 +2140,7 @@ end) describe('plugin/shada.vim', function() local epoch = os.date('%Y-%m-%dT%H:%M:%S', 0) - local eol = helpers.iswin() and '\r\n' or '\n' + local eol = helpers.is_os('win') and '\r\n' or '\n' before_each(function() -- Note: reset() is called explicitly in each test. os.remove(fname) diff --git a/test/functional/preload.lua b/test/functional/preload.lua index 74f03eaecf..6a5a2e907e 100644 --- a/test/functional/preload.lua +++ b/test/functional/preload.lua @@ -2,10 +2,11 @@ -- Busted started doing this to help provide more isolation. See issue #62 -- for more information about this. local helpers = require('test.functional.helpers')(nil) -local iswin = helpers.iswin +require('test.functional.ui.screen') local busted = require("busted") +local is_os = helpers.is_os -if iswin() then +if is_os('win') then local ffi = require('ffi') ffi.cdef[[ typedef int errno_t; diff --git a/test/functional/provider/clipboard_spec.lua b/test/functional/provider/clipboard_spec.lua index 401dc84ccc..2c5185a974 100644 --- a/test/functional/provider/clipboard_spec.lua +++ b/test/functional/provider/clipboard_spec.lua @@ -106,8 +106,53 @@ describe('clipboard', function() basic_register_test() end) - it('`:redir @+>` with invalid g:clipboard shows exactly one error #7184', - function() + it('using "+ in Normal mode with invalid g:clipboard always shows error', function() + insert('a') + command("let g:clipboard = 'bogus'") + feed('"+yl') + screen:expect([[ + ^a | + {0:~ }| + {0:~ }| + clipboard: No provider. Try ":checkhealth" or ":h clipboard". | + ]]) + feed('"+p') + screen:expect([[ + a^a | + {0:~ }| + {0:~ }| + clipboard: No provider. Try ":checkhealth" or ":h clipboard". | + ]]) + end) + + it('using clipboard=unnamedplus with invalid g:clipboard shows error once', function() + insert('a') + command("let g:clipboard = 'bogus'") + command('set clipboard=unnamedplus') + feed('yl') + screen:expect([[ + ^a | + {0:~ }| + {0:~ }| + clipboard: No provider. Try ":checkhealth" or ":h clipboard". | + ]]) + feed(':<CR>') + screen:expect([[ + ^a | + {0:~ }| + {0:~ }| + : | + ]]) + feed('p') + screen:expect([[ + a^a | + {0:~ }| + {0:~ }| + : | + ]]) + end) + + it('`:redir @+>` with invalid g:clipboard shows exactly one error #7184', function() command("let g:clipboard = 'bogus'") feed_command('redir @+> | :silent echo system("cat CONTRIBUTING.md") | redir END') screen:expect([[ @@ -118,8 +163,7 @@ describe('clipboard', function() ]]) end) - it('`:redir @+>|bogus_cmd|redir END` + invalid g:clipboard must not recurse #7184', - function() + it('`:redir @+>|bogus_cmd|redir END` + invalid g:clipboard must not recurse #7184', function() command("let g:clipboard = 'bogus'") feed_command('redir @+> | bogus_cmd | redir END') screen:expect{grid=[[ diff --git a/test/functional/provider/perl_spec.lua b/test/functional/provider/perl_spec.lua index aff5e36e24..ce92831f4c 100644 --- a/test/functional/provider/perl_spec.lua +++ b/test/functional/provider/perl_spec.lua @@ -9,6 +9,8 @@ local curbufmeths = helpers.curbufmeths local insert = helpers.insert local expect = helpers.expect local feed = helpers.feed +local is_os = helpers.is_os +local skip = helpers.skip do clear() @@ -24,7 +26,7 @@ before_each(function() end) describe('legacy perl provider', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) it('feature test', function() eq(1, eval('has("perl")')) @@ -68,7 +70,7 @@ describe('legacy perl provider', function() end) describe('perl provider', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) teardown(function () os.remove('Xtest-perl-hello.pl') os.remove('Xtest-perl-hello-plugin.pl') diff --git a/test/functional/shada/compatibility_spec.lua b/test/functional/shada/compatibility_spec.lua index a5ef60d91f..fb656735dd 100644 --- a/test/functional/shada/compatibility_spec.lua +++ b/test/functional/shada/compatibility_spec.lua @@ -12,7 +12,7 @@ local wshada, sdrcmd, shada_fname = get_shada_rw('Xtest-functional-shada-compati local mock_file_path = '/a/b/' local mock_file_path2 = '/d/e/' -if helpers.iswin() then +if helpers.is_os('win') then mock_file_path = 'C:/a/' mock_file_path2 = 'C:/d/' end diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua index 2d44b0a950..da2fbbe029 100644 --- a/test/functional/shada/merging_spec.lua +++ b/test/functional/shada/merging_spec.lua @@ -14,7 +14,7 @@ local wshada, sdrcmd, shada_fname = get_shada_rw('Xtest-functional-shada-merging.shada') local mock_file_path = '/a/b/' -if helpers.iswin() then +if helpers.is_os('win') then mock_file_path = 'C:/a/' end diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua index f5a81eb2ef..88a99d9b55 100644 --- a/test/functional/shada/shada_spec.lua +++ b/test/functional/shada/shada_spec.lua @@ -5,6 +5,8 @@ local meths, nvim_command, funcs, eq = local write_file, spawn, set_session, nvim_prog, exc_exec = helpers.write_file, helpers.spawn, helpers.set_session, helpers.nvim_prog, helpers.exc_exec +local is_os = helpers.is_os +local skip = helpers.skip local lfs = require('lfs') local paths = require('test.cmakeconfig.paths') @@ -248,7 +250,7 @@ describe('ShaDa support code', function() end) it('does not crash when ShaDa file directory is not writable', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) funcs.mkdir(dirname, '', 0) eq(0, funcs.filewritable(dirname)) diff --git a/test/functional/terminal/altscreen_spec.lua b/test/functional/terminal/altscreen_spec.lua index e4e1aa5fa2..cbe5e06005 100644 --- a/test/functional/terminal/altscreen_spec.lua +++ b/test/functional/terminal/altscreen_spec.lua @@ -6,7 +6,7 @@ local feed_data = thelpers.feed_data local enter_altscreen = thelpers.enter_altscreen local exit_altscreen = thelpers.exit_altscreen -if helpers.pending_win32(pending) then return end +if helpers.skip(helpers.is_os('win')) then return end describe(':terminal altscreen', function() local screen diff --git a/test/functional/terminal/api_spec.lua b/test/functional/terminal/api_spec.lua index 5305b8af9c..724791343d 100644 --- a/test/functional/terminal/api_spec.lua +++ b/test/functional/terminal/api_spec.lua @@ -2,7 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local child_session = require('test.functional.terminal.helpers') local ok = helpers.ok -if helpers.pending_win32(pending) then return end +if helpers.skip(helpers.is_os('win')) then return end describe('api', function() local screen @@ -19,6 +19,16 @@ describe('api', function() end) it("qa! RPC request during insert-mode", function() + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + | + {3:-- TERMINAL --} | + ]]} + -- Start the socket from the child nvim. child_session.feed_data(":echo serverstart('"..socket_name.."')\n") @@ -67,4 +77,3 @@ describe('api', function() socket_session1:request("nvim_command", "qa!") end) end) - diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 9d10f43ec6..9c8b983ff7 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -15,6 +15,8 @@ local matches = helpers.matches local exec_lua = helpers.exec_lua local sleep = helpers.sleep local funcs = helpers.funcs +local is_os = helpers.is_os +local skip = helpers.skip describe(':terminal buffer', function() local screen @@ -200,7 +202,7 @@ describe(':terminal buffer', function() -- Save the buffer number of the terminal for later testing. local tbuf = eval('bufnr("%")') - local exitcmd = helpers.iswin() + local exitcmd = helpers.is_os('win') and "['cmd', '/c', 'exit']" or "['sh', '-c', 'exit']" source([[ @@ -262,7 +264,7 @@ describe(':terminal buffer', function() end) it('it works with set rightleft #11438', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) local columns = eval('&columns') feed(string.rep('a', columns)) command('set rightleft') @@ -410,6 +412,14 @@ describe('on_lines does not emit out-of-bounds line indexes when', function() feed_command('bdelete!') eq('', exec_lua([[return _G.cb_error]])) end) + + it('runs TextChangedT event', function() + meths.set_var('called', 0) + command('autocmd TextChangedT * ++once let g:called = 1') + feed_command('terminal') + feed('iaa') + eq(1, meths.get_var('called')) + end) end) it('terminal truncates number of composing characters to 5', function() diff --git a/test/functional/terminal/channel_spec.lua b/test/functional/terminal/channel_spec.lua index b5f3c2bd31..2ca7cdb0a2 100644 --- a/test/functional/terminal/channel_spec.lua +++ b/test/functional/terminal/channel_spec.lua @@ -7,6 +7,7 @@ local command = helpers.command local pcall_err = helpers.pcall_err local feed = helpers.feed local poke_eventloop = helpers.poke_eventloop +local is_os = helpers.is_os describe('terminal channel is closed and later released if', function() local screen @@ -92,3 +93,17 @@ describe('terminal channel is closed and later released if', function() eq(chans - 1, eval('len(nvim_list_chans())')) end) end) + +it('chansend sends lines to terminal channel in proper order', function() + clear() + local screen = Screen.new(100, 20) + screen:attach() + local shells = is_os('win') and {'cmd.exe', 'pwsh.exe -nop', 'powershell.exe -nop'} or {'sh'} + for _, sh in ipairs(shells) do + command([[bdelete! | let id = termopen(']] .. sh .. [[')]]) + command([[call chansend(id, ['echo "hello"', 'echo "world"', ''])]]) + screen:expect{ + any=[[echo "hello".*echo "world"]] + } + end +end) diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua index 2d1c790d2f..98ac03211a 100644 --- a/test/functional/terminal/cursor_spec.lua +++ b/test/functional/terminal/cursor_spec.lua @@ -9,6 +9,8 @@ local matches = helpers.matches local feed_command = helpers.feed_command local hide_cursor = thelpers.hide_cursor local show_cursor = thelpers.show_cursor +local is_os = helpers.is_os +local skip = helpers.skip describe(':terminal cursor', function() local screen @@ -88,7 +90,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 + skip(is_os('win')) hide_cursor() screen:expect([[ tty ready | @@ -361,7 +363,7 @@ describe('buffer cursor position is correct in terminal without number column', 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? + if skip(is_os('win'), "Encoding problem?") then return end before_each(function() setup_ex_register('µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳') @@ -444,7 +446,7 @@ describe('buffer cursor position is correct in terminal without number column', 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? + skip(is_os('win'), "Encoding problem?") before_each(function() setup_ex_register('哦哦哦哦哦哦哦哦') @@ -741,7 +743,7 @@ describe('buffer cursor position is correct in terminal with number column', fun 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? + if skip(is_os('win'), "Encoding problem?") then return end before_each(function() setup_ex_register('µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳') @@ -824,7 +826,7 @@ describe('buffer cursor position is correct in terminal with number column', fun 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? + skip(is_os('win'), "Encoding problem?") before_each(function() setup_ex_register('哦哦哦哦哦哦哦哦') diff --git a/test/functional/terminal/edit_spec.lua b/test/functional/terminal/edit_spec.lua index aeb4b7cc2e..80287bb3d0 100644 --- a/test/functional/terminal/edit_spec.lua +++ b/test/functional/terminal/edit_spec.lua @@ -36,7 +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 + if helpers.skip(helpers.is_os('win')) 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 36f9f90143..6b7e93a864 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -8,9 +8,10 @@ local feed_command, eval = helpers.feed_command, helpers.eval local funcs = helpers.funcs local retry = helpers.retry local ok = helpers.ok -local iswin = helpers.iswin local command = helpers.command -local uname = helpers.uname +local skip = helpers.skip +local is_os = helpers.is_os +local is_ci = helpers.is_ci describe(':terminal', function() local screen @@ -46,11 +47,8 @@ describe(':terminal', function() end) it("reads output buffer on terminal reporting #4151", function() - if uname() == 'freebsd' then - pending('Failing FreeBSD test') - end - if helpers.pending_win32(pending) then return end - if iswin() then + skip(is_ci('cirrus') or is_os('win')) + if is_os('win') then feed_command([[terminal powershell -NoProfile -NoLogo -Command Write-Host -NoNewline "\"$([char]27)[6n\""; Start-Sleep -Milliseconds 500 ]]) else feed_command([[terminal printf '\e[6n'; sleep 0.5 ]]) @@ -59,7 +57,7 @@ describe(':terminal', function() end) it("in normal-mode :split does not move cursor", function() - if iswin() then + if is_os('win') then feed_command([[terminal for /L \\%I in (1,0,2) do ( echo foo & ping -w 100 -n 1 127.0.0.1 > nul )]]) else feed_command([[terminal while true; do echo foo; sleep .1; done]]) @@ -146,7 +144,7 @@ describe(':terminal (with fake shell)', function() end it('with no argument, acts like termopen()', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) terminal_with_fake_shell() retry(nil, 4 * screen.timeout, function() screen:expect([[ @@ -170,7 +168,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 + skip(is_os('win')) nvim('set_option', 'shell', testprg('shell-test')..' -t jeff') terminal_with_fake_shell() screen:expect([[ @@ -182,7 +180,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 + skip(is_os('win')) command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell('echo hi') screen:expect([[ @@ -194,7 +192,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 + skip(is_os('win')) nvim('set_option', 'shell', testprg('shell-test')..' -t jeff') command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell('echo hi') @@ -207,7 +205,7 @@ describe(':terminal (with fake shell)', function() end) it('allows quotes and slashes', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell([[echo 'hello' \ "world"]]) screen:expect([[ @@ -244,7 +242,7 @@ describe(':terminal (with fake shell)', function() end) it('works with :find', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) terminal_with_fake_shell() screen:expect([[ ^ready $ | @@ -255,7 +253,7 @@ describe(':terminal (with fake shell)', function() eq('term://', string.match(eval('bufname("%")'), "^term://")) feed([[<C-\><C-N>]]) feed_command([[find */shadacat.py]]) - if iswin() then + if is_os('win') then eq('scripts\\shadacat.py', eval('bufname("%")')) else eq('scripts/shadacat.py', eval('bufname("%")')) @@ -263,7 +261,7 @@ describe(':terminal (with fake shell)', function() end) it('works with gf', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) 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 d69f3207f1..7247361649 100644 --- a/test/functional/terminal/helpers.lua +++ b/test/functional/terminal/helpers.lua @@ -4,19 +4,31 @@ local helpers = require('test.functional.helpers')(nil) local Screen = require('test.functional.ui.screen') local testprg = helpers.testprg +local exec_lua = helpers.exec_lua local feed_command, nvim = helpers.feed_command, helpers.nvim local function feed_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)') + if type(data) == 'table' then + data = table.concat(data, '\n') + end + exec_lua('vim.api.nvim_chan_send(vim.b.terminal_job_id, ...)', data) end local function feed_termcode(data) - -- feed with the job API - nvim('command', 'call jobsend(b:terminal_job_id, "\\x1b'..data..'")') + feed_data('\027' .. data) +end + +local function make_lua_executor(session) + return function(code, ...) + local status, rv = session:request('nvim_exec_lua', code, {...}) + if not status then + session:stop() + error(rv[2]) + end + return rv + end end + -- some helpers for controlling the terminal. the codes were taken from -- infocmp xterm-256color which is less what libvterm understands -- civis/cnorm @@ -62,7 +74,10 @@ local function screen_setup(extra_rows, command, cols, opts) [9] = {foreground = 4}, [10] = {foreground = 121}, -- "Press ENTER" in embedded :terminal session. [11] = {foreground = tonumber('0x00000b')}, - [12] = {reverse = true, foreground = tonumber('0x000079')}, + [12] = {underline = true}, + [13] = {underline = true, reverse = true}, + [14] = {underline = true, reverse = true, bold = true}, + [15] = {underline = true, foreground = 12}, }) screen:attach(opts or {rgb=false}) @@ -109,6 +124,7 @@ end return { feed_data = feed_data, feed_termcode = feed_termcode, + make_lua_executor = make_lua_executor, hide_cursor = hide_cursor, show_cursor = show_cursor, enter_altscreen = enter_altscreen, diff --git a/test/functional/terminal/highlight_spec.lua b/test/functional/terminal/highlight_spec.lua index a119d4acd3..2ac45771d4 100644 --- a/test/functional/terminal/highlight_spec.lua +++ b/test/functional/terminal/highlight_spec.lua @@ -7,6 +7,8 @@ local nvim_prog_abs = helpers.nvim_prog_abs local eq, eval = helpers.eq, helpers.eval local funcs = helpers.funcs local nvim_set = helpers.nvim_set +local is_os = helpers.is_os +local skip = helpers.skip describe(':terminal highlight', function() local screen @@ -58,7 +60,7 @@ describe(':terminal highlight', function() end) local function pass_attrs() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) screen:expect(sub([[ tty ready | {NUM:text}text{10: } | @@ -73,7 +75,7 @@ describe(':terminal highlight', function() it('will pass the corresponding attributes', pass_attrs) it('will pass the corresponding attributes on scrollback', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) pass_attrs() local lines = {} for i = 1, 8 do @@ -197,7 +199,7 @@ describe(':terminal highlight forwarding', function() end) it('will handle cterm and rgb attributes', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) thelpers.set_fg(3) thelpers.feed_data('text') thelpers.feed_termcode('[38:2:255:128:0m') @@ -249,7 +251,7 @@ describe(':terminal highlight with custom palette', function() end) it('will use the custom color', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) thelpers.set_fg(3) thelpers.feed_data('text') thelpers.clear_attrs() diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua index 6e2c851df7..50c8f5e7df 100644 --- a/test/functional/terminal/mouse_spec.lua +++ b/test/functional/terminal/mouse_spec.lua @@ -3,6 +3,8 @@ local thelpers = require('test.functional.terminal.helpers') local clear, eq, eval = helpers.clear, helpers.eq, helpers.eval local feed, nvim, command = helpers.feed, helpers.nvim, helpers.command local feed_data = thelpers.feed_data +local is_os = helpers.is_os +local skip = helpers.skip describe(':terminal mouse', function() local screen @@ -66,7 +68,7 @@ describe(':terminal mouse', function() end) it('does not leave terminal mode on left-release', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) feed('<LeftRelease>') eq('t', eval('mode(1)')) end) @@ -87,7 +89,7 @@ describe(':terminal mouse', function() end) it('will forward mouse press, drag and release to the program', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) feed('<LeftMouse><1,2>') screen:expect([[ line27 | @@ -131,7 +133,7 @@ describe(':terminal mouse', function() end) it('will forward mouse scroll to the program', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) feed('<ScrollWheelUp><0,0>') screen:expect([[ line27 | @@ -145,7 +147,7 @@ describe(':terminal mouse', function() end) it('dragging and scrolling do not interfere with each other', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) feed('<LeftMouse><1,2>') screen:expect([[ line27 | @@ -199,7 +201,7 @@ describe(':terminal mouse', function() 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 + skip(is_os('win')) command('set number') -- When the display area such as a number is clicked, it returns to the -- normal mode. @@ -230,7 +232,7 @@ describe(':terminal mouse', function() end) describe('with a split window and other buffer', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) before_each(function() feed('<c-\\><c-n>:vsp<cr>') screen:expect([[ @@ -287,7 +289,7 @@ describe(':terminal mouse', function() ]]) end) - it('wont lose focus if another window is scrolled', function() + it("won't lose focus if another window is scrolled", function() feed('<ScrollWheelUp><4,0><ScrollWheelUp><4,0>') screen:expect([[ {7: 21 }line │line30 | @@ -310,6 +312,34 @@ describe(':terminal mouse', function() ]]) end) + it("scrolling another window respects 'mousescroll'", function() + command('set mousescroll=ver:1') + feed('<ScrollWheelUp><4,0>') + screen:expect([[ + {7: 26 }line │line30 | + {7: 27 }line │rows: 5, cols: 25 | + {7: 28 }line │rows: 5, cols: 24 | + {7: 29 }line │mouse enabled | + {7: 30 }line │{1: } | + ========== ========== | + {3:-- TERMINAL --} | + ]]) + command('set mousescroll=ver:10') + feed('<ScrollWheelUp><4,0>') + screen:expect([[ + {7: 16 }line │line30 | + {7: 17 }line │rows: 5, cols: 25 | + {7: 18 }line │rows: 5, cols: 24 | + {7: 19 }line │mouse enabled | + {7: 20 }line │{1: } | + ========== ========== | + {3:-- TERMINAL --} | + ]]) + command('set mousescroll=ver:0') + feed('<ScrollWheelUp><4,0>') + screen:expect_unchanged() + end) + it('will lose focus if another window is clicked', function() feed('<LeftMouse><5,1>') screen:expect([[ diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index b491cb2735..a4899c8219 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -3,7 +3,6 @@ local helpers = require('test.functional.helpers')(after_each) local thelpers = require('test.functional.terminal.helpers') local clear, eq, curbuf = helpers.clear, helpers.eq, helpers.curbuf local feed, testprg, feed_command = helpers.feed, helpers.testprg, helpers.feed_command -local iswin = helpers.iswin local eval = helpers.eval local command = helpers.command local matches = helpers.matches @@ -15,6 +14,8 @@ local feed_data = thelpers.feed_data local pcall_err = helpers.pcall_err local exec_lua = helpers.exec_lua local assert_alive = helpers.assert_alive +local skip = helpers.skip +local is_os = helpers.is_os describe(':terminal scrollback', function() local screen @@ -139,7 +140,7 @@ describe(':terminal scrollback', function() describe('and height decreased by 1', function() - if helpers.pending_win32(pending) then return end + if skip(is_os('win')) then return end local function will_hide_top_line() feed([[<C-\><C-N>]]) screen:try_resize(screen._width - 2, screen._height - 1) @@ -185,7 +186,7 @@ describe(':terminal scrollback', function() -- XXX: Can't test this reliably on Windows unless the cursor is _moved_ -- by the resize. http://docs.libuv.org/en/v1.x/signal.html -- See also: https://github.com/rprichard/winpty/issues/110 - if helpers.pending_win32(pending) then return end + if skip(is_os('win')) then return end describe('and the height is decreased by 2', function() before_each(function() @@ -264,7 +265,7 @@ describe(':terminal scrollback', function() -- XXX: Can't test this reliably on Windows unless the cursor is _moved_ -- by the resize. http://docs.libuv.org/en/v1.x/signal.html -- See also: https://github.com/rprichard/winpty/issues/110 - if helpers.pending_win32(pending) then return end + if skip(is_os('win')) then return end local function pop_then_push() screen:try_resize(screen._width, screen._height + 1) screen:expect([[ @@ -346,7 +347,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 + skip(is_os('win')) clear() local screen = Screen.new(30, 7) screen:attach({rgb=false}) @@ -396,21 +397,21 @@ describe("'scrollback' option", function() it('set to 0 behaves as 1', function() local screen - if iswin() then + if is_os('win') then screen = thelpers.screen_setup(nil, "['cmd.exe']", 30) else screen = thelpers.screen_setup(nil, "['sh']", 30) end curbufmeths.set_option('scrollback', 0) - feed_data(('%s REP 31 line%s'):format(testprg('shell-test'), iswin() and '\r' or '\n')) + feed_data(('%s REP 31 line%s'):format(testprg('shell-test'), is_os('win') and '\r' or '\n')) screen:expect{any='30: line '} retry(nil, nil, function() expect_lines(7) end) end) it('deletes lines (only) if necessary', function() local screen - if iswin() then + if is_os('win') then command([[let $PROMPT='$$']]) screen = thelpers.screen_setup(nil, "['cmd.exe']", 30) else @@ -423,7 +424,7 @@ describe("'scrollback' option", function() -- Wait for prompt. screen:expect{any='%$'} - feed_data(('%s REP 31 line%s'):format(testprg('shell-test'), iswin() and '\r' or '\n')) + feed_data(('%s REP 31 line%s'):format(testprg('shell-test'), is_os('win') and '\r' or '\n')) screen:expect{any='30: line '} retry(nil, nil, function() expect_lines(33, 2) end) @@ -436,8 +437,8 @@ describe("'scrollback' option", function() -- 'scrollback' option is synchronized with the internal sb_buffer. command('sleep 100m') - feed_data(('%s REP 41 line%s'):format(testprg('shell-test'), iswin() and '\r' or '\n')) - if iswin() then + feed_data(('%s REP 41 line%s'):format(testprg('shell-test'), is_os('win') and '\r' or '\n')) + if is_os('win') then screen:expect{grid=[[ 37: line | 38: line | @@ -461,8 +462,8 @@ describe("'scrollback' option", function() expect_lines(58) -- Verify off-screen state - matches((iswin() and '^36: line[ ]*$' or '^35: line[ ]*$'), eval("getline(line('w0') - 1)")) - matches((iswin() and '^27: line[ ]*$' or '^26: line[ ]*$'), eval("getline(line('w0') - 10)")) + matches((is_os('win') and '^36: line[ ]*$' or '^35: line[ ]*$'), eval("getline(line('w0') - 1)")) + matches((is_os('win') and '^27: line[ ]*$' or '^26: line[ ]*$'), eval("getline(line('w0') - 10)")) end) it('deletes extra lines immediately', function() @@ -606,7 +607,7 @@ describe("pending scrollback line handling", function() end) it("does not crash after nvim_buf_call #14891", function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) exec_lua [[ local a = vim.api local bufnr = a.nvim_create_buf(false, true) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 3c56ad5f79..b28728057f 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -5,15 +5,15 @@ -- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Bracketed-Paste-Mode local helpers = require('test.functional.helpers')(after_each) -local uname = helpers.uname local thelpers = require('test.functional.terminal.helpers') local Screen = require('test.functional.ui.screen') -local assert_alive = helpers.assert_alive local eq = helpers.eq local feed_command = helpers.feed_command local feed_data = thelpers.feed_data local clear = helpers.clear local command = helpers.command +local dedent = helpers.dedent +local exec = helpers.exec local testprg = helpers.testprg local retry = helpers.retry local nvim_prog = helpers.nvim_prog @@ -22,16 +22,24 @@ local ok = helpers.ok local read_file = helpers.read_file local funcs = helpers.funcs local meths = helpers.meths +local is_ci = helpers.is_ci +local is_os = helpers.is_os +local new_pipename = helpers.new_pipename +local spawn_argv = helpers.spawn_argv +local set_session = helpers.set_session +local feed = helpers.feed +local eval = helpers.eval -if helpers.pending_win32(pending) then return end +if helpers.skip(helpers.is_os('win')) then return end describe('TUI', function() local screen local child_session + local child_exec_lua before_each(function() clear() - local child_server = helpers.new_pipename() + local child_server = new_pipename() screen = thelpers.screen_setup(0, string.format([=[["%s", "--listen", "%s", "-u", "NONE", "-i", "NONE", "--cmd", "%s laststatus=2 background=dark"]]=], nvim_prog, child_server, nvim_set)) @@ -45,6 +53,7 @@ describe('TUI', function() {3:-- TERMINAL --} | ]]) child_session = helpers.connect(child_server) + child_exec_lua = thelpers.make_lua_executor(child_session) end) -- Wait for mode in the child Nvim (avoid "typeahead race" #10826). @@ -67,7 +76,16 @@ describe('TUI', function() it('rapid resize #7572 #7628', function() -- Need buffer rows to provoke the behavior. - feed_data(":edit test/functional/fixtures/bigfile.txt:") + feed_data(":edit test/functional/fixtures/bigfile.txt\n") + screen:expect([[ + {1:0}000;<control>;Cc;0;BN;;;;;N;NULL;;;; | + 0001;<control>;Cc;0;BN;;;;;N;START OF HEADING;;;; | + 0002;<control>;Cc;0;BN;;;;;N;START OF TEXT;;;; | + 0003;<control>;Cc;0;BN;;;;;N;END OF TEXT;;;; | + {5:test/functional/fixtures/bigfile.txt }| + :edit test/functional/fixtures/bigfile.txt | + {3:-- TERMINAL --} | + ]]) command('call jobresize(b:terminal_job_id, 58, 9)') command('call jobresize(b:terminal_job_id, 62, 13)') command('call jobresize(b:terminal_job_id, 100, 42)') @@ -84,25 +102,9 @@ describe('TUI', function() command('call jobresize(b:terminal_job_id, 1, 4)') screen:try_resize(57, 17) command('call jobresize(b:terminal_job_id, 57, 17)') - assert_alive() - end) - - it('resize at startup', function() - -- Issues: #17285 #15044 #11330 - screen:try_resize(50, 10) - feed_command([[call termopen([v:progpath, '--clean', '--cmd', 'let start = reltime() | while v:true | if reltimefloat(reltime(start)) > 2 | break | endif | endwhile']) | sleep 500m | vs new]]) - screen:expect([[ - {1: } │ | - {4:~ }│{4:~ }| - {4:~ }│{4:~ }| - {4:~ }│{4:~ }| - {4:~ }│{4:~ }| - {4:~ }│{5:[No Name] 0,0-1 All}| - {4:~ }│ | - {5:new }{MATCH:<.*[/\]nvim }| - | - {3:-- TERMINAL --} | - ]]) + retry(nil, nil, function() + eq({true, 57}, {child_session:request('nvim_win_get_width', 0)}) + end) end) it('accepts resize while pager is active', function() @@ -634,37 +636,35 @@ describe('TUI', function() tabnew highlight Tabline ctermbg=NONE ctermfg=NONE cterm=underline ]]) - local attrs = screen:get_default_attr_ids() - attrs[11] = {underline = true} screen:expect([[ - {11: + [No Name] + [No Name] }{3: [No Name] }{1: }{11:X}| + {12: + [No Name] + [No Name] }{3: [No Name] }{1: }{12:X}| {1: } | {4:~ }| {4:~ }| {5:[No Name] }| | {3:-- TERMINAL --} | - ]], attrs) + ]]) feed_data('\027[57421;5u') -- CTRL + KP_PAGE_UP screen:expect([[ - {11: + [No Name] }{3: + [No Name] }{11: [No Name] }{1: }{11:X}| + {12: + [No Name] }{3: + [No Name] }{12: [No Name] }{1: }{12:X}| 0123456789/*-{1:+} | = | {4:~ }| {5:[No Name] [+] }| | {3:-- TERMINAL --} | - ]], attrs) + ]]) feed_data('\027[57422;5u') -- CTRL + KP_PAGE_DOWN screen:expect([[ - {11: + [No Name] + [No Name] }{3: [No Name] }{1: }{11:X}| + {12: + [No Name] + [No Name] }{3: [No Name] }{1: }{12:X}| {1: } | {4:~ }| {4:~ }| {5:[No Name] }| | {3:-- TERMINAL --} | - ]], attrs) + ]]) end) it('mouse events work with right-click menu', function() @@ -679,31 +679,28 @@ describe('TUI', function() highlight Pmenu ctermbg=NONE ctermfg=NONE cterm=underline,reverse highlight PmenuSel ctermbg=NONE ctermfg=NONE cterm=underline,reverse,bold ]]) - local attrs = screen:get_default_attr_ids() - attrs[11] = {underline = true, reverse = true} - attrs[12] = {underline = true, reverse = true, bold = true} meths.input_mouse('right', 'press', '', 0, 0, 4) screen:expect([[ {1:p}opup menu test | - {4:~ }{11: foo }{4: }| - {4:~ }{11: bar }{4: }| - {4:~ }{11: baz }{4: }| + {4:~ }{13: foo }{4: }| + {4:~ }{13: bar }{4: }| + {4:~ }{13: baz }{4: }| {5:[No Name] [+] }| | {3:-- TERMINAL --} | - ]], attrs) + ]]) meths.input_mouse('right', 'release', '', 0, 0, 4) screen:expect_unchanged() meths.input_mouse('move', '', '', 0, 3, 6) screen:expect([[ {1:p}opup menu test | - {4:~ }{11: foo }{4: }| - {4:~ }{11: bar }{4: }| - {4:~ }{12: baz }{4: }| + {4:~ }{13: foo }{4: }| + {4:~ }{13: bar }{4: }| + {4:~ }{14: baz }{4: }| {5:[No Name] [+] }| | {3:-- TERMINAL --} | - ]], attrs) + ]]) meths.input_mouse('left', 'press', '', 0, 2, 6) screen:expect([[ {1:p}opup menu test | @@ -713,7 +710,7 @@ describe('TUI', function() {5:[No Name] [+] }| :let g:menustr = 'bar' | {3:-- TERMINAL --} | - ]], attrs) + ]]) meths.input_mouse('left', 'release', '', 0, 2, 6) screen:expect_unchanged() end) @@ -819,12 +816,11 @@ describe('TUI', function() end) it('paste: terminal mode', function() - if os.getenv('GITHUB_ACTIONS') ~= nil then + if is_ci('github') then pending("tty-test complains about not owning the terminal -- actions/runner#241") - return end - feed_data(':set statusline=^^^^^^^\n') - feed_data(':terminal '..testprg('tty-test')..'\n') + child_exec_lua('vim.o.statusline="^^^^^^^"') + child_exec_lua('vim.cmd.terminal(...)', testprg('tty-test')) feed_data('i') screen:expect{grid=[[ tty ready | @@ -1086,8 +1082,7 @@ describe('TUI', function() wait_for_mode('i') -- "bracketed paste" feed_data('\027[200~'..expected..'\027[201~') - -- FIXME: Data race between the two feeds - if uname() == 'freebsd' then screen:sleep(1) end + expect_child_buf_lines({expected}) feed_data(' end') expected = expected..' end' screen:expect([[ @@ -1206,6 +1201,15 @@ describe('TUI', function() it('paste: split "start paste" code', function() feed_data('i') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} -- Send split "start paste" sequence. feed_data('\027[2') feed_data('00~pasted from terminal\027[201~') @@ -1222,6 +1226,15 @@ describe('TUI', function() it('paste: split "stop paste" code', function() feed_data('i') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} -- Send split "stop paste" sequence. feed_data('\027[200~pasted from terminal\027[20') feed_data('1~') @@ -1247,6 +1260,15 @@ describe('TUI', function() end)(vim.paste) ]], {}) feed_data('i') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} feed_data('\027[200~pasted') -- phase 1 screen:expect([[ pasted{1: } | @@ -1286,6 +1308,7 @@ describe('TUI', function() [7] = {reverse = true, foreground = Screen.colors.SeaGreen4}, [8] = {foreground = Screen.colors.SeaGreen4}, [9] = {bold = true, foreground = Screen.colors.Blue1}, + [10] = {foreground = Screen.colors.Blue}, }) feed_data(':hi SpecialKey ctermfg=3 guifg=SeaGreen\n') @@ -1306,9 +1329,9 @@ describe('TUI', function() feed_data(':set termguicolors\n') screen:expect([[ {7:^}{8:G} | - {9:~ }| - {9:~ }| - {9:~ }| + {9:~}{10: }| + {9:~}{10: }| + {9:~}{10: }| {3:[No Name] [+] }| :set termguicolors | {4:-- TERMINAL --} | @@ -1327,9 +1350,8 @@ describe('TUI', function() end) it('forwards :term palette colors with termguicolors', function() - if os.getenv('GITHUB_ACTIONS') ~= nil then + if is_ci('github') then pending("tty-test complains about not owning the terminal -- actions/runner#241") - return end screen:set_rgb_cterm(true) screen:set_default_attr_ids({ @@ -1340,12 +1362,9 @@ describe('TUI', function() [5] = {{foreground = tonumber('0xff8000')}, {}}, }) - feed_data(':set statusline=^^^^^^^\n') - feed_data(':set termguicolors\n') - feed_data(':terminal '..testprg('tty-test')..'\n') - -- Depending on platform the above might or might not fit in the cmdline - -- so clear it for consistent behavior. - feed_data(':\027') + child_exec_lua('vim.o.statusline="^^^^^^^"') + child_exec_lua('vim.o.termguicolors=true') + child_exec_lua('vim.cmd.terminal(...)', testprg('tty-test')) screen:expect{grid=[[ {1:t}ty ready | | @@ -1384,7 +1403,7 @@ describe('TUI', function() | {4:~ }| {5: }| - [[['chan', 0], ['height', 6], ['override', v:false| + [[['chan', 1], ['height', 6], ['override', v:false| ], ['rgb', v:false], ['width', 50]]] | {10:Press ENTER or type command to continue}{1: } | {3:-- TERMINAL --} | @@ -1392,18 +1411,14 @@ describe('TUI', function() end) it('allows grid to assume wider ambiguous-width characters than host terminal #19686', function() - child_session:request('nvim_buf_set_lines', 0, 0, 0, true, { ('℃'):rep(60), ('℃'):rep(60) }) + child_session:request('nvim_buf_set_lines', 0, 0, -1, true, { ('℃'):rep(60), ('℃'):rep(60) }) child_session:request('nvim_win_set_option', 0, 'cursorline', true) child_session:request('nvim_win_set_option', 0, 'list', true) child_session:request('nvim_win_set_option', 0, 'listchars', 'eol:$') - local attrs = screen:get_default_attr_ids() - attrs[11] = {underline = true} -- CursorLine - attrs[12] = {underline = true, reverse = true} -- CursorLine and TermCursor - attrs[13] = {underline = true, foreground = 12} -- CursorLine and NonText feed_data('gg') local singlewidth_screen = [[ - {12:℃}{11:℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃}| - {11:℃℃℃℃℃℃℃℃℃℃}{13:$}{11: }| + {13:℃}{12:℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃}| + {12:℃℃℃℃℃℃℃℃℃℃}{15:$}{12: }| ℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃| ℃℃℃℃℃℃℃℃℃℃{4:$} | {5:[No Name] [+] }| @@ -1413,23 +1428,83 @@ describe('TUI', function() -- When grid assumes "℃" to be double-width but host terminal assumes it to be single-width, the -- second cell of "℃" is a space and the attributes of the "℃" are applied to it. local doublewidth_screen = [[ - {12:℃}{11: ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| - {11:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| - {11:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }{13:$}{11: }| + {13:℃}{12: ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| + {12:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| + {12:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }{15:$}{12: }| ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ >{4:@@@}| {5:[No Name] [+] }| | {3:-- TERMINAL --} | ]] - screen:expect(singlewidth_screen, attrs) + screen:expect(singlewidth_screen) child_session:request('nvim_set_option', 'ambiwidth', 'double') - screen:expect(doublewidth_screen, attrs) + screen:expect(doublewidth_screen) child_session:request('nvim_set_option', 'ambiwidth', 'single') - screen:expect(singlewidth_screen, attrs) + screen:expect(singlewidth_screen) child_session:request('nvim_call_function', 'setcellwidths', {{{0x2103, 0x2103, 2}}}) - screen:expect(doublewidth_screen, attrs) + screen:expect(doublewidth_screen) child_session:request('nvim_call_function', 'setcellwidths', {{{0x2103, 0x2103, 1}}}) - screen:expect(singlewidth_screen, attrs) + screen:expect(singlewidth_screen) + end) + + it('draws correctly when cursor_address overflows #21643', function() + helpers.skip(helpers.is_os('mac'), 'FIXME: crashes/errors on macOS') + screen:try_resize(77, 834) + retry(nil, nil, function() + eq({true, 831}, {child_session:request('nvim_win_get_height', 0)}) + end) + -- Use full screen message so that redrawing afterwards is more deterministic. + child_session:notify('nvim_command', 'intro') + screen:expect({any = 'Nvim'}) + -- Going to top-left corner needs 3 bytes. + -- Setting underline attribute needs 9 bytes. + -- With screen width 77, 63857 characters need 829 full screen lines. + -- Drawing each full screen line needs 77 + 2 = 79 bytes (2 bytes for CR LF). + -- The incomplete screen line needs 24 + 3 = 27 bytes. + -- The whole line needs 3 + 9 + 79 * 829 + 27 = 65530 bytes. + -- The cursor_address that comes after will overflow the 65535-byte buffer. + local line = ('a'):rep(63857) .. '℃' + child_session:notify('nvim_exec_lua', [[ + vim.api.nvim_buf_set_lines(0, 0, -1, true, {...}) + vim.o.cursorline = true + ]], {line, 'b'}) + -- Close the :intro message and redraw the lines. + feed_data('\n') + screen:expect( + '{13:a}{12:' .. ('a'):rep(76) .. '}|\n' + .. ('{12:' .. ('a'):rep(77) .. '}|\n'):rep(828) + .. '{12:' .. ('a'):rep(24) .. '℃' .. (' '):rep(52) .. '}|\n' .. dedent([[ + b | + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} |]])) + end) + + it('visual bell (padding) does not crash #21610', function() + feed_data ':set visualbell\n' + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + :set visualbell | + {3:-- TERMINAL --} | + ]]} + + -- move left is enough to invoke the bell + feed_data 'h' + -- visual change to show we process events after this + feed_data 'i' + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} end) end) @@ -1439,6 +1514,35 @@ describe('TUI', function() os.remove('testF') end) + it('resize at startup #17285 #15044 #11330', function() + local screen = Screen.new(50, 10) + screen:set_default_attr_ids({ + [1] = {reverse = true}, + [2] = {bold = true, foreground = Screen.colors.Blue}, + [3] = {bold = true}, + [4] = {foreground = tonumber('0x4040ff'), fg_indexed = true}, + [5] = {bold = true, reverse = true}, + }) + screen:attach() + exec([[ + call termopen([v:progpath, '--clean', '--cmd', 'let start = reltime() | while v:true | if reltimefloat(reltime(start)) > 2 | break | endif | endwhile']) + sleep 500m + vs new + ]]) + screen:expect([[ + ^ │ | + {2:~ }│{4:~ }| + {2:~ }│{4:~ }| + {2:~ }│{4:~ }| + {2:~ }│{4:~ }| + {2:~ }│{4:~ }| + {2:~ }│{5:[No Name] 0,0-1 All}| + {2:~ }│ | + {5:new }{MATCH:<.*[/\]nvim }| + | + ]]) + end) + it('with non-tty (pipe) stdout/stderr', function() local screen = thelpers.screen_setup(0, '"'..nvim_prog ..' -u NONE -i NONE --cmd \'set noswapfile noshowcmd noruler\' --cmd \'normal iabc\' > /dev/null 2>&1 && cat testF && rm testF"') @@ -1457,6 +1561,15 @@ describe('TUI', function() it('<C-h> #10134', function() local screen = thelpers.screen_setup(0, '["'..nvim_prog ..[[", "-u", "NONE", "-i", "NONE", "--cmd", "set noruler", "--cmd", ':nnoremap <C-h> :echomsg "\<C-h\>"<CR>']]..']') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]]} command([[call chansend(b:terminal_job_id, "\<C-h>")]]) screen:expect([[ @@ -1483,6 +1596,15 @@ describe('TUI UIEnter/UILeave', function() ..[[, "--cmd", "autocmd VimEnter * :call add(g:evs, 'VimEnter')"]] ..']' ) + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]]} feed_data(":echo g:evs\n") screen:expect{grid=[[ {1: } | @@ -1503,61 +1625,88 @@ describe('TUI FocusGained/FocusLost', function() clear() screen = thelpers.screen_setup(0, '["'..nvim_prog ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile noshowcmd noruler"]') - feed_data(":autocmd FocusGained * echo 'gained'\n") - feed_data(":autocmd FocusLost * echo 'lost'\n") - feed_data("\034\016") -- CTRL-\ CTRL-N - end) - - it('in normal-mode', function() - retry(2, 3 * screen.timeout, function() - feed_data('\027[I') - screen:expect([[ + screen:expect{grid=[[ {1: } | {4:~ }| {4:~ }| {4:~ }| {5:[No Name] }| - gained | + | {3:-- TERMINAL --} | - ]]) + ]]} + feed_data(":autocmd FocusGained * echo 'gained'\n") + feed_data(":autocmd FocusLost * echo 'lost'\n") + feed_data("\034\016") -- CTRL-\ CTRL-N + end) - feed_data('\027[O') - screen:expect([[ + it('in normal-mode', function() + screen:expect{grid=[[ {1: } | {4:~ }| {4:~ }| {4:~ }| {5:[No Name] }| - lost | + :autocmd FocusLost * echo 'lost' | {3:-- TERMINAL --} | - ]]) + ]]} + retry(2, 3 * screen.timeout, function() + feed_data('\027[I') + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + gained | + {3:-- TERMINAL --} | + ]]) + + feed_data('\027[O') + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + lost | + {3:-- TERMINAL --} | + ]]) end) end) it('in insert-mode', function() feed_command('set noshowmode') feed_data('i') - retry(2, 3 * screen.timeout, function() - feed_data('\027[I') - screen:expect([[ - {1: } | - {4:~ }| - {4:~ }| - {4:~ }| - {5:[No Name] }| - gained | - {3:-- TERMINAL --} | - ]]) - feed_data('\027[O') - screen:expect([[ + screen:expect{grid=[[ {1: } | {4:~ }| {4:~ }| {4:~ }| {5:[No Name] }| - lost | + :set noshowmode | {3:-- TERMINAL --} | - ]]) + ]]} + retry(2, 3 * screen.timeout, function() + feed_data('\027[I') + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + gained | + {3:-- TERMINAL --} | + ]]) + feed_data('\027[O') + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + lost | + {3:-- TERMINAL --} | + ]]) end) end) @@ -1594,6 +1743,15 @@ describe('TUI FocusGained/FocusLost', function() feed_data(":autocmd!\n") feed_data(":autocmd FocusLost * call append(line('$'), 'lost')\n") feed_data(":autocmd FocusGained * call append(line('$'), 'gained')\n") + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]]} retry(2, 3 * screen.timeout, function() -- Enter cmdline-mode. feed_data(':') @@ -1652,9 +1810,18 @@ describe('TUI FocusGained/FocusLost', function() feed_data(":echom 'msg1'|echom 'msg2'|echom 'msg3'|echom 'msg4'|echom 'msg5'\n") -- Execute :messages to provoke the press-enter prompt. feed_data(":messages\n") + screen:expect{grid=[[ + msg1 | + msg2 | + msg3 | + msg4 | + msg5 | + {10:Press ENTER or type command to continue}{1: } | + {3:-- TERMINAL --} | + ]]} feed_data('\027[I') feed_data('\027[I') - screen:expect([[ + screen:expect{grid=[[ msg1 | msg2 | msg3 | @@ -1662,7 +1829,7 @@ describe('TUI FocusGained/FocusLost', function() msg5 | {10:Press ENTER or type command to continue}{1: } | {3:-- TERMINAL --} | - ]]) + ]], unchanged=true} end) end) @@ -1670,7 +1837,6 @@ end) -- does not initialize the TUI. describe("TUI 't_Co' (terminal colors)", function() local screen - local is_freebsd = (uname() == 'freebsd') local function assert_term_colors(term, colorterm, maxcolors) helpers.clear({env={TERM=term}, args={}}) @@ -1773,7 +1939,7 @@ describe("TUI 't_Co' (terminal colors)", function() -- which is raised to 16 by COLORTERM. it("TERM=screen no COLORTERM uses 8/256 colors", function() - if is_freebsd then + if is_os('freebsd') then assert_term_colors("screen", nil, 256) else assert_term_colors("screen", nil, 8) @@ -1781,7 +1947,7 @@ describe("TUI 't_Co' (terminal colors)", function() end) it("TERM=screen COLORTERM=screen uses 16/256 colors", function() - if is_freebsd then + if is_os('freebsd') then assert_term_colors("screen", "screen", 256) else assert_term_colors("screen", "screen", 16) @@ -1944,8 +2110,6 @@ end) -- does not initialize the TUI. describe("TUI 'term' option", function() local screen - local is_bsd = not not string.find(uname(), 'bsd') - local is_macos = not not string.find(uname(), 'darwin') local function assert_term(term_envvar, term_expected) clear() @@ -1971,11 +2135,11 @@ describe("TUI 'term' option", function() end) it('gets system-provided term if $TERM is valid', function() - if uname() == "openbsd" then + if is_os('openbsd') then assert_term("xterm", "xterm") - elseif is_bsd then -- BSD lacks terminfo, builtin is always used. + elseif is_os('bsd') then -- BSD lacks terminfo, builtin is always used. assert_term("xterm", "builtin_xterm") - elseif is_macos then + elseif is_os('mac') then local status, _ = pcall(assert_term, "xterm", "xterm") if not status then pending("macOS: unibilium could not find terminfo") @@ -2033,7 +2197,7 @@ describe("TUI", function() retry(nil, 3000, function() -- Wait for log file to be flushed. local log = read_file('Xtest_tui_verbose_log') or '' - eq('--- Terminal info --- {{{\n', string.match(log, '%-%-%- Terminal.-\n')) + eq('--- Terminal info --- {{{\n', string.match(log, '%-%-%- Terminal.-\n')) -- }}} ok(#log > 50) end) end) @@ -2157,3 +2321,163 @@ describe('TUI bg color', function() screen:expect{any='new_bg=dark'} end) end) + +-- These tests require `thelpers` because --headless/--embed +-- does not initialize the TUI. +describe("TUI as a client", function() + + it("connects to remote instance (with its own TUI)", function() + local server_super = spawn_argv(false) -- equivalent to clear() + local client_super = spawn_argv(true) + + set_session(server_super) + local server_pipe = new_pipename() + local screen_server = thelpers.screen_setup(0, + string.format([=[["%s", "--listen", "%s", "-u", "NONE", "-i", "NONE", "--cmd", "%s laststatus=2 background=dark"]]=], + nvim_prog, server_pipe, nvim_set)) + + feed_data("iHello, World") + screen_server:expect{grid=[[ + Hello, World{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} + feed_data("\027") + screen_server:expect{grid=[[ + Hello, Worl{1:d} | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + + set_session(client_super) + local screen_client = thelpers.screen_setup(0, + string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--server", "%s", "--remote-ui"]]=], + nvim_prog, server_pipe)) + + screen_client:expect{grid=[[ + Hello, Worl{1:d} | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + + feed_data(":q!\n") + + server_super:close() + client_super:close() + end) + + it("connects to remote instance (--headless)", function() + local server = helpers.spawn_argv(false) -- equivalent to clear() + local client_super = spawn_argv(true) + + set_session(server) + local server_pipe = eval'v:servername' + feed'iHalloj!<esc>' + + set_session(client_super) + local screen = thelpers.screen_setup(0, + string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--server", "%s", "--remote-ui"]]=], + nvim_prog, server_pipe)) + + screen:expect{grid=[[ + Halloj{1:!} | + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + | + {3:-- TERMINAL --} | + ]]} + + client_super:close() + server:close() + end) + + + it("throws error when no server exists", function() + clear() + local screen = thelpers.screen_setup(0, + string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--server", "127.0.0.1:2436546", "--remote-ui"]]=], + nvim_prog), 60) + + screen:expect([[ + Remote ui failed to start: {MATCH:.*}| + | + [Process exited 1]{1: } | + | + | + | + {3:-- TERMINAL --} | + ]]) + end) + + it("exits when server quits", function() + local server_super = spawn_argv(false) -- equivalent to clear() + local client_super = spawn_argv(true) + + set_session(server_super) + local server_pipe = new_pipename() + local screen_server = thelpers.screen_setup(0, + string.format([=[["%s", "--listen", "%s", "-u", "NONE", "-i", "NONE", "--cmd", "%s laststatus=2 background=dark"]]=], + nvim_prog, server_pipe, nvim_set)) + + feed_data("iHello, World") + screen_server:expect{grid=[[ + Hello, World{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} + feed_data("\027") + screen_server:expect{grid=[[ + Hello, Worl{1:d} | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + + set_session(client_super) + local screen_client = thelpers.screen_setup(0, + string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--server", "%s", "--remote-ui"]]=], + nvim_prog, server_pipe)) + + screen_client:expect{grid=[[ + Hello, Worl{1:d} | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + + -- quitting the server + set_session(server_super) + feed_data(":q!\n") + screen_server:expect({any="Process exited 0"}) + + -- assert that client has exited + screen_client:expect({any="Process exited 0"}) + + server_super:close() + client_super:close() + end) +end) diff --git a/test/functional/terminal/window_spec.lua b/test/functional/terminal/window_spec.lua index 0d3295cf32..80e9d78400 100644 --- a/test/functional/terminal/window_spec.lua +++ b/test/functional/terminal/window_spec.lua @@ -3,11 +3,12 @@ local thelpers = require('test.functional.terminal.helpers') local feed_data = thelpers.feed_data local feed, clear = helpers.feed, helpers.clear local poke_eventloop = helpers.poke_eventloop -local iswin = helpers.iswin local command = helpers.command local retry = helpers.retry local eq = helpers.eq local eval = helpers.eval +local skip = helpers.skip +local is_os = helpers.is_os describe(':terminal window', function() local screen @@ -18,7 +19,7 @@ describe(':terminal window', function() end) it('sets topline correctly #8556', function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) -- Test has hardcoded assumptions of dimensions. eq(7, eval('&lines')) feed_data('\n\n\n') -- Add blank lines. @@ -54,9 +55,7 @@ describe(':terminal window', function() {3:-- TERMINAL --} | ]]) - if iswin() then - return -- win: :terminal resize is unreliable #7007 - end + skip(is_os('win'), 'win: :terminal resize is unreliable #7007') -- numberwidth=9 feed([[<C-\><C-N>]]) @@ -172,7 +171,7 @@ describe(':terminal with multigrid', function() ]]) screen:try_resize_grid(2, 20, 10) - if iswin() then + if is_os('win') then screen:expect{any="rows: 10, cols: 20"} else screen:expect([[ @@ -201,7 +200,7 @@ describe(':terminal with multigrid', function() end screen:try_resize_grid(2, 70, 3) - if iswin() then + if is_os('win') then screen:expect{any="rows: 3, cols: 70"} else screen:expect([[ @@ -223,7 +222,7 @@ describe(':terminal with multigrid', function() end screen:try_resize_grid(2, 0, 0) - if iswin() then + if is_os('win') then screen:expect{any="rows: 6, cols: 50"} else screen:expect([[ diff --git a/test/functional/terminal/window_split_tab_spec.lua b/test/functional/terminal/window_split_tab_spec.lua index b62d173cea..1d77e1e92e 100644 --- a/test/functional/terminal/window_split_tab_spec.lua +++ b/test/functional/terminal/window_split_tab_spec.lua @@ -8,9 +8,9 @@ local command = helpers.command local eq = helpers.eq local eval = helpers.eval local meths = helpers.meths -local iswin = helpers.iswin local sleep = helpers.sleep local retry = helpers.retry +local is_os = helpers.is_os describe(':terminal', function() local screen @@ -96,7 +96,7 @@ describe(':terminal', function() local w1, h1 = screen._width - 3, screen._height - 2 local w2, h2 = w1 - 6, h1 - 3 - if iswin() then + if is_os('win') then -- win: SIGWINCH is unreliable, use a weaker test. #7506 retry(3, 30000, function() screen:try_resize(w1, h1) diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua index ae3f42ff0a..2a2311c0fa 100644 --- a/test/functional/treesitter/highlight_spec.lua +++ b/test/functional/treesitter/highlight_spec.lua @@ -605,8 +605,8 @@ describe('treesitter highlighting', function() }} eq({ - {capture='Error', metadata = { priority='101' }}; - {capture='type', metadata = { } }; + {capture='Error', metadata = { priority='101' }, lang='c' }; + {capture='type', metadata = { }, lang='c' }; }, exec_lua [[ return vim.treesitter.get_captures_at_pos(0, 0, 2) ]]) end) diff --git a/test/functional/treesitter/language_spec.lua b/test/functional/treesitter/language_spec.lua index ed84dedb5a..f95b05a1cc 100644 --- a/test/functional/treesitter/language_spec.lua +++ b/test/functional/treesitter/language_spec.lua @@ -13,21 +13,21 @@ before_each(clear) describe('treesitter language API', function() -- error tests not requiring a parser library it('handles missing language', function() - eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", + eq(".../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", pcall_err(exec_lua, "parser = vim.treesitter.get_parser(0, 'borklang')")) -- actual message depends on platform - matches("Error executing lua: Failed to load parser: uv_dlopen: .+", + matches("Failed to load parser for language 'borklang': uv_dlopen: .+", pcall_err(exec_lua, "parser = vim.treesitter.require_language('borklang', 'borkbork.so')")) -- Should not throw an error when silent eq(false, exec_lua("return vim.treesitter.require_language('borklang', nil, true)")) eq(false, exec_lua("return vim.treesitter.require_language('borklang', 'borkbork.so', true)")) - eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", + eq(".../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", pcall_err(exec_lua, "parser = vim.treesitter.inspect_language('borklang')")) - matches("Error executing lua: Failed to load parser: uv_dlsym: .+", + matches("Failed to load parser: uv_dlsym: .+", pcall_err(exec_lua, 'vim.treesitter.require_language("c", nil, false, "borklang")')) end) @@ -76,7 +76,7 @@ describe('treesitter language API', function() eq('c', exec_lua("return vim.treesitter.get_parser(0):lang()")) command("set filetype=borklang") -- Should throw an error when filetype changes to borklang - eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", + eq(".../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", pcall_err(exec_lua, "new_parser = vim.treesitter.get_parser(0)")) end) diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index ccbd55df0e..f006ad4539 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -5,6 +5,8 @@ local eq = helpers.eq local insert = helpers.insert local exec_lua = helpers.exec_lua local feed = helpers.feed +local is_os = helpers.is_os +local skip = helpers.skip before_each(clear) @@ -12,8 +14,6 @@ describe('treesitter parser API', function() clear() it('parses buffer', function() - if helpers.pending_win32(pending) then return end - insert([[ int main() { int x = 3; @@ -684,7 +684,7 @@ int x = INT_MAX; end) it("should not inject bad languages", function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) exec_lua([=[ vim.treesitter.add_directive("inject-bad!", function(match, _, _, pred, metadata) metadata.language = "{" diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua index fa5771a8b3..eb5de693bd 100644 --- a/test/functional/ui/cmdline_highlight_spec.lua +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -61,7 +61,7 @@ before_each(function() endwhile return ret endfunction - function SplittedMultibyteStart(cmdline) + function SplitMultibyteStart(cmdline) let ret = [] let i = 0 while i < len(a:cmdline) @@ -77,7 +77,7 @@ before_each(function() endwhile return ret endfunction - function SplittedMultibyteEnd(cmdline) + function SplitMultibyteEnd(cmdline) let ret = [] let i = 0 while i < len(a:cmdline) @@ -296,7 +296,7 @@ describe('Command-line coloring', function() end it('does the right thing when hl start appears to split multibyte char', function() - set_color_cb('SplittedMultibyteStart') + set_color_cb('SplitMultibyteStart') start_prompt('echo "«') screen:expect{grid=[[ | @@ -322,7 +322,7 @@ describe('Command-line coloring', function() end) it('does the right thing when hl end appears to split multibyte char', function() - set_color_cb('SplittedMultibyteEnd') + set_color_cb('SplitMultibyteEnd') start_prompt('echo "«') screen:expect([[ | @@ -335,17 +335,17 @@ describe('Command-line coloring', function() :echo "«^ | ]]) end) - it('does the right thing when errorring', function() + it('does the right thing when erroring', function() set_color_cb('Echoerring') start_prompt('e') screen:expect([[ | {EOB:~ }| - {EOB:~ }| {MSEP: }| : | {ERR:E5407: Callback has thrown an exception:}| - {ERR: Vim(echoerr):HERE} | + {ERR: function DoPrompt[3]..Echoerring, line }| + {ERR:1: Vim(echoerr):HERE} | :e^ | ]]) end) @@ -400,16 +400,16 @@ describe('Command-line coloring', function() screen:expect([[ | {EOB:~ }| - {EOB:~ }| {MSEP: }| : | {ERR:E5407: Callback has thrown an exception:}| + {ERR: function DoPrompt[3]..Throwing, line 1:}| {ERR: ABC} | :e^ | ]]) end) it('stops executing callback after a number of errors', function() - set_color_cb('SplittedMultibyteStart') + set_color_cb('SplitMultibyteStart') start_prompt('let x = "«»«»«»«»«»"') screen:expect([[ | @@ -772,7 +772,7 @@ describe('Ex commands coloring', function() ]]) end) it('still executes command-line even if errored out', function() - meths.set_var('Nvim_color_cmdline', 'SplittedMultibyteStart') + meths.set_var('Nvim_color_cmdline', 'SplitMultibyteStart') feed(':let x = "«"\n') eq('«', meths.get_var('x')) local msg = 'E5405: Chunk 0 start 10 splits multibyte character' diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index 8003947078..1c9ac7f7ba 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -4,10 +4,12 @@ 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 poke_eventloop = helpers.poke_eventloop local exec = helpers.exec local eval = helpers.eval local eq = helpers.eq +local is_os = helpers.is_os +local meths = helpers.meths local function new_screen(opt) local screen = Screen.new(25, 5) @@ -717,7 +719,7 @@ describe('cmdline redraw', function() end) it('with <Cmd>', function() - if string.find(uname(), 'bsd') then + if is_os('bsd') then pending('FIXME #10804') end command('cmap a <Cmd>call sin(0)<CR>') -- no-op @@ -1351,4 +1353,57 @@ describe('cmdheight=0', function() {1:~ }│{1:~ }| ]]) end) + + it('no assert failure with showcmd', function() + command('set showcmd cmdheight=0') + feed('d') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]]) + assert_alive() + end) + + it('can only be resized to 0 if set explicitly', function() + command('set laststatus=2') + command('resize +1') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {2:[No Name] }| + | + ]]) + command('set cmdheight=0') + command('resize -1') + command('resize +1') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {2:[No Name] }| + ]]) + end) + + it("cannot be resized at all with external messages", function() + clear() + screen = new_screen({rgb=true, ext_messages=true}) + command('set laststatus=2 mouse=a') + command('resize -1') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {3:[No Name] }| + ]]) + meths.input_mouse('left', 'press', '', 0, 6, 10) + poke_eventloop() + meths.input_mouse('left', 'drag', '', 0, 5, 10) + screen:expect_unchanged() + end) end) diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index 03cd4bfd06..e261f0dfab 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -215,7 +215,7 @@ describe('ui/cursor', function() m.hl_id = 60 m.attr = {background = Screen.colors.DarkGray} end - if m.id_lm then m.id_lm = 61 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 0a5eefbf38..489c33d8b1 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -176,7 +176,13 @@ describe('decorations providers', function() beamtrace = {} local function on_do(kind, ...) if kind == 'win' or kind == 'spell' then - a.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 2, end_col = 23, spell = true, ephemeral = true }) + a.nvim_buf_set_extmark(0, ns, 0, 0, { + end_row = 2, + end_col = 23, + spell = true, + priority = 20, + ephemeral = true + }) end table.insert(beamtrace, {kind, ...}) end @@ -234,6 +240,36 @@ describe('decorations providers', function() {1:~ }| | ]]} + + -- spell=false with lower priority doesn't disable spell + local ns = meths.create_namespace "spell" + local id = helpers.curbufmeths.set_extmark(ns, 0, 0, { priority = 30, end_row = 2, end_col = 23, spell = false }) + + screen:expect{grid=[[ + I am well written text. | + i am not capitalized. | + I am a ^speling mistakke. | + | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + -- spell=false with higher priority does disable spell + helpers.curbufmeths.set_extmark(ns, 0, 0, { id = id, priority = 10, end_row = 2, end_col = 23, spell = false }) + + screen:expect{grid=[[ + I am well written text. | + {15:i} am not capitalized. | + I am a {16:^speling} {16:mistakke}. | + | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end) it('can predefine highlights', function() @@ -564,6 +600,7 @@ describe('extmark decorations', function() [24] = {bold = true}; [25] = {background = Screen.colors.LightRed}; [26] = {background=Screen.colors.DarkGrey, foreground=Screen.colors.LightGrey}; + [27] = {background = Screen.colors.Plum1}; } ns = meths.create_namespace 'test' @@ -959,6 +996,55 @@ end]] | ]]) end) + + it('avoids redraw issue #20651', function() + exec_lua[[ + vim.cmd.normal'10oXXX' + vim.cmd.normal'gg' + local ns = vim.api.nvim_create_namespace('ns') + + local bufnr = vim.api.nvim_create_buf(false, true) + vim.api.nvim_open_win(bufnr, false, { relative = 'win', height = 1, width = 1, row = 0, col = 0 }) + + vim.api.nvim_create_autocmd('CursorMoved', { callback = function() + local row = vim.api.nvim_win_get_cursor(0)[1] - 1 + vim.api.nvim_buf_set_extmark(0, ns, row, 0, { id = 1 }) + vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {}) + vim.schedule(function() + vim.api.nvim_buf_set_extmark(0, ns, row, 0, { + id = 1, + virt_text = {{'HELLO', 'Normal'}}, + }) + end) + end + }) + ]] + + for _ = 1, 3 do + helpers.sleep(10) + feed 'j' + end + + screen:expect{grid=[[ + {27: } | + XXX | + XXX | + ^XXX HELLO | + XXX | + XXX | + XXX | + XXX | + XXX | + XXX | + XXX | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + end) + end) describe('decorations: virtual lines', function() @@ -1065,7 +1151,7 @@ if (h->n_buckets < new_n_buckets) { // expand ]]} meths.buf_set_extmark(0, ns, 5, 0, { - virt_lines = { {{"^^ REVIEW:", "Todo"}, {" new_vals variable seems unneccesary?", "Comment"}} }; + virt_lines = { {{"^^ REVIEW:", "Todo"}, {" new_vals variable seems unnecessary?", "Comment"}} }; }) -- TODO: what about the cursor?? screen:expect{grid=[[ @@ -1078,7 +1164,7 @@ if (h->n_buckets < new_n_buckets) { // expand if (kh_is_map && val_size) { | ^char *new_vals = {3:krealloc}( h->vals_buf, new_n_| buckets * val_size); | - {5:^^ REVIEW:}{6: new_vals variable seems unneccesary?} | + {5:^^ REVIEW:}{6: new_vals variable seems unnecessary?} | h->vals_buf = new_vals; | | ]]} @@ -2085,4 +2171,39 @@ describe('decorations: virt_text', function() ]]} end) + it('redraws correctly when re-using extmark ids', function() + command 'normal 5ohello' + + screen:expect{grid=[[ + | + hello | + hello | + hello | + hello | + hell^o | + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]} + + local ns = meths.create_namespace('ns') + for row = 1, 5 do + meths.buf_set_extmark(0, ns, row, 0, { id = 1, virt_text = {{'world', 'Normal'}} }) + end + + screen:expect{grid=[[ + | + hello | + hello | + hello | + hello | + hell^o world | + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]} + end) + end) diff --git a/test/functional/ui/diff_spec.lua b/test/functional/ui/diff_spec.lua index 36dc5addcd..dbdf3823ec 100644 --- a/test/functional/ui/diff_spec.lua +++ b/test/functional/ui/diff_spec.lua @@ -8,6 +8,8 @@ local insert = helpers.insert local write_file = helpers.write_file local dedent = helpers.dedent local exec = helpers.exec +local eq = helpers.eq +local meths = helpers.meths describe('Diff mode screen', function() local fname = 'Xtest-functional-diff-screen-1' @@ -1073,6 +1075,182 @@ int main(int argc, char **argv) :e | ]]) end) + + describe('line matching diff algorithm', function() + setup(function() + local f1 = [[if __name__ == "__main__": + import sys + app = QWidgets.QApplication(sys.args) + MainWindow = QtWidgets.QMainWindow() + ui = UI_MainWindow() + ui.setupUI(MainWindow) + MainWindow.show() + sys.exit(app.exec_())]] + write_file(fname, f1, false) + local f2 = [[if __name__ == "__main__": + import sys + comment these things + #app = QWidgets.QApplication(sys.args) + #MainWindow = QtWidgets.QMainWindow() + add a completely different line here + #ui = UI_MainWindow() + add another new line + ui.setupUI(MainWindow) + MainWindow.show() + sys.exit(app.exec_())]] + write_file(fname_2, f2, false) + end) + + it('diffopt+=linematch:20', function() + reread() + feed(':set diffopt=internal,filler<cr>') + screen:expect([[ + {1: }^if __name__ == "__│{1: }if __name__ == "_| + {1: } import sys │{1: } import sys | + {1: }{9: }{8:app = QWidgets}│{1: }{9: }{8:comment these}| + {1: }{9: }{8:MainWindow = Q}│{1: }{9: }{8:#app = QWidge}| + {1: }{9: }{8:ui = UI_}{9:MainWi}│{1: }{9: }{8:#MainWindow =}| + {1: }{2:------------------}│{1: }{4: add a complet}| + {1: }{2:------------------}│{1: }{4: #ui = UI_Main}| + {1: }{2:------------------}│{1: }{4: add another n}| + {1: } ui.setupUI(Mai│{1: } ui.setupUI(Ma| + {1: } MainWindow.sho│{1: } MainWindow.sh| + {1: } sys.exit(app.e│{1: } sys.exit(app.| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| + :set diffopt=internal,filler | + ]]) + + feed('G') + feed(':set diffopt+=linematch:20<cr>') + screen:expect([[ + {1: }if __name__ == "__│{1: }if __name__ == "_| + {1: } import sys │{1: } import sys | + {1: }{2:------------------}│{1: }{4: comment these}| + {1: }{9: app = QWidgets}│{1: }{9: }{8:#}{9:app = QWidge}| + {1: }{9: MainWindow = Q}│{1: }{9: }{8:#}{9:MainWindow =}| + {1: }{2:------------------}│{1: }{4: add a complet}| + {1: }{9: ui = UI_MainWi}│{1: }{9: }{8:#}{9:ui = UI_Main}| + {1: }{2:------------------}│{1: }{4: add another n}| + {1: } ui.setupUI(Mai│{1: } ui.setupUI(Ma| + {1: } MainWindow.sho│{1: } MainWindow.sh| + {1: } ^sys.exit(app.e│{1: } sys.exit(app.| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| + :set diffopt+=linematch:20 | + ]]) + end) + end) + + describe('line matching diff algorithm with icase', function() + setup(function() + local f1 = [[DDD +_aa]] + write_file(fname, f1, false) + local f2 = [[DDD +AAA +ccca]] + write_file(fname_2, f2, false) + end) + it('diffopt+=linematch:20,icase', function() + reread() + feed(':set diffopt=internal,filler,linematch:20<cr>') + screen:expect([[ + {1: }^DDD │{1: }DDD | + {1: }{2:------------------}│{1: }{4:AAA }| + {1: }{8:_a}{9:a }│{1: }{8:ccc}{9:a }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| + | + ]]) + feed(':set diffopt+=icase<cr>') + screen:expect([[ + {1: }^DDD │{1: }DDD | + {1: }{8:_}{9:aa }│{1: }{8:A}{9:AA }| + {1: }{2:------------------}│{1: }{4:ccca }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| + :set diffopt+=icase | + ]]) + end) + end) + + describe('line matching diff algorithm with iwhiteall', function() + setup(function() + local f1 = [[BB + AAA]] + write_file(fname, f1, false) + local f2 = [[BB + AAB +AAAB]] + write_file(fname_2, f2, false) + end) + it('diffopt+=linematch:20,iwhiteall', function() + reread() + feed(':set diffopt=internal,filler,linematch:20<cr>') + screen:expect{grid=[[ + {1: }^BB │{1: }BB | + {1: }{9: AA}{8:A}{9: }│{1: }{9: AA}{8:B}{9: }| + {1: }{2:------------------}│{1: }{4:AAAB }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| + | + ]]} + feed(':set diffopt+=iwhiteall<cr>') + screen:expect{grid=[[ + {1: }^BB │{1: }BB | + {1: }{2:------------------}│{1: }{4: AAB }| + {1: }{9: AAA }│{1: }{9:AAA}{8:B}{9: }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| + :set diffopt+=iwhiteall | + ]]} + end) + end) end) it('win_update redraws lines properly', function() @@ -1315,6 +1493,26 @@ it('Align the filler lines when changing text in diff mode', function() ]]} end) +it("diff mode doesn't restore invalid 'foldcolumn' value #21647", function() + clear() + local screen = Screen.new(60, 6) + screen:set_default_attr_ids({ + [0] = {foreground = Screen.colors.Blue, bold = true}; + }) + screen:attach() + eq('0', meths.get_option_value('foldcolumn', {})) + command('diffsplit | bd') + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + eq('0', meths.get_option_value('foldcolumn', {})) +end) + -- oldtest: Test_diff_binary() it('diff mode works properly if file contains NUL bytes vim-patch:8.2.3925', function() clear() diff --git a/test/functional/ui/embed_spec.lua b/test/functional/ui/embed_spec.lua index e7addd1b85..cd2b48213d 100644 --- a/test/functional/ui/embed_spec.lua +++ b/test/functional/ui/embed_spec.lua @@ -51,7 +51,7 @@ local function test_embed(ext_linegrid) end) it("doesn't erase output when setting color scheme", function() - if 'openbsd' == helpers.uname() then + if helpers.is_os('openbsd') then pending('FIXME #10804') end startup('--cmd', 'echoerr "foo"', '--cmd', 'color default', '--cmd', 'echoerr "bar"') diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 1a9a13f7d4..6759510ad1 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -25,36 +25,6 @@ describe('float window', function() clear() command('hi VertSplit gui=reverse') end) - local attrs = { - [0] = {bold=true, foreground=Screen.colors.Blue}, - [1] = {background = Screen.colors.LightMagenta}, - [2] = {background = Screen.colors.LightMagenta, bold = true, foreground = Screen.colors.Blue1}, - [3] = {bold = true}, - [4] = {bold = true, reverse = true}, - [5] = {reverse = true}, - [6] = {background = Screen.colors.LightMagenta, bold = true, reverse = true}, - [7] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, - [8] = {bold = true, foreground = Screen.colors.SeaGreen4}, - [9] = {background = Screen.colors.LightGrey, underline = true}, - [10] = {background = Screen.colors.LightGrey, underline = true, bold = true, foreground = Screen.colors.Magenta}, - [11] = {bold = true, foreground = Screen.colors.Magenta}, - [12] = {background = Screen.colors.Red, bold = true, foreground = Screen.colors.Blue1}, - [13] = {background = Screen.colors.WebGray}, - [14] = {foreground = Screen.colors.Brown}, - [15] = {background = Screen.colors.Grey20}, - [16] = {background = Screen.colors.Grey20, bold = true, foreground = Screen.colors.Blue1}, - [17] = {background = Screen.colors.Yellow}, - [18] = {foreground = Screen.colors.Brown, background = Screen.colors.Grey20}, - [19] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray}, - [20] = {bold = true, foreground = Screen.colors.Brown}, - [21] = {background = Screen.colors.Gray90}, - [22] = {background = Screen.colors.LightRed}, - [23] = {foreground = Screen.colors.Black, background = Screen.colors.White}; - [24] = {foreground = Screen.colors.Black, background = Screen.colors.Grey80}; - [25] = {blend = 100, background = Screen.colors.Gray0}; - [26] = {blend = 80, background = Screen.colors.Gray0}; - [27] = {background = Screen.colors.LightGray}; - } it('behavior', function() -- Create three windows and test that ":wincmd <direction>" changes to the @@ -93,7 +63,7 @@ describe('float window', function() end) it('closed immediately by autocmd #11383', function() - eq('Error executing lua: [string "<nvim>"]:0: Window was closed immediately', + eq('Window was closed immediately', pcall_err(exec_lua, [[ local a = vim.api local function crashes(contents) @@ -118,7 +88,7 @@ describe('float window', function() end) it('closed immediately by autocmd after win_enter #15548', function() - eq('Error executing lua: [string "<nvim>"]:0: Window was closed immediately', + eq('Window was closed immediately', pcall_err(exec_lua, [[ vim.cmd "autocmd BufLeave * ++once quit!" local buf = vim.api.nvim_create_buf(true, true) @@ -198,6 +168,29 @@ describe('float window', function() eq(7, pos[2]) end) + it('opened with correct position relative to the mouse', function() + meths.input_mouse('left', 'press', '', 0, 10, 10) + local pos = exec_lua([[ + local bufnr = vim.api.nvim_create_buf(false, true) + + local opts = { + width = 10, + height = 10, + col = 1, + row = 2, + relative = 'mouse', + style = 'minimal' + } + + local win_id = vim.api.nvim_open_win(bufnr, false, opts) + + return vim.api.nvim_win_get_position(win_id) + ]]) + + eq(12, pos[1]) + eq(11, pos[2]) + end) + it('opened with correct position relative to the cursor', function() local pos = exec_lua([[ local bufnr = vim.api.nvim_create_buf(false, true) @@ -421,6 +414,15 @@ describe('float window', function() eq(winids, eval('winids')) end) + it('no crash with bufpos and non-existent window', function() + command('new') + local closed_win = meths.get_current_win().id + command('close') + local buf = meths.create_buf(false,false) + meths.open_win(buf, true, {relative='win', win=closed_win, width=1, height=1, bufpos={0,0}}) + assert_alive() + end) + it("no segfault when setting minimal style after clearing local 'fillchars' #19510", function() local float_opts = {relative = 'editor', row = 1, col = 1, width = 1, height = 1} local float_win = meths.open_win(0, true, float_opts) @@ -430,6 +432,13 @@ describe('float window', function() assert_alive() end) + it("'scroll' is computed correctly when opening float with splitkeep=screen #20684", function() + meths.set_option('splitkeep', 'screen') + local float_opts = {relative = 'editor', row = 1, col = 1, width = 10, height = 10} + local float_win = meths.open_win(0, true, float_opts) + eq(5, meths.win_get_option(float_win, 'scroll')) + 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 @@ -717,10 +726,41 @@ describe('float window', function() end) local function with_ext_multigrid(multigrid) - local screen + local screen, attrs before_each(function() screen = Screen.new(40,7) screen:attach {ext_multigrid=multigrid} + attrs = { + [0] = {bold=true, foreground=Screen.colors.Blue}, + [1] = {background = Screen.colors.LightMagenta}, + [2] = {background = Screen.colors.LightMagenta, bold = true, foreground = Screen.colors.Blue1}, + [3] = {bold = true}, + [4] = {bold = true, reverse = true}, + [5] = {reverse = true}, + [6] = {background = Screen.colors.LightMagenta, bold = true, reverse = true}, + [7] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [8] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [9] = {background = Screen.colors.LightGrey, underline = true}, + [10] = {background = Screen.colors.LightGrey, underline = true, bold = true, foreground = Screen.colors.Magenta}, + [11] = {bold = true, foreground = Screen.colors.Magenta}, + [12] = {background = Screen.colors.Red, bold = true, foreground = Screen.colors.Blue1}, + [13] = {background = Screen.colors.WebGray}, + [14] = {foreground = Screen.colors.Brown}, + [15] = {background = Screen.colors.Grey20}, + [16] = {background = Screen.colors.Grey20, bold = true, foreground = Screen.colors.Blue1}, + [17] = {background = Screen.colors.Yellow}, + [18] = {foreground = Screen.colors.Brown, background = Screen.colors.Grey20}, + [19] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray}, + [20] = {bold = true, foreground = Screen.colors.Brown}, + [21] = {background = Screen.colors.Gray90}, + [22] = {background = Screen.colors.LightRed}, + [23] = {foreground = Screen.colors.Black, background = Screen.colors.White}; + [24] = {foreground = Screen.colors.Black, background = Screen.colors.Grey80}; + [25] = {blend = 100, background = Screen.colors.Gray0}; + [26] = {blend = 80, background = Screen.colors.Gray0}; + [27] = {background = Screen.colors.LightGray}; + [28] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGray}; + } screen:set_default_attr_ids(attrs) end) @@ -1312,6 +1352,54 @@ describe('float window', function() end end) + it("would not break 'minimal' style with statuscolumn set", function() + command('set number') + command('set signcolumn=yes') + command('set colorcolumn=1') + command('set cursorline') + command('set foldcolumn=1') + command('set statuscolumn=%l%s%C') + command('hi NormalFloat guibg=#333333') + feed('ix<cr>y<cr><esc>gg') + meths.open_win(0, false, {relative='editor', width=20, height=4, row=4, col=10, style='minimal'}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + {20:1}{19: }{20: }{22:^x}{21: }| + {14:2}{19: }{14: }{22:y} | + {14:3}{19: }{14: }{22: } | + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 4 + {15:x }| + {15:y }| + {15: }| + {15: }| + ]], float_pos={[4] = {{id = 1001}, "NW", 1, 4, 10, true}}} + else + screen:expect{grid=[[ + {20:1}{19: }{20: }{22:^x}{21: }| + {14:2}{19: }{14: }{22:y} | + {14:3}{19: }{14: }{22: } {15:x } | + {0:~ }{15:y }{0: }| + {0:~ }{15: }{0: }| + {0:~ }{15: }{0: }| + | + ]]} + end + end) + it('can have border', function() local buf = meths.create_buf(false, false) meths.buf_set_lines(buf, 0, -1, true, {' halloj! ', @@ -1709,6 +1797,222 @@ describe('float window', function() end end) + it('validates title title_pos', function() + local buf = meths.create_buf(false,false) + eq("title requires border to be set", + pcall_err(meths.open_win,buf, false, { + relative='editor', width=9, height=2, row=2, col=5, title='Title', + })) + eq("title_pos requires title to be set", + pcall_err(meths.open_win,buf, false, { + relative='editor', width=9, height=2, row=2, col=5, + border='single', title_pos='left', + })) + end) + + it('validate title_pos in nvim_win_get_config', function() + local title_pos = exec_lua([[ + local bufnr = vim.api.nvim_create_buf(false, false) + local opts = { + relative = 'editor', + col = 2, + row = 5, + height = 2, + width = 9, + border = 'double', + title = 'Test', + title_pos = 'center' + } + + local win_id = vim.api.nvim_open_win(bufnr, true, opts) + return vim.api.nvim_win_get_config(win_id).title_pos + ]]) + + eq('center', title_pos) + end) + + + it('border with title', function() + local buf = meths.create_buf(false, false) + meths.buf_set_lines(buf, 0, -1, true, {' halloj! ', + ' BORDAA '}) + local win = meths.open_win(buf, false, { + relative='editor', width=9, height=2, row=2, col=5, border="double", + title = "Left",title_pos = "left", + }) + + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5:╔}{11:Left}{5:═════╗}| + {5:║}{1: halloj! }{5:║}| + {5:║}{1: BORDAA }{5:║}| + {5:╚═════════╝}| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 2, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }{5:╔}{11:Left}{5:═════╗}{0: }| + {0:~ }{5:║}{1: halloj! }{5:║}{0: }| + {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| + {0:~ }{5:╚═════════╝}{0: }| + | + ]]} + end + + meths.win_set_config(win, {title= "Center",title_pos="center"}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5:╔═}{11:Center}{5:══╗}| + {5:║}{1: halloj! }{5:║}| + {5:║}{1: BORDAA }{5:║}| + {5:╚═════════╝}| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 2, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }{5:╔═}{11:Center}{5:══╗}{0: }| + {0:~ }{5:║}{1: halloj! }{5:║}{0: }| + {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| + {0:~ }{5:╚═════════╝}{0: }| + | + ]]} + end + + meths.win_set_config(win, {title= "Right",title_pos="right"}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5:╔════}{11:Right}{5:╗}| + {5:║}{1: halloj! }{5:║}| + {5:║}{1: BORDAA }{5:║}| + {5:╚═════════╝}| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 2, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }{5:╔════}{11:Right}{5:╗}{0: }| + {0:~ }{5:║}{1: halloj! }{5:║}{0: }| + {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| + {0:~ }{5:╚═════════╝}{0: }| + | + ]]} + end + + meths.win_set_config(win, {title= { {"🦄"},{"BB"}},title_pos="right"}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5:╔═════}🦄BB{5:╗}| + {5:║}{1: halloj! }{5:║}| + {5:║}{1: BORDAA }{5:║}| + {5:╚═════════╝}| + ]], float_pos={ + [5] = { { id = 1002 }, "NW", 1, 2, 5, true } + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }{5:╔═════}🦄BB{5:╗}{0: }| + {0:~ }{5:║}{1: halloj! }{5:║}{0: }| + {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| + {0:~ }{5:╚═════════╝}{0: }| + | + ]]} + end + end) + it('terminates border on edge of viewport when window extends past viewport', function() local buf = meths.create_buf(false, false) meths.open_win(buf, false, {relative='editor', width=40, height=7, row=0, col=0, border="single"}) @@ -2867,6 +3171,66 @@ describe('float window', function() ]]} end + command('set laststatus=0') + command('botright vnew') + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----]{5:│}[6:--------------------]| + [2:----]{5:│}[6:--------------------]| + [2:----]{5:│}[6:--------------------]| + [2:----]{5:│}[6:--------------------]| + [2:----]{5:│}[6:--------------------]| + [2:----]{5:│}[6:--------------------]| + [2:----]{5:│}[6:--------------------]| + [2:----]{5:│}[6:--------------------]| + [2:----]{5:│}[6:--------------------]| + [3:-------------------------]| + ## grid 2 + exam| + ple | + text| + tha| + t is| + wid| + er t| + han | + the | + ## grid 3 + | + ## grid 5 + {1:some info! }| + ## grid 6 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ]], float_pos={ + [5] = { { + id = 1002 + }, "SW", 2, 8, 0, true } + }} + else + screen:expect{grid=[[ + exam{5:│}^ | + ple {5:│}{0:~ }| + text{5:│}{0:~ }| + tha{5:│}{0:~ }| + t is{5:│}{0:~ }| + wid{5:│}{0:~ }| + er t{5:│}{0:~ }| + {1:some info! }{0: }| + the {5:│}{0:~ }| + | + ]]} + end + command('close') + meths.win_set_config(win, {relative='win', bufpos={1,32}, anchor='NW', col=-2}) if multigrid then screen:expect{grid=[[ @@ -2962,6 +3326,54 @@ describe('float window', function() | ]]} end + + command('%fold') + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:-------------------------]| + [2:-------------------------]| + [2:-------------------------]| + [2:-------------------------]| + [2:-------------------------]| + [2:-------------------------]| + [2:-------------------------]| + [2:-------------------------]| + [2:-------------------------]| + [3:-------------------------]| + ## grid 2 + {28:^+-- 5 lines: just some··}| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {1:some info! }| + ]], float_pos={ + [5] = { { + id = 1002 + }, "NW", 2, 2, 0, true } + }} + else + screen:expect{grid=[[ + {28:^+-- 5 lines: just some··}| + {0:~ }| + {1:some info! }{0: }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]} + end end) it('validates cursor even when window is not entered', function() @@ -8512,6 +8924,34 @@ describe('float window', function() ]]} end end) + + describe('no crash after moving and closing float window #21547', function() + local function test_float_move_close(cmd) + local float_opts = {relative = 'editor', row = 1, col = 1, width = 10, height = 10} + meths.open_win(meths.create_buf(false, false), true, float_opts) + if multigrid then + screen:expect({float_pos = {[4] = {{id = 1001}, 'NW', 1, 1, 1, true}}}) + end + command(cmd) + exec_lua([[ + vim.api.nvim_win_set_config(0, {relative = 'editor', row = 2, col = 2}) + vim.api.nvim_win_close(0, {}) + vim.api.nvim_echo({{''}}, false, {}) + ]]) + if multigrid then + screen:expect({float_pos = {}}) + end + assert_alive() + end + + it('if WinClosed autocommand flushes UI', function() + test_float_move_close('autocmd WinClosed * ++once redraw') + end) + + it('if closing buffer flushes UI', function() + test_float_move_close('autocmd BufWinLeave * ++once redraw') + end) + end) end describe('with ext_multigrid', function() diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua index 3c143d87ca..46a478c1ea 100644 --- a/test/functional/ui/fold_spec.lua +++ b/test/functional/ui/fold_spec.lua @@ -7,7 +7,8 @@ local insert = helpers.insert local expect = helpers.expect local funcs = helpers.funcs local meths = helpers.meths -local source = helpers.source +local exec = helpers.exec +local exec_lua = helpers.exec_lua local assert_alive = helpers.assert_alive @@ -199,50 +200,6 @@ describe("folded lines", function() end end) - it("highlighting with relative line numbers", function() - command("set relativenumber cursorline cursorlineopt=number foldmethod=marker") - feed_command("set foldcolumn=2") - funcs.setline(1, '{{{1') - funcs.setline(2, 'line 1') - funcs.setline(3, '{{{1') - funcs.setline(4, 'line 2') - feed("j") - if multigrid then - screen:expect([[ - ## grid 1 - [2:---------------------------------------------]| - [2:---------------------------------------------]| - [2:---------------------------------------------]| - [2:---------------------------------------------]| - [2:---------------------------------------------]| - [2:---------------------------------------------]| - [2:---------------------------------------------]| - [3:---------------------------------------------]| - ## grid 2 - {7:+ }{8: 1 }{5:+-- 2 lines: ·························}| - {7:+ }{9: 0 }{5:^+-- 2 lines: ·························}| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - ## grid 3 - :set foldcolumn=2 | - ]]) - else - screen:expect([[ - {7:+ }{8: 1 }{5:+-- 2 lines: ·························}| - {7:+ }{9: 0 }{5:^+-- 2 lines: ·························}| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - :set foldcolumn=2 | - ]]) - end - end) - it("work with spell", function() command("set spell") insert(content1) @@ -1714,7 +1671,7 @@ describe("folded lines", function() end) it('does not crash when foldtext is longer than columns #12988', function() - source([[ + exec([[ function! MyFoldText() abort return repeat('-', &columns + 100) endfunction @@ -1761,7 +1718,7 @@ describe("folded lines", function() it('work correctly with :move #18668', function() screen:try_resize(45, 12) - source([[ + exec([[ set foldmethod=expr foldexpr=indent(v:lnum) let content = ['', '', 'Line1', ' Line2', ' Line3', \ 'Line4', ' Line5', ' Line6', @@ -1896,6 +1853,128 @@ describe("folded lines", function() ]]) end end) + + it('fold attached virtual lines are drawn correctly #21837', function() + funcs.setline(1, 'line 1') + funcs.setline(2, 'line 2') + funcs.setline(3, 'line 3') + funcs.setline(4, 'line 4') + feed("zfj") + exec_lua([[ + local ns = vim.api.nvim_create_namespace("ns") + vim.api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_lines_above = true, virt_lines = {{{"virt_line above line 1", ""}}} }) + vim.api.nvim_buf_set_extmark(0, ns, 1, 0, { virt_lines = {{{"virt_line below line 2", ""}}} }) + vim.api.nvim_buf_set_extmark(0, ns, 2, 0, { virt_lines_above = true, virt_lines = {{{"virt_line above line 3", ""}}} }) + vim.api.nvim_buf_set_extmark(0, ns, 3, 0, { virt_lines = {{{"virt_line below line 4", ""}}} }) + ]]) + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {5:^+-- 2 lines: line 1·························}| + virt_line above line 3 | + line 3 | + line 4 | + virt_line below line 4 | + {1:~ }| + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + {5:^+-- 2 lines: line 1·························}| + virt_line above line 3 | + line 3 | + line 4 | + virt_line below line 4 | + {1:~ }| + {1:~ }| + | + ]]) + end + + feed('jzfj') + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {5:+-- 2 lines: line 1·························}| + {5:^+-- 2 lines: line 3·························}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + {5:+-- 2 lines: line 1·························}| + {5:^+-- 2 lines: line 3·························}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end + + feed('kzo<C-Y>') + funcs.setline(5, 'line 5') + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + virt_line above line 1 | + ^line 1 | + line 2 | + virt_line below line 2 | + {5:+-- 2 lines: line 3·························}| + line 5 | + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + virt_line above line 1 | + ^line 1 | + line 2 | + virt_line below line 2 | + {5:+-- 2 lines: line 3·························}| + line 5 | + {1:~ }| + | + ]]) + end + end) end describe("with ext_multigrid", function() diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index 2a6ee0d56c..61abe341c7 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -6,6 +6,7 @@ local command, exec = helpers.command, helpers.exec local eval = helpers.eval local feed_command, eq = helpers.feed_command, helpers.eq local curbufmeths = helpers.curbufmeths +local funcs = helpers.funcs local meths = helpers.meths describe('colorscheme compatibility', function() @@ -13,7 +14,9 @@ describe('colorscheme compatibility', function() clear() end) - it('t_Co is set to 256 by default', function() + it('&t_Co exists and is set to 256 by default', function() + eq(1, funcs.exists('&t_Co')) + eq(1, funcs.exists('+t_Co')) eq('256', eval('&t_Co')) end) end) @@ -1049,6 +1052,7 @@ describe('CursorLine and CursorLineNr highlights', function() ]]) end) + -- oldtest: Test_cursorline_after_yank() it('always updated. vim-patch:8.1.0849', function() local screen = Screen.new(50,5) screen:set_default_attr_ids({ @@ -1082,6 +1086,7 @@ describe('CursorLine and CursorLineNr highlights', function() ]]) end) + -- oldtest: Test_cursorline_with_visualmode() it('with visual area. vim-patch:8.1.1001', function() local screen = Screen.new(50,5) screen:set_default_attr_ids({ @@ -1109,6 +1114,7 @@ describe('CursorLine and CursorLineNr highlights', function() ]]) end) + -- oldtest: Test_cursorline_callback() 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({ @@ -1238,6 +1244,7 @@ describe('CursorLine and CursorLineNr highlights', function() }) end) + -- oldtest: Test_diff_with_cursorline_number() it('CursorLineNr shows correctly just below filler lines', function() local screen = Screen.new(50,12) screen:set_default_attr_ids({ @@ -1358,6 +1365,7 @@ describe('CursorColumn highlight', function() ]]) end) + -- oldtest: Test_cursorcolumn_callback() it('is updated if cursor is moved from timer', function() exec([[ call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd']) @@ -1399,7 +1407,7 @@ describe('ColorColumn highlight', function() before_each(function() clear() screen = Screen.new(40, 15) - Screen:set_default_attr_ids({ + screen:set_default_attr_ids({ [1] = {background = Screen.colors.LightRed}, -- ColorColumn [2] = {background = Screen.colors.Grey90}, -- CursorLine [3] = {foreground = Screen.colors.Brown}, -- LineNr @@ -1413,6 +1421,7 @@ describe('ColorColumn highlight', function() screen:attach() end) + -- oldtest: Test_colorcolumn() it('when entering a buffer vim-patch:8.1.2073', function() exec([[ set nohidden @@ -1444,6 +1453,7 @@ describe('ColorColumn highlight', function() ]]) end) + -- oldtest: Test_colorcolumn_bri() it("in 'breakindent' vim-patch:8.2.1689", function() exec([[ call setline(1, 'The quick brown fox jumped over the lazy dogs') @@ -1468,6 +1478,7 @@ describe('ColorColumn highlight', function() ]]) end) + -- oldtest: Test_colorcolumn_sbr() it("in 'showbreak' vim-patch:8.2.1689", function() exec([[ call setline(1, 'The quick brown fox jumped over the lazy dogs') @@ -1687,6 +1698,7 @@ describe("'number' and 'relativenumber' highlight", function() ]]) end) + -- oldtest: Test_relativenumber_callback() it('relative number highlight is updated if cursor is moved from timer', function() local screen = Screen.new(50, 8) screen:set_default_attr_ids({ @@ -1735,38 +1747,40 @@ describe("'winhighlight' highlight", function() clear() screen = Screen.new(20,8) screen:attach() - screen:set_default_attr_ids({ - [0] = {bold=true, foreground=Screen.colors.Blue}, - [1] = {background = Screen.colors.DarkBlue}, - [2] = {background = Screen.colors.DarkBlue, bold = true, foreground = Screen.colors.Blue1}, - [3] = {bold = true, reverse = true}, - [4] = {reverse = true}, - [5] = {background = Screen.colors.DarkGreen}, - [6] = {background = Screen.colors.DarkGreen, bold = true, foreground = Screen.colors.Blue1}, - [7] = {background = Screen.colors.DarkMagenta}, - [8] = {background = Screen.colors.DarkMagenta, bold = true, foreground = Screen.colors.Blue1}, - [9] = {foreground = Screen.colors.Brown}, - [10] = {foreground = Screen.colors.Brown, background = Screen.colors.DarkBlue}, - [11] = {background = Screen.colors.DarkBlue, bold = true, reverse = true}, - [12] = {background = Screen.colors.DarkGreen, reverse = true}, - [13] = {background = Screen.colors.Magenta4, reverse = true}, - [14] = {background = Screen.colors.DarkBlue, reverse = true}, - [15] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, - [16] = {foreground = Screen.colors.Blue1}, - [17] = {background = Screen.colors.LightRed}, - [18] = {background = Screen.colors.Gray90}, - [19] = {foreground = Screen.colors.LightGrey, background = Screen.colors.DarkGray}, - [20] = {background = Screen.colors.LightGrey, underline = true}, - [21] = {bold = true}, - [22] = {bold = true, foreground = Screen.colors.SeaGreen4}, - [23] = {background = Screen.colors.LightMagenta}, - [24] = {background = Screen.colors.WebGray}, - [25] = {bold = true, foreground = Screen.colors.Green1}, - [26] = {background = Screen.colors.Red}, - [27] = {background = Screen.colors.DarkBlue, bold = true, foreground = Screen.colors.Green1}, - [28] = {bold = true, foreground = Screen.colors.Brown}, + screen:set_default_attr_ids { + [0] = {bold=true, foreground=Screen.colors.Blue}; + [1] = {background = Screen.colors.DarkBlue}; + [2] = {background = Screen.colors.DarkBlue, bold = true, foreground = Screen.colors.Blue1}; + [3] = {bold = true, reverse = true}; + [4] = {reverse = true}; + [5] = {background = Screen.colors.DarkGreen}; + [6] = {background = Screen.colors.DarkGreen, bold = true, foreground = Screen.colors.Blue1}; + [7] = {background = Screen.colors.DarkMagenta}; + [8] = {background = Screen.colors.DarkMagenta, bold = true, foreground = Screen.colors.Blue1}; + [9] = {foreground = Screen.colors.Brown}; + [10] = {foreground = Screen.colors.Brown, background = Screen.colors.DarkBlue}; + [11] = {background = Screen.colors.DarkBlue, bold = true, reverse = true}; + [12] = {background = Screen.colors.DarkGreen, reverse = true}; + [13] = {background = Screen.colors.Magenta4, reverse = true}; + [14] = {background = Screen.colors.DarkBlue, reverse = true}; + [15] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}; + [16] = {foreground = Screen.colors.Blue1}; + [17] = {background = Screen.colors.LightRed}; + [18] = {background = Screen.colors.Gray90}; + [19] = {foreground = Screen.colors.LightGrey, background = Screen.colors.DarkGray}; + [20] = {background = Screen.colors.LightGrey, underline = true}; + [21] = {bold = true}; + [22] = {bold = true, foreground = Screen.colors.SeaGreen4}; + [23] = {background = Screen.colors.LightMagenta}; + [24] = {background = Screen.colors.WebGray}; + [25] = {bold = true, foreground = Screen.colors.Green1}; + [26] = {background = Screen.colors.Red}; + [27] = {background = Screen.colors.DarkBlue, bold = true, foreground = Screen.colors.Green1}; + [28] = {bold = true, foreground = Screen.colors.Brown}; [29] = {foreground = Screen.colors.Blue1, background = Screen.colors.Red, bold = true}; - }) + [30] = {background = tonumber('0xff8800')}; + [31] = {background = tonumber('0xff8800'), bold = true, foreground = Screen.colors.Blue}; + } command("hi Background1 guibg=DarkBlue") command("hi Background2 guibg=DarkGreen") end) @@ -2011,6 +2025,33 @@ describe("'winhighlight' highlight", function() ]]) end) + it('updates background to changed linked group', function() + command("split") + command("setlocal winhl=Normal:FancyGroup") -- does not yet exist + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }| + {3:[No Name] }| + | + {0:~ }| + {4:[No Name] }| + | + ]]} + + command("hi FancyGroup guibg=#FF8800") -- nice orange + screen:expect{grid=[[ + {30:^ }| + {31:~ }| + {31:~ }| + {3:[No Name] }| + | + {0:~ }| + {4:[No Name] }| + | + ]]} + end) + it('background applies also to non-text', function() command('set sidescroll=0') insert('Lorem ipsum dolor sit amet ') diff --git a/test/functional/ui/hlstate_spec.lua b/test/functional/ui/hlstate_spec.lua index dc74d6d401..55f873e827 100644 --- a/test/functional/ui/hlstate_spec.lua +++ b/test/functional/ui/hlstate_spec.lua @@ -4,9 +4,10 @@ local Screen = require('test.functional.ui.screen') local clear, insert = helpers.clear, helpers.insert local command = helpers.command local meths = helpers.meths -local iswin = helpers.iswin local testprg = helpers.testprg local thelpers = require('test.functional.terminal.helpers') +local skip = helpers.skip +local is_os = helpers.is_os describe('ext_hlstate detailed highlights', function() local screen @@ -182,7 +183,7 @@ describe('ext_hlstate detailed highlights', function() end) it("work with :terminal", function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) screen:set_default_attr_ids({ [1] = {{}, {{hi_name = "TermCursorNC", ui_name = "TermCursorNC", kind = "ui"}}}, @@ -211,7 +212,7 @@ describe('ext_hlstate detailed highlights', function() thelpers.set_bold() thelpers.feed_data('z\n') -- TODO(bfredl): check if this distinction makes sense - if iswin() then + if is_os('win') then screen:expect([[ ^tty ready | x {5:y z} | @@ -237,7 +238,7 @@ describe('ext_hlstate detailed highlights', function() thelpers.feed_termcode("[A") thelpers.feed_termcode("[2C") - if iswin() then + if is_os('win') then screen:expect([[ ^tty ready | x {6:y}{5: z} | diff --git a/test/functional/ui/linematch_spec.lua b/test/functional/ui/linematch_spec.lua new file mode 100644 index 0000000000..697677aa67 --- /dev/null +++ b/test/functional/ui/linematch_spec.lua @@ -0,0 +1,995 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local feed = helpers.feed +local clear = helpers.clear +local write_file = helpers.write_file + +describe('Diff mode screen with 3 diffs open', function() + local fname = 'Xtest-functional-diff-screen-1' + local fname_2 = fname .. '.2' + local fname_3 = fname .. '.3' + local screen + + local reread = function() + feed(':e<cr><c-w>w:e<cr><c-w>w:e<cr><c-w>w') + end + + setup(function() + clear() + os.remove(fname) + os.remove(fname_2) + os.remove(fname_3) + end) + + teardown(function() + os.remove(fname) + os.remove(fname_2) + os.remove(fname_3) + end) + + before_each(function() + clear() + feed(':set diffopt+=linematch:30<cr>') + feed(':e ' .. fname .. '<cr>') + feed(':vnew ' .. fname_2 .. '<cr>') + feed(':vnew ' .. fname_3 .. '<cr>') + feed(':windo diffthis<cr>') + + screen = Screen.new(100, 16) + screen:attach() + screen:set_default_attr_ids({ + [1] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.Gray}; + [2] = {foreground = Screen.colors.Blue1, bold = true, background = Screen.colors.LightCyan1}; + [3] = {reverse = true}; + [4] = {background = Screen.colors.LightBlue}; + [5] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGray}; + [6] = {foreground = Screen.colors.Blue1, bold = true}; + [7] = {reverse = true, bold = true}; + [8] = {background = Screen.colors.Red1, bold = true}; + [10] = {foreground = Screen.colors.Brown}; + [9] = {background = Screen.colors.Plum1}; + }) + feed('<c-w>=') + feed(':windo set nu!<cr>') + end) + + describe('setup the diff screen to look like a merge conflict with 3 files in diff mode', function() + before_each(function() + + local f1 = [[ + + common line + AAA + AAA + AAA + ]] + local f2 = [[ + + common line + <<<<<<< HEAD + AAA + AAA + AAA + ======= + BBB + BBB + BBB + >>>>>>> branch1 + ]] + local f3 = [[ + + common line + BBB + BBB + BBB + ]] + + write_file(fname, f1, false) + write_file(fname_2, f2, false) + write_file(fname_3, f3, false) + reread() + end) + + it('get from window 1', function() + feed('1<c-w>w') + feed(':2,6diffget screen-1.2<cr>') + screen:expect([[ + {1: }{10: 1 }^ │{1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }{9:<<<<<<< HEAD }│{1: }{10: 3 }{9:<<<<<<< HEAD }│{1: }{10: }{2:---------------------------}| + {1: }{10: 4 } AAA │{1: }{10: 4 } AAA │{1: }{10: 3 } AAA | + {1: }{10: 5 } AAA │{1: }{10: 5 } AAA │{1: }{10: 4 } AAA | + {1: }{10: 6 } AAA │{1: }{10: 6 } AAA │{1: }{10: 5 } AAA | + {1: }{10: 7 }{9:======= }│{1: }{10: 7 }{9:======= }│{1: }{10: }{2:---------------------------}| + {1: }{10: 8 }{9: BBB }│{1: }{10: 8 }{9: BBB }│{1: }{10: }{2:---------------------------}| + {1: }{10: 9 }{9: BBB }│{1: }{10: 9 }{9: BBB }│{1: }{10: }{2:---------------------------}| + {1: }{10: 10 }{9: BBB }│{1: }{10: 10 }{9: BBB }│{1: }{10: }{2:---------------------------}| + {1: }{10: 11 }{9:>>>>>>> branch1 }│{1: }{10: 11 }{9:>>>>>>> branch1 }│{1: }{10: }{2:---------------------------}| + {1: }{10: 12 } │{1: }{10: 12 } │{1: }{10: 6 } | + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {7:<-functional-diff-screen-1.3 [+] }{3:<est-functional-diff-screen-1.2 Xtest-functional-diff-screen-1 }| + :2,6diffget screen-1.2 | + ]]) + end) + + it('get from window 2', function() + feed('2<c-w>w') + feed(':5,7diffget screen-1.3<cr>') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 }^ │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: }{2:---------------------------}│{1: }{10: 3 }{4:<<<<<<< HEAD }│{1: }{10: }{2:---------------------------}| + {1: }{10: }{2:---------------------------}│{1: }{10: 4 }{9: AAA }│{1: }{10: 3 }{9: AAA }| + {1: }{10: 3 }{9: BBB }│{1: }{10: 5 }{9: BBB }│{1: }{10: }{2:---------------------------}| + {1: }{10: 4 }{9: }{8:BBB}{9: }│{1: }{10: 6 }{9: }{8:BBB}{9: }│{1: }{10: 4 }{9: }{8:AAA}{9: }| + {1: }{10: 5 }{9: }{8:BBB}{9: }│{1: }{10: 7 }{9: }{8:BBB}{9: }│{1: }{10: 5 }{9: }{8:AAA}{9: }| + {1: }{10: }{2:---------------------------}│{1: }{10: 8 }{4:>>>>>>> branch1 }│{1: }{10: }{2:---------------------------}| + {1: }{10: 6 } │{1: }{10: 9 } │{1: }{10: 6 } | + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {3:<test-functional-diff-screen-1.3 }{7:<functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :5,7diffget screen-1.3 | + ]]) + end) + + it('get from window 3', function() + feed('3<c-w>w') + feed(':5,6diffget screen-1.2<cr>') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } │{1: }{10: 1 }^ | + {1: }{10: 2 }common line │{1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: }{2:---------------------------}│{1: }{10: 3 }{4:<<<<<<< HEAD }│{1: }{10: }{2:---------------------------}| + {1: }{10: }{2:---------------------------}│{1: }{10: 4 }{9: AAA }│{1: }{10: 3 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 5 }{9: AAA }│{1: }{10: 4 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 6 }{9: AAA }│{1: }{10: 5 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 7 }{9:======= }│{1: }{10: 6 }{9:======= }| + {1: }{10: 3 } BBB │{1: }{10: 8 } BBB │{1: }{10: 7 } BBB | + {1: }{10: 4 } BBB │{1: }{10: 9 } BBB │{1: }{10: 8 } BBB | + {1: }{10: 5 } BBB │{1: }{10: 10 } BBB │{1: }{10: 9 } BBB | + {1: }{10: }{2:---------------------------}│{1: }{10: 11 }{9:>>>>>>> branch1 }│{1: }{10: 10 }{9:>>>>>>> branch1 }| + {1: }{10: 6 } │{1: }{10: 12 } │{1: }{10: 11 } | + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {3:<test-functional-diff-screen-1.3 <est-functional-diff-screen-1.2 }{7:<st-functional-diff-screen-1 [+] }| + :5,6diffget screen-1.2 | + ]]) + end) + + it('put from window 2 - part', function() + feed('2<c-w>w') + feed(':6,8diffput screen-1<cr>') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 }^ │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: }{2:---------------------------}│{1: }{10: 3 }{4:<<<<<<< HEAD }│{1: }{10: }{2:---------------------------}| + {1: }{10: }{2:---------------------------}│{1: }{10: 4 }{9: AAA }│{1: }{10: 3 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 5 }{9: AAA }│{1: }{10: 4 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 6 }{9: AAA }│{1: }{10: 5 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 7 }{9:======= }│{1: }{10: 6 }{9:======= }| + {1: }{10: 3 }{9: BBB }│{1: }{10: 8 }{9: BBB }│{1: }{10: }{2:---------------------------}| + {1: }{10: 4 }{9: BBB }│{1: }{10: 9 }{9: BBB }│{1: }{10: }{2:---------------------------}| + {1: }{10: 5 } BBB │{1: }{10: 10 } BBB │{1: }{10: 7 } BBB | + {1: }{10: }{2:---------------------------}│{1: }{10: 11 }{4:>>>>>>> branch1 }│{1: }{10: }{2:---------------------------}| + {1: }{10: 6 } │{1: }{10: 12 } │{1: }{10: 8 } | + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {3:<test-functional-diff-screen-1.3 }{7:<est-functional-diff-screen-1.2 }{3:<st-functional-diff-screen-1 [+] }| + :6,8diffput screen-1 | + ]]) + + end) + it('put from window 2 - part to end', function() + feed('2<c-w>w') + feed(':6,11diffput screen-1<cr>') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 }^ │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: }{2:---------------------------}│{1: }{10: 3 }{4:<<<<<<< HEAD }│{1: }{10: }{2:---------------------------}| + {1: }{10: }{2:---------------------------}│{1: }{10: 4 }{9: AAA }│{1: }{10: 3 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 5 }{9: AAA }│{1: }{10: 4 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 6 }{9: AAA }│{1: }{10: 5 }{9: AAA }| + {1: }{10: }{2:---------------------------}│{1: }{10: 7 }{9:======= }│{1: }{10: 6 }{9:======= }| + {1: }{10: 3 } BBB │{1: }{10: 8 } BBB │{1: }{10: 7 } BBB | + {1: }{10: 4 } BBB │{1: }{10: 9 } BBB │{1: }{10: 8 } BBB | + {1: }{10: 5 } BBB │{1: }{10: 10 } BBB │{1: }{10: 9 } BBB | + {1: }{10: }{2:---------------------------}│{1: }{10: 11 }{9:>>>>>>> branch1 }│{1: }{10: 10 }{9:>>>>>>> branch1 }| + {1: }{10: 6 } │{1: }{10: 12 } │{1: }{10: 11 } | + {6:~ }│{6:~ }│{6:~ }| + {6:~ }│{6:~ }│{6:~ }| + {3:<test-functional-diff-screen-1.3 }{7:<est-functional-diff-screen-1.2 }{3:<st-functional-diff-screen-1 [+] }| + :6,11diffput screen-1 | + ]]) + + end) + end) +end) + +describe('Diff mode screen with 2 diffs open', function() + local fname = 'Xtest-functional-diff-screen-1' + local fname_2 = fname .. '.2' + local screen + + local reread = function() + feed(':e<cr><c-w>w:e<cr><c-w>w:e<cr><c-w>w') + end + + setup(function() + clear() + os.remove(fname) + os.remove(fname_2) + end) + + teardown(function() + os.remove(fname) + os.remove(fname_2) + end) + + before_each(function() + clear() + feed(':e ' .. fname .. '<cr>') + feed(':vnew ' .. fname_2 .. '<cr>') + feed(':windo diffthis<cr>') + + screen = Screen.new(100, 20) + screen:attach() + screen:set_default_attr_ids({ + [1] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.Gray}; + [2] = {foreground = Screen.colors.Blue1, bold = true, background = Screen.colors.LightCyan1}; + [3] = {reverse = true}; + [4] = {background = Screen.colors.LightBlue}; + [5] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGray}; + [6] = {foreground = Screen.colors.Blue1, bold = true}; + [7] = {reverse = true, bold = true}; + [8] = {background = Screen.colors.Red1, bold = true}; + [10] = {foreground = Screen.colors.Brown}; + [9] = {background = Screen.colors.Plum1}; + }) + feed('<c-w>=') + feed(':windo set nu!<cr>') + end) + + describe('setup a diff with 2 files and set linematch:30', function() + before_each(function() + feed(':set diffopt+=linematch:30<cr>') + local f1 = [[ + +common line +common line + +DEFabc +xyz +xyz +xyz +DEFabc +DEFabc +DEFabc +common line +common line +DEF +common line +DEF +something + ]] + local f2 = [[ + +common line +common line + +ABCabc +ABCabc +ABCabc +ABCabc +common line +common line +common line +something + ]] + write_file(fname, f1, false) + write_file(fname_2, f2, false) + reread() + end) + + it('get from window 1 from line 5 to 9', function() + feed('1<c-w>w') + feed(':5,9diffget<cr>') + screen:expect([[ + {1:+ }{10: 1 }{5:^+-- 7 lines: common line··················}│{1:+ }{10: 1 }{5:+-- 7 lines: common line···················}| + {1: }{10: 8 }xyz │{1: }{10: 8 }xyz | + {1: }{10: 9 }DEFabc │{1: }{10: 9 }DEFabc | + {1: }{10: 10 }DEFabc │{1: }{10: 10 }DEFabc | + {1: }{10: 11 }DEFabc │{1: }{10: 11 }DEFabc | + {1: }{10: 12 }common line │{1: }{10: 12 }common line | + {1: }{10: 13 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 14 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 15 }something │{1: }{10: 17 }something | + {1: }{10: 16 } │{1: }{10: 18 } | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :5,9diffget | + ]]) + end) + it('get from window 2 from line 5 to 10', function() + feed('2<c-w>w') + feed(':5,10diffget<cr>') + screen:expect([[ + {1:- }{10: 1 } │{1:- }{10: 1 }^ | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }ABCabc │{1: }{10: 5 }ABCabc | + {1: }{10: 6 }ABCabc │{1: }{10: 6 }ABCabc | + {1: }{10: 7 }ABCabc │{1: }{10: 7 }ABCabc | + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 8 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 9 }common line | + {1: }{10: 10 }common line │{1: }{10: 10 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 11 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 12 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 13 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 14 }something | + {1: }{10: 13 } │{1: }{10: 15 } | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {3:Xtest-functional-diff-screen-1.2 }{7:Xtest-functional-diff-screen-1 [+] }| + :5,10diffget | + ]]) + end) + it('get all from window 2', function() + feed('2<c-w>w') + feed(':4,17diffget<cr>') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 }^ | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }ABCabc │{1: }{10: 5 }ABCabc | + {1: }{10: 6 }ABCabc │{1: }{10: 6 }ABCabc | + {1: }{10: 7 }ABCabc │{1: }{10: 7 }ABCabc | + {1: }{10: 8 }ABCabc │{1: }{10: 8 }ABCabc | + {1: }{10: 9 }common line │{1: }{10: 9 }common line | + {1: }{10: 10 }common line │{1: }{10: 10 }common line | + {1: }{10: 11 }common line │{1: }{10: 11 }common line | + {1: }{10: 12 }something │{1: }{10: 12 }something | + {1: }{10: 13 } │{1: }{10: 13 } | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {3:Xtest-functional-diff-screen-1.2 }{7:Xtest-functional-diff-screen-1 [+] }| + :4,17diffget | + ]]) + + end) + it('get all from window 1', function() + feed('1<c-w>w') + feed(':4,12diffget<cr>') + screen:expect([[ + {1: }{10: 1 }^ │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }DEFabc │{1: }{10: 5 }DEFabc | + {1: }{10: 6 }xyz │{1: }{10: 6 }xyz | + {1: }{10: 7 }xyz │{1: }{10: 7 }xyz | + {1: }{10: 8 }xyz │{1: }{10: 8 }xyz | + {1: }{10: 9 }DEFabc │{1: }{10: 9 }DEFabc | + {1: }{10: 10 }DEFabc │{1: }{10: 10 }DEFabc | + {1: }{10: 11 }DEFabc │{1: }{10: 11 }DEFabc | + {1: }{10: 12 }common line │{1: }{10: 12 }common line | + {1: }{10: 13 }common line │{1: }{10: 13 }common line | + {1: }{10: 14 }DEF │{1: }{10: 14 }DEF | + {1: }{10: 15 }common line │{1: }{10: 15 }common line | + {1: }{10: 16 }DEF │{1: }{10: 16 }DEF | + {1: }{10: 17 }something │{1: }{10: 17 }something | + {1: }{10: 18 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :4,12diffget | + ]]) + end) + it('get from window 1 using do 1 line 5', function() + feed('1<c-w>w') + feed('5gg') + feed('do') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }^DEFabc │{1: }{10: 5 }DEFabc | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('get from window 1 using do 2 line 6', function() + feed('1<c-w>w') + feed('6gg') + feed('do') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }^DEFabc │{1: }{10: 9 }DEFabc | + {1: }{10: 7 }DEFabc │{1: }{10: 10 }DEFabc | + {1: }{10: 8 }DEFabc │{1: }{10: 11 }DEFabc | + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('get from window 1 using do 2 line 7', function() + feed('1<c-w>w') + feed('7gg') + feed('do') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }DEFabc │{1: }{10: 9 }DEFabc | + {1: }{10: 7 }^DEFabc │{1: }{10: 10 }DEFabc | + {1: }{10: 8 }DEFabc │{1: }{10: 11 }DEFabc | + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('get from window 1 using do 2 line 11', function() + feed('1<c-w>w') + feed('11gg') + feed('do') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: 11 }DEF │{1: }{10: 14 }DEF | + {1: }{10: 12 }^common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 13 }something │{1: }{10: 17 }something | + {1: }{10: 14 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('get from window 1 using do 2 line 12', function() + feed('1<c-w>w') + feed('12gg') + feed('do') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: 12 }DEF │{1: }{10: 16 }DEF | + {1: }{10: 13 }^something │{1: }{10: 17 }something | + {1: }{10: 14 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('put from window 1 using dp 1 line 5', function() + feed('1<c-w>w') + feed('5gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }^ABCabc │{1: }{10: 5 }ABCabc | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 [+] }| + :e | + ]]) + end) + it('put from window 1 using dp 2 line 6', function() + feed('1<c-w>w') + feed('6gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }^ABCabc │{1: }{10: 9 }ABCabc | + {1: }{10: 7 }ABCabc │{1: }{10: 10 }ABCabc | + {1: }{10: 8 }ABCabc │{1: }{10: 11 }ABCabc | + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 [+] }| + :e | + ]]) + end) + it('put from window 1 using dp 2 line 7', function() + feed('1<c-w>w') + feed('7gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }ABCabc │{1: }{10: 9 }ABCabc | + {1: }{10: 7 }^ABCabc │{1: }{10: 10 }ABCabc | + {1: }{10: 8 }ABCabc │{1: }{10: 11 }ABCabc | + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 [+] }| + :e | + ]]) + end) + it('put from window 1 using dp 2 line 11', function() + feed('1<c-w>w') + feed('11gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: 11 }^common line │{1: }{10: 14 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 15 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 16 }something | + {1: }{10: 13 } │{1: }{10: 17 } | + {6:~ }│{6:~ }| + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 [+] }| + :e | + ]]) + end) + it('put from window 1 using dp 2 line 12', function() + feed('1<c-w>w') + feed('12gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: 12 }^something │{1: }{10: 16 }something | + {1: }{10: 13 } │{1: }{10: 17 } | + {6:~ }│{6:~ }| + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 [+] }| + :e | + ]]) + end) + it('put from window 2 using dp line 6', function() + feed('2<c-w>w') + feed('6gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: 6 }xyz │{1: }{10: 6 }^xyz | + {1: }{10: 7 }xyz │{1: }{10: 7 }xyz | + {1: }{10: 8 }xyz │{1: }{10: 8 }xyz | + {1: }{10: 9 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 10 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 11 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 12 }common line │{1: }{10: 12 }common line | + {1: }{10: 13 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 14 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 15 }something │{1: }{10: 17 }something | + {1: }{10: 16 } │{1: }{10: 18 } | + {3:Xtest-functional-diff-screen-1.2 [+] }{7:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('put from window 2 using dp line 8', function() + feed('2<c-w>w') + feed('8gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: 6 }xyz │{1: }{10: 6 }xyz | + {1: }{10: 7 }xyz │{1: }{10: 7 }xyz | + {1: }{10: 8 }xyz │{1: }{10: 8 }^xyz | + {1: }{10: 9 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 10 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 11 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 12 }common line │{1: }{10: 12 }common line | + {1: }{10: 13 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 14 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 15 }something │{1: }{10: 17 }something | + {1: }{10: 16 } │{1: }{10: 18 } | + {3:Xtest-functional-diff-screen-1.2 [+] }{7:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('put from window 2 using dp line 9', function() + feed('2<c-w>w') + feed('9gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }DEFabc │{1: }{10: 9 }^DEFabc | + {1: }{10: 7 }DEFabc │{1: }{10: 10 }DEFabc | + {1: }{10: 8 }DEFabc │{1: }{10: 11 }DEFabc | + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 16 }{4:DEF }| + {1: }{10: 12 }something │{1: }{10: 17 }something | + {1: }{10: 13 } │{1: }{10: 18 } | + {3:Xtest-functional-diff-screen-1.2 [+] }{7:Xtest-functional-diff-screen-1 }| + :e | + ]]) + end) + it('put from window 2 using dp line 17', function() + feed('2<c-w>w') + feed('17gg') + feed('dp') + screen:expect([[ + {1: }{10: 1 } │{1: }{10: 1 } | + {1: }{10: 2 }common line │{1: }{10: 2 }common line | + {1: }{10: 3 }common line │{1: }{10: 3 }common line | + {1: }{10: 4 } │{1: }{10: 4 } | + {1: }{10: 5 }{8:ABC}{9:abc }│{1: }{10: 5 }{8:DEF}{9:abc }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 6 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 7 }{4:xyz }| + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 8 }{4:xyz }| + {1: }{10: 6 }{8:ABC}{9:abc }│{1: }{10: 9 }{8:DEF}{9:abc }| + {1: }{10: 7 }{8:ABC}{9:abc }│{1: }{10: 10 }{8:DEF}{9:abc }| + {1: }{10: 8 }{8:ABC}{9:abc }│{1: }{10: 11 }{8:DEF}{9:abc }| + {1: }{10: 9 }common line │{1: }{10: 12 }common line | + {1: }{10: 10 }common line │{1: }{10: 13 }common line | + {1: }{10: }{2:-------------------------------------------}│{1: }{10: 14 }{4:DEF }| + {1: }{10: 11 }common line │{1: }{10: 15 }common line | + {1: }{10: 12 }DEF │{1: }{10: 16 }DEF | + {1: }{10: 13 }something │{1: }{10: 17 }^something | + {1: }{10: 14 } │{1: }{10: 18 } | + {3:Xtest-functional-diff-screen-1.2 [+] }{7:Xtest-functional-diff-screen-1 }| + :e | + ]]) + + end) + end) + describe('setup a diff with 2 files and set linematch:10', function() + before_each(function() + feed(':set diffopt+=linematch:10<cr>') + local f1 = [[ +common line +HIL + +aABCabc +aABCabc +aABCabc +aABCabc +common line +HIL +common line +something + ]] + local f2 = [[ +common line +DEF +GHI +something + +aDEFabc +xyz +xyz +xyz +aDEFabc +aDEFabc +aDEFabc +common line +DEF +GHI +something else +common line +something + ]] + write_file(fname, f1, false) + write_file(fname_2, f2, false) + reread() + end) + + it('enable linematch for the longest diff block by increasing the number argument passed to linematch', function() + feed('1<c-w>w') + -- linematch is disabled for the longest diff because it's combined line length is over 10 + screen:expect([[ + {1: }{10: 1 }^common line │{1: }{10: 1 }common line | + {1: }{10: 2 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 3 }{8:GHI}{9: }│{1: }{10: 2 }{8:HIL}{9: }| + {1: }{10: 4 }{4:something }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 5 } │{1: }{10: 3 } | + {1: }{10: 6 }{9:a}{8:DEF}{9:abc }│{1: }{10: 4 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 7 }{8:xyz}{9: }│{1: }{10: 5 }{8:aABCabc}{9: }| + {1: }{10: 8 }{8:xyz}{9: }│{1: }{10: 6 }{8:aABCabc}{9: }| + {1: }{10: 9 }{8:xyz}{9: }│{1: }{10: 7 }{8:aABCabc}{9: }| + {1: }{10: 10 }{4:aDEFabc }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 11 }{4:aDEFabc }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 12 }{4:aDEFabc }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 13 }common line │{1: }{10: 8 }common line | + {1: }{10: 14 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 15 }{8:GHI}{9: }│{1: }{10: 9 }{8:HIL}{9: }| + {1: }{10: 16 }{4:something else }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 17 }common line │{1: }{10: 10 }common line | + {1: }{10: 18 }something │{1: }{10: 11 }something | + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 }| + :e | + ]]) + -- enable it by increasing the number + feed(":set diffopt-=linematch:10<cr>") + feed(":set diffopt+=linematch:30<cr>") + screen:expect([[ + {1: }{10: 1 }^common line │{1: }{10: 1 }common line | + {1: }{10: 2 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 3 }{8:GHI}{9: }│{1: }{10: 2 }{8:HIL}{9: }| + {1: }{10: 4 }{4:something }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 5 } │{1: }{10: 3 } | + {1: }{10: 6 }{9:a}{8:DEF}{9:abc }│{1: }{10: 4 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 7 }{4:xyz }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 8 }{4:xyz }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 9 }{4:xyz }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 10 }{9:a}{8:DEF}{9:abc }│{1: }{10: 5 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 11 }{9:a}{8:DEF}{9:abc }│{1: }{10: 6 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 12 }{9:a}{8:DEF}{9:abc }│{1: }{10: 7 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 13 }common line │{1: }{10: 8 }common line | + {1: }{10: 14 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 15 }{8:GHI}{9: }│{1: }{10: 9 }{8:HIL}{9: }| + {1: }{10: 16 }{4:something else }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 17 }common line │{1: }{10: 10 }common line | + {1: }{10: 18 }something │{1: }{10: 11 }something | + {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 }| + :set diffopt+=linematch:30 | + ]]) + end) + it('get all from second window', function() + feed('2<c-w>w') + feed(':1,12diffget<cr>') + screen:expect([[ + {1: }{10: 1 }common line │{1: }{10: 1 }^common line | + {1: }{10: 2 }DEF │{1: }{10: 2 }DEF | + {1: }{10: 3 }GHI │{1: }{10: 3 }GHI | + {1: }{10: 4 }something │{1: }{10: 4 }something | + {1: }{10: 5 } │{1: }{10: 5 } | + {1: }{10: 6 }aDEFabc │{1: }{10: 6 }aDEFabc | + {1: }{10: 7 }xyz │{1: }{10: 7 }xyz | + {1: }{10: 8 }xyz │{1: }{10: 8 }xyz | + {1: }{10: 9 }xyz │{1: }{10: 9 }xyz | + {1: }{10: 10 }aDEFabc │{1: }{10: 10 }aDEFabc | + {1: }{10: 11 }aDEFabc │{1: }{10: 11 }aDEFabc | + {1: }{10: 12 }aDEFabc │{1: }{10: 12 }aDEFabc | + {1: }{10: 13 }common line │{1: }{10: 13 }common line | + {1: }{10: 14 }DEF │{1: }{10: 14 }DEF | + {1: }{10: 15 }GHI │{1: }{10: 15 }GHI | + {1: }{10: 16 }something else │{1: }{10: 16 }something else | + {1: }{10: 17 }common line │{1: }{10: 17 }common line | + {1: }{10: 18 }something │{1: }{10: 18 }something | + {3:Xtest-functional-diff-screen-1.2 }{7:Xtest-functional-diff-screen-1 [+] }| + :1,12diffget | + ]]) + end) + it('get all from first window', function() + feed('1<c-w>w') + feed(':1,19diffget<cr>') + screen:expect([[ + {1: }{10: 1 }^common line │{1: }{10: 1 }common line | + {1: }{10: 2 }HIL │{1: }{10: 2 }HIL | + {1: }{10: 3 } │{1: }{10: 3 } | + {1: }{10: 4 }aABCabc │{1: }{10: 4 }aABCabc | + {1: }{10: 5 }aABCabc │{1: }{10: 5 }aABCabc | + {1: }{10: 6 }aABCabc │{1: }{10: 6 }aABCabc | + {1: }{10: 7 }aABCabc │{1: }{10: 7 }aABCabc | + {1: }{10: 8 }common line │{1: }{10: 8 }common line | + {1: }{10: 9 }HIL │{1: }{10: 9 }HIL | + {1: }{10: 10 }common line │{1: }{10: 10 }common line | + {1: }{10: 11 }something │{1: }{10: 11 }something | + {1: }{10: 12 } │{1: }{10: 12 } | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {7:Xtest-functional-diff-screen-1.2 [+] }{3:Xtest-functional-diff-screen-1 }| + :1,19diffget | + ]]) + end) + it('get part of the non linematched diff block in window 2 line 7 - 8 (non line matched block)', function() + feed('2<c-w>w') + feed(':7,8diffget<cr>') + screen:expect([[ + {1: }{10: 1 }common line │{1: }{10: 1 }^common line | + {1: }{10: 2 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 3 }{8:GHI}{9: }│{1: }{10: 2 }{8:HIL}{9: }| + {1: }{10: 4 }{4:something }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 5 } │{1: }{10: 3 } | + {1: }{10: 6 }{9:a}{8:DEF}{9:abc }│{1: }{10: 4 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 7 }{8:xyz}{9: }│{1: }{10: 5 }{8:aABCabc}{9: }| + {1: }{10: 8 }{8:xyz}{9: }│{1: }{10: 6 }{8:aABCabc}{9: }| + {1: }{10: 9 }xyz │{1: }{10: 7 }xyz | + {1: }{10: 10 }aDEFabc │{1: }{10: 8 }aDEFabc | + {1: }{10: 11 }aDEFabc │{1: }{10: 9 }aDEFabc | + {1: }{10: 12 }aDEFabc │{1: }{10: 10 }aDEFabc | + {1: }{10: 13 }common line │{1: }{10: 11 }common line | + {1: }{10: 14 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 15 }{8:GHI}{9: }│{1: }{10: 12 }{8:HIL}{9: }| + {1: }{10: 16 }{4:something else }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 17 }common line │{1: }{10: 13 }common line | + {1: }{10: 18 }something │{1: }{10: 14 }something | + {3:Xtest-functional-diff-screen-1.2 }{7:Xtest-functional-diff-screen-1 [+] }| + :7,8diffget | + ]]) + end) + it('get part of the non linematched diff block in window 2 line 8 - 10 (line matched block)', function() + feed('2<c-w>w') + feed(':8,10diffget<cr>') + screen:expect([[ + {1: }{10: 1 }common line │{1: }{10: 1 }^common line | + {1: }{10: 2 }{4:DEF }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 3 }{8:GHI}{9: }│{1: }{10: 2 }{8:HIL}{9: }| + {1: }{10: 4 }{4:something }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 5 } │{1: }{10: 3 } | + {1: }{10: 6 }{9:a}{8:DEF}{9:abc }│{1: }{10: 4 }{9:a}{8:ABC}{9:abc }| + {1: }{10: 7 }{8:xyz}{9: }│{1: }{10: 5 }{8:aABCabc}{9: }| + {1: }{10: 8 }{8:xyz}{9: }│{1: }{10: 6 }{8:aABCabc}{9: }| + {1: }{10: 9 }{8:xyz}{9: }│{1: }{10: 7 }{8:aABCabc}{9: }| + {1: }{10: 10 }{4:aDEFabc }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 11 }{4:aDEFabc }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 12 }{4:aDEFabc }│{1: }{10: }{2:--------------------------------------------}| + {1: }{10: 13 }common line │{1: }{10: 8 }common line | + {1: }{10: 14 }DEF │{1: }{10: 9 }DEF | + {1: }{10: 15 }GHI │{1: }{10: 10 }GHI | + {1: }{10: 16 }something else │{1: }{10: 11 }something else | + {1: }{10: 17 }common line │{1: }{10: 12 }common line | + {1: }{10: 18 }something │{1: }{10: 13 }something | + {3:Xtest-functional-diff-screen-1.2 }{7:Xtest-functional-diff-screen-1 [+] }| + :8,10diffget | + ]]) + end) + end) +end) + +describe('regressions', function() + local screen + + it("doesn't crash with long lines", function() + clear() + feed(':set diffopt+=linematch:30<cr>') + screen = Screen.new(100, 20) + screen:attach() + -- line must be greater than MATCH_CHAR_MAX_LEN + helpers.curbufmeths.set_lines(0, -1, false, { string.rep('a', 1000)..'hello' }) + helpers.exec 'vnew' + helpers.curbufmeths.set_lines(0, -1, false, { string.rep('a', 1010)..'world' }) + helpers.exec 'windo diffthis' + end) +end) diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 0898d7fe75..3052a74f38 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -9,10 +9,13 @@ local meths = helpers.meths local async_meths = helpers.async_meths local test_build_dir = helpers.test_build_dir local nvim_prog = helpers.nvim_prog -local iswin = helpers.iswin +local exec = helpers.exec local exc_exec = helpers.exc_exec local exec_lua = helpers.exec_lua local poke_eventloop = helpers.poke_eventloop +local assert_alive = helpers.assert_alive +local is_os = helpers.is_os +local is_ci = helpers.is_ci describe('ui/ext_messages', function() local screen @@ -905,6 +908,13 @@ stack traceback: {1:~ }| ]]} end) + + it('does not truncate messages', function() + command('write Xtest') + screen:expect({messages={ + {content = { { '"Xtest" [New] 0L, 0B written' } }, kind = "" } + }}) + end) end) describe('ui/builtin messages', function() @@ -1234,6 +1244,45 @@ vimComment xxx match /\s"[^\-:.%#=*].*$/ms=s+1,lc=1 excludenl contains=@vim bar^ | ]]) end) + + it('consecutive calls to win_move_statusline() work after multiline message #21014',function() + async_meths.exec([[ + echo "\n" + call win_move_statusline(0, -4) + call win_move_statusline(0, 4) + ]], false) + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {3: }| + | + {4:Press ENTER or type command to continue}^ | + ]]) + feed('<CR>') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + eq(1, meths.get_option('cmdheight')) + end) +end) + +it('calling screenstring() after redrawing between messages without UI #20999', function() + clear() + exec([[ + echo repeat('a', 100) + redraw + echo "\n" + call screenstring(1, 1) + ]]) + assert_alive() end) describe('ui/ext_messages', function() @@ -1263,7 +1312,6 @@ describe('ui/ext_messages', function() {1:~ }| {1:~ }| {1:~ }| - {1:~ }| {MATCH:.*}| {1:~ }| {1:~ }Nvim is open source and freely distributable{1: }| @@ -1274,6 +1322,8 @@ describe('ui/ext_messages', function() {1:~ }type :q{5:<Enter>} to exit {1: }| {1:~ }type :help{5:<Enter>} for help {1: }| {1:~ }| + {1:~ }type :help news{5:<Enter>} to see changes in v{MATCH:%d+%.%d+}| + {1:~ }| {MATCH:.*}| {MATCH:.*}| {1:~ }| @@ -1281,7 +1331,6 @@ describe('ui/ext_messages', function() {1:~ }| {1:~ }| {1:~ }| - {1:~ }| ]]) feed("<c-l>") @@ -1319,7 +1368,6 @@ describe('ui/ext_messages', function() | | | - | {MATCH:.*}| | Nvim is open source and freely distributable | @@ -1330,6 +1378,8 @@ describe('ui/ext_messages', function() type :q{5:<Enter>} to exit | type :help{5:<Enter>} for help | | + type :help news{5:<Enter>} to see changes in v{MATCH:%d+%.%d+}| + | {MATCH:.*}| {MATCH:.*}| | @@ -1337,7 +1387,6 @@ describe('ui/ext_messages', function() | | | - | ]], messages={ {content = { { "Press ENTER or type command to continue", 4 } }, kind = "return_prompt" } }} @@ -1410,7 +1459,6 @@ describe('ui/ext_messages', function() feed(":set mouse=a<cr>") meths.input_mouse('left', 'press', '', 0, 12, 10) poke_eventloop() - meths.input_mouse('left', 'drag', '', 0, 12, 10) meths.input_mouse('left', 'drag', '', 0, 11, 10) feed("<c-l>") feed(":set cmdheight<cr>") @@ -1455,7 +1503,7 @@ describe('ui/msg_puts_printf', function() screen = Screen.new(25, 5) screen:attach() - if iswin() then + if is_os('win') then if os.execute('chcp 932 > NUL 2>&1') ~= 0 then pending('missing japanese language features', function() end) return @@ -1466,7 +1514,7 @@ describe('ui/msg_puts_printf', function() if (exc_exec('lang ja_JP.UTF-8') ~= 0) then pending('Locale ja_JP.UTF-8 not supported', function() end) return - elseif helpers.isCI() then + elseif is_ci() then -- Fails non--Windows CI. Message catalog directory issue? pending('fails on unix CI', function() end) return diff --git a/test/functional/ui/mode_spec.lua b/test/functional/ui/mode_spec.lua index 9390f268b3..cf4eb034e0 100644 --- a/test/functional/ui/mode_spec.lua +++ b/test/functional/ui/mode_spec.lua @@ -17,6 +17,7 @@ describe('ui mode_change event', function() [1] = {bold=true, reverse=true}, [2] = {bold=true}, [3] = {reverse=true}, + [4] = {background=Screen.colors.Red, foreground=Screen.colors.White}, -- ErrorMsg }) end) @@ -43,6 +44,25 @@ describe('ui mode_change event', function() {0:~ }| | ]], mode="normal"} + + screen:try_resize(50, 4) + command('set nomodifiable') + + feed('c') + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }| + | + ]], mode="operator"} + + feed('c') + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }| + {4:E21: Cannot make changes, 'modifiable' is off} | + ]], mode="normal"} end) it('works in insert mode', function() diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index cb8dfdb8e1..f705678bd5 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -4,6 +4,7 @@ local clear, feed, meths = helpers.clear, helpers.feed, helpers.meths local insert, feed_command = helpers.insert, helpers.feed_command local eq, funcs = helpers.eq, helpers.funcs local command = helpers.command +local exec = helpers.exec describe('ui/mouse/input', function() local screen @@ -967,6 +968,49 @@ describe('ui/mouse/input', function() ]]) end) + it("'sidescrolloff' applies to horizontal scrolling", function() + command('set nowrap') + command('set sidescrolloff=4') + + feed("I <esc>020ib<esc>0") + screen:expect([[ + testing | + mouse | + ^bbbbbbbbbbbbbbbbbbbb supp| + {0:~ }| + | + ]]) + + meths.input_mouse('wheel', 'right', '', 0, 0, 27) + screen:expect([[ + g | + | + bbbb^bbbbbbbbbb support an| + {0:~ }| + | + ]]) + + -- window-local 'sidescrolloff' should override global value. #21162 + command('setlocal sidescrolloff=2') + feed('0') + screen:expect([[ + testing | + mouse | + ^bbbbbbbbbbbbbbbbbbbb supp| + {0:~ }| + | + ]]) + + meths.input_mouse('wheel', 'right', '', 0, 0, 27) + screen:expect([[ + g | + | + bb^bbbbbbbbbbbb support an| + {0:~ }| + | + ]]) + end) + describe('on concealed text', function() -- Helpful for reading the test expectations: -- :match Error /\^/ @@ -1687,4 +1731,133 @@ describe('ui/mouse/input', function() helpers.poke_eventloop() helpers.assert_alive() end) + + it('mousemodel=popup_setpos', function() + screen:try_resize(80, 24) + exec([[ + 5new + call setline(1, ['the dish ran away with the spoon', + \ 'the cow jumped over the moon' ]) + + set mouse=a mousemodel=popup_setpos + + aunmenu PopUp + nmenu PopUp.foo :let g:menustr = 'foo'<CR> + nmenu PopUp.bar :let g:menustr = 'bar'<CR> + nmenu PopUp.baz :let g:menustr = 'baz'<CR> + vmenu PopUp.foo y:<C-U>let g:menustr = 'foo'<CR> + vmenu PopUp.bar y:<C-U>let g:menustr = 'bar'<CR> + vmenu PopUp.baz y:<C-U>let g:menustr = 'baz'<CR> + ]]) + + meths.win_set_cursor(0, {1, 0}) + meths.input_mouse('right', 'press', '', 0, 0, 4) + meths.input_mouse('right', 'release', '', 0, 0, 4) + feed('<Down><Down><CR>') + eq('bar', meths.get_var('menustr')) + eq({1, 4}, meths.win_get_cursor(0)) + + -- Test for right click in visual mode inside the selection + funcs.setreg('"', '') + meths.win_set_cursor(0, {1, 9}) + feed('vee') + meths.input_mouse('right', 'press', '', 0, 0, 11) + meths.input_mouse('right', 'release', '', 0, 0, 11) + feed('<Down><CR>') + eq({1, 9}, meths.win_get_cursor(0)) + eq('ran away', funcs.getreg('"')) + + -- Test for right click in visual mode right before the selection + funcs.setreg('"', '') + meths.win_set_cursor(0, {1, 9}) + feed('vee') + meths.input_mouse('right', 'press', '', 0, 0, 8) + meths.input_mouse('right', 'release', '', 0, 0, 8) + feed('<Down><CR>') + eq({1, 8}, meths.win_get_cursor(0)) + eq('', funcs.getreg('"')) + + -- Test for right click in visual mode right after the selection + funcs.setreg('"', '') + meths.win_set_cursor(0, {1, 9}) + feed('vee') + meths.input_mouse('right', 'press', '', 0, 0, 17) + meths.input_mouse('right', 'release', '', 0, 0, 17) + feed('<Down><CR>') + eq({1, 17}, meths.win_get_cursor(0)) + eq('', funcs.getreg('"')) + + -- Test for right click in block-wise visual mode inside the selection + funcs.setreg('"', '') + meths.win_set_cursor(0, {1, 15}) + feed('<C-V>j3l') + meths.input_mouse('right', 'press', '', 0, 1, 16) + meths.input_mouse('right', 'release', '', 0, 1, 16) + feed('<Down><CR>') + eq({1, 15}, meths.win_get_cursor(0)) + eq('\0224', funcs.getregtype('"')) + + -- Test for right click in block-wise visual mode outside the selection + funcs.setreg('"', '') + meths.win_set_cursor(0, {1, 15}) + feed('<C-V>j3l') + meths.input_mouse('right', 'press', '', 0, 1, 1) + meths.input_mouse('right', 'release', '', 0, 1, 1) + feed('<Down><CR>') + eq({2, 1}, meths.win_get_cursor(0)) + eq('v', funcs.getregtype('"')) + eq('', funcs.getreg('"')) + + -- Test for right click in line-wise visual mode inside the selection + funcs.setreg('"', '') + meths.win_set_cursor(0, {1, 15}) + feed('V') + meths.input_mouse('right', 'press', '', 0, 0, 9) + meths.input_mouse('right', 'release', '', 0, 0, 9) + feed('<Down><CR>') + eq({1, 0}, meths.win_get_cursor(0)) -- After yanking, the cursor goes to 1,1 + eq('V', funcs.getregtype('"')) + eq(1, #funcs.getreg('"', 1, true)) + + -- Test for right click in multi-line line-wise visual mode inside the selection + funcs.setreg('"', '') + meths.win_set_cursor(0, {1, 15}) + feed('Vj') + meths.input_mouse('right', 'press', '', 0, 1, 19) + meths.input_mouse('right', 'release', '', 0, 1, 19) + feed('<Down><CR>') + eq({1, 0}, meths.win_get_cursor(0)) -- After yanking, the cursor goes to 1,1 + eq('V', funcs.getregtype('"')) + eq(2, #funcs.getreg('"', 1, true)) + + -- Test for right click in line-wise visual mode outside the selection + funcs.setreg('"', '') + meths.win_set_cursor(0, {1, 15}) + feed('V') + meths.input_mouse('right', 'press', '', 0, 1, 9) + meths.input_mouse('right', 'release', '', 0, 1, 9) + feed('<Down><CR>') + eq({2, 9}, meths.win_get_cursor(0)) + eq('', funcs.getreg('"')) + + -- Try clicking on the status line + funcs.setreg('"', '') + meths.win_set_cursor(0, {1, 9}) + feed('vee') + meths.input_mouse('right', 'press', '', 0, 5, 1) + meths.input_mouse('right', 'release', '', 0, 5, 1) + feed('<Down><CR>') + eq({1, 9}, meths.win_get_cursor(0)) + eq('ran away', funcs.getreg('"')) + + -- Try clicking outside the window + funcs.setreg('"', '') + meths.win_set_cursor(0, {2, 1}) + feed('vee') + meths.input_mouse('right', 'press', '', 0, 6, 1) + meths.input_mouse('right', 'release', '', 0, 6, 1) + feed('<Down><CR>') + eq(2, funcs.winnr()) + eq('', funcs.getreg('"')) + end) end) diff --git a/test/functional/ui/multigrid_spec.lua b/test/functional/ui/multigrid_spec.lua index 78a1e8c677..71adeb42a4 100644 --- a/test/functional/ui/multigrid_spec.lua +++ b/test/functional/ui/multigrid_spec.lua @@ -2381,8 +2381,52 @@ describe('ext_multigrid', function() end) it('with winbar', function() - command 'split' - command 'setlocal winbar=very\\ bar' + command('split') + screen:expect{grid=[[ + ## grid 1 + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] }| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + {12:[No Name] }| + [3:-----------------------------------------------------]| + ## grid 2 + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 4 + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]], win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + }} + + -- XXX: hack to get notifications. Could use next_msg() also. + local orig_handle_win_pos = screen._handle_win_pos + local win_pos = {} + function screen._handle_win_pos(self, grid, win, startrow, startcol, width, height) + table.insert(win_pos, {grid, win, startrow, startcol, width, height}) + orig_handle_win_pos(self, grid, win, startrow, startcol, width, height) + end + + command('setlocal winbar=very%=bar') screen:expect{grid=[[ ## grid 1 [4:-----------------------------------------------------]| @@ -2408,7 +2452,7 @@ describe('ext_multigrid', function() ## grid 3 | ## grid 4 - {7:very bar }| + {7:very bar}| ^ | {1:~ }| {1:~ }| @@ -2418,6 +2462,45 @@ describe('ext_multigrid', function() [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; }} + eq({}, win_pos) + + command('setlocal winbar=') + screen:expect{grid=[[ + ## grid 1 + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] }| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + {12:[No Name] }| + [3:-----------------------------------------------------]| + ## grid 2 + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 4 + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]], win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + }} + eq({}, win_pos) end) it('with winbar dragging statusline with mouse works correctly', function() diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua index 6f9cea8f24..9d20229ce1 100644 --- a/test/functional/ui/options_spec.lua +++ b/test/functional/ui/options_spec.lua @@ -24,6 +24,7 @@ describe('UI receives option updates', function() termguicolors=false, ttimeout=true, ttimeoutlen=50, + verbose=0, ext_cmdline=false, ext_popupmenu=false, ext_tabline=false, diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua index 9bb067ed8e..223844405e 100644 --- a/test/functional/ui/output_spec.lua +++ b/test/functional/ui/output_spec.lua @@ -6,13 +6,14 @@ local mkdir, write_file, rmdir = helpers.mkdir, helpers.write_file, helpers.rmdi local eq = helpers.eq local feed = helpers.feed local feed_command = helpers.feed_command -local iswin = helpers.iswin local clear = helpers.clear local command = helpers.command local testprg = helpers.testprg local nvim_dir = helpers.nvim_dir local has_powershell = helpers.has_powershell local set_shell_powershell = helpers.set_shell_powershell +local skip = helpers.skip +local is_os = helpers.is_os describe("shell command :!", function() local screen @@ -36,7 +37,7 @@ describe("shell command :!", function() end) it("displays output without LF/EOF. #4646 #4569 #3772", function() - if helpers.pending_win32(pending) then return end + skip(is_os('win')) -- NOTE: We use a child nvim (within a :term buffer) -- to avoid triggering a UI flush. child_session.feed_data(":!printf foo; sleep 200\n") @@ -52,9 +53,7 @@ describe("shell command :!", function() end) it("throttles shell-command output greater than ~10KB", function() - if 'openbsd' == helpers.uname() then - pending('FIXME #10804') - end + skip(is_os('openbsd'), 'FIXME #10804') child_session.feed_data((":!%s REP 30001 foo\n"):format(testprg('shell-test'))) -- If we observe any line starting with a dot, then throttling occurred. @@ -96,9 +95,7 @@ describe("shell command :!", function() end) it('handles control codes', function() - if iswin() then - pending('missing printf') - end + skip(is_os('win'), 'missing printf') local screen = Screen.new(50, 4) screen:set_default_attr_ids { [1] = {bold = true, reverse = true}; @@ -173,10 +170,10 @@ describe("shell command :!", function() end) it("doesn't truncate Last line of shell output #3269", function() - command(helpers.iswin() + command(is_os('win') and [[nnoremap <silent>\l :!dir /b bang_filter_spec<cr>]] or [[nnoremap <silent>\l :!ls bang_filter_spec<cr>]]) - local result = (helpers.iswin() + local result = (is_os('win') and [[:!dir /b bang_filter_spec]] or [[:!ls bang_filter_spec ]]) feed([[\l]]) @@ -215,7 +212,7 @@ describe("shell command :!", function() it('handles multibyte sequences split over buffer boundaries', function() command('cd '..nvim_dir) - local cmd = iswin() and '!shell-test UTF-8 ' or '!./shell-test UTF-8' + local cmd = is_os('win') and '!shell-test UTF-8 ' or '!./shell-test UTF-8' feed_command(cmd) -- Note: only the first example of split composed char works screen:expect([[ @@ -265,7 +262,7 @@ describe("shell command :!", function() | Press ENTER or type command to continue^ | ]]) - if iswin() then + if is_os('win') then feed_command([[!& 'cmd.exe' /c 'echo $a']]) screen:expect([[ :!& 'cmd.exe' /c 'echo $a' | diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 3c752875f0..c681453294 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -259,174 +259,339 @@ describe('ui/ext_popupmenu', function() {2:-- INSERT --} | ]]) - command('imap <f1> <cmd>call nvim_select_popupmenu_item(2,v:true,v:false,{})<cr>') - command('imap <f2> <cmd>call nvim_select_popupmenu_item(-1,v:false,v:false,{})<cr>') - command('imap <f3> <cmd>call nvim_select_popupmenu_item(1,v:false,v:true,{})<cr>') - feed('<C-r>=TestComplete()<CR>') - screen:expect{grid=[[ + command('set wildmenu') + command('set wildoptions=pum') + local expected_wildpum = { + { "define", "", "", "" }, + { "jump", "", "", "" }, + { "list", "", "", "" }, + { "place", "", "", "" }, + { "undefine", "", "", "" }, + { "unplace", "", "", "" }, + } + feed('<Esc>:sign <Tab>') + screen:expect({grid = [[ + | | - foo^ | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| - {2:-- INSERT --} | - ]], popupmenu={ - items=expected, - pos=0, - anchor={1,1,0}, - }} + :sign define^ | + ]], popupmenu = { + items = expected_wildpum, + pos = 0, + anchor = { 1, 7, 6 }, + }}) - feed('<f1>') - screen:expect{grid=[[ + meths.select_popupmenu_item(-1, true, false, {}) + screen:expect({grid = [[ + | | - spam^ | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| - {2:-- INSERT --} | - ]], popupmenu={ - items=expected, - pos=2, - anchor={1,1,0}, - }} + :sign ^ | + ]], popupmenu = { + items = expected_wildpum, + pos = -1, + anchor = { 1, 7, 6 }, + }}) - feed('<f2>') - screen:expect{grid=[[ + meths.select_popupmenu_item(5, true, false, {}) + screen:expect({grid = [[ + | | - spam^ | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| - {2:-- INSERT --} | - ]], popupmenu={ - items=expected, - pos=-1, - anchor={1,1,0}, - }} + :sign unplace^ | + ]], popupmenu = { + items = expected_wildpum, + pos = 5, + anchor = { 1, 7, 6 }, + }}) - feed('<f3>') - screen:expect([[ + meths.select_popupmenu_item(-1, true, true, {}) + screen:expect({grid = [[ + | | - bar^ | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| - {2:-- INSERT --} | - ]]) + :sign ^ | + ]]}) - -- also should work for builtin popupmenu - screen:set_option('ext_popupmenu', false) - feed('<C-r>=TestComplete()<CR>') - screen:expect([[ + feed('<Tab>') + screen:expect({grid = [[ | - 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 --} | - ]]) + {1:~ }| + :sign define^ | + ]], popupmenu = { + items = expected_wildpum, + pos = 0, + anchor = { 1, 7, 6 }, + }}) - feed('<f3>') - screen:expect([[ + meths.select_popupmenu_item(5, true, true, {}) + screen:expect({grid = [[ + | | - bar^ | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| - {2:-- INSERT --} | - ]]) + :sign unplace^ | + ]]}) - 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 --} | - ]]) + local function test_pum_select_mappings() + screen:set_option('ext_popupmenu', true) + feed('<Esc>A<C-r>=TestComplete()<CR>') + screen:expect{grid=[[ + | + foo^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]], popupmenu={ + items=expected, + pos=0, + anchor={1,1,0}, + }} - feed('<f1>') - screen:expect([[ - | - spam^ | - {7:fo x the foo }{1: }| - {7:bar }{1: }| - {6:spam }{1: }| - {1:~ }| - {1:~ }| - {2:-- INSERT --} | - ]]) + feed('<f1>') + screen:expect{grid=[[ + | + spam^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]], popupmenu={ + items=expected, + pos=2, + anchor={1,1,0}, + }} - feed('<f2>') - screen:expect([[ - | - spam^ | - {7:fo x the foo }{1: }| - {7:bar }{1: }| - {7:spam }{1: }| - {1:~ }| - {1:~ }| - {2:-- INSERT --} | - ]]) + feed('<f2>') + screen:expect{grid=[[ + | + spam^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]], popupmenu={ + items=expected, + pos=-1, + anchor={1,1,0}, + }} - feed('<f3>') - screen:expect([[ - | - bar^ | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {2:-- INSERT --} | + feed('<f3>') + screen:expect([[ + | + bar^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + + feed('<Esc>:sign <Tab>') + screen:expect({grid = [[ + | + bar | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :sign define^ | + ]], popupmenu = { + items = expected_wildpum, + pos = 0, + anchor = { 1, 7, 6 }, + }}) + + feed('<f1>') + screen:expect({grid = [[ + | + bar | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :sign list^ | + ]], popupmenu = { + items = expected_wildpum, + pos = 2, + anchor = { 1, 7, 6 }, + }}) + + feed('<f2>') + screen:expect({grid = [[ + | + bar | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :sign ^ | + ]], popupmenu = { + items = expected_wildpum, + pos = -1, + anchor = { 1, 7, 6 }, + }}) + + feed('<f3>') + screen:expect({grid = [[ + | + bar | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :sign jump^ | + ]]}) + + -- also should work for builtin popupmenu + screen:set_option('ext_popupmenu', false) + feed('<Esc>A<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>:sign <Tab>') + screen:expect([[ + | + bar {6: define } | + {1:~ }{7: jump }{1: }| + {1:~ }{7: list }{1: }| + {1:~ }{7: place }{1: }| + {1:~ }{7: undefine }{1: }| + {1:~ }{7: unplace }{1: }| + :sign define^ | + ]]) + + feed('<f1>') + screen:expect([[ + | + bar {7: define } | + {1:~ }{7: jump }{1: }| + {1:~ }{6: list }{1: }| + {1:~ }{7: place }{1: }| + {1:~ }{7: undefine }{1: }| + {1:~ }{7: unplace }{1: }| + :sign list^ | + ]]) + + feed('<f2>') + screen:expect([[ + | + bar {7: define } | + {1:~ }{7: jump }{1: }| + {1:~ }{7: list }{1: }| + {1:~ }{7: place }{1: }| + {1:~ }{7: undefine }{1: }| + {1:~ }{7: unplace }{1: }| + :sign ^ | + ]]) + + feed('<f3>') + screen:expect([[ + | + bar | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :sign jump^ | + ]]) + end + + command('map! <f1> <cmd>call nvim_select_popupmenu_item(2,v:true,v:false,{})<cr>') + command('map! <f2> <cmd>call nvim_select_popupmenu_item(-1,v:false,v:false,{})<cr>') + command('map! <f3> <cmd>call nvim_select_popupmenu_item(1,v:false,v:true,{})<cr>') + test_pum_select_mappings() + + command('unmap! <f1>') + command('unmap! <f2>') + command('unmap! <f3>') + exec_lua([[ + vim.keymap.set('!', '<f1>', function() vim.api.nvim_select_popupmenu_item(2, true, false, {}) end) + vim.keymap.set('!', '<f2>', function() vim.api.nvim_select_popupmenu_item(-1, false, false, {}) end) + vim.keymap.set('!', '<f3>', function() vim.api.nvim_select_popupmenu_item(1, false, true, {}) end) ]]) + test_pum_select_mappings() feed('<esc>ddiaa bb cc<cr>') feed('<c-x><c-n>') @@ -780,6 +945,82 @@ describe('ui/ext_popupmenu', function() }} end) + + it('does not interfere with mousemodel=popup', function() + exec([[ + set mouse=a mousemodel=popup + + aunmenu PopUp + menu PopUp.foo :let g:menustr = 'foo'<CR> + menu PopUp.bar :let g:menustr = 'bar'<CR> + menu PopUp.baz :let g:menustr = 'baz'<CR> + ]]) + feed('o<C-r>=TestComplete()<CR>') + screen:expect{grid=[[ + | + foo^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]], popupmenu={ + items=expected, + pos=0, + anchor={1,1,0}, + }} + + feed('<c-p>') + screen:expect{grid=[[ + | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]], popupmenu={ + items=expected, + pos=-1, + anchor={1,1,0}, + }} + + feed('<esc>') + screen:expect{grid=[[ + | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + feed('<RightMouse><0,0>') + screen:expect([[ + | + {7:^foo } | + {7:bar }{1: }| + {7:baz }{1: }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + feed('<esc>') + screen:expect([[ + | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) end) @@ -802,6 +1043,8 @@ describe('builtin popupmenu', function() [4] = {bold = true, reverse = true}, [5] = {bold = true, foreground = Screen.colors.SeaGreen}, [6] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [7] = {background = Screen.colors.Yellow}, -- Search + [8] = {foreground = Screen.colors.Red}, }) end) @@ -949,6 +1192,66 @@ describe('builtin popupmenu', function() ]]) end) + -- oldtest: Test_pum_with_preview_win() + it('preview window opened during completion', function() + exec([[ + funct Omni_test(findstart, base) + if a:findstart + return col(".") - 1 + endif + return [#{word: "one", info: "1info"}, #{word: "two", info: "2info"}, #{word: "three", info: "3info"}] + endfunc + set omnifunc=Omni_test + set completeopt+=longest + ]]) + feed('Gi<C-X><C-O>') + screen:expect([[ + ^ | + {n:one }{1: }| + {n:two }{1: }| + {n:three }{1: }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- }{8:Back at original} | + ]]) + feed('<C-N>') + screen:expect([[ + 1info | + | + {1:~ }| + {3:[Scratch] [Preview] }| + one^ | + {s:one }{1: }| + {n:two }{1: }| + {n:three }{1: }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:[No Name] [+] }| + {2:-- }{5:match 1 of 3} | + ]]) + end) + it('with vsplits', function() insert('aaa aab aac\n') feed(':vsplit<cr>') @@ -2222,8 +2525,130 @@ describe('builtin popupmenu', function() {1:~ }{n: xyz }{1: }| :e あいう/123^ | ]]) + feed('<Esc>') - feed('<esc>') + -- Pressing <PageDown> should scroll the menu downward + feed(':sign <Tab><PageDown>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{n: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{s: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign undefine^ | + ]]) + feed('<PageDown>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{n: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{s: unplace }{1: }| + :sign unplace^ | + ]]) + feed('<PageDown>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{n: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign ^ | + ]]) + feed('<PageDown>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{s: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign define^ | + ]]) + feed('<C-U>sign <Tab><Right><Right><PageDown>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{n: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{s: unplace }{1: }| + :sign unplace^ | + ]]) + + -- Pressing <PageUp> should scroll the menu upward + feed('<C-U>sign <Tab><PageUp>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{n: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign ^ | + ]]) + feed('<PageUp>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{n: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{s: unplace }{1: }| + :sign unplace^ | + ]]) + feed('<PageUp>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{n: define }{1: }| + {1:~ }{s: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign jump^ | + ]]) + feed('<PageUp>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{s: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign define^ | + ]]) + + feed('<Esc>') -- check positioning with multibyte char in pattern command("e långfile1") @@ -2320,7 +2745,7 @@ describe('builtin popupmenu', function() ]]) end) - it('wildoptions=pum with scrolled messages ', function() + it('wildoptions=pum with scrolled messages', function() screen:try_resize(40,10) command('set wildmenu') command('set wildoptions=pum') @@ -2417,6 +2842,49 @@ describe('builtin popupmenu', function() ]]} end) + it('wildoptions=pum with a wrapped line in buffer vim-patch:8.2.4655', function() + screen:try_resize(32, 10) + meths.buf_set_lines(0, 0, -1, true, { ('a'):rep(100) }) + command('set wildoptions+=pum') + feed('$') + feed(':sign <Tab>') + screen:expect([[ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaa {s: define } | + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign define^ | + ]]) + end) + + -- oldtest: Test_wildmenu_pum_clear_entries() + it('wildoptions=pum when using Ctrl-E as wildchar vim-patch:9.0.1030', function() + screen:try_resize(30, 10) + exec([[ + set wildoptions=pum + set wildchar=<C-E> + ]]) + feed(':sign <C-E><C-E>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{s: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign define^ | + ]]) + assert_alive() + end) + it("'pumblend' RGB-color", function() screen:try_resize(60,14) screen:set_default_attr_ids({ @@ -2872,6 +3340,156 @@ describe('builtin popupmenu', function() eq(false, screen.options.mousemoveevent) eq('bar', meths.get_var('menustr')) end) + + -- oldtest: Test_popup_command_dump() + it(':popup command', function() + exec([[ + func ChangeMenu() + aunmenu PopUp.&Paste + nnoremenu 1.40 PopUp.&Paste :echomsg "pasted"<CR> + echomsg 'changed' + return "\<Ignore>" + endfunc + + let lines =<< trim END + one two three four five + and one two Xthree four five + one more two three four five + END + call setline(1, lines) + + aunmenu * + source $VIMRUNTIME/menu.vim + ]]) + feed('/X<CR>:popup PopUp<CR>') + screen:expect([[ + one two three four five | + and one two {7:^X}three four five | + one more tw{n: Undo } | + {1:~ }{n: }{1: }| + {1:~ }{n: Paste }{1: }| + {1:~ }{n: }{1: }| + {1:~ }{n: Select Word }{1: }| + {1:~ }{n: Select Sentence }{1: }| + {1:~ }{n: Select Paragraph }{1: }| + {1:~ }{n: Select Line }{1: }| + {1:~ }{n: Select Block }{1: }| + {1:~ }{n: Select All }{1: }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :popup PopUp | + ]]) + + -- go to the Paste entry in the menu + feed('jj') + screen:expect([[ + one two three four five | + and one two {7:^X}three four five | + one more tw{n: Undo } | + {1:~ }{n: }{1: }| + {1:~ }{s: Paste }{1: }| + {1:~ }{n: }{1: }| + {1:~ }{n: Select Word }{1: }| + {1:~ }{n: Select Sentence }{1: }| + {1:~ }{n: Select Paragraph }{1: }| + {1:~ }{n: Select Line }{1: }| + {1:~ }{n: Select Block }{1: }| + {1:~ }{n: Select All }{1: }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :popup PopUp | + ]]) + + -- Select a word + feed('j') + screen:expect([[ + one two three four five | + and one two {7:^X}three four five | + one more tw{n: Undo } | + {1:~ }{n: }{1: }| + {1:~ }{n: Paste }{1: }| + {1:~ }{n: }{1: }| + {1:~ }{s: Select Word }{1: }| + {1:~ }{n: Select Sentence }{1: }| + {1:~ }{n: Select Paragraph }{1: }| + {1:~ }{n: Select Line }{1: }| + {1:~ }{n: Select Block }{1: }| + {1:~ }{n: Select All }{1: }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :popup PopUp | + ]]) + + feed('<Esc>') + + -- Set an <expr> mapping to change a menu entry while it's displayed. + -- The text should not change but the command does. + -- Also verify that "changed" shows up, which means the mapping triggered. + command('nnoremap <expr> <F2> ChangeMenu()') + feed('/X<CR>:popup PopUp<CR><F2>') + screen:expect([[ + one two three four five | + and one two {7:^X}three four five | + one more tw{n: Undo } | + {1:~ }{n: }{1: }| + {1:~ }{n: Paste }{1: }| + {1:~ }{n: }{1: }| + {1:~ }{n: Select Word }{1: }| + {1:~ }{n: Select Sentence }{1: }| + {1:~ }{n: Select Paragraph }{1: }| + {1:~ }{n: Select Line }{1: }| + {1:~ }{n: Select Block }{1: }| + {1:~ }{n: Select All }{1: }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + changed | + ]]) + + -- Select the Paste entry, executes the changed menu item. + feed('jj<CR>') + screen:expect([[ + one two three four five | + and one two {7:^X}three four five | + one more two three four five | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + pasted | + ]]) + end) end) describe('builtin popupmenu with ui/ext_multigrid', function() diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index c44e147c4d..3b9cce0e6f 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -101,13 +101,10 @@ end local default_screen_timeout = default_timeout_factor * 3500 -do - local spawn, nvim_prog = helpers.spawn, helpers.nvim_prog - local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '-N', '--embed'}) +function Screen._init_colors(session) local status, rv = session:request('nvim_get_color_map') if not status then - print('failed to get color map') - os.exit(1) + error('failed to get color map') end local colors = rv local colornames = {} @@ -116,12 +113,15 @@ do -- this is just a helper to get any canonical name of a color colornames[rgb] = name end - session:close() Screen.colors = colors Screen.colornames = colornames end function Screen.new(width, height) + if not Screen.colors then + Screen._init_colors(get_session()) + end + if not width then width = 53 end @@ -1550,7 +1550,8 @@ function Screen:_get_attr_id(attr_state, attrs, hl_id) attr_state.modified = true return id end - return "UNEXPECTED "..self:_pprint_attrs(self._attr_table[hl_id][1]) + local kind = self._options.rgb and 1 or 2 + return "UNEXPECTED "..self:_pprint_attrs(self._attr_table[hl_id][kind]) else if self:_equal_attrs(attrs, {}) then -- ignore this attrs diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index 58c8238c35..3bd2289a73 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -5,8 +5,8 @@ local feed, command = helpers.feed, helpers.command 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 +local is_os = helpers.is_os describe('screen', function() local screen @@ -128,18 +128,18 @@ 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 = (is_os('win') and 'myfile (C:\\mydir) - NVIM' or 'myfile (/mydir) - NVIM') command('set title') - command(iswin() and 'file C:\\mydir\\myfile' or 'file /mydir/myfile') + command(is_os('win') 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 file1 = is_os('win') and 'C:\\mydir\\myfile1' or '/mydir/myfile1' + local file2 = is_os('win') and 'C:\\mydir\\myfile2' or '/mydir/myfile2' + local expected = (is_os('win') and 'myfile1 (C:\\mydir) - NVIM' or 'myfile1 (/mydir) - NVIM') local buf2 before_each(function() @@ -894,6 +894,31 @@ local function screen_tests(linegrid) :ls^ | ]]) end) + + it('VimResized autocommand does not cause invalid UI events #20692 #20759', function() + feed('<Esc>') + command([[autocmd VimResized * redrawtabline]]) + command([[autocmd VimResized * lua vim.api.nvim_echo({ { 'Hello' } }, false, {})]]) + command([[autocmd VimResized * let g:echospace = v:echospace]]) + meths.set_option('showtabline', 2) + screen:expect([[ + {2: + [No Name] }{3: }| + resiz^e | + {0:~ }| + {0:~ }| + | + ]]) + screen:try_resize(30, 6) + screen:expect([[ + {2: + [No Name] }{3: }| + resiz^e | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + eq(29, meths.get_var('echospace')) + end) end) describe('press enter', function() @@ -1027,3 +1052,36 @@ describe('Screen default colors', function() end} end) end) + +it('CTRL-F or CTRL-B scrolls a page after UI attach/resize #20605', function() + clear() + local screen = Screen.new(100, 100) + screen:attach() + eq(100, meths.get_option('lines')) + eq(99, meths.get_option('window')) + eq(99, meths.win_get_height(0)) + feed('1000o<Esc>') + eq(903, funcs.line('w0')) + feed('<C-B>') + eq(806, funcs.line('w0')) + feed('<C-B>') + eq(709, funcs.line('w0')) + feed('<C-F>') + eq(806, funcs.line('w0')) + feed('<C-F>') + eq(903, funcs.line('w0')) + feed('G') + screen:try_resize(50, 50) + eq(50, meths.get_option('lines')) + eq(49, meths.get_option('window')) + eq(49, meths.win_get_height(0)) + eq(953, funcs.line('w0')) + feed('<C-B>') + eq(906, funcs.line('w0')) + feed('<C-B>') + eq(859, funcs.line('w0')) + feed('<C-F>') + eq(906, funcs.line('w0')) + feed('<C-F>') + eq(953, funcs.line('w0')) +end) diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index ff3e143126..7dcd4cff25 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -156,9 +156,12 @@ describe('Signs', function() {0:~ }| | ]]) + -- Check that 'statuscolumn' correctly applies numhl + command('set statuscolumn=%s%=%l\\ ') + screen:expect_unchanged() end) - it('higlights the cursorline sign with culhl', function() + it('highlights the cursorline sign with culhl', function() feed('ia<cr>b<cr>c<esc>') command('sign define piet text=>> texthl=Search culhl=ErrorMsg') command('sign place 1 line=1 name=piet buffer=1') @@ -233,11 +236,13 @@ describe('Signs', function() | ]]) command('set cursorlineopt=number') + command('hi! link SignColumn IncSearch') + feed('Go<esc>2G') screen:expect([[ {1:>>}a | {8:>>}^b | {1:>>}c | - {0:~ }| + {5: } | {0:~ }| {0:~ }| {0:~ }| @@ -249,6 +254,9 @@ describe('Signs', function() {0:~ }| | ]]) + -- Check that 'statuscolumn' cursorline/signcolumn highlights are the same (#21726) + command('set statuscolumn=%s') + screen:expect_unchanged() end) it('multiple signs #9295', function() diff --git a/test/functional/ui/spell_spec.lua b/test/functional/ui/spell_spec.lua index 1aa73e7b13..361f83d1ce 100644 --- a/test/functional/ui/spell_spec.lua +++ b/test/functional/ui/spell_spec.lua @@ -5,8 +5,8 @@ local Screen = require('test.functional.ui.screen') local clear = helpers.clear local feed = helpers.feed local insert = helpers.insert -local uname = helpers.uname local command = helpers.command +local is_os = helpers.is_os describe("'spell'", function() local screen @@ -27,7 +27,7 @@ describe("'spell'", function() end) it('joins long lines #7937', function() - if uname() == 'openbsd' then pending('FIXME #12104', function() end) return end + if is_os('openbsd') then pending('FIXME #12104', function() end) return end command('set spell') insert([[ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod @@ -51,6 +51,7 @@ describe("'spell'", function() end) + -- oldtest: Test_spell_screendump() it('has correct highlight at start of line', function() insert([[ "This is some text without any spell errors. Everything", @@ -70,7 +71,7 @@ describe("'spell'", function() "with missing caps here.", | ^ | | - ]]) + ]]) end) it('"noplainbuffer" and syntax #20385', function() diff --git a/test/functional/ui/statuscolumn_spec.lua b/test/functional/ui/statuscolumn_spec.lua new file mode 100644 index 0000000000..ae3b95fb0f --- /dev/null +++ b/test/functional/ui/statuscolumn_spec.lua @@ -0,0 +1,462 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local eval = helpers.eval +local exec_lua = helpers.exec_lua +local feed = helpers.feed +local meths = helpers.meths +local pcall_err = helpers.pcall_err + +describe('statuscolumn', function() + local screen + before_each(function() + clear('--cmd', 'set number nuw=1 | call setline(1, repeat(["aaaaa"], 16)) | norm GM') + screen = Screen.new() + screen:attach() + end) + + it("fails with invalid 'statuscolumn'", function() + command([[set stc=%{v:relnum?v:relnum:(v:lnum==5?invalid:v:lnum)}\ ]]) + screen:expect([[ + 4 aaaaa | + 3 aaaaa | + 2 aaaaa | + 1 aaaaa | + 8 ^aaaaa | + 1 aaaaa | + 2 aaaaa | + 3 aaaaa | + 4 aaaaa | + 5 aaaaa | + 6 aaaaa | + 7 aaaaa | + 8 aaaaa | + | + ]]) + command('norm 5G') + eq('Vim(redraw):E121: Undefined variable: invalid', pcall_err(command, 'redraw!')) + eq('', eval('&statuscolumn')) + end) + + it("widens with irregular 'statuscolumn' width", function() + command([[set stc=%{v:relnum?v:relnum:(v:lnum==5?'bbbbb':v:lnum)}]]) + command('norm 5G | redraw!') + screen:expect([[ + 1 aaaaa | + bbbbba^eaaa | + 1 aaaaa | + 2 aaaaa | + 3 aaaaa | + 4 aaaaa | + 5 aaaaa | + 6 aaaaa | + 7 aaaaa | + 8 aaaaa | + 9 aaaaa | + 10 aaaaa | + 11 aaaaa | + | + ]]) + end) + + it("works with 'number' and 'relativenumber'", function() + command([[set stc=%{&nu?v:lnum:''}%=%{&rnu?'\ '.v:relnum:''}│]]) + screen:expect([[ + 4 │aaaaa | + 5 │aaaaa | + 6 │aaaaa | + 7 │aaaaa | + 8 │^aaaaa | + 9 │aaaaa | + 10│aaaaa | + 11│aaaaa | + 12│aaaaa | + 13│aaaaa | + 14│aaaaa | + 15│aaaaa | + 16│aaaaa | + | + ]]) + command([[set stc=%l%=%{&rnu?'\ ':''}%r│]]) + screen:expect_unchanged() + command([[set stc=%{&nu?v:lnum:''}%=%{&rnu?'\ '.v:relnum:''}│]]) + command('set relativenumber') + screen:expect([[ + 4 4│aaaaa | + 5 3│aaaaa | + 6 2│aaaaa | + 7 1│aaaaa | + 8 0│^aaaaa | + 9 1│aaaaa | + 10 2│aaaaa | + 11 3│aaaaa | + 12 4│aaaaa | + 13 5│aaaaa | + 14 6│aaaaa | + 15 7│aaaaa | + 16 8│aaaaa | + | + ]]) + command([[set stc=%l%=%{&rnu?'\ ':''}%r│]]) + screen:expect_unchanged() + command([[set stc=%{&nu?v:lnum:''}%=%{&rnu?'\ '.v:relnum:''}│]]) + command('norm 12GH') + screen:expect([[ + 4 0│^aaaaa | + 5 1│aaaaa | + 6 2│aaaaa | + 7 3│aaaaa | + 8 4│aaaaa | + 9 5│aaaaa | + 10 6│aaaaa | + 11 7│aaaaa | + 12 8│aaaaa | + 13 9│aaaaa | + 14 10│aaaaa | + 15 11│aaaaa | + 16 12│aaaaa | + | + ]]) + command([[set stc=%l%=%{&rnu?'\ ':''}%r│]]) + screen:expect_unchanged() + command([[set stc=%{&nu?v:lnum:''}%=%{&rnu?'\ '.v:relnum:''}│]]) + end) + + it("works with highlighted 'statuscolumn'", function() + command([[set stc=%#NonText#%{&nu?v:lnum:''}]] .. + [[%=%{&rnu&&(v:lnum%2)?'\ '.v:relnum:''}]] .. + [[%#LineNr#%{&rnu&&!(v:lnum%2)?'\ '.v:relnum:''}│]]) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, + [1] = {foreground = Screen.colors.Brown}, + }) + screen:expect([[ + {0:4 }{1:│}aaaaa | + {0:5 }{1:│}aaaaa | + {0:6 }{1:│}aaaaa | + {0:7 }{1:│}aaaaa | + {0:8 }{1:│}^aaaaa | + {0:9 }{1:│}aaaaa | + {0:10}{1:│}aaaaa | + {0:11}{1:│}aaaaa | + {0:12}{1:│}aaaaa | + {0:13}{1:│}aaaaa | + {0:14}{1:│}aaaaa | + {0:15}{1:│}aaaaa | + {0:16}{1:│}aaaaa | + | + ]]) + command('set relativenumber') + screen:expect([[ + {0:4 }{1: 4│}aaaaa | + {0:5 3}{1:│}aaaaa | + {0:6 }{1: 2│}aaaaa | + {0:7 1}{1:│}aaaaa | + {0:8 }{1: 0│}^aaaaa | + {0:9 1}{1:│}aaaaa | + {0:10}{1: 2│}aaaaa | + {0:11 3}{1:│}aaaaa | + {0:12}{1: 4│}aaaaa | + {0:13 5}{1:│}aaaaa | + {0:14}{1: 6│}aaaaa | + {0:15 7}{1:│}aaaaa | + {0:16}{1: 8│}aaaaa | + | + ]]) + command('set nonumber') + screen:expect([[ + {1:4│}aaaaa | + {0:3}{1:│}aaaaa | + {1:2│}aaaaa | + {0:1}{1:│}aaaaa | + {1:0│}^aaaaa | + {0:1}{1:│}aaaaa | + {1:2│}aaaaa | + {0:3}{1:│}aaaaa | + {1:4│}aaaaa | + {0:5}{1:│}aaaaa | + {1:6│}aaaaa | + {0:7}{1:│}aaaaa | + {1:8│}aaaaa | + | + ]]) + end) + + it('works with wrapped lines, signs and folds', function() + command([[set stc=%C%s%=%{v:virtnum?'':v:lnum}│\ ]]) + command("call setline(1,repeat([repeat('aaaaa',10)],16))") + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, + [1] = {foreground = Screen.colors.Brown}, + [2] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGrey}, + [3] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey}, + [4] = {bold = true, foreground = Screen.colors.Brown}, + [5] = {background = Screen.colors.Grey90}, + }) + screen:expect([[ + {1: 4│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }a | + {1: 5│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }a | + {1: 6│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }a | + {1: 7│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }a | + {1: 8│ }^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }a | + {1: 9│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1: │ }a | + {1:10│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{0:@@@}| + | + ]]) + command("set stc=%C%s%=%l│\\ ") + screen:expect_unchanged() + command('set signcolumn=auto:2 foldcolumn=auto') + command('sign define piet1 text=>> texthl=LineNr') + command('sign define piet2 text=>! texthl=NonText') + command('sign place 1 line=4 name=piet1 buffer=1') + command('sign place 2 line=5 name=piet2 buffer=1') + command('sign place 3 line=6 name=piet1 buffer=1') + command('sign place 4 line=6 name=piet2 buffer=1') + screen:expect([[ + {1:>>}{2: }{1: 4│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │ }aaaaa | + {0:>!}{2: }{1: 5│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │ }aaaaa | + {1:>>}{0:>!}{1: 6│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │ }aaaaa | + {2: }{1: 7│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │ }aaaaa | + {2: }{1: 8│ }^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │ }aaaaa | + {2: }{1: 9│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │ }aaaaa | + {2: }{1:10│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{0:@@@}| + | + ]]) + command('norm zf$') + -- Check that alignment works properly with signs after %= + command([[set stc=%C%=%{v:virtnum?'':v:lnum}│%s\ ]]) + screen:expect([[ + {2: }{1: 4│>>}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 5│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 6│>>}{0:>!}{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 7│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2:+}{1: 8│}{2: }{1: }{3:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {2: }{1: 9│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1:10│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + | + ]]) + command('set cursorline') + screen:expect([[ + {2: }{1: 4│>>}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 5│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 6│>>}{0:>!}{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 7│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2:+}{4: 8│}{2: }{4: }{5:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {2: }{1: 9│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1:10│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + | + ]]) + -- v:lnum is the same value on wrapped lines + command([[set stc=%C%=%{v:lnum}│%s\ ]]) + screen:expect([[ + {2: }{1: 4│>>}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 4│}{2: }{1: }aaaaaa | + {2: }{1: 5│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 5│}{2: }{1: }aaaaaa | + {2: }{1: 6│>>}{0:>!}{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 6│}{2: }{1: }aaaaaa | + {2: }{1: 7│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 7│}{2: }{1: }aaaaaa | + {2:+}{4: 8│}{2: }{4: }{5:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {2: }{1: 9│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 9│}{2: }{1: }aaaaaa | + {2: }{1:10│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1:10│}{2: }{1: }aaaaaa | + | + ]]) + -- v:relnum is the same value on wrapped lines + command([[set stc=%C%=\ %{v:relnum}│%s\ ]]) + screen:expect([[ + {2: }{1: 4│>>}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 4│}{2: }{1: }aaaaaa | + {2: }{1: 3│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 3│}{2: }{1: }aaaaaa | + {2: }{1: 2│>>}{0:>!}{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 2│}{2: }{1: }aaaaaa | + {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 1│}{2: }{1: }aaaaaa | + {2:+}{4: 0│}{2: }{4: }{5:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 1│}{2: }{1: }aaaaaa | + {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 2│}{2: }{1: }aaaaaa | + | + ]]) + command([[set stc=%C%=\ %{v:virtnum?'':v:relnum}│%s\ ]]) + screen:expect([[ + {2: }{1: 4│>>}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 3│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 2│>>}{0:>!}{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2:+}{4: 0│}{2: }{4: }{5:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaa | + | + ]]) + -- Up to 9 signs in a line + command('set signcolumn=auto:9 foldcolumn=auto') + command('sign place 5 line=6 name=piet1 buffer=1') + command('sign place 6 line=6 name=piet2 buffer=1') + command('sign place 7 line=6 name=piet1 buffer=1') + command('sign place 8 line=6 name=piet2 buffer=1') + command('sign place 9 line=6 name=piet1 buffer=1') + command('sign place 10 line=6 name=piet2 buffer=1') + command('sign place 11 line=6 name=piet1 buffer=1') + screen:expect([[ + {2: }{1: 4│>>}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa | + {2: }{1: 3│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa | + {2: }{1: 2│>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa | + {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa | + {2:+}{4: 0│}{2: }{4: }{5:^+-- 1 line: aaaaaaaaaaaaaaaaa}| + {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa | + {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa | + | + ]]) + -- Status column is re-evaluated for virt_lines, buffer line, and wrapped line + exec_lua([[ + local ns = vim.api.nvim_create_namespace("ns") + vim.api.nvim_buf_set_extmark(0, ns, 5, 0, { + virt_lines_above = true, virt_lines = {{{"virt_line above", ""}}} }) + vim.api.nvim_buf_set_extmark(0, ns, 4, 0, { virt_lines = {{{"virt_line", ""}}} }) + ]]) + command('set foldcolumn=0 signcolumn=no') + command([[set stc=%{v:virtnum<0?'virtual':(!v:virtnum?'buffer':'wrapped')}%=%{'\ '.v:virtnum.'\ '.v:lnum}]]) + screen:expect([[ + {1:buffer 0 4}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1:wrapped 1 4}aaaaaaaa | + {1:buffer 0 5}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1:wrapped 1 5}aaaaaaaa | + {1:virtual-2 5}virt_line | + {1:virtual-2 5}virt_line above | + {1:buffer 0 6}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1:wrapped 1 6}aaaaaaaa | + {1:buffer 0 7}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1:wrapped 1 7}aaaaaaaa | + {4:buffer 0 8}{5:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {1:buffer 0 9}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {1:wrapped 1 9}aaaaaaaa | + | + ]]) + end) + + it("works with 'statuscolumn' clicks", function() + command('set mousemodel=extend') + command([[ + function! MyClickFunc(minwid, clicks, button, mods) + let g:testvar = printf("%d %d %s %d", a:minwid, a:clicks, a:button, getmousepos().line) + if a:mods !=# ' ' + let g:testvar ..= '(' .. a:mods .. ')' + endif + endfunction + set stc=%0@MyClickFunc@%=%l%T + ]]) + meths.input_mouse('left', 'press', '', 0, 0, 0) + eq('0 1 l 4', eval("g:testvar")) + meths.input_mouse('left', 'press', '', 0, 0, 0) + eq('0 2 l 4', eval("g:testvar")) + meths.input_mouse('left', 'press', '', 0, 0, 0) + eq('0 3 l 4', eval("g:testvar")) + meths.input_mouse('left', 'press', '', 0, 0, 0) + eq('0 4 l 4', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 3, 0) + eq('0 1 r 7', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 3, 0) + eq('0 2 r 7', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 3, 0) + eq('0 3 r 7', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 3, 0) + eq('0 4 r 7', eval("g:testvar")) + command('set laststatus=2 winbar=%f') + command('let g:testvar=""') + -- Check that winbar click doesn't register as statuscolumn click + meths.input_mouse('right', 'press', '', 0, 0, 0) + eq('', eval("g:testvar")) + -- Check that statusline click doesn't register as statuscolumn click + meths.input_mouse('right', 'press', '', 0, 12, 0) + eq('', eval("g:testvar")) + end) + + it('click labels do not leak memory', function() + command([[ + set laststatus=2 + setlocal statuscolumn=%0@MyClickFunc@abcd%T + 4vsplit + setlocal statusline=abcd + redrawstatus + setlocal statusline= + only + redraw + ]]) + end) + + it('works with foldcolumn', function() + -- Fits maximum multibyte foldcolumn #21759 + command([[set stc=%C%=%l\ fdc=9 fillchars=foldsep:𒀀]]) + for _ = 0,8 do command('norm zfjzo') end + -- 'statuscolumn' is not drawn for `virt_lines_leftcol` lines + exec_lua([[ + local ns = vim.api.nvim_create_namespace("ns") + vim.api.nvim_buf_set_extmark(0, ns, 6, 0, { + virt_lines_leftcol = true, virt_lines = {{{"virt", ""}}} }) + vim.api.nvim_buf_set_extmark(0, ns, 7, 0, { + virt_lines_leftcol = true, virt_lines = {{{"virt", ""}}} }) + ]]) + feed('lh') -- force update wcol/row + screen:expect([[ + 4 aaaaa | + 5 aaaaa | + 6 aaaaa | + 7 aaaaa | + virt | + --------- 8 ^aaaaa | + virt | + 𒀀𒀀𒀀𒀀𒀀𒀀𒀀𒀀𒀀 9 aaaaa | + 10 aaaaa | + 11 aaaaa | + 12 aaaaa | + 13 aaaaa | + 14 aaaaa | + | + ]]) + command('set stc=') -- also for the default sign column + screen:expect_unchanged() + end) +end) diff --git a/test/functional/ui/statusline_spec.lua b/test/functional/ui/statusline_spec.lua index 18391a575d..1c184ff27d 100644 --- a/test/functional/ui/statusline_spec.lua +++ b/test/functional/ui/statusline_spec.lua @@ -164,6 +164,24 @@ describe('statusline clicks', function() meths.input_mouse('right', 'press', '', 0, 6, 5) eq('0 1 r', eval("g:testvar")) end) + + it('no memory leak with zero-width click labels', function() + command([[ + let &stl = '%@Test@%T%@MyClickFunc@%=%T%@Test@' + ]]) + meths.input_mouse('left', 'press', '', 0, 6, 0) + eq('0 1 l', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 6, 39) + eq('0 1 r', eval("g:testvar")) + end) + + it('no memory leak with truncated click labels', function() + command([[ + let &stl = '%@MyClickFunc@foo%X' .. repeat('a', 40) .. '%<t%@Test@bar%X%@Test@baz' + ]]) + meths.input_mouse('left', 'press', '', 0, 6, 2) + eq('0 1 l', eval("g:testvar")) + end) end) describe('global statusline', function() @@ -545,3 +563,29 @@ it('statusline is redrawn with :resize from <Cmd> mapping #19629', function() | ]]) end) + +it('showcmdloc=statusline does not show if statusline is too narrow', function() + clear() + local screen = Screen.new(40, 8) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {bold = true, reverse = true}, -- StatusLine + [2] = {reverse = true}, -- StatusLineNC + }) + screen:attach() + command('set showcmd') + command('set showcmdloc=statusline') + command('1vsplit') + screen:expect([[ + ^ │ | + {0:~}│{0:~ }| + {0:~}│{0:~ }| + {0:~}│{0:~ }| + {0:~}│{0:~ }| + {0:~}│{0:~ }| + {1:< }{2:[No Name] }| + | + ]]) + feed('1234') + screen:expect_unchanged() +end) diff --git a/test/functional/ui/syntax_conceal_spec.lua b/test/functional/ui/syntax_conceal_spec.lua index e8798ddd93..1391985823 100644 --- a/test/functional/ui/syntax_conceal_spec.lua +++ b/test/functional/ui/syntax_conceal_spec.lua @@ -4,7 +4,7 @@ local clear, feed, command = helpers.clear, helpers.feed, helpers.command local eq = helpers.eq local insert = helpers.insert local poke_eventloop = helpers.poke_eventloop -local expect_exit = helpers.expect_exit +local exec = helpers.exec describe('Screen', function() local screen @@ -1001,25 +1001,32 @@ describe('Screen', function() eq({}, grid_lines) -- no redraw was done end) - -- Copy of Test_cursor_column_in_concealed_line_after_window_scroll in - -- test/functional/ui/syntax_conceal_spec.lua. - describe('concealed line after window scroll', function() - after_each(function() - expect_exit(command, ':qall!') - os.remove('Xcolesearch') - end) - - it('has the correct cursor column', function() + describe('concealed line has the correct cursor column', function() + -- oldtest: Test_cursor_column_in_concealed_line_after_window_scroll() + it('after window scroll', function() insert([[ - 3split - let m = matchadd('Conceal', '=') - setl conceallevel=2 concealcursor=nc - normal gg - "==expr== - ]]) + 3split + let m = matchadd('Conceal', '=') + setl conceallevel=2 concealcursor=nc + normal gg + "==expr==]]) + feed('gg') + command('file Xcolesearch') + command('set nomodified') - command('write Xcolesearch') - feed(":so %<CR>") + command('so') + screen:expect{grid=[[ + ^3split | + let m matchadd('Conceal', '') | + setl conceallevel2 concealcursornc | + {2:Xcolesearch }| + 3split | + let m = matchadd('Conceal', '=') | + setl conceallevel=2 concealcursor=nc | + normal gg | + {3:Xcolesearch }| + | + ]]} -- Jump to something that is beyond the bottom of the window, -- so there's a scroll down. @@ -1033,13 +1040,42 @@ describe('Screen', function() normal gg | "{5:^expr} | {2:Xcolesearch }| + 3split | + let m = matchadd('Conceal', '=') | + setl conceallevel=2 concealcursor=nc | normal gg | - "=={5:expr}== | - | - {0:~ }| {3:Xcolesearch }| /expr | ]]} end) + + -- oldtest: Test_cursor_column_in_concealed_line_after_leftcol_change() + it('after leftcol change', function() + exec([[ + 0put = 'ab' .. repeat('-', &columns) .. 'c' + call matchadd('Conceal', '-') + set nowrap ss=0 cole=3 cocu=n + ]]) + + -- Go to the end of the line (3 columns beyond the end of the screen). + -- Horizontal scroll would center the cursor in the screen line, but conceal + -- makes it go to screen column 1. + feed('$') + + -- Are the concealed parts of the current line really hidden? + -- Is the window's cursor column properly updated for conceal? + screen:expect{grid=[[ + ^c | + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]} + end) end) end) diff --git a/test/functional/ui/tabline_spec.lua b/test/functional/ui/tabline_spec.lua index 809486d4db..2cdec62d01 100644 --- a/test/functional/ui/tabline_spec.lua +++ b/test/functional/ui/tabline_spec.lua @@ -84,3 +84,45 @@ describe('ui/ext_tabline', function() end} end) end) + +describe("tabline", function() + local screen + + before_each(function() + clear() + screen = Screen.new(42, 5) + screen:attach() + end) + + it('redraws when tabline option is set', function() + command('set tabline=asdf') + command('set showtabline=2') + screen:expect{grid=[[ + {1:asdf }| + ^ | + {2:~ }| + {2:~ }| + | + ]], attr_ids={ + [1] = {reverse = true}; + [2] = {bold = true, foreground = Screen.colors.Blue1}; + }} + command('set tabline=jkl') + screen:expect{grid=[[ + {1:jkl }| + ^ | + {2:~ }| + {2:~ }| + | + ]], attr_ids={ + [1] = {reverse = true}; + [2] = {bold = true, foreground = Screen.colors.Blue}; + }} + end) + + it('click definitions do not leak memory #21765', function() + command('set tabline=%@MyClickFunc@MyClickText%T') + command('set showtabline=2') + command('redrawtabline') + end) +end) diff --git a/test/functional/ui/wildmode_spec.lua b/test/functional/ui/wildmode_spec.lua index f1e818119e..50466c9473 100644 --- a/test/functional/ui/wildmode_spec.lua +++ b/test/functional/ui/wildmode_spec.lua @@ -1,13 +1,13 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear, feed, command = helpers.clear, helpers.feed, helpers.command -local iswin = helpers.iswin local funcs = helpers.funcs local meths = helpers.meths local eq = helpers.eq local eval = helpers.eval local retry = helpers.retry local testprg = helpers.testprg +local is_os = helpers.is_os describe("'wildmenu'", function() local screen @@ -159,7 +159,7 @@ describe("'wildmenu'", function() -- must wait the full timeout. So make it reasonable. screen.timeout = 1000 - if not iswin() then + if not is_os('win') then command('set shell=sh') -- Need a predictable "$" prompt. command('let $PS1 = "$"') end @@ -169,7 +169,7 @@ describe("'wildmenu'", function() -- Check for a shell prompt to verify that the terminal loaded. retry(nil, nil, function() - if iswin() then + if is_os('win') then eq('Microsoft', eval("matchstr(join(getline(1, '$')), 'Microsoft')")) else eq('$', eval([[matchstr(getline(1), '\$')]])) diff --git a/test/functional/vimscript/api_functions_spec.lua b/test/functional/vimscript/api_functions_spec.lua index 8ca245f61a..c032ac3030 100644 --- a/test/functional/vimscript/api_functions_spec.lua +++ b/test/functional/vimscript/api_functions_spec.lua @@ -5,6 +5,7 @@ local neq, eq, command = helpers.neq, helpers.eq, helpers.command local clear, curbufmeths = helpers.clear, helpers.curbufmeths local exc_exec, expect, eval = helpers.exc_exec, helpers.expect, helpers.eval local insert, pcall_err = helpers.insert, helpers.pcall_err +local matches = helpers.matches local meths = helpers.meths describe('eval-API', function() @@ -49,7 +50,7 @@ describe('eval-API', function() it('cannot change texts if textlocked', function() command("autocmd TextYankPost <buffer> ++once call nvim_buf_set_lines(0, 0, -1, v:false, [])") - eq('Vim(call):E5555: API call: E565: Not allowed to change text or change window', + matches('Vim%(call%):E5555: API call: E565: Not allowed to change text or change window$', pcall_err(command, "normal! yy")) end) diff --git a/test/functional/vimscript/ctx_functions_spec.lua b/test/functional/vimscript/ctx_functions_spec.lua index d92a81c55b..5ee84a6d13 100644 --- a/test/functional/vimscript/ctx_functions_spec.lua +++ b/test/functional/vimscript/ctx_functions_spec.lua @@ -173,9 +173,9 @@ describe('context functions', function() call('SaveSFuncs') call('DeleteSFuncs') - eq('Vim(call):E117: Unknown function: s:greet', + eq('function Greet, line 1: Vim(call):E117: Unknown function: s:greet', pcall_err(command, [[call Greet('World')]])) - eq('Vim(call):E117: Unknown function: s:greet_all', + eq('function GreetAll, line 1: Vim(call):E117: Unknown function: s:greet_all', pcall_err(command, [[call GreetAll('World', 'One', 'Two', 'Three')]])) call('RestoreFuncs') @@ -287,9 +287,11 @@ describe('context functions', function() local with_jumps = { ['jumps'] = eval(([[ - filter(map(getjumplist()[0], 'filter( - { "f": expand("#".v:val.bufnr.":p"), "l": v:val.lnum }, - { k, v -> k != "l" || v != 1 })'), '!empty(v:val.f)') + filter(map(add( + getjumplist()[0], { 'bufnr': bufnr('%'), 'lnum': getcurpos()[1] }), + 'filter( + { "f": expand("#".v:val.bufnr.":p"), "l": v:val.lnum }, + { k, v -> k != "l" || v != 1 })'), '!empty(v:val.f)') ]]):gsub('\n', '')) } diff --git a/test/functional/vimscript/eval_spec.lua b/test/functional/vimscript/eval_spec.lua index 1fbdedb815..a8a901042b 100644 --- a/test/functional/vimscript/eval_spec.lua +++ b/test/functional/vimscript/eval_spec.lua @@ -16,13 +16,16 @@ local lfs = require('lfs') local clear = helpers.clear local eq = helpers.eq local exc_exec = helpers.exc_exec -local exec = helpers.exec +local exec_lua = helpers.exec_lua local exec_capture = helpers.exec_capture local eval = helpers.eval local command = helpers.command local write_file = helpers.write_file local meths = helpers.meths local sleep = helpers.sleep +local matches = helpers.matches +local pcall_err = helpers.pcall_err +local assert_alive = helpers.assert_alive local poke_eventloop = helpers.poke_eventloop local feed = helpers.feed @@ -68,13 +71,13 @@ describe("backtick expansion", function() end) it("with default 'shell'", function() - if helpers.iswin() then + if helpers.is_os('win') then command(":silent args `dir /b *2`") else command(":silent args `echo ***2`") end eq({ "file2", }, eval("argv()")) - if helpers.iswin() then + if helpers.is_os('win') then command(":silent args `dir /s/b *4`") eq({ "subdir\\file4", }, eval("map(argv(), 'fnamemodify(v:val, \":.\")')")) else @@ -148,79 +151,6 @@ describe('List support code', function() end) end) --- oldtest: Test_deep_nest() -it('Error when if/for/while/try/function is nested too deep',function() - clear() - local screen = Screen.new(80, 24) - screen:attach() - meths.set_option('laststatus', 2) - exec([[ - " Deep nesting of if ... endif - func Test1() - let @a = join(repeat(['if v:true'], 51), "\n") - let @a ..= "\n" - let @a ..= join(repeat(['endif'], 51), "\n") - @a - let @a = '' - endfunc - - " Deep nesting of for ... endfor - func Test2() - let @a = join(repeat(['for i in [1]'], 51), "\n") - let @a ..= "\n" - let @a ..= join(repeat(['endfor'], 51), "\n") - @a - let @a = '' - endfunc - - " Deep nesting of while ... endwhile - func Test3() - let @a = join(repeat(['while v:true'], 51), "\n") - let @a ..= "\n" - let @a ..= join(repeat(['endwhile'], 51), "\n") - @a - let @a = '' - endfunc - - " Deep nesting of try ... endtry - func Test4() - let @a = join(repeat(['try'], 51), "\n") - let @a ..= "\necho v:true\n" - let @a ..= join(repeat(['endtry'], 51), "\n") - @a - let @a = '' - endfunc - - " Deep nesting of function ... endfunction - func Test5() - let @a = join(repeat(['function X()'], 51), "\n") - let @a ..= "\necho v:true\n" - let @a ..= join(repeat(['endfunction'], 51), "\n") - @a - let @a = '' - endfunc - ]]) - screen:expect({any = '%[No Name%]'}) - feed(':call Test1()<CR>') - screen:expect({any = 'E579: '}) - feed('<C-C>') - screen:expect({any = '%[No Name%]'}) - feed(':call Test2()<CR>') - screen:expect({any = 'E585: '}) - feed('<C-C>') - screen:expect({any = '%[No Name%]'}) - feed(':call Test3()<CR>') - screen:expect({any = 'E585: '}) - feed('<C-C>') - screen:expect({any = '%[No Name%]'}) - feed(':call Test4()<CR>') - screen:expect({any = 'E601: '}) - feed('<C-C>') - screen:expect({any = '%[No Name%]'}) - feed(':call Test5()<CR>') - screen:expect({any = 'E1058: '}) -end) - describe("uncaught exception", function() before_each(clear) after_each(function() @@ -249,10 +179,10 @@ describe("uncaught exception", function() end) end) -describe('lambda function', function() +describe('listing functions using :function', function() before_each(clear) - it('can be shown using :function followed by <lambda> #20466', function() + it('works for lambda functions with <lambda> #20466', function() command('let A = {-> 1}') local num = exec_capture('echo A'):match("function%('<lambda>(%d+)'%)") eq(([[ @@ -260,4 +190,33 @@ describe('lambda function', function() 1 return 1 endfunction]]):format(num), exec_capture(('function <lambda>%s'):format(num))) end) + + -- FIXME: If the same function is deleted, the crash still happens. #20790 + it('does not crash if another function is deleted while listing', function() + local screen = Screen.new(80, 24) + screen:attach() + matches('Vim%(function%):E454: function list was modified', pcall_err(exec_lua, [=[ + vim.cmd([[ + func Func1() + endfunc + func Func2() + endfunc + func Func3() + endfunc + ]]) + + local ns = vim.api.nvim_create_namespace('test') + + vim.ui_attach(ns, { ext_messages = true }, function(event, _, content) + if event == 'msg_show' and content[1][2] == 'function Func1()' then + vim.cmd('delfunc Func3') + end + end) + + vim.cmd('function') + + vim.ui_detach(ns) + ]=])) + assert_alive() + end) end) diff --git a/test/functional/vimscript/executable_spec.lua b/test/functional/vimscript/executable_spec.lua index b49eb09512..43e4a29e1a 100644 --- a/test/functional/vimscript/executable_spec.lua +++ b/test/functional/vimscript/executable_spec.lua @@ -1,15 +1,16 @@ local helpers = require('test.functional.helpers')(after_each) -local eq, clear, call, iswin, write_file, command = - helpers.eq, helpers.clear, helpers.call, helpers.iswin, helpers.write_file, +local eq, clear, call, write_file, command = + helpers.eq, helpers.clear, helpers.call, helpers.write_file, helpers.command local exc_exec = helpers.exc_exec local eval = helpers.eval +local is_os = helpers.is_os describe('executable()', function() before_each(clear) it('returns 1 for commands in $PATH', function() - local exe = iswin() and 'ping' or 'ls' + local exe = is_os('win') and 'ping' or 'ls' eq(1, call('executable', exe)) command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")') eq(1, call('executable', 'null')) @@ -17,7 +18,7 @@ describe('executable()', function() eq(1, call('executable', 'false')) end) - if iswin() then + if is_os('win') then it('exepath respects shellslash', function() command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")') eq([[test\functional\fixtures\bin\null.CMD]], call('fnamemodify', call('exepath', 'null'), ':.')) @@ -56,8 +57,8 @@ describe('executable()', function() -- Some executable in build/bin/, *not* in $PATH nor CWD. local sibling_exe = 'printargs-test' -- Windows: siblings are in Nvim's "pseudo-$PATH". - local expected = iswin() and 1 or 0 - if iswin() then + local expected = is_os('win') and 1 or 0 + if is_os('win') then eq('arg1=lemon;arg2=sky;arg3=tree;', call('system', sibling_exe..' lemon sky tree')) end @@ -69,7 +70,7 @@ describe('executable()', function() clear() write_file('Xtest_not_executable', 'non-executable file') write_file('Xtest_executable', 'executable file (exec-bit set)') - if not iswin() then -- N/A for Windows. + if not is_os('win') then -- N/A for Windows. call('system', {'chmod', '-x', 'Xtest_not_executable'}) call('system', {'chmod', '+x', 'Xtest_executable'}) end @@ -90,14 +91,17 @@ describe('executable()', function() end) it('set, qualified as a path', function() - local expected = iswin() and 0 or 1 + local expected = is_os('win') and 0 or 1 eq(expected, call('executable', './Xtest_executable')) end) end) end) describe('executable() (Windows)', function() - if not iswin() then return end -- N/A for Unix. + if not is_os('win') then + pending('N/A for non-windows') + return + end local exts = {'bat', 'exe', 'com', 'cmd'} setup(function() diff --git a/test/functional/vimscript/execute_spec.lua b/test/functional/vimscript/execute_spec.lua index a733b098f5..5fe3d787cb 100644 --- a/test/functional/vimscript/execute_spec.lua +++ b/test/functional/vimscript/execute_spec.lua @@ -8,7 +8,7 @@ local funcs = helpers.funcs local Screen = require('test.functional.ui.screen') local command = helpers.command local feed = helpers.feed -local iswin = helpers.iswin +local is_os = helpers.is_os describe('execute()', function() before_each(clear) @@ -265,7 +265,7 @@ describe('execute()', function() -- This deviates from vim behavior, but is consistent -- with how nvim currently displays the output. it('captures shell-command output', function() - local win_lf = iswin() and '\13' or '' + local win_lf = is_os('win') and '\13' or '' eq('\n:!echo foo\r\n\nfoo'..win_lf..'\n', funcs.execute('!echo foo')) end) diff --git a/test/functional/vimscript/exepath_spec.lua b/test/functional/vimscript/exepath_spec.lua index 439dd96fcd..056f67e0ad 100644 --- a/test/functional/vimscript/exepath_spec.lua +++ b/test/functional/vimscript/exepath_spec.lua @@ -1,19 +1,20 @@ local helpers = require('test.functional.helpers')(after_each) -local eq, clear, call, iswin = - helpers.eq, helpers.clear, helpers.call, helpers.iswin +local eq, clear, call = + helpers.eq, helpers.clear, helpers.call local command = helpers.command local exc_exec = helpers.exc_exec local matches = helpers.matches +local is_os = helpers.is_os describe('exepath()', function() before_each(clear) it('returns 1 for commands in $PATH', function() - local exe = iswin() and 'ping' or 'ls' - local ext_pat = iswin() and '%.EXE$' or '$' + local exe = is_os('win') and 'ping' or 'ls' + local ext_pat = is_os('win') and '%.EXE$' or '$' matches(exe .. ext_pat, call('exepath', exe)) command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")') - ext_pat = iswin() and '%.CMD$' or '$' + ext_pat = is_os('win') and '%.CMD$' or '$' matches('null' .. ext_pat, call('exepath', 'null')) matches('true' .. ext_pat, call('exepath', 'true')) matches('false' .. ext_pat, call('exepath', 'false')) @@ -23,14 +24,14 @@ describe('exepath()', function() for _, input in ipairs({'v:null', 'v:true', 'v:false', '{}', '[]'}) do eq('Vim(call):E1174: String required for argument 1', exc_exec('call exepath('..input..')')) end - eq('Vim(call):E1142: Non-empty string required for argument 1', exc_exec('call exepath("")')) + eq('Vim(call):E1175: Non-empty string required for argument 1', exc_exec('call exepath("")')) command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")') for _, input in ipairs({'v:null', 'v:true', 'v:false'}) do eq('Vim(call):E1174: String required for argument 1', exc_exec('call exepath('..input..')')) end end) - if iswin() then + if is_os('win') then it('append extension if omitted', function() local filename = 'cmd' local pathext = '.exe' diff --git a/test/functional/vimscript/fnamemodify_spec.lua b/test/functional/vimscript/fnamemodify_spec.lua index d54a6db417..c3ecdd853c 100644 --- a/test/functional/vimscript/fnamemodify_spec.lua +++ b/test/functional/vimscript/fnamemodify_spec.lua @@ -1,12 +1,12 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local eq = helpers.eq -local iswin = helpers.iswin local fnamemodify = helpers.funcs.fnamemodify local getcwd = helpers.funcs.getcwd local command = helpers.command local write_file = helpers.write_file local alter_slashes = helpers.alter_slashes +local is_os = helpers.is_os local function eq_slashconvert(expected, got) eq(alter_slashes(expected), alter_slashes(got)) @@ -27,7 +27,7 @@ describe('fnamemodify()', function() local root = helpers.pathroot() eq(root, fnamemodify([[/]], ':p:h')) eq(root, fnamemodify([[/]], ':p')) - if iswin() then + if is_os('win') then eq(root, fnamemodify([[\]], ':p:h')) eq(root, fnamemodify([[\]], ':p')) command('set shellslash') @@ -114,7 +114,7 @@ describe('fnamemodify()', function() it('handles shell escape', function() local expected - if iswin() then + if is_os('win') then -- we expand with double-quotes on Windows expected = [["hello there! quote ' newline]] .. '\n' .. [["]] else diff --git a/test/functional/vimscript/functions_spec.lua b/test/functional/vimscript/functions_spec.lua index 20c1400030..09b3334989 100644 --- a/test/functional/vimscript/functions_spec.lua +++ b/test/functional/vimscript/functions_spec.lua @@ -9,12 +9,12 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local eval = helpers.eval -local iswin = helpers.iswin local matches = helpers.matches +local is_os = helpers.is_os before_each(clear) it('windowsversion()', function() clear() - matches(iswin() and '^%d+%.%d+$' or '^$', eval('windowsversion()')) + matches(is_os('win') and '^%d+%.%d+$' or '^$', eval('windowsversion()')) end) diff --git a/test/functional/vimscript/has_spec.lua b/test/functional/vimscript/has_spec.lua index 4d9b226434..2e26d603b3 100644 --- a/test/functional/vimscript/has_spec.lua +++ b/test/functional/vimscript/has_spec.lua @@ -2,7 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local eq = helpers.eq local clear = helpers.clear local funcs = helpers.funcs -local iswin = helpers.iswin +local is_os = helpers.is_os describe('has()', function() before_each(clear) @@ -51,7 +51,7 @@ describe('has()', function() end) it('"unnamedplus"', function() - if (not iswin()) and funcs.has("clipboard") == 1 then + if (not is_os('win')) and funcs.has("clipboard") == 1 then eq(1, funcs.has("unnamedplus")) else eq(0, funcs.has("unnamedplus")) diff --git a/test/functional/vimscript/hostname_spec.lua b/test/functional/vimscript/hostname_spec.lua index 6112cf64e3..7d4baa7213 100644 --- a/test/functional/vimscript/hostname_spec.lua +++ b/test/functional/vimscript/hostname_spec.lua @@ -3,7 +3,7 @@ local eq = helpers.eq local ok = helpers.ok local call = helpers.call local clear = helpers.clear -local iswin = helpers.iswin +local is_os = helpers.is_os describe('hostname()', function() before_each(clear) @@ -13,8 +13,8 @@ describe('hostname()', function() ok(string.len(actual) > 0) if call('executable', 'hostname') == 1 then local expected = string.gsub(call('system', 'hostname'), '[\n\r]', '') - eq((iswin() and expected:upper() or expected), - (iswin() and actual:upper() or actual)) + eq((is_os('win') and expected:upper() or expected), + (is_os('win') and actual:upper() or actual)) end end) end) diff --git a/test/functional/vimscript/map_functions_spec.lua b/test/functional/vimscript/map_functions_spec.lua index 8645b1e506..ba1b4d7a76 100644 --- a/test/functional/vimscript/map_functions_spec.lua +++ b/test/functional/vimscript/map_functions_spec.lua @@ -246,9 +246,9 @@ describe('mapset()', function() end) it('does not leak memory if lhs is missing', function() - eq('Error executing lua: Vim:E460: entries missing in mapset() dict argument', + eq('Vim:E460: entries missing in mapset() dict argument', pcall_err(exec_lua, [[vim.fn.mapset('n', false, {rhs = 'foo'})]])) - eq('Error executing lua: Vim:E460: entries missing in mapset() dict argument', + eq('Vim:E460: entries missing in mapset() dict argument', pcall_err(exec_lua, [[vim.fn.mapset('n', false, {callback = function() end})]])) end) end) diff --git a/test/functional/vimscript/msgpack_functions_spec.lua b/test/functional/vimscript/msgpack_functions_spec.lua index cab67d77e4..de5a721efe 100644 --- a/test/functional/vimscript/msgpack_functions_spec.lua +++ b/test/functional/vimscript/msgpack_functions_spec.lua @@ -5,7 +5,7 @@ local eval, eq = helpers.eval, helpers.eq local command = helpers.command local nvim = helpers.nvim local exc_exec = helpers.exc_exec -local iswin = helpers.iswin +local is_os = helpers.is_os describe('msgpack*() functions', function() before_each(clear) @@ -467,7 +467,7 @@ describe('msgpackparse() function', function() eval(cmd) eval(cmd) -- do it again (try to force segfault) local api_info = eval(cmd) -- do it again - if iswin() then + if is_os('win') then helpers.assert_alive() pending('msgpackparse() has a bug on windows') return diff --git a/test/functional/vimscript/null_spec.lua b/test/functional/vimscript/null_spec.lua index 2451da983e..1153baac46 100644 --- a/test/functional/vimscript/null_spec.lua +++ b/test/functional/vimscript/null_spec.lua @@ -69,7 +69,7 @@ describe('NULL', function() null_expr_test('can be splice-indexed', 'L[:]', 0, {}) null_expr_test('is not locked', 'islocked("v:_null_list")', 0, 0) null_test('is accepted by :for', 'for x in L|throw x|endfor', 0) - null_expr_test('does not crash append()', 'append(1, L)', 0, 0, function() + null_expr_test('does not crash append()', 'append(0, L)', 0, 0, function() eq({''}, curbufmeths.get_lines(0, -1, false)) end) null_expr_test('does not crash setline()', 'setline(1, L)', 0, 0, function() diff --git a/test/functional/vimscript/server_spec.lua b/test/functional/vimscript/server_spec.lua index 6e95459630..c89a0c4e93 100644 --- a/test/functional/vimscript/server_spec.lua +++ b/test/functional/vimscript/server_spec.lua @@ -1,11 +1,14 @@ local helpers = require('test.functional.helpers')(after_each) +local assert_log = helpers.assert_log local eq, neq, eval = helpers.eq, helpers.neq, helpers.eval local clear, funcs, meths = helpers.clear, helpers.funcs, helpers.meths -local iswin = helpers.iswin local ok = helpers.ok local matches = helpers.matches local pcall_err = helpers.pcall_err local mkdir = helpers.mkdir +local is_os = helpers.is_os + +local testlog = 'Xtest-server-log' local function clear_serverlist() for _, server in pairs(funcs.serverlist()) do @@ -14,12 +17,16 @@ local function clear_serverlist() end describe('server', function() + after_each(function() + os.remove(testlog) + end) + it('serverstart() stores sockets in $XDG_RUNTIME_DIR', function() local dir = 'Xtest_xdg_run' mkdir(dir) clear({ env={ XDG_RUNTIME_DIR=dir } }) matches(dir, funcs.stdpath('run')) - if not iswin() then + if not is_os('win') then matches(dir, funcs.serverstart()) end end) @@ -65,7 +72,7 @@ describe('server', function() eq('', meths.get_vvar('servername')) -- v:servername and $NVIM take the next available server. - local servername = (iswin() and [[\\.\pipe\Xtest-functional-server-pipe]] + local servername = (is_os('win') and [[\\.\pipe\Xtest-functional-server-pipe]] or './Xtest-functional-server-socket') funcs.serverstart(servername) eq(servername, meths.get_vvar('servername')) @@ -74,13 +81,20 @@ describe('server', function() end) it('serverstop() returns false for invalid input', function() - clear() + clear{env={ + NVIM_LOG_FILE=testlog, + NVIM_LISTEN_ADDRESS='.', + }} eq(0, eval("serverstop('')")) eq(0, eval("serverstop('bogus-socket-name')")) + assert_log('Not listening on bogus%-socket%-name', testlog, 10) end) it('parses endpoints', function() - clear() + clear{env={ + NVIM_LOG_FILE=testlog, + NVIM_LISTEN_ADDRESS='.', + }} clear_serverlist() eq({}, funcs.serverlist()) @@ -104,6 +118,7 @@ describe('server', function() if status then table.insert(expected, v4) pcall(funcs.serverstart, v4) -- exists already; ignore + assert_log('Failed to start server: address already in use: 127%.0%.0%.1', testlog, 10) end local v6 = '::1:12345' @@ -111,6 +126,7 @@ describe('server', function() if status then table.insert(expected, v6) pcall(funcs.serverstart, v6) -- exists already; ignore + assert_log('Failed to start server: address already in use: ::1', testlog, 10) end eq(expected, funcs.serverlist()) clear_serverlist() @@ -130,7 +146,7 @@ describe('server', function() local n = eval('len(serverlist())') -- Add some servers. - local servs = (iswin() + local servs = (is_os('win') and { [[\\.\pipe\Xtest-pipe0934]], [[\\.\pipe\Xtest-pipe4324]] } or { [[./Xtest-pipe0934]], [[./Xtest-pipe4324]] }) for _, s in ipairs(servs) do @@ -164,7 +180,7 @@ describe('startup --listen', function() end) it('sets v:servername, overrides $NVIM_LISTEN_ADDRESS', function() - local addr = (iswin() and [[\\.\pipe\Xtest-listen-pipe]] + local addr = (is_os('win') and [[\\.\pipe\Xtest-listen-pipe]] or './Xtest-listen-pipe') clear({ env={ NVIM_LISTEN_ADDRESS='./Xtest-env-pipe' }, args={ '--listen', addr } }) diff --git a/test/functional/vimscript/system_spec.lua b/test/functional/vimscript/system_spec.lua index ed822add72..7ada1c4bea 100644 --- a/test/functional/vimscript/system_spec.lua +++ b/test/functional/vimscript/system_spec.lua @@ -1,3 +1,5 @@ +-- Tests for system() and :! shell. + local helpers = require('test.functional.helpers')(after_each) local assert_alive = helpers.assert_alive @@ -9,9 +11,9 @@ local command = helpers.command local insert = helpers.insert local expect = helpers.expect local exc_exec = helpers.exc_exec -local iswin = helpers.iswin local os_kill = helpers.os_kill local pcall_err = helpers.pcall_err +local is_os = helpers.is_os local Screen = require('test.functional.ui.screen') @@ -85,7 +87,7 @@ describe('system()', function() end) it('does NOT run in shell', function() - if iswin() then + if is_os('win') then eq("%PATH%\n", eval("system(['powershell', '-NoProfile', '-NoLogo', '-ExecutionPolicy', 'RemoteSigned', '-Command', 'Write-Output', '%PATH%'])")) else eq("* $PATH %PATH%\n", eval("system(['echo', '*', '$PATH', '%PATH%'])")) @@ -94,7 +96,7 @@ describe('system()', function() end) it('sets v:shell_error', function() - if iswin() then + if is_os('win') then eval([[system("cmd.exe /c exit")]]) eq(0, eval('v:shell_error')) eval([[system("cmd.exe /c exit 1")]]) @@ -123,7 +125,7 @@ describe('system()', function() screen:attach() end) - if iswin() then + if is_os('win') then local function test_more() eq('root = true', eval([[get(split(system('"more" ".editorconfig"'), "\n"), 0, '')]])) end @@ -184,7 +186,7 @@ describe('system()', function() -- * on Windows, expected to default to Western European enc -- * on Linux, expected to default to UTF8 command([[let &shellcmdflag = '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command ']]) - eq(iswin() and '??\n' or 'ああ\n', eval([[system('Write-Output "ああ"')]])) + eq(is_os('win') and '??\n' or 'ああ\n', eval([[system('Write-Output "ああ"')]])) end) it('`echo` and waits for its return', function() @@ -213,7 +215,7 @@ describe('system()', function() screen:try_resize(72, 14) feed(':4verbose echo system("echo hi")<cr>') - if iswin() then + if is_os('win') then screen:expect{any=[[Executing command: "'fake_shell' 'cmdflag' '"echo hi"'"]]} else screen:expect{any=[[Executing command: "'fake_shell' 'cmdflag' 'echo hi'"]]} @@ -243,7 +245,7 @@ describe('system()', function() end) it('`yes` interrupted with CTRL-C', function() - feed(':call system("' .. (iswin() + feed(':call system("' .. (is_os('win') and 'for /L %I in (1,0,2) do @echo y' or 'yes') .. '")<cr>') screen:expect([[ @@ -260,7 +262,7 @@ describe('system()', function() ~ | ~ | ~ | -]] .. (iswin() +]] .. (is_os('win') and [[ :call system("for /L %I in (1,0,2) do @echo y") |]] or [[ @@ -286,7 +288,7 @@ describe('system()', function() it('`yes` interrupted with mapped CTRL-C', function() command('nnoremap <C-C> i') - feed(':call system("' .. (iswin() + feed(':call system("' .. (is_os('win') and 'for /L %I in (1,0,2) do @echo y' or 'yes') .. '")<cr>') screen:expect([[ @@ -303,7 +305,7 @@ describe('system()', function() ~ | ~ | ~ | -]] .. (iswin() +]] .. (is_os('win') and [[ :call system("for /L %I in (1,0,2) do @echo y") |]] or [[ @@ -330,7 +332,7 @@ describe('system()', function() describe('passing no input', function() it('returns the program output', function() - if iswin() then + if is_os('win') then eq("echoed\n", eval('system("echo echoed")')) else eq("echoed", eval('system("echo -n echoed")')) @@ -438,7 +440,7 @@ describe('systemlist()', function() before_each(clear) it('sets v:shell_error', function() - if iswin() then + if is_os('win') then eval([[systemlist("cmd.exe /c exit")]]) eq(0, eval('v:shell_error')) eval([[systemlist("cmd.exe /c exit 1")]]) @@ -617,12 +619,12 @@ describe('systemlist()', function() return end helpers.set_shell_powershell() - eq({iswin() and 'あ\r' or 'あ'}, eval([[systemlist('Write-Output あ')]])) + eq({is_os('win') and 'あ\r' or 'あ'}, eval([[systemlist('Write-Output あ')]])) -- Sanity test w/ default encoding -- * on Windows, expected to default to Western European enc -- * on Linux, expected to default to UTF8 command([[let &shellcmdflag = '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command ']]) - eq({iswin() and '?\r' or 'あ'}, eval([[systemlist('Write-Output あ')]])) + eq({is_os('win') and '?\r' or 'あ'}, eval([[systemlist('Write-Output あ')]])) end) end) @@ -639,7 +641,7 @@ describe('shell :!', function() 1 4 2]]) - if iswin() then + if is_os('win') then feed(':4verbose %!sort /R<cr>') screen:expect{ any=[[Executing command: .?& { Get%-Content .* | & sort /R } 2>&1 | Out%-File %-Encoding UTF8 .*; exit $LastExitCode"]] @@ -660,4 +662,33 @@ describe('shell :!', function() 1]]) end end) + + it(':{range}! without redirecting to buffer', function() + local screen = Screen.new(500, 10) + screen:attach() + insert([[ + 3 + 1 + 4 + 2]]) + feed(':4verbose %w !sort<cr>') + if is_os('win') then + screen:expect{ + any=[[Executing command: .?sort %< .*]] + } + else + screen:expect{ + any=[[Executing command: .?%(sort%) %< .*]] + + } + end + feed('<CR>') + helpers.set_shell_powershell(true) + feed(':4verbose %w !sort<cr>') + screen:expect{ + any=[[Executing command: .?& { Get%-Content .* | & sort }]] + } + feed('<CR>') + helpers.expect_exit(command, 'qall!') + end) end) diff --git a/test/functional/vimscript/writefile_spec.lua b/test/functional/vimscript/writefile_spec.lua index 5f693249a9..8c8da9dc88 100644 --- a/test/functional/vimscript/writefile_spec.lua +++ b/test/functional/vimscript/writefile_spec.lua @@ -111,6 +111,26 @@ describe('writefile()', function() pcall_err(command, ('call writefile([42], %s)'):format(ddname_tail))) end) + it('writefile(..., "p") creates missing parent directories', function() + os.remove(dname) + eq(nil, read_file(dfname)) + eq(0, funcs.writefile({'abc', 'def', 'ghi'}, dfname, 'p')) + eq('abc\ndef\nghi\n', read_file(dfname)) + os.remove(dfname) + os.remove(dname) + eq(nil, read_file(dfname)) + eq(0, funcs.writefile({'\na\nb\n'}, dfname, 'pb')) + eq('\0a\0b\0', read_file(dfname)) + os.remove(dfname) + os.remove(dname) + eq('Vim(call):E32: No file name', + pcall_err(command, ('call writefile([], "%s", "p")'):format(dfname .. '.d/'))) + eq(('Vim(call):E482: Can\'t open file ./ for writing: illegal operation on a directory'), + pcall_err(command, 'call writefile([], "./", "p")')) + eq(('Vim(call):E482: Can\'t open file . for writing: illegal operation on a directory'), + pcall_err(command, 'call writefile([], ".", "p")')) + end) + it('errors out with invalid arguments', function() write_file(fname, 'TEST') eq('Vim(call):E119: Not enough arguments for function: writefile', diff --git a/test/helpers.lua b/test/helpers.lua index a7eda60f87..82ff23bef8 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -1,6 +1,7 @@ require('test.compat') local shared = require('vim.shared') local assert = require('luassert') +local busted = require('busted') local luv = require('luv') local lfs = require('lfs') local relpath = require('pl.path').relpath @@ -45,6 +46,28 @@ function module.sleep(ms) luv.sleep(ms) end +-- Calls fn() until it succeeds, up to `max` times or until `max_ms` +-- milliseconds have passed. +function module.retry(max, max_ms, fn) + assert(max == nil or max > 0) + assert(max_ms == nil or max_ms > 0) + local tries = 1 + local timeout = (max_ms and max_ms or 10000) + local start_time = luv.now() + while true do + local status, result = pcall(fn) + if status then + return result + end + luv.update_time() -- Update cached value of luv.now() (libuv: uv_now()). + if (max and tries >= max) or (luv.now() - start_time > timeout) then + busted.fail(string.format("retry() attempts: %d\n%s", tries, tostring(result)), 2) + end + tries = tries + 1 + luv.sleep(20) -- Avoid hot loop... + end +end + local check_logs_useless_lines = { ['Warning: noted but unhandled ioctl']=1, ['could cause spurious value errors to appear']=2, @@ -87,6 +110,8 @@ end --- Asserts that `pat` matches (or *not* if inverse=true) any line in the tail of `logfile`. --- +--- Retries for 1 second in case of filesystem delay. +--- ---@param pat (string) Lua pattern to match lines in the log file ---@param logfile (string) Full path to log file (default=$NVIM_LOG_FILE) ---@param nrlines (number) Search up to this many log lines @@ -96,43 +121,34 @@ function module.assert_log(pat, logfile, nrlines, inverse) assert(logfile ~= nil, 'no logfile') nrlines = nrlines or 10 inverse = inverse or false - local lines = module.read_file_list(logfile, -nrlines) or {} - local msg = string.format('Pattern %q %sfound in log (last %d lines): %s:\n%s', - pat, (inverse and '' or 'not '), nrlines, logfile, ' '..table.concat(lines, '\n ')) - for _,line in ipairs(lines) do - if line:match(pat) then - if inverse then error(msg) else return end + + module.retry(nil, 1000, function() + local lines = module.read_file_list(logfile, -nrlines) or {} + local msg = string.format('Pattern %q %sfound in log (last %d lines): %s:\n%s', + pat, (inverse and '' or 'not '), nrlines, logfile, ' '..table.concat(lines, '\n ')) + for _,line in ipairs(lines) do + if line:match(pat) then + if inverse then error(msg) else return end + end end - end - if not inverse then error(msg) end + if not inverse then error(msg) end + end) end ---- Asserts that `pat` does NOT matche any line in the tail of `logfile`. +--- Asserts that `pat` does NOT match any line in the tail of `logfile`. --- --- @see assert_log function module.assert_nolog(pat, logfile, nrlines) return module.assert_log(pat, logfile, nrlines, true) end --- Invokes `fn` and returns the error string (with truncated paths), or raises --- an error if `fn` succeeds. --- --- Replaces line/column numbers with zero: --- shared.lua:0: in function 'gsplit' --- shared.lua:0: in function <shared.lua:0>' --- --- Usage: --- -- Match exact string. --- eq('e', pcall_err(function(a, b) error('e') end, 'arg1', 'arg2')) --- -- Match Lua pattern. --- matches('e[or]+$', pcall_err(function(a, b) error('some error') end, 'arg1', 'arg2')) --- -function module.pcall_err_withfile(fn, ...) +function module.pcall(fn, ...) assert(type(fn) == 'function') local status, rv = pcall(fn, ...) - if status == true then - error('expected failure, but got success') + if status then + return status, rv end + -- From: -- C:/long/path/foo.lua:186: Expected string, got number -- to: @@ -150,13 +166,37 @@ function module.pcall_err_withfile(fn, ...) -- We remove this so that the tests are not lua dependent. errmsg = errmsg:gsub('%s*%(tail call%): %?', '') - return errmsg + return status, errmsg +end + +-- Invokes `fn` and returns the error string (with truncated paths), or raises +-- an error if `fn` succeeds. +-- +-- Replaces line/column numbers with zero: +-- shared.lua:0: in function 'gsplit' +-- shared.lua:0: in function <shared.lua:0>' +-- +-- Usage: +-- -- Match exact string. +-- eq('e', pcall_err(function(a, b) error('e') end, 'arg1', 'arg2')) +-- -- Match Lua pattern. +-- matches('e[or]+$', pcall_err(function(a, b) error('some error') end, 'arg1', 'arg2')) +-- +function module.pcall_err_withfile(fn, ...) + assert(type(fn) == 'function') + local status, rv = module.pcall(fn, ...) + if status == true then + error('expected failure, but got success') + end + return rv end function module.pcall_err_withtrace(fn, ...) local errmsg = module.pcall_err_withfile(fn, ...) - return errmsg:gsub('.../helpers.lua:0: ', '') + return errmsg:gsub('^%.%.%./helpers%.lua:0: ', '') + :gsub('^Error executing lua:- ' ,'') + :gsub('^%[string "<nvim>"%]:0: ' ,'') end function module.pcall_err(...) @@ -257,43 +297,26 @@ function module.check_logs() table.concat(runtime_errors, ', '))) end -function module.iswin() - return package.config:sub(1,1) == '\\' +function module.sysname() + local platform = luv.os_uname() + if platform and platform.sysname then + return platform.sysname:lower() + end end --- Gets (lowercase) OS name from CMake, uname, or "win" if iswin(). -module.uname = (function() - local platform = nil - return (function() - if platform then - return platform - end - - if os.getenv("SYSTEM_NAME") then -- From CMAKE_HOST_SYSTEM_NAME. - platform = string.lower(os.getenv("SYSTEM_NAME")) - return platform - end - - local status, f = pcall(module.popen_r, 'uname', '-s') - if status then - platform = string.lower(f:read("*l")) - f:close() - elseif module.iswin() then - platform = 'windows' - else - error('unknown platform') - end - return platform - end) -end)() - function module.is_os(s) - if not (s == 'win' or s == 'mac' or s == 'unix') then + if not (s == 'win' + or s == 'mac' + or s == 'freebsd' + or s == 'openbsd' + or s == 'bsd') then error('unknown platform: '..tostring(s)) end - return ((s == 'win' and module.iswin()) - or (s == 'mac' and module.uname() == 'darwin') - or (s == 'unix')) + return ((s == 'win' and module.sysname():find('windows')) + or (s == 'mac' and module.sysname() == 'darwin') + or (s == 'freebsd' and module.sysname() == 'freebsd') + or (s == 'openbsd' and module.sysname() == 'openbsd') + or (s == 'bsd' and module.sysname():find('bsd'))) end local function tmpdir_get() @@ -319,11 +342,11 @@ module.tmpname = (function() return fname else local fname = os.tmpname() - if module.uname() == 'windows' and fname:sub(1, 2) == '\\s' then + if module.is_os('win') and fname:sub(1, 2) == '\\s' then -- In Windows tmpname() returns a filename starting with -- special sequence \s, prepend $TEMP path return tmpdir..fname - elseif fname:match('^/tmp') and module.uname() == 'darwin' then + elseif fname:match('^/tmp') and module.is_os('mac') then -- In OS X /tmp links to /private/tmp return '/private'..fname else @@ -366,14 +389,14 @@ function module.check_cores(app, force) exc_re = { os.getenv('NVIM_TEST_CORE_EXC_RE'), local_tmpdir } db_cmd = os.getenv('NVIM_TEST_CORE_DB_CMD') or gdb_db_cmd random_skip = os.getenv('NVIM_TEST_CORE_RANDOM_SKIP') - elseif 'darwin' == module.uname() then + elseif module.is_os('mac') then initial_path = '/cores' re = nil exc_re = { local_tmpdir } db_cmd = lldb_db_cmd else initial_path = '.' - if 'freebsd' == module.uname() then + if module.is_os('freebsd') then re = '/nvim.core$' else re = '/core[^/]*$' @@ -788,19 +811,19 @@ function module.write_file(name, text, no_dedent, append) file:close() end -function module.isCI(name) +function module.is_ci(name) local any = (name == nil) - assert(any or name == 'github') + assert(any or name == 'github' or name == 'cirrus') local gh = ((any or name == 'github') and nil ~= os.getenv('GITHUB_ACTIONS')) - return gh - + local cirrus = ((any or name == 'cirrus') and nil ~= os.getenv('CIRRUS_CI')) + return gh or cirrus end -- Gets the (tail) contents of `logfile`. -- Also moves the file to "${NVIM_LOG_FILE}.displayed" on CI environments. function module.read_nvim_log(logfile, ci_rename) logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog' - local is_ci = module.isCI() + local is_ci = module.is_ci() local keep = is_ci and 100 or 10 local lines = module.read_file_list(logfile, -keep) or {} local log = (('-'):rep(78)..'\n' diff --git a/test/includes/CMakeLists.txt b/test/includes/CMakeLists.txt deleted file mode 100644 index b4da4c0611..0000000000 --- a/test/includes/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -file(GLOB_RECURSE PRE_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} pre/*.h) - -# We need to add the SDK directories on OS X, and perhaps other operating -# systems. -set(gen_cflags) -foreach(gen_include ${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}) - list(APPEND gen_cflags ${CMAKE_INCLUDE_FLAG_C}${gen_include}) -endforeach() - -get_directory_property(gen_cdefs COMPILE_DEFINITIONS) -foreach(gen_cdef ${gen_cdefs}) - if(NOT ${gen_cdef} MATCHES "INCLUDE_GENERATED_DECLARATIONS") - list(APPEND gen_cflags "-D${gen_cdef}") - endif() -endforeach() - -foreach(hfile ${PRE_HEADERS}) - string(REGEX REPLACE ^pre/ post/ post_hfile ${hfile}) - get_filename_component(hdir ${CMAKE_CURRENT_BINARY_DIR}/${post_hfile} DIRECTORY) - file(MAKE_DIRECTORY ${hdir}) - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${post_hfile} - COMMAND ${CMAKE_C_COMPILER} -std=c99 -E -P - ${CMAKE_CURRENT_SOURCE_DIR}/${hfile} - ${gen_cflags} - -I${LIBUV_INCLUDE_DIRS} - -o ${CMAKE_CURRENT_BINARY_DIR}/${post_hfile}) - list(APPEND POST_HEADERS ${post_hfile}) -endforeach() - -add_custom_target(unittest-headers DEPENDS ${POST_HEADERS}) -set_target_properties(unittest-headers PROPERTIES FOLDER test) diff --git a/test/unit/buffer_spec.lua b/test/unit/buffer_spec.lua index 5dccc2f5a2..a54ea8c656 100644 --- a/test/unit/buffer_spec.lua +++ b/test/unit/buffer_spec.lua @@ -28,9 +28,9 @@ describe('buffer functions', function() setup(function() -- create the files - io.open(path1, 'w').close() - io.open(path2, 'w').close() - io.open(path3, 'w').close() + io.open(path1, 'w'):close() + io.open(path2, 'w'):close() + io.open(path3, 'w'):close() end) teardown(function() @@ -233,10 +233,12 @@ describe('buffer functions', function() output_buffer, buffer_byte_size, to_cstr(pat), - false, + NULL, + 0, fillchar, maximum_cell_count, NULL, + NULL, NULL) end diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index 6387f89fe4..128625374e 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -2175,7 +2175,8 @@ describe('typval.c', function() eq({a='TSET'}, dct2tbl(d1)) eq({a='TSET'}, dct2tbl(d2)) end) - itp('disallows overriding builtin or user functions', function() + pending('disallows overriding builtin or user functions: here be the dragons', function() + -- pending: see TODO below local d = dict() d.dv_scope = lib.VAR_DEF_SCOPE local f_lua = { @@ -2196,6 +2197,7 @@ describe('typval.c', function() local d5 = dict({Test=f_tv}) local d6 = dict({Test=p_tv}) eval0([[execute("function Test()\nendfunction")]]) + -- TODO: test breaks at this point tv_dict_extend(d, d2, 'force', 'E704: Funcref variable name must start with a capital: tr') tv_dict_extend(d, d3, 'force', @@ -2253,8 +2255,8 @@ describe('typval.c', function() local d1 = dict() alloc_log:check({a.dict(d1)}) eq(1, d1.dv_refcount) - eq(false, tv_dict_equal(nil, d1)) - eq(false, tv_dict_equal(d1, nil)) + eq(true, tv_dict_equal(nil, d1)) + eq(true, tv_dict_equal(d1, nil)) eq(true, tv_dict_equal(d1, d1)) eq(1, d1.dv_refcount) alloc_log:check({}) @@ -2623,7 +2625,7 @@ describe('typval.c', function() describe('check_lock()', function() local function tv_check_lock(lock, name, name_len, emsg) return check_emsg(function() - return lib.var_check_lock(lock, name, name_len) + return lib.value_check_lock(lock, name, name_len) end, emsg) end itp('works', function() @@ -2721,8 +2723,8 @@ describe('typval.c', function() local d1 = lua2typvalt({}) alloc_log:check({a.dict(d1.vval.v_dict)}) eq(1, d1.vval.v_dict.dv_refcount) - eq(false, tv_equal(nd, d1)) - eq(false, tv_equal(d1, nd)) + eq(true, tv_equal(nd, d1)) + eq(true, tv_equal(d1, nd)) eq(true, tv_equal(d1, d1)) eq(1, d1.vval.v_dict.dv_refcount) alloc_log:check({}) diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index 29ea0235be..14d8eda357 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -7,9 +7,6 @@ local global_helpers = require('test.helpers') local assert = require('luassert') local say = require('say') -local posix = nil -local syscall = nil - local check_cores = global_helpers.check_cores local dedent = global_helpers.dedent local neq = global_helpers.neq @@ -373,122 +370,91 @@ local function to_cstr(string) return cstr(#string + 1, string) end -local sc - -if posix ~= nil then - sc = { - fork = posix.fork, - pipe = posix.pipe, - read = posix.read, - write = posix.write, - close = posix.close, - wait = posix.wait, - exit = posix._exit, - } -elseif syscall ~= nil then - sc = { - fork = syscall.fork, - pipe = function() - local ret = {syscall.pipe()} - return ret[3], ret[4] - end, - read = function(rd, len) - return rd:read(nil, len) - end, - write = function(wr, s) - return wr:write(s) - end, - close = function(p) - return p:close() - end, - wait = syscall.wait, - exit = syscall.exit, - } -else - cimport_immediate('./test/unit/fixtures/posix.h') - sc = { - fork = function() - return tonumber(ffi.C.fork()) - end, - pipe = function() - local ret = ffi.new('int[2]', {-1, -1}) - ffi.errno(0) - local res = ffi.C.pipe(ret) - if (res ~= 0) then +cimport_immediate('./test/unit/fixtures/posix.h') +local sc = { + fork = function() + return tonumber(ffi.C.fork()) + end, + pipe = function() + local ret = ffi.new('int[2]', {-1, -1}) + ffi.errno(0) + local res = ffi.C.pipe(ret) + if (res ~= 0) then + local err = ffi.errno(0) + assert(res == 0, ("pipe() error: %u: %s"):format( + err, ffi.string(ffi.C.strerror(err)))) + end + assert(ret[0] ~= -1 and ret[1] ~= -1) + return ret[0], ret[1] + end, + read = function(rd, len) + local ret = ffi.new('char[?]', len, {0}) + local total_bytes_read = 0 + ffi.errno(0) + while total_bytes_read < len do + local bytes_read = tonumber(ffi.C.read( + rd, + ffi.cast('void*', ret + total_bytes_read), + len - total_bytes_read)) + if bytes_read == -1 then local err = ffi.errno(0) - assert(res == 0, ("pipe() error: %u: %s"):format( - err, ffi.string(ffi.C.strerror(err)))) - end - assert(ret[0] ~= -1 and ret[1] ~= -1) - return ret[0], ret[1] - end, - read = function(rd, len) - local ret = ffi.new('char[?]', len, {0}) - local total_bytes_read = 0 - ffi.errno(0) - while total_bytes_read < len do - local bytes_read = tonumber(ffi.C.read( - rd, - ffi.cast('void*', ret + total_bytes_read), - len - total_bytes_read)) - if bytes_read == -1 then - local err = ffi.errno(0) - if err ~= ffi.C.kPOSIXErrnoEINTR then - assert(false, ("read() error: %u: %s"):format( - err, ffi.string(ffi.C.strerror(err)))) - end - elseif bytes_read == 0 then - break - else - total_bytes_read = total_bytes_read + bytes_read + if err ~= ffi.C.kPOSIXErrnoEINTR then + assert(false, ("read() error: %u: %s"):format( + err, ffi.string(ffi.C.strerror(err)))) end + elseif bytes_read == 0 then + break + else + total_bytes_read = total_bytes_read + bytes_read end - return ffi.string(ret, total_bytes_read) - end, - write = function(wr, s) - local wbuf = to_cstr(s) - local total_bytes_written = 0 - ffi.errno(0) - while total_bytes_written < #s do - local bytes_written = tonumber(ffi.C.write( - wr, - ffi.cast('void*', wbuf + total_bytes_written), - #s - total_bytes_written)) - if bytes_written == -1 then - local err = ffi.errno(0) - if err ~= ffi.C.kPOSIXErrnoEINTR then - assert(false, ("write() error: %u: %s ('%s')"):format( - err, ffi.string(ffi.C.strerror(err)), s)) - end - elseif bytes_written == 0 then - break - else - total_bytes_written = total_bytes_written + bytes_written + end + return ffi.string(ret, total_bytes_read) + end, + write = function(wr, s) + local wbuf = to_cstr(s) + local total_bytes_written = 0 + ffi.errno(0) + while total_bytes_written < #s do + local bytes_written = tonumber(ffi.C.write( + wr, + ffi.cast('void*', wbuf + total_bytes_written), + #s - total_bytes_written)) + if bytes_written == -1 then + local err = ffi.errno(0) + if err ~= ffi.C.kPOSIXErrnoEINTR then + assert(false, ("write() error: %u: %s ('%s')"):format( + err, ffi.string(ffi.C.strerror(err)), s)) end + elseif bytes_written == 0 then + break + else + total_bytes_written = total_bytes_written + bytes_written end - return total_bytes_written - end, - close = ffi.C.close, - wait = function(pid) - ffi.errno(0) - while true do - local r = ffi.C.waitpid(pid, nil, ffi.C.kPOSIXWaitWUNTRACED) - if r == -1 then - local err = ffi.errno(0) - if err == ffi.C.kPOSIXErrnoECHILD then - break - elseif err ~= ffi.C.kPOSIXErrnoEINTR then - assert(false, ("waitpid() error: %u: %s"):format( - err, ffi.string(ffi.C.strerror(err)))) - end - else - assert(r == pid) + end + return total_bytes_written + end, + close = ffi.C.close, + wait = function(pid) + ffi.errno(0) + local stat_loc = ffi.new('int[1]', {0}) + while true do + local r = ffi.C.waitpid(pid, stat_loc, ffi.C.kPOSIXWaitWUNTRACED) + if r == -1 then + local err = ffi.errno(0) + if err == ffi.C.kPOSIXErrnoECHILD then + break + elseif err ~= ffi.C.kPOSIXErrnoEINTR then + assert(false, ("waitpid() error: %u: %s"):format( + err, ffi.string(ffi.C.strerror(err)))) end + else + assert(r == pid) end - end, - exit = ffi.C._exit, - } -end + end + return stat_loc[0] + end, + exit = ffi.C._exit, +} local function format_list(lst) local ret = '' @@ -730,18 +696,22 @@ local function check_child_err(rd) end end -local function itp_parent(rd, pid, allow_failure) - local err, emsg = pcall(check_child_err, rd) - sc.wait(pid) +local function itp_parent(rd, pid, allow_failure, location) + local ok, emsg = pcall(check_child_err, rd) + local status = sc.wait(pid) sc.close(rd) - if not err then + if not ok then if allow_failure then - io.stderr:write('Errorred out:\n' .. tostring(emsg) .. '\n') + io.stderr:write('Errorred out ('..status..'):\n' .. tostring(emsg) .. '\n') os.execute([[ sh -c "source ci/common/test.sh check_core_dumps --delete \"]] .. Paths.test_luajit_prg .. [[\""]]) else - error(emsg) + error(tostring(emsg)..'\nexit code: '..status) + end + elseif status ~= 0 then + if not allow_failure then + error("child process errored out with status "..status.."!\n\n"..location) end end end @@ -758,6 +728,11 @@ local function gen_itp(it) -- FIXME Fix tests with this true return end + + -- Pre-emptively calculating error location, wasteful, ugh! + -- But the way this code messes around with busted implies the real location is strictly + -- not available in the parent when an actual error occurs. so we have to do this here. + local location = debug.traceback() it(name, function() local rd, wr = sc.pipe() child_pid = sc.fork() @@ -768,7 +743,7 @@ local function gen_itp(it) sc.close(wr) local saved_child_pid = child_pid child_pid = nil - itp_parent(rd, saved_child_pid, allow_failure) + itp_parent(rd, saved_child_pid, allow_failure, location) end end) end diff --git a/test/unit/os/fs_spec.lua b/test/unit/os/fs_spec.lua index 0bb33772cd..c718244ea4 100644 --- a/test/unit/os/fs_spec.lua +++ b/test/unit/os/fs_spec.lua @@ -72,9 +72,9 @@ describe('fs.c', function() before_each(function() lfs.mkdir('unit-test-directory'); - io.open('unit-test-directory/test.file', 'w').close() + io.open('unit-test-directory/test.file', 'w'):close() - io.open('unit-test-directory/test_2.file', 'w').close() + io.open('unit-test-directory/test_2.file', 'w'):close() lfs.link('test.file', 'unit-test-directory/test_link.file', true) lfs.link('non_existing_file.file', 'unit-test-directory/test_broken_link.file', true) @@ -472,7 +472,7 @@ describe('fs.c', function() describe('os_remove', function() before_each(function() - io.open('unit-test-directory/test_remove.file', 'w').close() + io.open('unit-test-directory/test_remove.file', 'w'):close() end) after_each(function() diff --git a/test/unit/path_spec.lua b/test/unit/path_spec.lua index eb23a3cff1..1fc4e2496e 100644 --- a/test/unit/path_spec.lua +++ b/test/unit/path_spec.lua @@ -82,8 +82,8 @@ describe('path.c', function() local f2 = 'f2.o' before_each(function() -- create the three files that will be used in this spec - io.open(f1, 'w').close() - io.open(f2, 'w').close() + io.open(f1, 'w'):close() + io.open(f2, 'w'):close() end) after_each(function() @@ -355,7 +355,7 @@ end) describe('path.c', function() setup(function() lfs.mkdir('unit-test-directory'); - io.open('unit-test-directory/test.file', 'w').close() + io.open('unit-test-directory/test.file', 'w'):close() -- Since the tests are executed, they are called by an executable. We use -- that executable for several asserts. @@ -504,6 +504,16 @@ describe('path.c', function() eq(OK, result) end) + itp('does not remove trailing slash from non-existing relative directory #20847', function() + local expected = lfs.currentdir() .. '/non_existing_dir/' + local filename = 'non_existing_dir/' + local buflen = get_buf_len(expected, filename) + local do_expand = 1 + local buf, result = vim_FullName(filename, buflen, do_expand) + eq(expected, ffi.string(buf)) + eq(OK, result) + end) + itp('expands "./" to the current directory #7117', function() local expected = lfs.currentdir() .. '/unit-test-directory/test.file' local filename = './unit-test-directory/test.file' diff --git a/test/unit/search_spec.lua b/test/unit/search_spec.lua index ce37ebfc3a..be905bf5f0 100644 --- a/test/unit/search_spec.lua +++ b/test/unit/search_spec.lua @@ -37,7 +37,7 @@ end) describe('search_regcomp', function() local search_regcomp = function(pat, pat_save, pat_use, options ) local regmatch = ffi.new("regmmatch_T") - local fail = search.search_regcomp(to_cstr(pat), pat_save, pat_use, options, regmatch) + local fail = search.search_regcomp(to_cstr(pat), nil, pat_save, pat_use, options, regmatch) return fail, regmatch end diff --git a/test/unit/tui_spec.lua b/test/unit/tui_spec.lua index 36ce4a1493..25b70a17c2 100644 --- a/test/unit/tui_spec.lua +++ b/test/unit/tui_spec.lua @@ -9,6 +9,7 @@ local cinput = cimport("./src/nvim/tui/input.h") local rbuffer = cimport("./test/unit/fixtures/rbuffer.h") local globals = cimport("./src/nvim/globals.h") local multiqueue = cimport("./test/unit/fixtures/multiqueue.h") +local ui_client = cimport("./src/nvim/ui_client.h") itp('handle_background_color', function() local handle_background_color = cinput.ut_handle_background_color @@ -33,11 +34,9 @@ itp('handle_background_color', function() term_input.waiting_for_bg_response = 1 eq(kComplete, handle_background_color(term_input)) eq(0, term_input.waiting_for_bg_response) - eq(1, multiqueue.multiqueue_size(events)) - - local event = multiqueue.multiqueue_get(events) - local bg_event = ffi.cast("Event*", event.argv[1]) - eq(bg, ffi.string(bg_event.argv[0])) + eq(0, multiqueue.multiqueue_size(events)) + eq(bg, ({[0]="light", [1] = "dark", [-1] = "none"}) + [tonumber(ui_client.ui_client_bg_response)]) -- Buffer has been consumed. eq(0, rbuf.size) @@ -114,9 +113,7 @@ itp('handle_background_color', function() eq(kComplete, handle_background_color(term_input)) eq(0, term_input.waiting_for_bg_response) - local event = multiqueue.multiqueue_get(events) - local bg_event = ffi.cast("Event*", event.argv[1]) - eq('light', ffi.string(bg_event.argv[0])) + eq(0, tonumber(ui_client.ui_client_bg_response)) eq(0, multiqueue.multiqueue_size(events)) eq(0, rbuf.size) @@ -133,9 +130,7 @@ itp('handle_background_color', function() eq(kComplete, handle_background_color(term_input)) eq(0, term_input.waiting_for_bg_response) - event = multiqueue.multiqueue_get(events) - bg_event = ffi.cast("Event*", event.argv[1]) - eq('light', ffi.string(bg_event.argv[0])) + eq(0, tonumber(ui_client.ui_client_bg_response)) eq(0, multiqueue.multiqueue_size(events)) eq(0, rbuf.size) @@ -161,7 +156,7 @@ itp('handle_background_color', function() eq(kComplete, handle_background_color(term_input)) eq(0, term_input.waiting_for_bg_response) - eq(1, multiqueue.multiqueue_size(events)) + eq(0, multiqueue.multiqueue_size(events)) eq(3, rbuf.size) rbuffer.rbuffer_consumed(rbuf, rbuf.size) end) |